Setting safe insets for Android on NativeScript - android

For iOS I am using app.ios.window.safeAreaInsets to discover the safe insets in NativeScript.
Similarly, I would like to discover the safe insets for Android phones, so I can properly handle Android devices that have a soft navigation for example or newer Android phones that have a notch as well (for example the OnePlus 6).
I see in the tns-platform-declarations that for SDK levels 20+ there is the class android.view.WindowInsets which has methods like getSystemWindowInsetTop() and getSystemWindowInsetBottom(), which seems to be exactly what I need.
I am struggling however to call those methods and was wondering whether anyone could advise on how to proceed.
So far I found that the decorView has a method onApplyWindowInsets(), but as far as I can see I already need to pass that one an instance of android.view.WindowInsets which as far as I can tell is what I am after.
This is what I have so far:
if (app.android && device.sdkVersion >= '20') {
const window = app.android.startActivity.getWindow();
const decorView = window.getDecorView();
//decorView.onApplyWindowInsets();
}
Thanks for any pointers!

This snippet works for me, at least for detecting the bigger height of a status bar with notch.
let androidStatusBarHeight = 0;
const context = application.android.context;
const metrics = new android.util.DisplayMetrics();
context.getSystemService(android.content.Context.WINDOW_SERVICE).getDefaultDisplay().getRealMetrics(metrics);
console.log(metrics.density);
const resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if(resourceId > 0) {
console.log(context.getResources().getDimensionPixelSize(resourceId));
androidStatusBarHeight = context.getResources().getDimensionPixelSize(resourceId) / metrics.density;
} else {
androidStatusBarHeight = 24;
}
return androidStatusBarHeight;

Related

Android: HapticGenerator effect does not work

I tried HapticGenerator audio effect introduced in the API level 31.
The code is very simple:
if (HapticGenerator.isAvailable()) {
val sessionId = mediaPlayer.audioSessionId
val effect = HapticGenerator.create(sessionId)
effect.enabled = isEnabled
}
But it does not work, there is no effect at all.
Can anyone explain how it actually should work? Thanks!
P.S. my device is Samsung s22.

Xamarin Forms How to change Android status bar icon color in API 30 (Android 11)

I have a Xamarin.Forms app being built for iOS and Android.
I'm having some difficulty in Android updating the icon colors when setting the status bar color. I have this working for API levels below 30 using the following code:
var isLight = false;
Window currentWindow = Platform.CurrentActivity.Window;
if (Color.FromHex(hexColor).Luminosity > 0.5)
{
isLight = true;
}
currentWindow.SetStatusBarColor(androidColor);
currentWindow.DecorView.SystemUiVisibility = isLight ? (StatusBarVisibility)(SystemUiFlags.LightStatusBar) : 0;
From what I can tell, DecorView.SystemUiVisibility is deprecated in API 30, and is supposed to be replaced with window.insetsController
What I can't figure out is if/where this API is exposed in Xamarin for me to use.
I looked at this SO question:
How to change the status bar color without a navigation page
and following the last answer, I attempted to use:
var lightStatusBars = isLight ? WindowInsetsControllerAppearance.LightStatusBars : 0;
currentWindow.InsetsController?.SetSystemBarsAppearance((int)lightStatusBars, (int)lightStatusBars);
but it will not build, saying Window doesn't have InsetsController
Has anyone figured this out? I definitely need to support the latest Android and this feature is killing me
Thanks in advance!
Your code looks correct. Change target framework to Android 11.0 (R). InsetsController was added in API level 30. Due to this you may receive build error.
public void UpdateStatusBarColor(String color)
{
Window.SetStatusBarColor(Color.ParseColor(color));
if (Build.VERSION.SdkInt >= BuildVersionCodes.R)
{
Window?.InsetsController?.SetSystemBarsAppearance((int)WindowInsetsControllerAppearance.LightStatusBars, (int)WindowInsetsControllerAppearance.LightStatusBars);
}
else
{
#pragma warning disable CS0618
Window.DecorView.SystemUiVisibility = (StatusBarVisibility)SystemUiFlags.LightStatusBar;
#pragma warning restore CS0618
}
}
Can you please try this in MainActivity.cs
$ Window.SetStatusBarColor(Android.Graphics.Color.Argb(255, 114, 75, 203));

Right way to get insets

