Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4162390
Apply color animations on tab-switching (Android-only) (#7975)
d4vidi Mar 5, 2025
9205aa0
Update package.json version to 7.44.0 [buildkite skip]
mobileoss Mar 5, 2025
749936c
Fix npm registry and recreate package-lock.json (#7987)
d4vidi Mar 31, 2025
a9a2e8e
fix(sdk35): Apply inset over bottom-tabs (#7991)
d4vidi Apr 3, 2025
9121357
Update package.json version to 7.45.0 [buildkite skip]
mobileoss Apr 6, 2025
4509824
Revert "Feat/rn76 oldarch (#7967)" (#8003)
d4vidi Apr 17, 2025
9b84952
Update package.json version to 7.46.0 [buildkite skip]
mobileoss Apr 17, 2025
1ee3747
Mocked side-menus - support (#8017)
d4vidi May 4, 2025
d3d6961
Update package.json version to 7.47.0 [buildkite skip]
mobileoss May 4, 2025
0bdfb20
Support drawer menus opening as overlay instead of pushing content in…
liatnetach May 27, 2025
a3a05d5
Update package.json version to 7.48.0 [buildkite skip]
mobileoss May 27, 2025
b38690d
Patch-fix some recent side-menu overlay mode changes (#8039)
d4vidi Jun 3, 2025
06195fd
Introduce options to make a floating bottom-tabs (Android) (#8064)
d4vidi Jul 27, 2025
ab1a697
Update package.json version to 7.49.0 [buildkite skip]
mobileoss Jul 27, 2025
18db32e
fix: top bar buttons disappeared when lock screen then unlock sreen (…
gosha212 Jul 30, 2025
37717da
Update package.json version to 7.50.0 [buildkite skip]
mobileoss Jul 30, 2025
2257ec8
Back-compat edge2edge for standard layout, incl. drawBehind
d4vidi Aug 7, 2025
6f66d03
merge master into me
markdevocht Mar 30, 2026
04d5371
first config to get it running
markdevocht Mar 30, 2026
b3ee6c1
bottom bar edge2edge complete?
markdevocht Mar 30, 2026
f0a8c75
statusbar e2e
markdevocht Mar 30, 2026
730eabb
cleanup
markdevocht Mar 30, 2026
57681c9
backward compatiblility
markdevocht Mar 30, 2026
5ea1125
final touches
markdevocht Mar 30, 2026
e0ac2db
Merge remote-tracking branch 'origin/master' into feat/edge2edge-android
markdevocht Mar 30, 2026
e4a6cbd
Fix yarn registry to use public npmjs.org instead of internal wixpress
markdevocht Mar 31, 2026
88bb092
https://github.com/wix/react-native-navigation/issues/8080
markdevocht Mar 31, 2026
0e13a7b
conflict fixes
markdevocht Mar 31, 2026
8350ea4
snapshot updates
markdevocht Mar 31, 2026
8d5f259
more updates
markdevocht Mar 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ npmMinimalAgeGate: 14d

nodeLinker: node-modules

npmRegistryServer: "https://npm.dev.wixpress.com"
npmRegistryServer: "https://registry.npmjs.org"

yarnPath: .yarn/releases/yarn-4.12.0.cjs
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout:2.0.4"

implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.activity:activity:1.9.0'
implementation 'androidx.annotation:annotation:1.2.0'
implementation 'com.google.android.material:material:1.2.0-alpha03'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;

import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionAwareActivity;
Expand All @@ -17,10 +18,12 @@
import com.reactnativenavigation.react.JsDevReloadHandler;
import com.reactnativenavigation.react.ReactGateway;
import com.reactnativenavigation.react.CommandListenerAdapter;
import com.reactnativenavigation.utils.SystemUiUtils;
import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry;
import com.reactnativenavigation.viewcontrollers.modal.ModalStack;
import com.reactnativenavigation.viewcontrollers.navigator.Navigator;

import androidx.activity.EdgeToEdge;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand All @@ -36,6 +39,7 @@ public class NavigationActivity extends AppCompatActivity implements DefaultHard

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
enableEdgeToEdge();
super.onCreate(savedInstanceState);
if (isFinishing()) {
return;
Expand Down Expand Up @@ -63,7 +67,9 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) {
@Override
public void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
navigator.setContentLayout(findViewById(android.R.id.content));
ViewGroup contentLayout = findViewById(android.R.id.content);
navigator.setContentLayout(contentLayout);
SystemUiUtils.setupSystemBarBackgrounds(this, contentLayout);
}

@Override
Expand All @@ -88,6 +94,7 @@ protected void onPause() {
@Override
protected void onDestroy() {
super.onDestroy();
SystemUiUtils.tearDown();
if (navigator != null) {
navigator.destroy();
}
Expand Down Expand Up @@ -146,6 +153,14 @@ public void onReload() {
navigator.destroyViews();
}

/**
* Override to disable or customize edge-to-edge behavior.
* Called at the start of onCreate, before super.onCreate.
*/
protected void enableEdgeToEdge() {
EdgeToEdge.enable(this);
}

protected void addDefaultSplashLayout() {
View view = new View(this);
setContentView(view);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ open class ModalHostLayout(reactContext: ThemedReactContext) : ViewGroup(reactCo
}

@TargetApi(23)
override fun dispatchProvideStructure(structure: ViewStructure) {
mHostView.dispatchProvideStructure(structure)
override fun dispatchProvideStructure(structure: ViewStructure?) {
structure?.let { mHostView.dispatchProvideStructure(it) }
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.views.text.ReactFontManager;
import com.facebook.react.views.text.ReactTextShadowNode;
import com.facebook.react.common.ReactConstants;
import java.util.ArrayList;
import java.util.List;

Expand Down
211 changes: 157 additions & 54 deletions android/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package com.reactnativenavigation.utils
import android.app.Activity
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.core.view.WindowCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import kotlin.math.abs
Expand All @@ -16,8 +20,14 @@ object SystemUiUtils {
private const val STATUS_BAR_HEIGHT_M = 24
internal const val STATUS_BAR_HEIGHT_TRANSLUCENCY = 0.65f
private var statusBarHeight = -1
var navigationBarDefaultColor = -1
private set

const val DEFAULT_NAV_BAR_COLOR = Color.BLACK
private const val THREE_BUTTON_NAV_BAR_OPACITY = 0.8f

private var statusBarBackgroundView: View? = null
private var navBarBackgroundView: View? = null
private var isEdgeToEdgeActive = false
private var isThreeButtonNav = false

@JvmStatic
fun getStatusBarHeight(activity: Activity?): Int {
Expand Down Expand Up @@ -45,98 +55,165 @@ object SystemUiUtils {
statusBarHeight = height
}


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

// region Setup

/**
* Initializes view-based system bar backgrounds for edge-to-edge.
* Call from Activity.onPostCreate after the navigator content layout is set.
*
* Status bar: reuses the system's android:id/statusBarBackground DecorView child.
* Navigation bar: creates a view in [contentLayout] sized by WindowInsets,
* since the system's navigationBarBackground is not available with EdgeToEdge.
*
* Both fall back to deprecated window APIs when the views are unavailable.
*/
@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
fun setupSystemBarBackgrounds(activity: Activity, contentLayout: ViewGroup) {
setupStatusBarBackground(activity)
setupNavigationBarBackground(contentLayout)
}

private fun setupStatusBarBackground(activity: Activity) {
if (statusBarBackgroundView != null) return
val sbView = activity.window.decorView.findViewById<View>(android.R.id.statusBarBackground)
if (sbView != null) {
sbView.setBackgroundColor(Color.BLACK)
statusBarBackgroundView = sbView
}
}

private fun setupNavigationBarBackground(contentLayout: ViewGroup) {
if (navBarBackgroundView != null) return
val view = View(contentLayout.context).apply {
setBackgroundColor(Color.BLACK)
}
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, 0, Gravity.BOTTOM
)
contentLayout.addView(view, params)
navBarBackgroundView = view

ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
val navBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
val tappableHeight = insets.getInsets(WindowInsetsCompat.Type.tappableElement()).bottom
val wasThreeButton = isThreeButtonNav
isEdgeToEdgeActive = navBarHeight > 0
isThreeButtonNav = tappableHeight > 0
if (isThreeButtonNav != wasThreeButton) {
v.setBackgroundColor(getDefaultNavBarColor())
}
val lp = v.layoutParams
if (lp.height != navBarHeight) {
lp.height = navBarHeight
v.layoutParams = lp
}
insets
}
view.requestApplyInsets()
}

/**
* Returns the default navigation bar color, applying 80% opacity for 3-button navigation.
* Gesture navigation gets a fully opaque color since the bar is minimal.
*/
@JvmStatic
fun showNavigationBar(window: Window?, view: View) {
window?.let {
WindowCompat.setDecorFitsSystemWindows(window, true)
WindowInsetsControllerCompat(window, view).show(WindowInsetsCompat.Type.navigationBars())
}
fun getDefaultNavBarColor(): Int {
if (!isThreeButtonNav) return DEFAULT_NAV_BAR_COLOR
val alpha = (THREE_BUTTON_NAV_BAR_OPACITY * 255).toInt()
return Color.argb(alpha, Color.red(DEFAULT_NAV_BAR_COLOR), Color.green(DEFAULT_NAV_BAR_COLOR), Color.blue(DEFAULT_NAV_BAR_COLOR))
}

/**
* Clears references to system bar background views.
* Call from Activity.onDestroy to avoid leaking views across activity recreation.
*/
@JvmStatic
fun tearDown() {
statusBarBackgroundView = null
navBarBackgroundView = null
isEdgeToEdgeActive = false
isThreeButtonNav = false
statusBarHeight = -1
}

// endregion

// region Status Bar

@JvmStatic
fun setStatusBarColorScheme(window: Window?, view: View, isDark: Boolean) {
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)
getStatusBarColor(window)?.let { currentColor ->
setStatusBarColor(window, currentColor, true)
}
}

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

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

@JvmStatic
fun setStatusBarColor(
window: Window?,
@ColorInt color: Int,
translucent: Boolean
) {
fun setStatusBarColor(window: Window?, @ColorInt color: Int, translucent: Boolean) {
val colorAlpha = Color.alpha(color)
val alpha = if (translucent && colorAlpha == 255) STATUS_BAR_HEIGHT_TRANSLUCENCY else colorAlpha/255.0f
val red: Int = Color.red(color)
val green: Int = Color.green(color)
val blue: Int = Color.blue(color)
val opaqueColor = Color.argb(ceil(alpha * 255).toInt(), red, green, blue)
val alpha = if (translucent && colorAlpha == 255) STATUS_BAR_HEIGHT_TRANSLUCENCY else colorAlpha / 255.0f
val opaqueColor = Color.argb(
ceil(alpha * 255).toInt(),
Color.red(color),
Color.green(color),
Color.blue(color)
)
setStatusBarColor(window, opaqueColor)
}

/**
* Sets the status bar background color.
* Uses the view-based background when available (edge-to-edge),
* falls back to the deprecated window API on older configurations.
*/
fun setStatusBarColor(window: Window?, color: Int) {
window?.statusBarColor = color
statusBarBackgroundView?.setBackgroundColor(color) ?: run {
@Suppress("DEPRECATION")
window?.statusBarColor = color
}
}

/**
* Gets the current status bar background color.
* Reads from the view-based background when available,
* falls back to the deprecated window API on older configurations.
*/
@JvmStatic
fun getStatusBarColor(window: Window?): Int? {
statusBarBackgroundView?.let { view ->
(view.background as? ColorDrawable)?.let { return it.color }
}
@Suppress("DEPRECATION")
return window?.statusBarColor
}

@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
Expand All @@ -147,22 +224,48 @@ object SystemUiUtils {
@JvmStatic
fun showStatusBar(window: Window?, view: View) {
window?.let {
WindowCompat.setDecorFitsSystemWindows(window, true)
WindowInsetsControllerCompat(window, view).show(WindowInsetsCompat.Type.statusBars())
}
}

// endregion

// region Navigation Bar

@JvmStatic
fun setNavigationBarBackgroundColor(window: Window?, color: Int, lightColor: Boolean) {
fun hideNavigationBar(window: Window?, view: View) {
window?.let {
if (navigationBarDefaultColor == -1) {
navigationBarDefaultColor = window.navigationBarColor
}
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.isAppearanceLightNavigationBars = lightColor
WindowInsetsControllerCompat(window, view).let { controller ->
controller.hide(WindowInsetsCompat.Type.navigationBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
window.navigationBarColor = color
}
}

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

/**
* Sets the navigation bar background color and icon appearance.
* Uses the view-based background when available (edge-to-edge),
* falls back to the deprecated window API on older configurations.
*/
@JvmStatic
fun setNavigationBarBackgroundColor(window: Window?, color: Int, lightColor: Boolean) {
window?.let {
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightNavigationBars = lightColor
}
if (isEdgeToEdgeActive) {
navBarBackgroundView?.setBackgroundColor(color)
} else {
@Suppress("DEPRECATION")
window?.navigationBarColor = color
}
}

// endregion
}
Loading