Skip to content

Commit 0a4dcb0

Browse files
birkirfacebook-github-bot
authored andcommitted
Add Appearance.setColorScheme support (#35989)
Summary: Both Android and iOS allow you to set application specific user interface style, which is useful for applications that support both light and dark mode. With the newly added `Appearance.setColorScheme`, you can natively manage the application's user interface style rather than keeping that preference in JavaScript. The benefit is that native dialogs like alert, keyboard, action sheets and more will also be affected by this change. Implemented using Android X [AppCompatDelegate.setDefaultNightMode](https://developer.android.com/reference/androidx/appcompat/app/AppCompatDelegate#setDefaultNightMode(int)) and iOS 13+ [overrideUserInterfaceStyle](https://developer.apple.com/documentation/uikit/uiview/3238086-overrideuserinterfacestyle?language=objc) ## Changelog [GENERAL] [ADDED] - Added `setColorScheme` to `Appearance` module Pull Request resolved: #35989 Test Plan: This is a void function so testing is rather limited. ```tsx // Lets assume a given device is set to **dark** mode. Appearance.getColorScheme(); // `dark` // Set the app's user interface to `light` Appearance.setColorScheme('light'); Appearance.getColorScheme(); // `light` // Set the app's user interface to `unspecified` Appearance.setColorScheme(null); Appearance.getColorScheme() // `dark` ``` Reviewed By: NickGerleman Differential Revision: D42801094 Pulled By: jacdebug fbshipit-source-id: ede810fe9ee98f313fd3fbbb16b60c84ef8c7204
1 parent 9af3c96 commit 0a4dcb0

File tree

8 files changed

+63
-0
lines changed

8 files changed

+63
-0
lines changed

Libraries/Utilities/Appearance.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ export namespace Appearance {
2828
*/
2929
export function getColorScheme(): ColorSchemeName;
3030

31+
/**
32+
* Set the color scheme preference. This is useful for overriding the default
33+
* color scheme preference for the app. Note that this will not change the
34+
* appearance of the system UI, only the appearance of the app.
35+
* Only available on iOS 13+ and Android 10+.
36+
*/
37+
export function setColorScheme(
38+
scheme: ColorSchemeName | null | undefined,
39+
): void;
40+
3141
/**
3242
* Add an event handler that is fired when appearance preferences change.
3343
*/

Libraries/Utilities/Appearance.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ module.exports = {
8585
return nativeColorScheme;
8686
},
8787

88+
setColorScheme(colorScheme: ?ColorSchemeName): void {
89+
const nativeColorScheme = colorScheme == null ? 'unspecified' : colorScheme;
90+
91+
invariant(
92+
colorScheme === 'dark' || colorScheme === 'light' || colorScheme == null,
93+
"Unrecognized color scheme. Did you mean 'dark', 'light' or null?",
94+
);
95+
96+
NativeAppearance?.setColorScheme?.(nativeColorScheme);
97+
},
98+
8899
/**
89100
* Add an event handler that is fired when appearance preferences change.
90101
*/

Libraries/Utilities/NativeAppearance.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface Spec extends TurboModule {
2626
// types.
2727
/* 'light' | 'dark' */
2828
+getColorScheme: () => ?string;
29+
+setColorScheme?: (colorScheme: string) => void;
2930

3031
// RCTEventEmitter
3132
+addListener: (eventName: string) => void;

React/CoreModules/RCTAppearance.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#import <UIKit/UIKit.h>
99

1010
#import <React/RCTBridgeModule.h>
11+
#import <React/RCTConvert.h>
1112
#import <React/RCTEventEmitter.h>
1213

1314
RCT_EXTERN void RCTEnableAppearancePreference(BOOL enabled);

React/CoreModules/RCTAppearance.mm

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import <FBReactNativeSpec/FBReactNativeSpec.h>
1111
#import <React/RCTConstants.h>
1212
#import <React/RCTEventEmitter.h>
13+
#import <React/RCTUtils.h>
1314

1415
#import "CoreModulesPlugins.h"
1516

@@ -68,6 +69,20 @@ void RCTOverrideAppearancePreference(NSString *const colorSchemeOverride)
6869
return RCTAppearanceColorSchemeLight;
6970
}
7071

72+
@implementation RCTConvert (UIUserInterfaceStyle)
73+
74+
RCT_ENUM_CONVERTER(
75+
UIUserInterfaceStyle,
76+
(@{
77+
@"light" : @(UIUserInterfaceStyleLight),
78+
@"dark" : @(UIUserInterfaceStyleDark),
79+
@"unspecified" : @(UIUserInterfaceStyleUnspecified)
80+
}),
81+
UIUserInterfaceStyleUnspecified,
82+
integerValue);
83+
84+
@end
85+
7186
@interface RCTAppearance () <NativeAppearanceSpec>
7287
@end
7388

@@ -92,6 +107,17 @@ - (dispatch_queue_t)methodQueue
92107
return std::make_shared<NativeAppearanceSpecJSI>(params);
93108
}
94109

110+
RCT_EXPORT_METHOD(setColorScheme : (NSString *)style)
111+
{
112+
UIUserInterfaceStyle userInterfaceStyle = [RCTConvert UIUserInterfaceStyle:style];
113+
NSArray<__kindof UIWindow *> *windows = RCTSharedApplication().windows;
114+
if (@available(iOS 13.0, *)) {
115+
for (UIWindow *window in windows) {
116+
window.overrideUserInterfaceStyle = userInterfaceStyle;
117+
}
118+
}
119+
}
120+
95121
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getColorScheme)
96122
{
97123
if (_currentColorScheme == nil) {

ReactAndroid/src/main/java/com/facebook/react/modules/appearance/AppearanceModule.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import android.content.Context;
1212
import android.content.res.Configuration;
1313
import androidx.annotation.Nullable;
14+
import androidx.appcompat.app.AppCompatDelegate;
1415
import com.facebook.fbreact.specs.NativeAppearanceSpec;
1516
import com.facebook.react.bridge.Arguments;
1617
import com.facebook.react.bridge.ReactApplicationContext;
@@ -66,6 +67,17 @@ private String colorSchemeForCurrentConfiguration(Context context) {
6667
return "light";
6768
}
6869

70+
@Override
71+
public void setColorScheme(String style) {
72+
if (style == "dark") {
73+
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
74+
} else if (style == "light") {
75+
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
76+
} else if (style == "unspecified") {
77+
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
78+
}
79+
}
80+
6981
@Override
7082
public String getColorScheme() {
7183
// Attempt to use the Activity context first in order to get the most up to date

ReactAndroid/src/main/java/com/facebook/react/modules/appearance/BUCK

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ rn_android_library(
1414
deps = [
1515
react_native_dep("third-party/java/jsr-305:jsr-305"),
1616
react_native_dep("third-party/android/androidx:annotation"),
17+
react_native_dep("third-party/android/androidx:appcompat"),
1718
react_native_target("java/com/facebook/react/bridge:bridge"),
1819
react_native_target("java/com/facebook/react/common:common"),
1920
react_native_target("java/com/facebook/react/module/annotations:annotations"),

ReactAndroid/src/main/java/com/facebook/react/shell/BUCK

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rn_android_library(
1515
react_native_dep("libraries/fresco/fresco-react-native:imagepipeline"),
1616
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
1717
react_native_dep("third-party/android/androidx:annotation"),
18+
react_native_dep("third-party/android/androidx:appcompat"),
1819
react_native_dep("third-party/android/androidx:core"),
1920
react_native_dep("third-party/android/androidx:fragment"),
2021
react_native_dep("third-party/android/androidx:legacy-support-core-utils"),

0 commit comments

Comments
 (0)