I have an Activity with a RecyclerView in a data binding layout. RecyclerView takes up the whole screen, and looking at making the UX go full screen, drawn under the status and nav bars.
I'm calling setSystemUiVisibility in activity's onCreate as below.
window.decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
)
Now the RecyclerView is drawn under the system bars, so I want to make sure it has enough padding so the items don't overlap with the system UI.
I found 2 ways of doing this, via a BindingAdapter.
Option 1
var statusBar = 0
var resourceId = view.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
statusBar = view.resources.getDimensionPixelSize(resourceId)
}
var navBar = 0
resourceId = view.resources.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) {
navBar = view.resources.getDimensionPixelSize(resourceId)
}
view.setPadding(0, statusBar, 0, navBar)
Option 2
var insets = view.rootWindowInsets.stableInsets
view.setPadding(0, insets.top, 0, insets.bottom)
I prefer the first, because it (with limited testing on emulators seems to) work on API 21, 28 and 29.
Option 2 only works on API 29, and also seems to get null on view.rootWindowInsets if/when the view is not attached. (So I guess I have to add a listener and wait for it to be attached before doing this)
So my question is, is there a down side to Option 1? Can I use it over the new API in 29? Is there any scenarios that Option 1 would not work?
(I think Option 1 might not work well on tablets where both nav and systems bars are on the bottom, so extra padding will be applied to the wrong side.)
A little bit late to the party, but here is the way that I've been doing, someone might need it.
For Android M and above, you can call View#rootWindowInsets directly, otherwise rely on Java's Reflection to access the private field mStableInsets
fun getStableInsets(view: View): Rect {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val windowInsets = view.rootWindowInsets
if (windowInsets != null) {
Rect(windowInsets.stableInsetLeft, windowInsets.stableInsetTop,
windowInsets.stableInsetRight, windowInsets.stableInsetBottom)
} else {
// TODO: Edge case, you might want to return a default value here
Rect(defaultInsetLeft, defaultInsetTop, defaultInsetRight, defaultInsetBottom)
}
} else {
val attachInfoField = View::class.java.getDeclaredField("mAttachInfo")
attachInfoField.isAccessible = true
val attachInfo = attachInfoField.get(view);
if (attachInfo != null) {
val stableInsetsField = attachInfo.javaClass.getDeclaredField("mStableInsets")
stableInsetsField.isAccessible = true
Rect(stableInsetsField.get(attachInfo) as Rect)
} else {
// TODO: Edge case, you might want to return a default value here
Rect(defaultInsetLeft, defaultInsetTop, defaultInsetRight, defaultInsetBottom)
}
}
}
Update:
stableInsetBottom .etc. are now deprecated with message
Use {#link #getInsetsIgnoringVisibility(int)} with {#link Type#systemBars()}
* instead.
Unfortunately systemBars() was graylisted in API 29 and is blacklisted in API 30 plus using this seems to work on API 30 emulator, however (some) real devices even running API 29 throws.
Below is logcat from Galaxy S20 FE
Accessing hidden method Landroid/view/WindowInsets$Type;->systemBars()I (blacklist, linking, denied)
2021-01-17 01:45:18.348 23013-23013/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: test.app.package, PID: 23013
java.lang.NoSuchMethodError: No static method systemBars()I in class Landroid/view/WindowInsets$Type; or its super classes (declaration of 'android.view.WindowInsets$Type' appears in /system/framework/framework.jar!classes3.dex)
No answer for this it seems. Please put an answer if you find anything not covered below.
Using Option 1 I noticed on devices that do OEM specific gesture navigation atleast, when those gesture modes are active, above will still return full navigation bar height even though no visible navigation bar is present. So above will still pad the UI when it shouldn't.
Option 2 keeps returning null for insets until the view is attached so if you're doing this on a BindingAdapter, it won't work. It needs to be called after the view is attached.
My current solution is as below.
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.doOnAttach {
var bottom = it.rootWindowInsets?.stableInsetBottom?: 0
var top = it.rootWindowInsets?.stableInsetTop?: 0
view.setPadding(0, top, 0, bottom)
}
}
else {
// use option1, old devices don't have custom OEM specific gesture navigation.
// or.. just don't support versions below Android M ¯\_(ツ)_/¯
}
Caveats
Some OEMs (well atleast OnePlus) decided not to restart some activities, especially ones that are paused, when the navigation mode changed. So if the user decides to switch away from your app, change the navigation mode and return, your app may still overlap navigation bar until the activity is restarted.

Cordova android keyboard push up content

