Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [3.0.95] - 2026-08-31

### Features
- **device-utils**: Add `setNavigationBarAppearance` so the Android main UI runtime can keep the system navigation bar aligned with the app theme while preserving a cross-platform no-op on iOS.

### Bug Fixes
- **device-utils (Android)**: Handle bottom and side three-button navigation on Android 15+, remove the protection view in gesture mode, and use a readable black fallback on API 24–25 when dark navigation icons are unavailable.

### Chores
- Bump all publishable packages to 3.0.95.

## [3.0.94] - 2026-08-30

### Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion native-modules/native-logger/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-native-logger",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-native-logger",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-aes-crypto/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-aes-crypto",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-aes-crypto",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-app-update/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-app-update",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-app-update",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-async-storage/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-async-storage",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-async-storage",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-background-thread/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-background-thread",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-background-thread",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-bundle-crypto/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-bundle-crypto",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-bundle-crypto",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-bundle-update/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-bundle-update",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-bundle-update",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-check-biometric-auth-changed",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-check-biometric-auth-changed",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-cloud-fs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-cloud-fs",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-cloud-fs TurboModule for OneKey",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-cloud-kit-module/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-cloud-kit-module",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-cloud-kit-module",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.Rect
import android.os.Build
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import com.margelo.nitro.nativelogger.OneKeyLog
import androidx.preference.PreferenceManager
import androidx.core.content.ContextCompat
import androidx.core.util.Consumer
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
Expand All @@ -28,6 +35,11 @@ data class Listener(
val callback: (Boolean) -> Unit
)

private data class NavigationBarAppearance(
val color: Int,
val useDarkIcons: Boolean,
)

@DoNotStrip
class ReactNativeDeviceUtils : HybridReactNativeDeviceUtilsSpec(), LifecycleEventListener {

Expand All @@ -42,6 +54,8 @@ class ReactNativeDeviceUtils : HybridReactNativeDeviceUtilsSpec(), LifecycleEven
private const val RECOVERY_PREFS_NAME = "onekey_recovery"
private const val BOOT_FAIL_COUNT_KEY = "consecutive_boot_fail_count"
private const val RECOVERY_ACTION_KEY = "recovery_action"
private const val NAVIGATION_BAR_PROTECTION_VIEW_TAG =
"com.onekeyfe.reactnativedeviceutils.navigationBarProtection"

@JvmStatic
var staticStartupTime: Long? = null
Expand Down Expand Up @@ -237,6 +251,7 @@ class ReactNativeDeviceUtils : HybridReactNativeDeviceUtilsSpec(), LifecycleEven
private var isObservingLayoutChanges = false
private var nextListenerId = 0.0
private var isDualScreenDeviceDetected: Boolean? = null
@Volatile private var navigationBarAppearance: NavigationBarAppearance? = null

init {
NitroModules.applicationContext?.let { ctx ->
Expand Down Expand Up @@ -850,8 +865,161 @@ class ReactNativeDeviceUtils : HybridReactNativeDeviceUtilsSpec(), LifecycleEven
}
}

// MARK: - System Navigation Bar

override fun setNavigationBarAppearance(
r: Double,
g: Double,
b: Double,
a: Double,
useDarkIcons: Boolean,
) {
val requestedColor = Color.argb(
a.toInt().coerceIn(0, 255),
r.toInt().coerceIn(0, 255),
g.toInt().coerceIn(0, 255),
b.toInt().coerceIn(0, 255),
)
val appearance = NavigationBarAppearance(
color = if (
Build.VERSION.SDK_INT < Build.VERSION_CODES.O && useDarkIcons
) Color.BLACK else requestedColor,
useDarkIcons = useDarkIcons,
)
navigationBarAppearance = appearance
getCurrentActivity()?.let { applyNavigationBarAppearance(it, appearance) }
}

@Suppress("DEPRECATION")
private fun applyNavigationBarAppearance(
activity: Activity,
appearance: NavigationBarAppearance,
) {
activity.runOnUiThread {
try {
val window = activity.window
val decorView = window.decorView

WindowCompat.getInsetsController(window, decorView)
.isAppearanceLightNavigationBars = appearance.useDarkIcons

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// Android 15 enforces edge-to-edge. Keep the platform bar transparent
// and draw the requested color only behind three-button navigation.
window.navigationBarColor = Color.TRANSPARENT
installNavigationBarProtection(decorView, appearance.color)
} else {
removeNavigationBarProtection(decorView)
window.navigationBarColor = appearance.color
}
} catch (e: Exception) {
OneKeyLog.error(
"DeviceUtils",
"Failed to update navigation bar appearance: ${e.message}",
)
}
}
}

