Skip to content

Commit

Permalink
Android API 30 fixes (#7343)
Browse files Browse the repository at this point in the history
Upgrading android target SDK to 30, to support android 12 changes.

- Refactor StatusBarUtil to SystemUIUtils to include status and navigation bars.
- Migrate deprecated code using flags to a modern one.
- Calculating WindowInsets per component where all views are laid as full screen.
- Keyboard enhancements to show hide keyboard.
- Fix applying Options of a child taking parent Options into consideration.
- Fix fixed status bar height and use a more elegant way.
- Add more tests for SystemUiUtils.
- Add a SystemUi screen to demonstrate system UI capabilities.
- Migrate Reanimated usage to support Reanimated 2.

Closes #7339.
Closes #7225.
Closes #7358.
Closes #7199.
Closes #7171.
Closes #7111.
Closes #6988.
Closes #4258.
Closes #7360.

Demo:

https://user-images.githubusercontent.com/7227793/142203865-d65b6910-21f8-4617-812e-b5576a6b58e4.mov





Co-authored-by: Ward Abbass <swabbass@gmail.com>
Co-authored-by: Yogev Ben David <yogev132@gmail.com>
Co-authored-by: svbutko <svbutko@hotmail.com>
Co-authored-by: Ward Abbass <warda@wix.com>
  • Loading branch information
4 people committed Nov 30, 2021
1 parent 29ac5e4 commit eae5831
Show file tree
Hide file tree
Showing 37 changed files with 662 additions and 302 deletions.
4 changes: 2 additions & 2 deletions e2e/Modals.test.js
Expand Up @@ -207,9 +207,9 @@ describe('modal', () => {
it.e2e('dismiss modal with side menu', async () => {
await elementById(TestIDs.MODAL_COMMANDS_BTN).tap();
await elementById(TestIDs.SHOW_SIDE_MENU_MODAL).tap();
await expect(elementByLabel('StatusBar Options')).toBeVisible();
await expect(elementByLabel('System UI Options')).toBeVisible();
await elementById(TestIDs.DISMISS_MODAL_TOPBAR_BTN).tap();
await expect(elementByLabel('StatusBar Options')).not.toBeVisible();
await expect(elementByLabel('System UI Options')).not.toBeVisible();
await expect(elementByLabel('Modal Commands')).toBeVisible();
});
});
6 changes: 3 additions & 3 deletions lib/android/app/build.gradle
Expand Up @@ -172,13 +172,13 @@ allprojects { p ->
}

dependencies {
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.core:core-ktx:1.6.0"
implementation "org.jetbrains.kotlin:$kotlinStdlib:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesCore"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.annotation:annotation:1.2.0'
implementation 'com.google.android.material:material:1.2.0-alpha03'

implementation 'com.github.wix-playground:ahbottomnavigation:3.3.0'
Expand Down
Expand Up @@ -23,7 +23,7 @@
import com.reactnativenavigation.react.events.EventEmitter;
import com.reactnativenavigation.utils.LaunchArgsParser;
import com.reactnativenavigation.utils.Now;
import com.reactnativenavigation.utils.StatusBarUtils;
import com.reactnativenavigation.utils.SystemUiUtils;
import com.reactnativenavigation.utils.UiThread;
import com.reactnativenavigation.utils.UiUtils;
import com.reactnativenavigation.viewcontrollers.navigator.Navigator;
Expand All @@ -34,6 +34,8 @@

import static com.reactnativenavigation.utils.UiUtils.pxToDp;

import android.app.Activity;

public class NavigationModule extends ReactContextBaseJavaModule {
private static final String NAME = "RNNBridgeModule";

Expand Down Expand Up @@ -88,10 +90,11 @@ public void getLaunchArgs(String commandId, Promise promise) {

private WritableMap createNavigationConstantsMap() {
ReactApplicationContext ctx = getReactApplicationContext();
final Activity currentActivity = ctx.getCurrentActivity();
WritableMap constants = Arguments.createMap();
constants.putString(Constants.BACK_BUTTON_JS_KEY, Constants.BACK_BUTTON_ID);
constants.putDouble(Constants.BOTTOM_TABS_HEIGHT_KEY, pxToDp(ctx, UiUtils.getBottomTabsHeight(ctx)));
constants.putDouble(Constants.STATUS_BAR_HEIGHT_KEY, pxToDp(ctx, StatusBarUtils.getStatusBarHeight(ctx)));
constants.putDouble(Constants.STATUS_BAR_HEIGHT_KEY, pxToDp(ctx, SystemUiUtils.getStatusBarHeight(currentActivity)));
constants.putDouble(Constants.TOP_BAR_HEIGHT_KEY, pxToDp(ctx, UiUtils.getTopBarHeight(ctx)));
return constants;
}
Expand Down
Expand Up @@ -2,7 +2,7 @@ package com.reactnativenavigation.react.modal

import android.widget.FrameLayout
import com.facebook.react.bridge.ReactContext
import com.reactnativenavigation.utils.StatusBarUtils
import com.reactnativenavigation.utils.SystemUiUtils

class ModalFrameLayout(context: ReactContext) : FrameLayout(context) {
val modalContentLayout = ModalContentLayout(context)
Expand All @@ -11,9 +11,9 @@ class ModalFrameLayout(context: ReactContext) : FrameLayout(context) {
addView(modalContentLayout, MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT)
.apply {
val translucent = context.currentActivity?.window?.let {
StatusBarUtils.isTranslucent(it)
SystemUiUtils.isTranslucent(it)
} ?: false
topMargin = if (translucent) 0 else StatusBarUtils.getStatusBarHeight(context)
topMargin = if (translucent) 0 else SystemUiUtils.getStatusBarHeight(context.currentActivity)
})
}
}
@@ -1,5 +1,6 @@
package com.reactnativenavigation.react.modal

import android.app.Activity
import android.content.Context
import android.graphics.Point
import android.view.WindowManager
Expand All @@ -21,7 +22,7 @@ import com.reactnativenavigation.options.parseTransitionAnimationOptions
import com.reactnativenavigation.options.parsers.JSONParser
import com.reactnativenavigation.react.CommandListener
import com.reactnativenavigation.react.CommandListenerAdapter
import com.reactnativenavigation.utils.StatusBarUtils
import com.reactnativenavigation.utils.SystemUiUtils
import com.reactnativenavigation.viewcontrollers.navigator.Navigator

private const val MODAL_MANAGER_NAME = "RNNModalViewManager"
Expand Down Expand Up @@ -107,26 +108,26 @@ class ModalViewManager(val reactContext: ReactContext) : ViewGroupManager<ModalH
}
}

private fun getModalHostSize(context: Context): Point {
private fun getModalHostSize(activity: Activity): Point {
val MIN_POINT = Point()
val MAX_POINT = Point()
val SIZE_POINT = Point()
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val wm = activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = Assertions.assertNotNull(wm).defaultDisplay
// getCurrentSizeRange will return the min and max width and height that the window can be
display.getCurrentSizeRange(MIN_POINT, MAX_POINT)
// getSize will return the dimensions of the screen in its current orientation
display.getSize(SIZE_POINT)
val attrs = intArrayOf(android.R.attr.windowFullscreen)
val theme = context.theme
val theme = activity.theme
val ta = theme.obtainStyledAttributes(attrs)
val windowFullscreen = ta.getBoolean(0, false)

// We need to add the status bar height to the height if we have a fullscreen window,
// because Display.getCurrentSizeRange doesn't include it.
var statusBarHeight = 0
if (windowFullscreen) {
statusBarHeight = StatusBarUtils.getStatusBarHeight(context)
statusBarHeight = SystemUiUtils.getStatusBarHeight(activity)
}
return if (SIZE_POINT.x < SIZE_POINT.y) {
// If we are vertical the width value comes from min width and height comes from max height
Expand All @@ -140,8 +141,10 @@ private fun getModalHostSize(context: Context): Point {
private class ModalHostShadowNode : LayoutShadowNode() {
override fun addChildAt(child: ReactShadowNodeImpl, i: Int) {
super.addChildAt(child, i)
val modalSize = getModalHostSize(themedContext)
child.setStyleWidth(modalSize.x.toFloat())
child.setStyleHeight(modalSize.y.toFloat())
themedContext?.currentActivity?.let {
val modalSize = getModalHostSize(it)
child.setStyleWidth(modalSize.x.toFloat())
child.setStyleHeight(modalSize.y.toFloat())
}
}
}

This file was deleted.

@@ -0,0 +1,168 @@
package com.reactnativenavigation.utils

import android.app.Activity
import android.graphics.Color
import android.graphics.Rect
import android.os.Build
import android.view.View
import android.view.Window
import androidx.annotation.ColorInt
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import kotlin.math.abs
import kotlin.math.ceil


object SystemUiUtils {
private const val STATUS_BAR_HEIGHT_M = 24
private const val STATUS_BAR_HEIGHT_L = 25
private const val STATUS_BAR_HEIGHT_TRANSLUCENCY = 0.65f
private var statusBarHeight = -1
var navigationBarDefaultColor = -1
private set


@JvmStatic
fun getStatusBarHeight(activity: Activity?): Int {
val res = if (statusBarHeight > 0) {
statusBarHeight
} else {
statusBarHeight = activity?.let {
val rectangle = Rect()
val window: Window = activity.window
window.decorView.getWindowVisibleDisplayFrame(rectangle)
val statusBarHeight: Int = rectangle.top
val contentView = window.findViewById<View>(Window.ID_ANDROID_CONTENT)
contentView?.let {
val contentViewTop = contentView.top
abs(contentViewTop - statusBarHeight)
}
} ?: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) STATUS_BAR_HEIGHT_M else STATUS_BAR_HEIGHT_L
statusBarHeight
}
return res
}

@JvmStatic
fun saveStatusBarHeight(height: Int) {
statusBarHeight = height
}


@JvmStatic
fun getStatusBarHeightDp(activity: Activity?): Int {
return UiUtils.pxToDp(activity, getStatusBarHeight(activity).toFloat())
.toInt()
}

@JvmStatic
fun hideNavigationBar(window: Window?, view: View) {
window?.let {
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, view).let { controller ->
controller.hide(WindowInsetsCompat.Type.navigationBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
}

@JvmStatic
fun showNavigationBar(window: Window?, view: View) {
window?.let {
WindowCompat.setDecorFitsSystemWindows(window, true)
WindowInsetsControllerCompat(window, view).show(WindowInsetsCompat.Type.navigationBars())
}
}

@JvmStatic
fun setStatusBarColorScheme(window: Window?, view: View, isDark: Boolean) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return

window?.let {
WindowInsetsControllerCompat(window, view).isAppearanceLightStatusBars = isDark
// Workaround: on devices with api 30 status bar icons flickers or get hidden when removing view
//turns out it is a bug on such devices, fixed by using system flags until it is fixed.
var flags = view.systemUiVisibility
flags = if (isDark) {
flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else {
flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
}

view.systemUiVisibility = flags
}
}

@JvmStatic
fun setStatusBarTranslucent(window: Window?) {
window?.let {
setStatusBarColor(window, window.statusBarColor, true)
}
}

@JvmStatic
fun isTranslucent(window: Window?): Boolean {
return window?.let {
Color.alpha(it.statusBarColor) < 255
} ?: false
}

@JvmStatic
fun clearStatusBarTranslucency(window: Window?) {
window?.let {
setStatusBarColor(it, it.statusBarColor, false)
}
}

@JvmStatic
fun setStatusBarColor(
window: Window?,
@ColorInt color: Int,
translucent: Boolean
) {
val opaqueColor = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Color.BLACK
}else{
val alpha = if (translucent) STATUS_BAR_HEIGHT_TRANSLUCENCY else 1f
val red: Int = Color.red(color)
val green: Int = Color.green(color)
val blue: Int = Color.blue(color)
Color.argb(ceil(alpha * 255).toInt(), red, green, blue)
}
window?.statusBarColor = opaqueColor
}

@JvmStatic
fun hideStatusBar(window: Window?, view: View) {
window?.let {
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, view).let { controller ->
controller.hide(WindowInsetsCompat.Type.statusBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
}

@JvmStatic
fun showStatusBar(window: Window?, view: View) {
window?.let {
WindowCompat.setDecorFitsSystemWindows(window, true)
WindowInsetsControllerCompat(window, view).show(WindowInsetsCompat.Type.statusBars())
}
}

@JvmStatic
fun setNavigationBarBackgroundColor(window: Window?, color: Int, lightColor: Boolean) {
window?.let {
if (navigationBarDefaultColor == -1) {
navigationBarDefaultColor = window.navigationBarColor
}
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.isAppearanceLightNavigationBars = lightColor
}
window.navigationBarColor = color
}
}

}

0 comments on commit eae5831

Please sign in to comment.