I have some content/input fields that are covered when the android keyboard is shown in my cordova app. I have
android:windowSoftInputMode="adjustPan" and <preference name="fullscreen" value="false" />
I tried android:windowSoftInputMode="adjustResize but it kept shrinking my content because it was resizing the window (My content is sized based on viewport width and viewport height). Thank you for any suggestions!
So I had a work around myself that may or may not work for everyone, but I figured I could post this to hopefully help someone who comes across this!
I found a lot of answers but none really helped me. So in my AndroidManinfest.xml file I set android:windowSoftInputMode="adjustPan|stateHidden". Yes, this will still cover the content below the keyboard when it's opened.
To avoid that, I gave all of my scroll views that would be affected by the keyboard being shown a class of inputScrollContainer. Name them whatever you would like.
Since every container (for me) was the same height as was the top bar for each page, I did the following: (you will have to install the device plugin and the keyboard plugin from cordova
Got window.innerHeight at the beginning of my js (if you do this inside of your native.keyboardshow function, iOS will give you the resized view based on the keyboard's height)
Then, inside my native.keyboardShow function, I did the following:
- Then got the height of the top bar (I chose one as they were all the same)
- Added the added the keyboard height and top bar height together
- Then I subtracted those from the window height
Doing this now gave me the height "leftover" for the scroll view to have. After that I:
Got all elements by class name inputScrollContainer
Looped through them and assigned the new height to each (you can assign it to the only scroll view currently in view, but I only had three affected views so I wasn't worried about it)
Now the scroll view was resized to whatever was left between the top bar and the keyboard. Then on my native.keyboardhide function, I just restored the height to what the original height for all of the scroll views was before.
I'm sure there are other ways to do this, but doing it this way gave me flexibility and consistency across iOS and Android. I hope this helps someone!
To move the layout up when the keyboard is visible/shown add the following activity.
<activity android:windowSoftInputMode="adjustPan|adjustResize"> </activity>
adjustResize : The activity's main window is always resized to make room for the soft keyboard on screen.
adjustPan : The activity's main window is not resized to make room for the soft keyboard. Rather, the contents of the window are automatically panned so that the current focus is never obscured by the keyboard and users can always see what they are typing. This is generally less desirable than resizing, because the user may need to close the soft keyboard to get at and interact with obscured parts of the window.
In your scenario you can make use of adjust pan
However it works based on the android versions. It may not work in particular versions. please be find and use.
Please have look at this answer you will come to know a lot.
Viewport height is the problem here.
There is some way to correct the problem with mediaqueries, or with javascript (modifying all of your dom element with the correct height).
But in my case, I had lots of dom elements, and really didn't want to change all of this with javascript.
My trick is :
- Change all of your vh with rem and divide your value by 4
- use this little javascript in all of your page :
$("html").css({"font-size": ($(window).height()/25)+"px"});
Here we go, in this example, font-size is 4% of window height (cause font-size has a minimum value on mobile app), so :
1rem=4% of widow height=4vh
0.25rem = 1vh etc...
In my case, I use a SASS function to divide with 4 all of my vh, so it was easier to change all css. (1h = rem(1) = 0.25rem)
Hope this will help someday.
This JS option delivers a UX similar to iOS:
let events = {
android: {
keyboard: {
threshold: 300, //px
transition: 300, //ms
visible: false,
last_el: null
}
}
}
onAndroidKeyboard() {
if(is_android) {
let threshold = events.android.keyboard.threshold;
let transition = events.android.keyboard.transition;
function onIn(e) {
let target = e.target;
if(target.nodeName.toLowerCase() !== 'input') {
return false
}
let visible = events.android.keyboard.visible;
let h = window.innerHeight;
try {
let bottom = target.getBoundingClientRect().bottom;
if(bottom) {
let diff = h - bottom;
if(diff < threshold) {
if(!visible) {
let animate_amount = threshold - diff;
events.android.keyboard.visible = true;
document.body.style.transform = 'translateY(0)';
document.body.style.webkitTransition = `all ${transition}ms`;
document.body.style.transition = `all ${transition}ms`;
events.android.keyboard.visible = true;
events.android.keyboard.last_el = target;
requestAnimationFrame(function () {
document.body.style.transform = `translateY(-${animate_amount}px)`;
});
}
}
}
} catch (e) {
console.error(e);
}
}
function onOut(e) {
let visible = events.android.keyboard.visible;
if(visible) {
document.body.style.transform = 'translateY(0)';
setTimeout(function () {
requestAnimationFrame(function () {
document.body.style.removeProperty('transform');
document.body.style.removeProperty('transition');
document.body.style.removeProperty('webkitTransition');
events.android.keyboard.visible = false;
events.android.keyboard.last_el = null;
});
}, transition)
}
}
document.addEventListener('focusin', onIn, false);
document.addEventListener('focusout', onOut, false);
}
}

Is there a way to programmatically control the back-light for the android back button?

I'm currently making an android app and testing it on a Samsung GT-S5830.
The problem I'm having is that the back button back-light is always off when the app is running (so it's not visible), which seems to confuse the users who I have asked to test the app.
The question is whether there is a way to programmatically ensure that the back-light for the back button is always on?
I'm dubious about it, as the problem seems to be phone model dependent.
Thanks.
there is already a stackoverflow answer but I will post again:
private void setDimButtons(boolean dimButtons) {
Window window = getWindow();
LayoutParams layoutParams = window.getAttributes();
float val = dimButtons ? 0 : -1;
try {
Field buttonBrightness = layoutParams.getClass().getField(
"buttonBrightness");
buttonBrightness.set(layoutParams, val);
} catch (Exception e) {
e.printStackTrace();
}
window.setAttributes(layoutParams);
}
Or try to find something in here (the new design is horrible ...) http://developer.android.com/reference/android/os/PowerManager.html

Categories

Resources