private fun installNavigationBarProtection(decorView: View, color: Int) {
val decorContainer = decorView as? ViewGroup ?: return
val protectionView = findNavigationBarProtection(decorContainer) ?: View(
decorContainer.context
).also { view ->
view.tag = NAVIGATION_BAR_PROTECTION_VIEW_TAG
view.isClickable = false
view.isFocusable = false
view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
decorContainer.addView(
view,
FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
0,
Gravity.BOTTOM,
),
)
}

protectionView.setBackgroundColor(color)
protectionView.visibility = View.VISIBLE
protectionView.bringToFront()
ViewCompat.setOnApplyWindowInsetsListener(protectionView) { view, windowInsets ->
val navigationBarInsets = windowInsets.getInsets(
WindowInsetsCompat.Type.navigationBars()
)
val tappableElementInsets = windowInsets.getInsets(
WindowInsetsCompat.Type.tappableElement()
)
val leftInset = minOf(navigationBarInsets.left, tappableElementInsets.left)
val rightInset = minOf(navigationBarInsets.right, tappableElementInsets.right)
val bottomInset = minOf(navigationBarInsets.bottom, tappableElementInsets.bottom)
val layoutParams = view.layoutParams as FrameLayout.LayoutParams
val (width, height, gravity) = when {
leftInset > 0 -> Triple(
leftInset,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.LEFT,
)
rightInset > 0 -> Triple(
rightInset,
ViewGroup.LayoutParams.MATCH_PARENT,
Gravity.RIGHT,
)
bottomInset > 0 -> Triple(
ViewGroup.LayoutParams.MATCH_PARENT,
bottomInset,
Gravity.BOTTOM,
)
else -> Triple(0, 0, Gravity.BOTTOM)
}
if (
layoutParams.width != width ||
layoutParams.height != height ||
layoutParams.gravity != gravity
) {
layoutParams.width = width
layoutParams.height = height
layoutParams.gravity = gravity
view.layoutParams = layoutParams
}
view.visibility = if (
leftInset > 0 || rightInset > 0 || bottomInset > 0
) View.VISIBLE else View.INVISIBLE

// This view is only a visual protection; descendants still need the insets.
windowInsets
}
ViewCompat.requestApplyInsets(protectionView)
}

private fun findNavigationBarProtection(decorContainer: ViewGroup): View? {
for (index in 0 until decorContainer.childCount) {
val child = decorContainer.getChildAt(index)
if (child.tag == NAVIGATION_BAR_PROTECTION_VIEW_TAG) {
return child
}
}
return null
}

private fun removeNavigationBarProtection(decorView: View) {
val decorContainer = decorView as? ViewGroup ?: return
val protectionView = findNavigationBarProtection(decorContainer) ?: return
ViewCompat.setOnApplyWindowInsetsListener(protectionView, null)
decorContainer.removeView(protectionView)
}

override fun onHostResume() {
startObservingLayoutChanges()
val activity = getCurrentActivity()
val appearance = navigationBarAppearance
if (activity != null && appearance != null) {
applyNavigationBarAppearance(activity, appearance)
}
}

override fun onHostPause() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ class ReactNativeDeviceUtils: HybridReactNativeDeviceUtilsSpec {
}
}

public func setNavigationBarAppearance(
r: Double,
g: Double,
b: Double,
a: Double,
useDarkIcons: Bool
) throws -> Void {
// Android-only system UI.
}

// MARK: - LaunchOptionsManager

func getLaunchOptions() throws -> Promise<LaunchOptions> {
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-device-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-device-utils",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-device-utils",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ export interface ReactNativeDeviceUtils
getWindowRects(): Promise<DualScreenInfoRect[]>;
getHingeBounds(): Promise<DualScreenInfoRect>;
changeBackgroundColor(r: number, g: number, b: number, a: number): void;
/**
* Updates the Android system navigation bar from the UI runtime.
* Color channels use the [0, 255] range. `useDarkIcons` should be true for
* light backgrounds. Android API 24-25 cannot render dark navigation icons,
* so those versions use an opaque black background fallback when requested.
* This is a no-op on iOS.
*/
setNavigationBarAppearance(
r: number,
g: number,
b: number,
a: number,
useDarkIcons: boolean
): void;
addSpanningChangedListener(callback: (isSpanning: boolean) => void): number;
removeSpanningChangedListener(id: number): void;
setUserInterfaceStyle(style: UserInterfaceStyle): void;
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-dns-lookup/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-dns-lookup",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-dns-lookup",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-get-random-values/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-get-random-values",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-get-random-values",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-keychain-module/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-keychain-module",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-keychain-module",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-lite-card/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-lite-card",
"version": "3.0.94",
"version": "3.0.95",
"description": "lite card",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion native-modules/react-native-network-info/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/react-native-network-info",
"version": "3.0.94",
"version": "3.0.95",
"description": "react-native-network-info",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down
Loading