diff --git a/e2e/Modals.test.js b/e2e/Modals.test.js index b950a9a69c8..94f38f0ccc0 100644 --- a/e2e/Modals.test.js +++ b/e2e/Modals.test.js @@ -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(); }); }); diff --git a/lib/android/app/build.gradle b/lib/android/app/build.gradle index e0a280c8b5d..9f848de3158 100644 --- a/lib/android/app/build.gradle +++ b/lib/android/app/build.gradle @@ -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' diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java index ccae0c70fa4..2e8acc08270 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java @@ -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; @@ -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"; @@ -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; } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalFrameLayout.kt b/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalFrameLayout.kt index 3ee9ca54849..8f2ce3686f9 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalFrameLayout.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalFrameLayout.kt @@ -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) @@ -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) }) } } \ No newline at end of file diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalViewManager.kt b/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalViewManager.kt index 23a93c54060..2ab4afcd71e 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalViewManager.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalViewManager.kt @@ -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 @@ -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" @@ -107,18 +108,18 @@ class ModalViewManager(val reactContext: ReactContext) : ViewGroupManager 0) { - return statusBarHeight - } - val resources = context.resources - val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") - statusBarHeight = if (resourceId > 0) resources.getDimensionPixelSize(resourceId) else UiUtils.dpToPx( - context, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) STATUS_BAR_HEIGHT_M else STATUS_BAR_HEIGHT_L - ) - return statusBarHeight - } - @JvmStatic - fun getStatusBarHeightDp(context: Context): Int { - return UiUtils.pxToDp(context, getStatusBarHeight(context).toFloat()) - .toInt() - } - @JvmStatic - fun isTranslucent(window: Window): Boolean { - val lp = window.attributes - return lp != null && lp.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS == WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS - } -} \ No newline at end of file diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt b/lib/android/app/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt new file mode 100644 index 00000000000..d1133e13386 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt @@ -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(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 + } + } + +} \ No newline at end of file diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/child/ChildController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/child/ChildController.java index 0fc83e394e5..3acc41db26a 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/child/ChildController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/child/ChildController.java @@ -2,12 +2,16 @@ import android.app.Activity; import android.content.res.Configuration; +import android.os.Build; +import android.util.Log; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import com.reactnativenavigation.options.Options; +import com.reactnativenavigation.utils.LogKt; +import com.reactnativenavigation.viewcontrollers.parent.ParentController; import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter; -import com.reactnativenavigation.utils.StatusBarUtils; import com.reactnativenavigation.viewcontrollers.viewcontroller.NoOpYellowBoxDelegate; import com.reactnativenavigation.viewcontrollers.navigator.Navigator; import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController; @@ -15,7 +19,9 @@ import com.reactnativenavigation.views.component.Component; import androidx.annotation.CallSuper; +import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; +import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; public abstract class ChildController extends ViewController { @@ -61,7 +67,7 @@ public void onViewDisappear() { } public void onViewBroughtToFront() { - presenter.onViewBroughtToFront(resolveCurrentOptions()); + presenter.onViewBroughtToFront(this, resolveCurrentOptions()); } @Override @@ -73,7 +79,7 @@ public void applyOptions(Options options) { @Override public void mergeOptions(Options options) { if (options == Options.EMPTY) return; - if (isViewShown()) presenter.mergeOptions(getView(), options); + if (isViewShown()) presenter.mergeOptions(this, options); super.mergeOptions(options); performOnParentController(parentController -> parentController.mergeChildOptions(options, this)); } @@ -93,23 +99,13 @@ public boolean isRoot() { getView().getParent() != null; } - private WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets) { - StatusBarUtils.saveStatusBarHeight(insets.getSystemWindowInsetTop()); - return applyWindowInsets(findController(view), insets); - } - - protected WindowInsetsCompat applyWindowInsets(ViewController view, WindowInsetsCompat insets) { - return insets.replaceSystemWindowInsets( - insets.getSystemWindowInsetLeft(), - 0, - insets.getSystemWindowInsetRight(), - insets.getSystemWindowInsetBottom() - ); + protected WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets) { + return insets; } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - presenter.onConfigurationChanged(this,options); + presenter.onConfigurationChanged(this, options); } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java index e4e6704c4cc..c2fbac72a53 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java @@ -4,10 +4,11 @@ import android.content.res.Configuration; import android.view.View; +import com.reactnativenavigation.utils.LogKt; import com.reactnativenavigation.viewcontrollers.viewcontroller.ScrollEventListener; import com.reactnativenavigation.options.Options; import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter; -import com.reactnativenavigation.utils.StatusBarUtils; +import com.reactnativenavigation.utils.SystemUiUtils; import com.reactnativenavigation.viewcontrollers.viewcontroller.ReactViewCreator; import com.reactnativenavigation.viewcontrollers.child.ChildController; import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry; @@ -127,8 +128,9 @@ public void applyTopInset() { @Override public int getTopInset() { - int statusBarInset = resolveCurrentOptions(presenter.defaultOptions).statusBar.isHiddenOrDrawBehind() ? 0 : StatusBarUtils.getStatusBarHeight(getActivity()); - return statusBarInset + perform(getParentController(), 0, p -> p.getTopInset(this)); + int statusBarInset = resolveCurrentOptions(presenter.defaultOptions).statusBar.isHiddenOrDrawBehind() ? 0 : SystemUiUtils.getStatusBarHeight(getActivity()); + final Integer perform = perform(getParentController(), 0, p -> p.getTopInset(this)); + return statusBarInset + perform; } @Override @@ -137,14 +139,19 @@ public void applyBottomInset() { } @Override - protected WindowInsetsCompat applyWindowInsets(ViewController view, WindowInsetsCompat insets) { - final WindowInsetsCompat.Builder builder = new WindowInsetsCompat.Builder(); - final WindowInsetsCompat finalInsets = builder.setSystemWindowInsets(Insets.of(insets.getSystemWindowInsetLeft(), - Math.max(insets.getSystemWindowInsetTop() - getTopInset(), 0), - insets.getSystemWindowInsetRight(), - Math.max(insets.getSystemWindowInsetBottom() - getBottomInset(), 0))).build(); - ViewCompat.onApplyWindowInsets(view.getView(), finalInsets); - return finalInsets; + protected WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat insets) { + ViewController viewController = findController(view); + if (viewController == null || viewController.getView() == null) return insets; + final Insets keyboardInsets = insets.getInsets( WindowInsetsCompat.Type.ime()); + final Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() ); + final int visibleNavBar = resolveCurrentOptions(presenter.defaultOptions).navigationBar.isVisible.isTrueOrUndefined()?1:0; + final WindowInsetsCompat finalInsets = new WindowInsetsCompat.Builder().setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime(), + Insets.of(systemBarsInsets.left, + 0, + systemBarsInsets.right, + Math.max(visibleNavBar*systemBarsInsets.bottom,keyboardInsets.bottom)) + ).build(); + return ViewCompat.onApplyWindowInsets(viewController.getView(), finalInsets); } @Override diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java index 2d631071eea..e45f3f425d2 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/externalcomponent/ExternalComponentViewController.java @@ -10,7 +10,7 @@ import com.reactnativenavigation.react.events.ComponentType; import com.reactnativenavigation.react.events.EventEmitter; import com.reactnativenavigation.utils.CoordinatorLayoutUtils; -import com.reactnativenavigation.utils.StatusBarUtils; +import com.reactnativenavigation.utils.SystemUiUtils; import com.reactnativenavigation.viewcontrollers.child.ChildController; import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry; import com.reactnativenavigation.views.BehaviourDelegate; @@ -72,7 +72,7 @@ public void applyTopInset() { @Override public int getTopInset() { - int statusBarInset = resolveCurrentOptions().statusBar.drawBehind.isTrue() ? 0 : StatusBarUtils.getStatusBarHeight(getActivity()); + int statusBarInset = resolveCurrentOptions().statusBar.drawBehind.isTrue() ? 0 : SystemUiUtils.getStatusBarHeight(getActivity()); return statusBarInset + perform(getParentController(), 0, p -> p.getTopInset(this)); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java index 4fc0950ef75..09a79f7f9e5 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java @@ -36,7 +36,7 @@ import com.reactnativenavigation.utils.CollectionUtils; import com.reactnativenavigation.utils.ObjectUtils; import com.reactnativenavigation.utils.RenderChecker; -import com.reactnativenavigation.utils.StatusBarUtils; +import com.reactnativenavigation.utils.SystemUiUtils; import com.reactnativenavigation.utils.UiUtils; import com.reactnativenavigation.viewcontrollers.bottomtabs.BottomTabsController; import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarBackgroundViewController; @@ -244,24 +244,38 @@ private void applyTopBarOptions(Options options, StackController stack, ViewCont private void applyStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Options withDefault) { if (withDefault.statusBar.visible.isTrueOrUndefined() && withDefault.statusBar.drawBehind.isTrue()) { - topBar.setTopPadding(StatusBarUtils.getStatusBarHeight(activity)); - topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + StatusBarUtils.getStatusBarHeightDp(activity)); + topBar.setTopPadding(SystemUiUtils.getStatusBarHeight(activity)); + topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + SystemUiUtils.getStatusBarHeightDp(activity)); + } else { topBar.setTopPadding(0); topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity))); } } - - private void mergeStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Options toMerge) { - if (toMerge.statusBar.drawBehind.hasValue()) { - if (toMerge.statusBar.visible.isTrueOrUndefined() && toMerge.statusBar.drawBehind.isTrue()) { - topBar.setTopPadding(StatusBarUtils.getStatusBarHeight(activity)); - topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + StatusBarUtils.getStatusBarHeightDp(activity)); - } else { - topBar.setTopPadding(0); - topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity))); + + private void mergeStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Options childOptions) { + if(childOptions.statusBar.visible.isTrueOrUndefined()){ + if (childOptions.statusBar.drawBehind.hasValue()) { + if (childOptions.statusBar.drawBehind.isTrue()) { + topBar.setTopPadding(SystemUiUtils.getStatusBarHeight(activity)); + topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + SystemUiUtils.getStatusBarHeightDp(activity)); + } else { + topBar.setTopPadding(0); + topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity))); + } + } + }else{ + if (childOptions.statusBar.drawBehind.hasValue()) { + if (childOptions.statusBar.drawBehind.isFalseOrUndefined()) { + topBar.setTopPadding(SystemUiUtils.getStatusBarHeight(activity)); + topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + SystemUiUtils.getStatusBarHeightDp(activity)); + } else { + topBar.setTopPadding(0); + topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity))); + } } } + } @Nullable @@ -351,7 +365,6 @@ public List getAdditionalSetRootAnimations(StackController stack, View perform(bottomTabsController, null, btc -> btc.getSetStackRootAnimation(appearingOptions)) ); } - public void mergeChildOptions(Options toMerge, Options resolvedOptions, StackController stack, ViewController child) { TopBarOptions topBar = toMerge.copy().topBar.mergeWithDefault(resolvedOptions.topBar).mergeWithDefault(defaultOptions.topBar); mergeOrientation(toMerge.layout.orientation); @@ -485,16 +498,18 @@ private List mergeButtonsWithColor(@NonNull List b return result; } - private void mergeTopBarOptions(TopBarOptions resolveOptions, Options options, StackController stack, ViewController child) { - TopBarOptions topBarOptions = options.topBar; + private void mergeTopBarOptions(TopBarOptions resolveOptions, Options toMerge, StackController stack, + ViewController child) { + TopBarOptions topBarOptions = toMerge.topBar; final View component = child.getView(); - if (options.layout.direction.hasValue()) topBar.setLayoutDirection(options.layout.direction); + if (toMerge.layout.direction.hasValue()) topBar.setLayoutDirection(toMerge.layout.direction); if (topBarOptions.height.hasValue()) topBar.setHeight(topBarOptions.height.get()); if (topBarOptions.elevation.hasValue()) topBar.setElevation(topBarOptions.elevation.get()); if (topBarOptions.topMargin.hasValue() && topBar.getLayoutParams() instanceof MarginLayoutParams) { ((MarginLayoutParams) topBar.getLayoutParams()).topMargin = UiUtils.dpToPx(activity, topBarOptions.topMargin.get()); } - mergeStatusBarDrawBehindOptions(resolveOptions, options); + Options childOptions = stack.resolveChildOptions(child).mergeWith(toMerge).withDefaultOptions(defaultOptions); + mergeStatusBarDrawBehindOptions(resolveOptions, childOptions); if (topBarOptions.title.height.hasValue()) topBar.setTitleHeight(topBarOptions.title.height.get()); if (topBarOptions.title.topMargin.hasValue()) topBar.setTitleTopMargin(topBarOptions.title.topMargin.get()); @@ -652,7 +667,7 @@ private int getTopBarTranslationAnimationDelta(StackController stack, ViewContro private int getTopBarTopMargin(StackController stack, ViewController child) { Options withDefault = stack.resolveChildOptions(child).withDefaultOptions(defaultOptions); int topMargin = UiUtils.dpToPx(activity, withDefault.topBar.topMargin.get(0)); - int statusBarInset = withDefault.statusBar.visible.isTrueOrUndefined() && !withDefault.statusBar.drawBehind.isTrue() ? StatusBarUtils.getStatusBarHeight(child.getActivity()) : 0; + int statusBarInset = withDefault.statusBar.visible.isTrueOrUndefined() && !withDefault.statusBar.drawBehind.isTrue() ? SystemUiUtils.getStatusBarHeight(child.getActivity()) : 0; return topMargin + statusBarInset; } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/Presenter.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/Presenter.java index 9feab908f3e..fa6292eced8 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/Presenter.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/Presenter.java @@ -10,47 +10,52 @@ import android.view.ViewGroup.MarginLayoutParams; import android.view.Window; +import androidx.core.content.ContextCompat; + import com.reactnativenavigation.options.NavigationBarOptions; import com.reactnativenavigation.options.Options; import com.reactnativenavigation.options.OrientationOptions; import com.reactnativenavigation.options.StatusBarOptions; import com.reactnativenavigation.options.StatusBarOptions.TextColorScheme; import com.reactnativenavigation.options.params.Bool; -import com.reactnativenavigation.utils.StatusBarUtils; +import com.reactnativenavigation.utils.SystemUiUtils; import com.reactnativenavigation.viewcontrollers.parent.ParentController; import com.reactnativenavigation.viewcontrollers.navigator.Navigator; -import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; - public class Presenter { private final Activity activity; private Options defaultOptions; - public Presenter(Activity activity, Options defaultOptions) { this.activity = activity; this.defaultOptions = defaultOptions; + } public void setDefaultOptions(Options defaultOptions) { this.defaultOptions = defaultOptions; } - public void mergeOptions(View view, Options options) { - mergeStatusBarOptions(view, options.statusBar); - mergeNavigationBarOptions(options.navigationBar); + public Options getDefaultOptions() { + return defaultOptions; + } + + public void mergeOptions(ViewController viewController, Options options) { + final Options withDefaults = viewController.resolveCurrentOptions().copy().mergeWith(options).withDefaultOptions(defaultOptions); + mergeStatusBarOptions(viewController.getView(), withDefaults.statusBar); + mergeNavigationBarOptions(withDefaults.navigationBar); } public void applyOptions(ViewController view, Options options) { Options withDefaultOptions = options.copy().withDefaultOptions(defaultOptions); applyOrientation(withDefaultOptions.layout.orientation); applyViewOptions(view, withDefaultOptions); - applyStatusBarOptions(withDefaultOptions); + applyStatusBarOptions(view, withDefaultOptions); applyNavigationBarOptions(withDefaultOptions.navigationBar); } - public void onViewBroughtToFront(Options options) { + public void onViewBroughtToFront(ViewController viewController, Options options) { Options withDefaultOptions = options.copy().withDefaultOptions(defaultOptions); - applyStatusBarOptions(withDefaultOptions); + applyStatusBarOptions(viewController, withDefaultOptions); } private void applyOrientation(OrientationOptions options) { @@ -73,7 +78,7 @@ private void applyBackgroundColor(ViewController view, Options options) { if (view instanceof Navigator) return; LayerDrawable ld = new LayerDrawable(new Drawable[]{new ColorDrawable(options.layout.backgroundColor.get())}); - int top = view.resolveCurrentOptions().statusBar.drawBehind.isTrue() ? 0 : StatusBarUtils.getStatusBarHeight(view.getActivity()); + int top = view.resolveCurrentOptions().statusBar.drawBehind.isTrue() ? 0 : SystemUiUtils.getStatusBarHeight(view.getActivity()); if (!(view instanceof ParentController)) { MarginLayoutParams lp = (MarginLayoutParams) view.getView().getLayoutParams(); if (lp.topMargin != 0) top = 0; @@ -83,37 +88,37 @@ private void applyBackgroundColor(ViewController view, Options options) { } } - private void applyStatusBarOptions(Options options) { + private void applyStatusBarOptions(ViewController viewController, Options options) { StatusBarOptions statusBar = options.copy().withDefaultOptions(defaultOptions).statusBar; setStatusBarBackgroundColor(statusBar); setTextColorScheme(statusBar); setTranslucent(statusBar); - setStatusBarVisible(statusBar.visible); + setStatusBarVisible(viewController, statusBar.visible); } private void setTranslucent(StatusBarOptions options) { Window window = activity.getWindow(); if (options.translucent.isTrue()) { - window.setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS); - } else if (StatusBarUtils.isTranslucent(window)) { - window.clearFlags(FLAG_TRANSLUCENT_STATUS); + SystemUiUtils.setStatusBarTranslucent(window); + } else if (SystemUiUtils.isTranslucent(window)) { + SystemUiUtils.clearStatusBarTranslucency(window); } } - private void setStatusBarVisible(Bool visible) { - View decorView = activity.getWindow().getDecorView(); - int flags = decorView.getSystemUiVisibility(); + private void setStatusBarVisible(ViewController viewController, Bool visible) { + final View view = viewController.view != null ? viewController.view : activity.getWindow().getDecorView(); if (visible.isFalse()) { - flags |= View.SYSTEM_UI_FLAG_FULLSCREEN; + SystemUiUtils.hideStatusBar(activity.getWindow(), view); } else { - flags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN; + SystemUiUtils.showStatusBar(activity.getWindow(), view); } - decorView.setSystemUiVisibility(flags); } private void setStatusBarBackgroundColor(StatusBarOptions statusBar) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && statusBar.backgroundColor.canApplyValue()) { - activity.getWindow().setStatusBarColor(getStatusBarBackgroundColor(statusBar)); + if (statusBar.backgroundColor.canApplyValue()) { + final int statusBarBackgroundColor = getStatusBarBackgroundColor(statusBar); + SystemUiUtils.setStatusBarColor(activity.getWindow(), statusBarBackgroundColor, + statusBar.translucent.isTrue()); } } @@ -133,20 +138,11 @@ private int getStatusBarBackgroundColor(StatusBarOptions statusBar) { } private void setTextColorScheme(StatusBarOptions statusBar) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; - final View view = activity.getWindow().getDecorView(); //View.post is a Workaround, added to solve internal Samsung //Android 9 issues. For more info see https://github.com/wix/react-native-navigation/pull/7231 - view.post(()->{ - int flags = view.getSystemUiVisibility(); - if (isDarkTextColorScheme(statusBar)) { - flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } else { - flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } - - view.setSystemUiVisibility(flags); + view.post(() -> { + SystemUiUtils.setStatusBarColorScheme(activity.getWindow(), view, isDarkTextColorScheme(statusBar)); }); } @@ -154,12 +150,14 @@ private void mergeStatusBarOptions(View view, StatusBarOptions statusBar) { mergeStatusBarBackgroundColor(statusBar); mergeTextColorScheme(statusBar); mergeTranslucent(statusBar); - mergeStatusBarVisible(view, statusBar.visible, statusBar.drawBehind); + mergeStatusBarVisible(view, statusBar.visible); } private void mergeStatusBarBackgroundColor(StatusBarOptions statusBar) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && statusBar.backgroundColor.hasValue()) { - activity.getWindow().setStatusBarColor(getStatusBarBackgroundColor(statusBar)); + if (statusBar.backgroundColor.hasValue()) { + final int statusBarBackgroundColor = getStatusBarBackgroundColor(statusBar); + SystemUiUtils.setStatusBarColor(activity.getWindow(), statusBarBackgroundColor, + statusBar.translucent.isTrue()); } } @@ -171,22 +169,19 @@ private void mergeTextColorScheme(StatusBarOptions statusBar) { private void mergeTranslucent(StatusBarOptions options) { Window window = activity.getWindow(); if (options.translucent.isTrue()) { - window.setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS); - } else if (options.translucent.isFalse() && StatusBarUtils.isTranslucent(window)) { - window.clearFlags(FLAG_TRANSLUCENT_STATUS); + SystemUiUtils.setStatusBarTranslucent(window); + } else if (options.translucent.isFalse() && SystemUiUtils.isTranslucent(window)) { + SystemUiUtils.clearStatusBarTranslucency(window); } } - private void mergeStatusBarVisible(View view, Bool visible, Bool drawBehind) { + private void mergeStatusBarVisible(View view, Bool visible) { if (visible.hasValue()) { - int flags = view.getSystemUiVisibility(); if (visible.isTrue()) { - flags &= ~View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN & ~View.SYSTEM_UI_FLAG_FULLSCREEN; + SystemUiUtils.showStatusBar(activity.getWindow(), view); } else { - flags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN; + SystemUiUtils.hideStatusBar(activity.getWindow(), view); } - if (flags != view.getSystemUiVisibility()) view.requestLayout(); - view.setSystemUiVisibility(flags); } } @@ -206,36 +201,22 @@ private void mergeNavigationBarVisibility(NavigationBarOptions options) { private void applyNavigationBarVisibility(NavigationBarOptions options) { View decorView = activity.getWindow().getDecorView(); - int flags = decorView.getSystemUiVisibility(); - boolean defaultVisibility = (flags & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0; - int hideNavigationBarFlags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - if (options.isVisible.get(defaultVisibility)) { - flags &= ~hideNavigationBarFlags; + if (options.isVisible.isTrueOrUndefined()) { + SystemUiUtils.showNavigationBar(activity.getWindow(), decorView); } else { - flags |= hideNavigationBarFlags; + SystemUiUtils.hideNavigationBar(activity.getWindow(), decorView); } - decorView.setSystemUiVisibility(flags); } private void setNavigationBarBackgroundColor(NavigationBarOptions navigationBar) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && navigationBar.backgroundColor.canApplyValue()) { - int defaultColor = activity.getWindow().getNavigationBarColor(); - int color = navigationBar.backgroundColor.get(defaultColor); - activity.getWindow().setNavigationBarColor(color); - setNavigationBarButtonsColor(color); - } - } + int navigationBarDefaultColor = SystemUiUtils.INSTANCE.getNavigationBarDefaultColor(); + navigationBarDefaultColor = navigationBarDefaultColor==-1?Color.BLACK:navigationBarDefaultColor; + if (navigationBar.backgroundColor.canApplyValue()) { + int color = navigationBar.backgroundColor.get(navigationBarDefaultColor); + SystemUiUtils.setNavigationBarBackgroundColor(activity.getWindow(), color, isColorLight(color)); + }else{ + SystemUiUtils.setNavigationBarBackgroundColor(activity.getWindow(), navigationBarDefaultColor, isColorLight(navigationBarDefaultColor)); - private void setNavigationBarButtonsColor(int color) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - View decorView = activity.getWindow().getDecorView(); - int flags = decorView.getSystemUiVisibility(); - if (isColorLight(color)) { - flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; - } else { - flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; - } - decorView.setSystemUiVisibility(flags); } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/views/component/ComponentLayout.java b/lib/android/app/src/main/java/com/reactnativenavigation/views/component/ComponentLayout.java index ad74ecc260c..845b882ed9a 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/views/component/ComponentLayout.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/views/component/ComponentLayout.java @@ -4,6 +4,7 @@ import android.content.Context; import android.view.MotionEvent; import android.view.ViewGroup; +import android.view.WindowInsets; import com.reactnativenavigation.options.ButtonOptions; import com.reactnativenavigation.viewcontrollers.viewcontroller.ScrollEventListener; @@ -99,6 +100,7 @@ public void onPress(ButtonOptions button) { reactView.sendOnNavigationButtonPressed(button.id); } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return touchDelegate.onInterceptTouchEvent(ev); diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ReactImageMatrixAnimator.kt b/lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ReactImageMatrixAnimator.kt index 6eb09d74266..1d8f81ce235 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ReactImageMatrixAnimator.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ReactImageMatrixAnimator.kt @@ -13,7 +13,6 @@ import com.reactnativenavigation.options.SharedElementTransitionOptions import com.reactnativenavigation.utils.ViewUtils import kotlin.math.max import kotlin.math.roundToInt -import android.animation.TypeEvaluator class ReactImageMatrixAnimator(from: View, to: View) : PropertyAnimatorCreator(from, to) { override fun shouldAnimateProperty(fromChild: ReactImageView, toChild: ReactImageView): Boolean { @@ -39,7 +38,7 @@ class ReactImageMatrixAnimator(from: View, to: View) : PropertyAnimatorCreator { fraction: Float, _: Any, _: Any -> + return ObjectAnimator.ofObject({ fraction: Float, _: Any, _: Any -> hierarchy.actualImageScaleType?.let { (hierarchy.actualImageScaleType as? InterpolatingScaleType)?.let { it.value = fraction @@ -47,7 +46,7 @@ class ReactImageMatrixAnimator(from: View, to: View) : PropertyAnimatorCreator theMock = Mockito.mockStatic(StatusBarUtils.class)) { + + public void mockSystemUiUtils(int statusBarHeight, int statusBarHeightDp, Functions.Func1> mockedBlock) { + try (MockedStatic theMock = Mockito.mockStatic(SystemUiUtils.class)) { theMock.when(() -> { - StatusBarUtils.getStatusBarHeight(any()); + SystemUiUtils.getStatusBarHeight(any()); }).thenReturn(statusBarHeight); theMock.when(() -> { - StatusBarUtils.getStatusBarHeightDp(any()); + SystemUiUtils.getStatusBarHeightDp(any()); }).thenReturn(statusBarHeightDp); - block.run(); + mockedBlock.run(theMock); } } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/presentation/PresenterTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/presentation/PresenterTest.java index b487eb27ded..a57ff3c86ec 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/presentation/PresenterTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/presentation/PresenterTest.java @@ -1,41 +1,56 @@ package com.reactnativenavigation.presentation; import android.app.Activity; +import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import com.reactnativenavigation.BaseTest; import com.reactnativenavigation.options.Options; import com.reactnativenavigation.options.params.Bool; +import com.reactnativenavigation.utils.SystemUiUtils; import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter; +import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController; import org.junit.Test; import org.mockito.Mockito; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class PresenterTest extends BaseTest { private Presenter uut; private Activity activity; + private ViewController controller; @Override public void beforeEach() { super.beforeEach(); activity = newActivity(); + controller = mock(ViewController.class); uut = new Presenter(activity, Options.EMPTY); } @Test - public void mergeStatusBarVisible_requestLayout() { - ViewGroup spy = Mockito.spy(new FrameLayout(activity)); - Options options = new Options(); - options.statusBar.visible = new Bool(false); + public void mergeStatusBarVisible_callsShowHide() { + mockSystemUiUtils(1,1,(mockedStatic)->{ + ViewGroup spy = Mockito.spy(new FrameLayout(activity)); + Mockito.when(controller.getView()).thenReturn(spy); + Mockito.when(controller.resolveCurrentOptions()).thenReturn(Options.EMPTY); + Options options = new Options(); + options.statusBar.visible = new Bool(false); + uut.mergeOptions(controller, options); + mockedStatic.verify( + ()-> SystemUiUtils.hideStatusBar(any(),eq(spy)),times(1)); + + options.statusBar.visible = new Bool(true); + uut.mergeOptions(controller, options); + mockedStatic.verify( + ()-> SystemUiUtils.showStatusBar(any(),eq(spy)),times(1)); + }); - uut.mergeOptions(spy, options); - verify(spy).requestLayout(); - - // requested only if needed - uut.mergeOptions(spy, options); - verify(spy).requestLayout(); } } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java index bb1ef6cbe57..617d9acc1eb 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java @@ -24,7 +24,7 @@ import com.reactnativenavigation.react.events.EventEmitter; import com.reactnativenavigation.utils.ImageLoader; import com.reactnativenavigation.utils.OptionHelper; -import com.reactnativenavigation.utils.StatusBarUtils; +import com.reactnativenavigation.utils.SystemUiUtils; import com.reactnativenavigation.viewcontrollers.bottomtabs.attacher.BottomTabsAttacher; import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry; import com.reactnativenavigation.viewcontrollers.fakes.FakeParentController; @@ -87,7 +87,7 @@ public void beforeEach() { childRegistry = new ChildControllersRegistry(); eventEmitter = Mockito.mock(EventEmitter.class); prepareViewsForTests(); - StatusBarUtils.saveStatusBarHeight(63); + SystemUiUtils.saveStatusBarHeight(63); } @Test @@ -184,6 +184,9 @@ public void handleBack_DelegatesToSelectedChild() { @Test public void applyChildOptions_bottomTabsOptionsAreClearedAfterApply() { ParentController parent = Mockito.mock(ParentController.class); + Mockito.when(parent.resolveChildOptions(uut)).thenReturn(Options.EMPTY); + Mockito.when(parent.resolveChildOptions(child1)).thenReturn(Options.EMPTY); + uut.setParentController(parent); child1.options.bottomTabsOptions.backgroundColor = new ThemeColour(new Colour(Color.RED)); @@ -506,6 +509,6 @@ protected BottomTabs createBottomTabs() { } private int getStatusBarHeight() { - return StatusBarUtils.getStatusBarHeight(activity); + return SystemUiUtils.getStatusBarHeight(activity); } } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllerTest.java index e32434e973a..500a4136110 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/child/ChildControllerTest.java @@ -32,6 +32,7 @@ public Options resolveCurrentOptions() { } }; ParentController parent = Mockito.mock(ParentController.class); + Mockito.when(parent.resolveChildOptions(uut)).thenReturn(Options.EMPTY); uut.setParentController(parent); } @@ -55,7 +56,7 @@ public void mergeOptions() { Options options = new Options(); uut.mergeOptions(options); - verify(presenter).mergeOptions(uut.getView(), options); + verify(presenter).mergeOptions(uut, options); } @Test diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewControllerTest.java index a03eb5b85ec..d112d179ab3 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewControllerTest.java @@ -11,7 +11,7 @@ import com.reactnativenavigation.mocks.TestReactView; import com.reactnativenavigation.options.Options; import com.reactnativenavigation.options.params.Bool; -import com.reactnativenavigation.utils.StatusBarUtils; +import com.reactnativenavigation.utils.SystemUiUtils; import com.reactnativenavigation.views.component.ComponentLayout; import org.assertj.core.api.Java6Assertions; @@ -31,7 +31,7 @@ public class ComponentViewControllerTest extends BaseTest { public void beforeEach() { super.beforeEach(); activity = newActivity(); - StatusBarUtils.saveStatusBarHeight(63); + SystemUiUtils.saveStatusBarHeight(63); view = Mockito.spy(new TestComponentLayout(activity, new TestReactView(activity))); parent = TestUtils.newStackController(activity).build(); Presenter presenter = new Presenter(activity, new Options()); @@ -156,13 +156,13 @@ public void applyTopInset_delegatesToPresenter() { public void getTopInset_returnsStatusBarHeight() { //noinspection ConstantConditions uut.setParentController(null); - Java6Assertions.assertThat(uut.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity)); + Java6Assertions.assertThat(uut.getTopInset()).isEqualTo(SystemUiUtils.getStatusBarHeight(activity)); } @Test public void getTopInset_resolveWithParent() { Java6Assertions - .assertThat(uut.getTopInset()).isEqualTo(StatusBarUtils.getStatusBarHeight(activity) + parent.getTopInset(uut)); + .assertThat(uut.getTopInset()).isEqualTo(SystemUiUtils.getStatusBarHeight(activity) + parent.getTopInset(uut)); } @Test diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/parent/ParentControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/parent/ParentControllerTest.java index 5e2fe6401ef..3b6eb9ddb68 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/parent/ParentControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/parent/ParentControllerTest.java @@ -264,6 +264,8 @@ public void getTopInset() { @Test public void getTopInsetForChild() { ParentController parent = Mockito.mock(ParentController.class); + Mockito.when(parent.resolveChildOptions(uut)).thenReturn(Options.EMPTY); + when(parent.getTopInset(any())).thenReturn(123); uut.setParentController(parent); @@ -280,6 +282,8 @@ public void applyBottomInset() { @Test public void getBottomInsetForChild() { ParentController parent = Mockito.mock(ParentController.class); + Mockito.when(parent.resolveChildOptions(uut)).thenReturn(Options.EMPTY); + when(parent.getBottomInset(any())).thenReturn(123); uut.setParentController(parent); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuControllerTest.java index 1a5b6d709d9..e59e4783d32 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/sidemenu/SideMenuControllerTest.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.content.res.Resources; +import android.graphics.Path; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -55,6 +56,7 @@ public class SideMenuControllerTest extends BaseTest { @Override public void beforeEach() { + super.beforeEach(); activity = createActivity(); childRegistry = new ChildControllersRegistry(); @@ -72,6 +74,7 @@ public Options resolveCurrentOptions() { }; uut.setCenterController(center); parent = mock(ParentController.class); + Mockito.when(parent.resolveChildOptions(uut)).thenReturn(Options.EMPTY); uut.setParentController(parent); } @@ -118,12 +121,12 @@ public void getCurrentChild() { @Test public void onViewAppeared() { - ViewController left = spy(this.left); + ViewController left = spy(this.left); ViewGroup leftView = spy(left.getView()); when(left.findController(leftView)).thenReturn(left); Mockito.doReturn(leftView).when(left).getView(); - ViewController right = spy(this.right); + ViewController right = spy(this.right); ViewGroup rightView = spy(right.getView()); when(right.findController(rightView)).thenReturn(right); Mockito.doReturn(rightView).when(right).getView(); @@ -369,6 +372,7 @@ public void applyTopInsets_delegatesToChildren() { @Test public void onMeasureChild_topInsetsAreApplied() { setLeftRight(spy(left), spy(right)); + idleMainLooper(); uut.applyTopInset(); forEach(uut.getChildControllers(), c -> verify(c).applyTopInset()); } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.kt index ad7d06ea8c5..7d1b4deb532 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.kt @@ -61,7 +61,7 @@ class StackControllerTest : BaseTest() { eventEmitter = mock() backButtonHelper = spy(BackButtonHelper()) activity = newActivity() - StatusBarUtils.saveStatusBarHeight(63) + SystemUiUtils.saveStatusBarHeight(63) animator = spy(StackAnimator(activity)) childRegistry = ChildControllersRegistry() presenter = spy(StackPresenter( @@ -1089,7 +1089,7 @@ class StackControllerTest : BaseTest() { disablePushAnimation(child1) uut.push(child1, CommandListenerAdapter()) ShadowLooper.idleMainLooper() - assertThat(ViewUtils.topMargin(uut.topBar)).isEqualTo(StatusBarUtils.getStatusBarHeight(activity)) + assertThat(ViewUtils.topMargin(uut.topBar)).isEqualTo(SystemUiUtils.getStatusBarHeight(activity)) } @Test diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt index 5470d53cf03..4b1b3ce0d85 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt @@ -945,7 +945,7 @@ class StackPresenterTest : BaseTest() { statusBar.drawBehind = Bool(true) } Mockito.`when`(child.resolveCurrentOptions()).thenReturn(options) - mockStatusBarUtils(statusBarHeight, statusBarHeightDp) { + mockSystemUiUtils(statusBarHeight, statusBarHeightDp) { uut.applyChildOptions(Options.EMPTY.copy().apply { topBar.height = Number(topBarHeightDp) }, parent, child) @@ -961,7 +961,7 @@ class StackPresenterTest : BaseTest() { val statusBarHeightDp = 20 val topBarHeightDp = 100 - mockStatusBarUtils(statusBarHeight, statusBarHeightDp) { + mockSystemUiUtils(statusBarHeight, statusBarHeightDp) { uut.mergeChildOptions(Options.EMPTY.copy().apply { topBar.height = Number(topBarHeightDp) statusBar.drawBehind = Bool(true) @@ -978,7 +978,7 @@ class StackPresenterTest : BaseTest() { val statusBarHeightDp = 20 val topBarHeightDp = 100 - mockStatusBarUtils(statusBarHeight, statusBarHeightDp) { + mockSystemUiUtils(statusBarHeight, statusBarHeightDp) { uut.mergeChildOptions(Options.EMPTY.copy().apply { topBar.height = Number(topBarHeightDp) statusBar.drawBehind = Bool(false) diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/viewcontroller/ViewControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/viewcontroller/ViewControllerTest.java index adf30de646e..90c29e32e16 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/viewcontroller/ViewControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/viewcontroller/ViewControllerTest.java @@ -31,6 +31,7 @@ import java.lang.reflect.Field; import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -51,7 +52,9 @@ public void beforeEach() { activity = newActivity(); childRegistry = new ChildControllersRegistry(); uut = new SimpleViewController(activity, childRegistry, "uut", new Options()); - uut.setParentController(mock(ParentController.class)); + final ParentController parent = mock(ParentController.class); + uut.setParentController(parent); + Mockito.when(parent.resolveChildOptions(any())).thenReturn(Options.EMPTY); } @Test diff --git a/package.json b/package.json index e56ce67ec32..d02715a87bf 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "metro-react-native-babel-preset": "0.66.2", "prettier": "2.1.2", "react": "17.0.2", - "react-native": "0.66.2", + "react-native": "0.66.3", "react-native-fast-image": "^8.3.4", "react-native-gesture-handler": "^1.6.1", "react-native-reanimated": "2.3.0-beta.2", diff --git a/playground/android/app/build.gradle b/playground/android/app/build.gradle index c156cec030c..5820f7e1e79 100644 --- a/playground/android/app/build.gradle +++ b/playground/android/app/build.gradle @@ -54,8 +54,8 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.google.android.material:material:1.0.0' - implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.appcompat:appcompat:1.3.1' //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' diff --git a/playground/android/app/src/main/AndroidManifest.xml b/playground/android/app/src/main/AndroidManifest.xml index 292a7424c8e..a149613daaf 100644 --- a/playground/android/app/src/main/AndroidManifest.xml +++ b/playground/android/app/src/main/AndroidManifest.xml @@ -13,13 +13,15 @@ android:supportsRtl="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true" - tools:ignore="GoogleAppIndexingWarning"> + tools:ignore="GoogleAppIndexingWarning" + tools:targetApi="m"> + android:windowSoftInputMode="adjustResize" + android:exported="true"> diff --git a/playground/android/app/src/main/res/values/attrs.xml b/playground/android/app/src/main/res/values/attrs.xml new file mode 100644 index 00000000000..b4a13fc49a0 --- /dev/null +++ b/playground/android/app/src/main/res/values/attrs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/playground/src/commons/Colors.ts b/playground/src/commons/Colors.ts index 608e0f53963..eef5fea7009 100644 --- a/playground/src/commons/Colors.ts +++ b/playground/src/commons/Colors.ts @@ -2,6 +2,7 @@ import { Platform, PlatformColor } from 'react-native'; const colors = { background: { light: '#e8e8e8', dark: '#282528' }, + statusBarColor: { light: '#FFFFFF', dark: '#282528' }, barBackground: { light: 'white', dark: '#282528' }, primary: { light: '#5847ff', dark: '#BA292E' }, secondary: { light: '#FFC249', dark: '#5847ff' }, diff --git a/playground/src/commons/options/Options.ts b/playground/src/commons/options/Options.ts index c09850947a3..2f9f250261f 100644 --- a/playground/src/commons/options/Options.ts +++ b/playground/src/commons/options/Options.ts @@ -5,6 +5,9 @@ import animations from './Animations'; const setDefaultOptions = () => Navigation.setDefaultOptions({ animations, + statusBar: { + backgroundColor: Colors.statusBarColor, + }, window: { backgroundColor: Colors.primary, }, diff --git a/playground/src/screens/ModalCommandsScreen.tsx b/playground/src/screens/ModalCommandsScreen.tsx index bb56ccf1135..e64cb3d9dae 100644 --- a/playground/src/screens/ModalCommandsScreen.tsx +++ b/playground/src/screens/ModalCommandsScreen.tsx @@ -92,5 +92,5 @@ export default class ModalScreen extends NavigationComponent Navigation.showModal(Screens.StatusBar); + showSideMenuModal = () => Navigation.showModal(Screens.SystemUi); } diff --git a/playground/src/screens/OptionsScreen.tsx b/playground/src/screens/OptionsScreen.tsx index da9dafe91d8..a391df08556 100644 --- a/playground/src/screens/OptionsScreen.tsx +++ b/playground/src/screens/OptionsScreen.tsx @@ -66,7 +66,7 @@ export default class Options extends React.Component { testID={GOTO_BUTTONS_SCREEN} onPress={this.pushButtonsScreen} /> -