Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] Preview: Appearance Native Module #26172

Closed
wants to merge 5 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
#import "FBReactNativeSpec.h"

#import <folly/Optional.h>


namespace facebook {
Expand Down Expand Up @@ -452,6 +453,69 @@ + (RCTManagedPointer *)JS_NativeAppState_SpecGetCurrentAppStateSuccessAppState:(

} // namespace react
} // namespace facebook
namespace facebook {
namespace react {


static facebook::jsi::Value __hostFunction_NativeAppearanceSpecJSI_getColorScheme(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, StringKind, "getColorScheme", @selector(getColorScheme), args, count);
}

static facebook::jsi::Value __hostFunction_NativeAppearanceSpecJSI_addListener(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "addListener", @selector(addListener:), args, count);
}

static facebook::jsi::Value __hostFunction_NativeAppearanceSpecJSI_removeListeners(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "removeListeners", @selector(removeListeners:), args, count);
}


NativeAppearanceSpecJSI::NativeAppearanceSpecJSI(id<RCTTurboModule> instance, std::shared_ptr<JSCallInvoker> jsInvoker)
: ObjCTurboModule("Appearance", instance, jsInvoker) {

methodMap_["getColorScheme"] = MethodMetadata {0, __hostFunction_NativeAppearanceSpecJSI_getColorScheme};


methodMap_["addListener"] = MethodMetadata {1, __hostFunction_NativeAppearanceSpecJSI_addListener};


methodMap_["removeListeners"] = MethodMetadata {1, __hostFunction_NativeAppearanceSpecJSI_removeListeners};



}

} // namespace react
} // namespace facebook
folly::Optional<NativeAppearanceColorSchemeName> NSStringToNativeAppearanceColorSchemeName(NSString *value) {
static NSDictionary *dict = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dict = @{
@"light": @0,
@"dark": @1,
};
});
return value ? (NativeAppearanceColorSchemeName)[dict[value] integerValue] : folly::Optional<NativeAppearanceColorSchemeName>{};
}

NSString *NativeAppearanceColorSchemeNameToNSString(folly::Optional<NativeAppearanceColorSchemeName> value) {
static NSDictionary *dict = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dict = @{
@0: @"light",
@1: @"dark",
};
});
return value.hasValue() ? dict[@(value.value())] : nil;
}
@implementation RCTCxxConvert (NativeAppearance_AppearancePreferences)
+ (RCTManagedPointer *)JS_NativeAppearance_AppearancePreferences:(id)json
{
return facebook::react::managedPointer<JS::NativeAppearance::AppearancePreferences>(json);
}
@end
@implementation RCTCxxConvert (NativeAsyncStorage_SpecMultiGetCallbackErrorsElement)
+ (RCTManagedPointer *)JS_NativeAsyncStorage_SpecMultiGetCallbackErrorsElement:(id)json
{
Expand Down
48 changes: 48 additions & 0 deletions Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,49 @@ namespace facebook {
};
} // namespace react
} // namespace facebook
@protocol NativeAppearanceSpec <RCTBridgeModule, RCTTurboModule>

- (NSString *)getColorScheme;
- (void)addListener:(NSString *)eventName;
- (void)removeListeners:(double)count;

@end
namespace facebook {
namespace react {
/**
* ObjC++ class for module 'Appearance'
*/

class JSI_EXPORT NativeAppearanceSpecJSI : public ObjCTurboModule {
public:
NativeAppearanceSpecJSI(id<RCTTurboModule> instance, std::shared_ptr<JSCallInvoker> jsInvoker);

};
} // namespace react
} // namespace facebook
typedef NS_ENUM(NSInteger, NativeAppearanceColorSchemeName) {
NativeAppearanceColorSchemeNameLight = 0,
NativeAppearanceColorSchemeNameDark,
};

folly::Optional<NativeAppearanceColorSchemeName> NSStringToNativeAppearanceColorSchemeName(NSString *value);
NSString *NativeAppearanceColorSchemeNameToNSString(folly::Optional<NativeAppearanceColorSchemeName> value);

