From 10bbcb733cdf0c7d35af2ccc1fdddc49278d45c7 Mon Sep 17 00:00:00 2001 From: qflen Date: Tue, 21 Apr 2026 19:11:38 +0200 Subject: [PATCH] Support PlatformColor in Android StatusBar backgroundColor Passing a PlatformColor to StatusBar.backgroundColor previously tripped an invariant in _updatePropsStack because processColor returns an object for PlatformColor and the native setColor TurboModule only accepted a number. Adds a setColorObject TurboModule method that accepts a resource-paths object and resolves it on the native side via ColorPropConverter.getColor. StatusBar.js now dispatches to setColor for numeric colors and to setColorObject for PlatformColor values. Fixes #48402 --- .../Components/StatusBar/StatusBar.js | 12 ++++++----- .../modules/statusbar/StatusBarModule.kt | 21 ++++++++++++++++++- .../modules/NativeStatusBarManagerAndroid.js | 12 +++++++++++ .../api-snapshots/ReactAndroidDebugCxx.api | 14 +++++++++++++ .../api-snapshots/ReactAndroidNewarchCxx.api | 13 ++++++++++++ .../api-snapshots/ReactAndroidReleaseCxx.api | 13 ++++++++++++ 6 files changed, 79 insertions(+), 6 deletions(-) diff --git a/packages/react-native/Libraries/Components/StatusBar/StatusBar.js b/packages/react-native/Libraries/Components/StatusBar/StatusBar.js index 5df3131bdcf0..a405a6d2de91 100644 --- a/packages/react-native/Libraries/Components/StatusBar/StatusBar.js +++ b/packages/react-native/Libraries/Components/StatusBar/StatusBar.js @@ -477,15 +477,17 @@ class StatusBar extends React.Component { console.warn( `\`StatusBar._updatePropsStack\`: Color ${mergedProps.backgroundColor.value} parsed to null or undefined`, ); - } else { - invariant( - typeof processedColor === 'number', - 'Unexpected color given in StatusBar._updatePropsStack', - ); + } else if (typeof processedColor === 'number') { NativeStatusBarManagerAndroid.setColor( processedColor, mergedProps.backgroundColor.animated, ); + } else { + NativeStatusBarManagerAndroid.setColorObject( + // $FlowFixMe[incompatible-type] - Opaque NativeColorValue on Android matches the spec shape {resource_paths: Array}. + processedColor, + mergedProps.backgroundColor.animated, + ); } if (!oldProps || oldProps.hidden?.value !== mergedProps.hidden.value) { NativeStatusBarManagerAndroid.setHidden(mergedProps.hidden.value); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index 97725bfcb7d2..5708b64fabc3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -16,9 +16,11 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import com.facebook.common.logging.FLog import com.facebook.fbreact.specs.NativeStatusBarManagerAndroidSpec +import com.facebook.react.bridge.ColorPropConverter import com.facebook.react.bridge.GuardedRunnable import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.common.ReactConstants import com.facebook.react.interfaces.ExtraWindowEventListener @@ -79,7 +81,24 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : @Suppress("DEPRECATION") override fun setColor(colorDouble: Double, animated: Boolean) { - val color = colorDouble.toInt() + applyStatusBarColor(colorDouble.toInt(), animated) + } + + @Suppress("DEPRECATION") + override fun setColorObject(color: ReadableMap, animated: Boolean) { + val resolved = ColorPropConverter.getColor(color, reactApplicationContext) + if (resolved == null) { + FLog.w( + ReactConstants.TAG, + "StatusBarModule: Ignored status bar change, unable to resolve color.", + ) + return + } + applyStatusBarColor(resolved, animated) + } + + @Suppress("DEPRECATION") + private fun applyStatusBarColor(color: Int, animated: Boolean) { val activity = reactApplicationContext.getCurrentActivity() if (activity == null) { FLog.w( diff --git a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeStatusBarManagerAndroid.js b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeStatusBarManagerAndroid.js index 4ef517a7928b..6eac76c1bd68 100644 --- a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeStatusBarManagerAndroid.js +++ b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeStatusBarManagerAndroid.js @@ -12,12 +12,20 @@ import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry'; +export type NativePlatformColorValue = { + +resource_paths: Array, +}; + export interface Spec extends TurboModule { readonly getConstants: () => { readonly HEIGHT: number, readonly DEFAULT_BACKGROUND_COLOR: number, }; readonly setColor: (color: number, animated: boolean) => void; + readonly setColorObject: ( + color: NativePlatformColorValue, + animated: boolean, + ) => void; readonly setTranslucent: (translucent: boolean) => void; /** @@ -47,6 +55,10 @@ const NativeStatusBarManager = { NativeModule.setColor(color, animated); }, + setColorObject(color: NativePlatformColorValue, animated: boolean): void { + NativeModule.setColorObject(color, animated); + }, + setTranslucent(translucent: boolean): void { NativeModule.setTranslucent(translucent); }, diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 9a10db992942..d399561240ee 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -8591,6 +8591,12 @@ struct facebook::react::NativeSourceCodeSourceCodeConstants { public bool operator==(const facebook::react::NativeSourceCodeSourceCodeConstants& other) const; } +template +struct facebook::react::NativeStatusBarManagerAndroidNativePlatformColorValue { + public P0 resource_paths; + public bool operator==(const facebook::react::NativeStatusBarManagerAndroidNativePlatformColorValue& other) const; +} + template class facebook::react::SyncCallback { public R call(Args... args) const; @@ -9433,6 +9439,14 @@ struct facebook::react::NativeSourceCodeSourceCodeConstantsBridging { public static facebook::jsi::String scriptURLToJs(facebook::jsi::Runtime& rt, decltype(types.scriptURL) value); } +template +struct facebook::react::NativeStatusBarManagerAndroidNativePlatformColorValueBridging { + public static T fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value, const std::shared_ptr& jsInvoker); + public static T types; + public static facebook::jsi::Array resource_pathsToJs(facebook::jsi::Runtime& rt, decltype(types.resource_paths) value); + public static facebook::jsi::Object toJs(facebook::jsi::Runtime& rt, const T& value, const std::shared_ptr& jsInvoker); +} + template struct facebook::react::RectangleCorners { public T bottomLeft; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 9d8211cbf16b..efb0bcc7c6c3 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -8367,6 +8367,12 @@ struct facebook::react::NativeSourceCodeSourceCodeConstants { public bool operator==(const facebook::react::NativeSourceCodeSourceCodeConstants& other) const; } +template +struct facebook::react::NativeStatusBarManagerAndroidNativePlatformColorValue { + public P0 resource_paths; + public bool operator==(const facebook::react::NativeStatusBarManagerAndroidNativePlatformColorValue& other) const; +} + template class facebook::react::SyncCallback { public R call(Args... args) const; @@ -9071,6 +9077,13 @@ struct facebook::react::NativeSourceCodeSourceCodeConstantsBridging { public static facebook::jsi::Object toJs(facebook::jsi::Runtime& rt, const T& value, const std::shared_ptr& jsInvoker); } +template +struct facebook::react::NativeStatusBarManagerAndroidNativePlatformColorValueBridging { + public static T fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value, const std::shared_ptr& jsInvoker); + public static T types; + public static facebook::jsi::Object toJs(facebook::jsi::Runtime& rt, const T& value, const std::shared_ptr& jsInvoker); +} + template struct facebook::react::RectangleCorners { public T bottomLeft; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 93f507c31385..bb839c7b83dd 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -8582,6 +8582,12 @@ struct facebook::react::NativeSourceCodeSourceCodeConstants { public bool operator==(const facebook::react::NativeSourceCodeSourceCodeConstants& other) const; } +template +struct facebook::react::NativeStatusBarManagerAndroidNativePlatformColorValue { + public P0 resource_paths; + public bool operator==(const facebook::react::NativeStatusBarManagerAndroidNativePlatformColorValue& other) const; +} + template class facebook::react::SyncCallback { public R call(Args... args) const; @@ -9286,6 +9292,13 @@ struct facebook::react::NativeSourceCodeSourceCodeConstantsBridging { public static facebook::jsi::Object toJs(facebook::jsi::Runtime& rt, const T& value, const std::shared_ptr& jsInvoker); } +template +struct facebook::react::NativeStatusBarManagerAndroidNativePlatformColorValueBridging { + public static T fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value, const std::shared_ptr& jsInvoker); + public static T types; + public static facebook::jsi::Object toJs(facebook::jsi::Runtime& rt, const T& value, const std::shared_ptr& jsInvoker); +} + template struct facebook::react::RectangleCorners { public T bottomLeft;