namespace JS {
namespace NativeAppearance {
struct AppearancePreferences {
NSString *colorScheme() const;

AppearancePreferences(NSDictionary *const v) : _v(v) {}
private:
NSDictionary *_v;
};
}
}

@interface RCTCxxConvert (NativeAppearance_AppearancePreferences)
+ (RCTManagedPointer *)JS_NativeAppearance_AppearancePreferences:(id)json;
@end

namespace JS {
namespace NativeAsyncStorage {
Expand Down Expand Up @@ -2553,6 +2596,11 @@ inline JS::NativeAppState::Constants::Builder::Builder(const Input i) : _factory
inline JS::NativeAppState::Constants::Builder::Builder(Constants i) : _factory(^{
return i.unsafeRawValue();
}) {}
inline NSString *JS::NativeAppearance::AppearancePreferences::colorScheme() const
{
id const p = _v[@"colorScheme"];
return RCTBridgingToString(p);
}
inline NSString *JS::NativeAsyncStorage::SpecMultiGetCallbackErrorsElement::message() const
{
id const p = _v[@"message"];
Expand Down
79 changes: 79 additions & 0 deletions Libraries/Utilities/Appearance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/

'use strict';

import EventEmitter from '../vendor/emitter/EventEmitter';
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import NativeAppearance, {
type AppearancePreferences,
type ColorSchemeName,
} from './NativeAppearance';
import invariant from 'invariant';

type AppearanceListener = (preferences: AppearancePreferences) => void;
const eventEmitter = new EventEmitter();

const nativeColorScheme: ?string =
NativeAppearance == null ? null : NativeAppearance.getColorScheme();
invariant(
nativeColorScheme === 'dark' ||
nativeColorScheme === 'light' ||
nativeColorScheme == null,
"Unrecognized color scheme. Did you mean 'dark' or 'light'?",
);

let currentColorScheme: ?ColorSchemeName = nativeColorScheme;

if (NativeAppearance) {
const nativeEventEmitter = new NativeEventEmitter(NativeAppearance);
nativeEventEmitter.addListener(
'appearanceChanged',
(newAppearance: AppearancePreferences) => {
const {colorScheme} = newAppearance;
invariant(
colorScheme === 'dark' ||
colorScheme === 'light' ||
colorScheme == null,
"Unrecognized color scheme. Did you mean 'dark' or 'light'?",
);
currentColorScheme = colorScheme;
eventEmitter.emit('change', {colorScheme});
},
);
}

module.exports = {
/**
* Note: Although color scheme is available immediately, it may change at any
* time. Any rendering logic or styles that depend on this should try to call
* this function on every render, rather than caching the value (for example,
* using inline styles rather than setting a value in a `StyleSheet`).
*
* Example: `const colorScheme = Appearance.getColorScheme();`
*
* @returns {?ColorSchemeName} Value for the color scheme preference.
*/
getColorScheme(): ?ColorSchemeName {
return currentColorScheme;
},
/**
* Add an event handler that is fired when appearance preferences change.
*/
addChangeListener(listener: AppearanceListener): void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
addChangeListener(listener: AppearanceListener): void {
addChangeListener(listener: AppearanceListener): EventSubscription {

eventEmitter.addListener('change', listener);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
eventEmitter.addListener('change', listener);
return eventEmitter.addListener('change', listener);

},
/**
* Remove an event handler.
*/
removeChangeListener(listener: AppearanceListener): void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo we should remove this function and have people just call subscription.remove() on the returned value from addChangeListener

eventEmitter.removeListener('change', listener);
},
};
36 changes: 36 additions & 0 deletions Libraries/Utilities/NativeAppearance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

'use strict';

import type {TurboModule} from '../TurboModule/RCTExport';
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';

export type ColorSchemeName = 'light' | 'dark';

export type AppearancePreferences = {|
// TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union
// types.
/* 'light' | 'dark' */
colorScheme?: ?string,
|};

export interface Spec extends TurboModule {
// TODO: (hramos) T52919652 Use ?ColorSchemeName once codegen supports union
// types.
/* 'light' | 'dark' */
+getColorScheme: () => ?string;

// RCTEventEmitter
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
}

export default (TurboModuleRegistry.get<Spec>('Appearance'): ?Spec);
31 changes: 31 additions & 0 deletions Libraries/Utilities/useColorScheme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/

'use strict';

import {useMemo} from 'react';
import {useSubscription} from 'use-subscription';
import Appearance from './Appearance';
import type {ColorSchemeName} from './NativeAppearance';

export default function useColorScheme(): ?ColorSchemeName {
const subscription = useMemo(
() => ({
getCurrentValue: () => Appearance.getColorScheme(),
subscribe: callback => {
Appearance.addChangeListener(callback);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Appearance.addChangeListener(callback);
let eventSubscription = Appearance.addChangeListener(callback);

return () => Appearance.removeChangeListener(callback);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return () => Appearance.removeChangeListener(callback);
return () => eventSubscription.remove();

},
}),
[],
);

return useSubscription(subscription);
}
8 changes: 8 additions & 0 deletions Libraries/react-native/react-native-implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import typeof VirtualizedSectionList from '../Lists/VirtualizedSectionList';
import typeof ActionSheetIOS from '../ActionSheetIOS/ActionSheetIOS';
import typeof Alert from '../Alert/Alert';
import typeof Animated from '../Animated/src/Animated';
import typeof Appearance from '../Utilities/Appearance';
import typeof AppRegistry from '../ReactNative/AppRegistry';
import typeof AppState from '../AppState/AppState';
import typeof AsyncStorage from '../Storage/AsyncStorage';
Expand Down Expand Up @@ -81,6 +82,7 @@ import typeof ToastAndroid from '../Components/ToastAndroid/ToastAndroid';
import typeof * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
import typeof TVEventHandler from '../Components/AppleTV/TVEventHandler';
import typeof UIManager from '../ReactNative/UIManager';
import typeof useColorScheme from '../Utilities/useColorScheme';
import typeof useWindowDimensions from '../Utilities/useWindowDimensions';
import typeof UTFSequence from '../UTFSequence';
import typeof Vibration from '../Vibration/Vibration';
Expand Down Expand Up @@ -252,6 +254,9 @@ module.exports = {
get Animated(): Animated {
return require('../Animated/src/Animated');
},
get Appearance(): Appearance {
return require('../Utilities/Appearance');
},
get AppRegistry(): AppRegistry {
return require('../ReactNative/AppRegistry');
},
Expand Down Expand Up @@ -389,6 +394,9 @@ module.exports = {
> {
return require('../Renderer/shims/ReactNative').unstable_batchedUpdates;
},
get useColorScheme(): useColorScheme {
return require('../Utilities/useColorScheme').default;
},
get useWindowDimensions(): useWindowDimensions {
return require('../Utilities/useWindowDimensions').default;
},
Expand Down
4 changes: 2 additions & 2 deletions RNTester/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def enableProguardInReleaseBuilds = true
def useIntlJsc = false

android {
compileSdkVersion 28
compileSdkVersion 29

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand All @@ -121,7 +121,7 @@ android {
defaultConfig {
applicationId "com.facebook.react.uiapp"
minSdkVersion 16
targetSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "1.0"
}
Expand Down
2 changes: 1 addition & 1 deletion RNTester/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
android:name=".RNTesterActivity"
android:label="@string/app_name"
android:screenOrientation="fullSensor"
android:configChanges="orientation|screenSize" >
android:configChanges="orientation|screenSize|uiMode" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
*/
package com.facebook.react.uiapp;

import android.content.res.Configuration;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactInstanceManager;

public class RNTesterActivity extends ReactActivity {
public static class RNTesterActivityDelegate extends ReactActivityDelegate {
Expand Down Expand Up @@ -53,4 +55,14 @@ protected ReactActivityDelegate createReactActivityDelegate() {
protected String getMainComponentName() {
return "RNTesterApp";
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ReactInstanceManager instanceManager = getReactInstanceManager();

if (instanceManager != null) {
instanceManager.onConfigurationChanged(newConfig);
}
}
}
Loading