PlatformColor implementations for iOS and Android (#27908)
Summary: This Pull Request implements the PlatformColor proposal discussed at https://github.com/react-native-community/discussions-and-proposals/issues/126. The changes include implementations for iOS and Android as well as a PlatformColorExample page in RNTester. Every native platform has the concept of system defined colors. Instead of specifying a concrete color value the app developer can choose a system color that varies in appearance depending on a system theme settings such Light or Dark mode, accessibility settings such as a High Contrast mode, and even its context within the app such as the traits of a containing view or window. The proposal is to add true platform color support to react-native by extending the Flow type `ColorValue` with platform specific color type information for each platform and to provide a convenience function, `PlatformColor()`, for instantiating platform specific ColorValue objects. `PlatformColor(name [, name ...])` where `name` is a system color name on a given platform. If `name` does not resolve to a color for any reason, the next `name` in the argument list will be resolved and so on. If none of the names resolve, a RedBox error occurs. This allows a latest platform color to be used, but if running on an older platform it will fallback to a previous version. The function returns a `ColorValue`. On iOS the values of `name` is one of the iOS [UI Element](https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors) or [Standard Color](https://developer.apple.com/documentation/uikit/uicolor/standard_colors) names such as `labelColor` or `systemFillColor`. On Android the `name` values are the same [app resource](https://developer.android.com/guide/topics/resources/providing-resources) path strings that can be expressed in XML: XML Resource: `@ [<package_name>:]<resource_type>/<resource_name>` Style reference from current theme: `?[<package_name>:][<resource_type>/]<resource_name>` For example: - `?android:colorError` - `?android:attr/colorError` - `?attr/colorPrimary` - `?colorPrimaryDark` - `android:color/holo_purple` - `color/catalyst_redbox_background` On iOS another type of system dynamic color can be created using the `IOSDynamicColor({dark: <color>, light:<color>})` method. The arguments are a tuple containing custom colors for light and dark themes. Such dynamic colors are useful for branding colors or other app specific colors that still respond automatically to system setting changes. Example: `<View style={{ backgroundColor: IOSDynamicColor({light: 'black', dark: 'white'}) }}/>` Other platforms could create platform specific functions similar to `IOSDynamicColor` per the needs of those platforms. For example, macOS has a similar dynamic color type that could be implemented via a `MacDynamicColor`. On Windows custom brushes that tint or otherwise modify a system brush could be created using a platform specific method. ## Changelog [General] [Added] - Added PlatformColor implementations for iOS and Android Pull Request resolved: https://github.com/facebook/react-native/pull/27908 Test Plan: The changes have been tested using the RNTester test app for iOS and Android. On iOS a set of XCTestCase's were added to the Unit Tests. <img width="924" alt="PlatformColor-ios-android" src="https://user-images.githubusercontent.com/30053638/73472497-ff183a80-433f-11ea-90d8-2b04338bbe79.png"> In addition `PlatformColor` support has been added to other out-of-tree platforms such as macOS and Windows has been implemented using these changes: react-native for macOS branch: https://github.com/microsoft/react-native/compare/master...tom-un:tomun/platformcolors react-native for Windows branch: https://github.com/microsoft/react-native-windows/compare/master...tom-un:tomun/platformcolors iOS |Light|Dark| |{F229354502}|{F229354515}| Android |Light|Dark| |{F230114392}|{F230114490}| {F230122700} Reviewed By: hramos Differential Revision: D19837753 Pulled By: TheSavior fbshipit-source-id: 82ca70d40802f3b24591bfd4b94b61f3c38ba829
This commit is contained in:
Родитель
5166856d04
Коммит
f4de45800f
|
@ -47,6 +47,7 @@
|
|||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
[super drawRect:rect];
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
for (ARTNode *node in self.subviews) {
|
||||
[node renderTo:context];
|
||||
|
|
|
@ -14,6 +14,8 @@ import RCTActionSheetManager from './NativeActionSheetManager';
|
|||
|
||||
const invariant = require('invariant');
|
||||
const processColor = require('../StyleSheet/processColor');
|
||||
import type {ColorValue} from '../StyleSheet/StyleSheetTypes';
|
||||
import type {ProcessedColorValue} from '../StyleSheet/processColor';
|
||||
|
||||
/**
|
||||
* Display action sheets and share sheets on iOS.
|
||||
|
@ -45,7 +47,7 @@ const ActionSheetIOS = {
|
|||
+destructiveButtonIndex?: ?number | ?Array<number>,
|
||||
+cancelButtonIndex?: ?number,
|
||||
+anchor?: ?number,
|
||||
+tintColor?: number | string,
|
||||
+tintColor?: ColorValue | ProcessedColorValue,
|
||||
+userInterfaceStyle?: string,
|
||||
|},
|
||||
callback: (buttonIndex: number) => void,
|
||||
|
|
|
@ -164,17 +164,17 @@ function interpolate(
|
|||
}
|
||||
|
||||
function colorToRgba(input: string): string {
|
||||
let int32Color = normalizeColor(input);
|
||||
if (int32Color === null) {
|
||||
let normalizedColor = normalizeColor(input);
|
||||
if (normalizedColor === null || typeof normalizedColor !== 'number') {
|
||||
return input;
|
||||
}
|
||||
|
||||
int32Color = int32Color || 0;
|
||||
normalizedColor = normalizedColor || 0;
|
||||
|
||||
const r = (int32Color & 0xff000000) >>> 24;
|
||||
const g = (int32Color & 0x00ff0000) >>> 16;
|
||||
const b = (int32Color & 0x0000ff00) >>> 8;
|
||||
const a = (int32Color & 0x000000ff) / 255;
|
||||
const r = (normalizedColor & 0xff000000) >>> 24;
|
||||
const g = (normalizedColor & 0x00ff0000) >>> 16;
|
||||
const b = (normalizedColor & 0x0000ff00) >>> 8;
|
||||
const a = (normalizedColor & 0x000000ff) / 255;
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ const StyleSheet = require('../../StyleSheet/StyleSheet');
|
|||
const View = require('../View/View');
|
||||
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
|
||||
import type {ViewProps} from '../View/ViewPropTypes';
|
||||
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
|
||||
|
||||
const PlatformActivityIndicator =
|
||||
Platform.OS === 'android'
|
||||
|
@ -50,7 +51,7 @@ type Props = $ReadOnly<{|
|
|||
*
|
||||
* See https://reactnative.dev/docs/activityindicator.html#color
|
||||
*/
|
||||
color?: ?string,
|
||||
color?: ?ColorValue,
|
||||
|
||||
/**
|
||||
* Size of the indicator (default is 'small').
|
||||
|
|
|
@ -21,6 +21,7 @@ const View = require('./View/View');
|
|||
const invariant = require('invariant');
|
||||
|
||||
import type {PressEvent} from '../Types/CoreEventTypes';
|
||||
import type {ColorValue} from '../StyleSheet/StyleSheetTypes';
|
||||
|
||||
type ButtonProps = $ReadOnly<{|
|
||||
/**
|
||||
|
@ -41,7 +42,7 @@ type ButtonProps = $ReadOnly<{|
|
|||
/**
|
||||
* Color of the text (iOS), or background color of the button (Android)
|
||||
*/
|
||||
color?: ?string,
|
||||
color?: ?ColorValue,
|
||||
|
||||
/**
|
||||
* TV preferred focus (see documentation for the View component).
|
||||
|
|
|
@ -19,6 +19,7 @@ const requireNativeComponent = require('../../ReactNative/requireNativeComponent
|
|||
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
|
||||
import type {ViewProps} from '../View/ViewPropTypes';
|
||||
import type {SyntheticEvent} from '../../Types/CoreEventTypes';
|
||||
import type {ProcessedColorValue} from '../../StyleSheet/processColor';
|
||||
|
||||
type CheckBoxEvent = SyntheticEvent<
|
||||
$ReadOnly<{|
|
||||
|
@ -47,7 +48,12 @@ type NativeProps = $ReadOnly<{|
|
|||
|
||||
on?: ?boolean,
|
||||
enabled?: boolean,
|
||||
tintColors: {|true: ?number, false: ?number|} | typeof undefined,
|
||||
tintColors:
|
||||
| {|
|
||||
true: ?ProcessedColorValue,
|
||||
false: ?ProcessedColorValue,
|
||||
|}
|
||||
| typeof undefined,
|
||||
|}>;
|
||||
|
||||
type NativeType = HostComponent<NativeProps>;
|
||||
|
|
|
@ -185,7 +185,7 @@ class DrawerLayoutAndroid extends React.Component<Props, State> {
|
|||
...props
|
||||
} = this.props;
|
||||
const drawStatusBar =
|
||||
Platform.Version >= 21 && this.props.statusBarBackgroundColor;
|
||||
Platform.Version >= 21 && this.props.statusBarBackgroundColor != null;
|
||||
const drawerViewWrapper = (
|
||||
<View
|
||||
style={[
|
||||
|
|
|
@ -23,11 +23,12 @@ import type {
|
|||
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
|
||||
import type {TextStyleProp} from '../../StyleSheet/StyleSheet';
|
||||
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
|
||||
import type {ProcessedColorValue} from '../../StyleSheet/processColor';
|
||||
import type {ViewProps} from '../../Components/View/ViewPropTypes';
|
||||
|
||||
type PickerItem = $ReadOnly<{|
|
||||
label: string,
|
||||
color?: ?Int32,
|
||||
color?: ?ProcessedColorValue,
|
||||
|}>;
|
||||
|
||||
type PickerItemSelectEvent = $ReadOnly<{|
|
||||
|
|
|
@ -23,11 +23,12 @@ import type {
|
|||
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
|
||||
import type {TextStyleProp} from '../../StyleSheet/StyleSheet';
|
||||
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
|
||||
import type {ProcessedColorValue} from '../../StyleSheet/processColor';
|
||||
import type {ViewProps} from '../../Components/View/ViewPropTypes';
|
||||
|
||||
type PickerItem = $ReadOnly<{|
|
||||
label: string,
|
||||
color?: ?Int32,
|
||||
color?: ?ProcessedColorValue,
|
||||
|}>;
|
||||
|
||||
type PickerItemSelectEvent = $ReadOnly<{|
|
||||
|
|
|
@ -24,6 +24,7 @@ import RCTPickerNativeComponent, {
|
|||
} from './RCTPickerNativeComponent';
|
||||
import type {TextStyleProp} from '../../StyleSheet/StyleSheet';
|
||||
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
|
||||
import type {ProcessedColorValue} from '../../StyleSheet/processColor';
|
||||
import type {SyntheticEvent} from '../../Types/CoreEventTypes';
|
||||
import type {ViewProps} from '../View/ViewPropTypes';
|
||||
|
||||
|
@ -37,7 +38,7 @@ type PickerIOSChangeEvent = SyntheticEvent<
|
|||
type RCTPickerIOSItemType = $ReadOnly<{|
|
||||
label: ?Label,
|
||||
value: ?(number | string),
|
||||
textColor: ?number,
|
||||
textColor: ?ProcessedColorValue,
|
||||
|}>;
|
||||
|
||||
type Label = Stringish | number;
|
||||
|
|
|
@ -15,6 +15,7 @@ const requireNativeComponent = require('../../ReactNative/requireNativeComponent
|
|||
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
|
||||
import type {SyntheticEvent} from '../../Types/CoreEventTypes';
|
||||
import type {TextStyleProp} from '../../StyleSheet/StyleSheet';
|
||||
import type {ProcessedColorValue} from '../../StyleSheet/processColor';
|
||||
import codegenNativeCommands from '../../Utilities/codegenNativeCommands';
|
||||
import * as React from 'react';
|
||||
|
||||
|
@ -28,7 +29,7 @@ type PickerIOSChangeEvent = SyntheticEvent<
|
|||
type RCTPickerIOSItemType = $ReadOnly<{|
|
||||
label: ?Label,
|
||||
value: ?(number | string),
|
||||
textColor: ?number,
|
||||
textColor: ?ProcessedColorValue,
|
||||
|}>;
|
||||
|
||||
type Label = Stringish | number;
|
||||
|
|
|
@ -15,6 +15,7 @@ const React = require('react');
|
|||
import ProgressBarAndroidNativeComponent from './ProgressBarAndroidNativeComponent';
|
||||
|
||||
import type {ViewProps} from '../View/ViewPropTypes';
|
||||
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
|
||||
|
||||
export type ProgressBarAndroidProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|
@ -49,7 +50,7 @@ export type ProgressBarAndroidProps = $ReadOnly<{|
|
|||
/**
|
||||
* Color of the progress bar.
|
||||
*/
|
||||
color?: ?string,
|
||||
color?: ?ColorValue,
|
||||
/**
|
||||
* Used to locate this view in end-to-end tests.
|
||||
*/
|
||||
|
|
|
@ -15,6 +15,7 @@ const React = require('react');
|
|||
|
||||
const invariant = require('invariant');
|
||||
const processColor = require('../../StyleSheet/processColor');
|
||||
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
|
||||
|
||||
import NativeStatusBarManagerAndroid from './NativeStatusBarManagerAndroid';
|
||||
import NativeStatusBarManagerIOS from './NativeStatusBarManagerIOS';
|
||||
|
@ -62,7 +63,7 @@ type AndroidProps = $ReadOnly<{|
|
|||
* The background color of the status bar.
|
||||
* @platform android
|
||||
*/
|
||||
backgroundColor?: ?string,
|
||||
backgroundColor?: ?ColorValue,
|
||||
/**
|
||||
* If the status bar is translucent.
|
||||
* When translucent is set to true, the app will draw under the status bar.
|
||||
|
|
|
@ -11,12 +11,14 @@
|
|||
'use strict';
|
||||
|
||||
import normalizeColor from '../StyleSheet/normalizeColor.js';
|
||||
import type {ColorValue} from '../StyleSheet/StyleSheetTypes';
|
||||
|
||||
import Touchable from '../Components/Touchable/Touchable';
|
||||
import View from '../Components/View/View';
|
||||
import * as React from 'react';
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
color: string,
|
||||
color: ColorValue,
|
||||
hitSlop: ?$ReadOnly<{|
|
||||
bottom?: ?number,
|
||||
left?: ?number,
|
||||
|
@ -43,8 +45,12 @@ type Props = $ReadOnly<{|
|
|||
export function PressabilityDebugView({color, hitSlop}: Props): React.Node {
|
||||
if (__DEV__) {
|
||||
if (isEnabled()) {
|
||||
const normalizedColor = normalizeColor(color);
|
||||
if (typeof normalizedColor !== 'number') {
|
||||
return null;
|
||||
}
|
||||
const baseColor =
|
||||
'#' + (normalizeColor(color) ?? 0).toString(16).padStart(8, '0');
|
||||
'#' + (normalizedColor ?? 0).toString(16).padStart(8, '0');
|
||||
|
||||
return (
|
||||
<View
|
||||
|
|
|
@ -125,7 +125,7 @@ class Share {
|
|||
typeof content.message === 'string' ? content.message : undefined,
|
||||
url: typeof content.url === 'string' ? content.url : undefined,
|
||||
subject: options.subject,
|
||||
tintColor: tintColor != null ? tintColor : undefined,
|
||||
tintColor: typeof tintColor === 'number' ? tintColor : undefined,
|
||||
excludedActivityTypes: options.excludedActivityTypes,
|
||||
},
|
||||
error => reject(error),
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* 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 strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ColorValue} from './StyleSheetTypes';
|
||||
import type {ProcessedColorValue} from './processColor';
|
||||
|
||||
export opaque type NativeColorValue = {
|
||||
resource_paths?: Array<string>,
|
||||
};
|
||||
|
||||
export const PlatformColor = (...names: Array<string>): ColorValue => {
|
||||
return {resource_paths: names};
|
||||
};
|
||||
|
||||
export const ColorAndroidPrivate = (color: string): ColorValue => {
|
||||
return {resource_paths: [color]};
|
||||
};
|
||||
|
||||
export const normalizeColorObject = (
|
||||
color: NativeColorValue,
|
||||
): ?ProcessedColorValue => {
|
||||
if ('resource_paths' in color) {
|
||||
return color;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const processColorObject = (
|
||||
color: NativeColorValue,
|
||||
): ?NativeColorValue => {
|
||||
return color;
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* 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 strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ColorValue} from './StyleSheetTypes';
|
||||
import type {ProcessedColorValue} from './processColor';
|
||||
|
||||
export opaque type NativeColorValue = {
|
||||
semantic?: Array<string>,
|
||||
dynamic?: {
|
||||
light: ?(ColorValue | ProcessedColorValue),
|
||||
dark: ?(ColorValue | ProcessedColorValue),
|
||||
},
|
||||
};
|
||||
|
||||
export const PlatformColor = (...names: Array<string>): ColorValue => {
|
||||
return {semantic: names};
|
||||
};
|
||||
|
||||
export type DynamicColorIOSTuplePrivate = {
|
||||
light: ColorValue,
|
||||
dark: ColorValue,
|
||||
};
|
||||
|
||||
export const DynamicColorIOSPrivate = (
|
||||
tuple: DynamicColorIOSTuplePrivate,
|
||||
): ColorValue => {
|
||||
return {dynamic: {light: tuple.light, dark: tuple.dark}};
|
||||
};
|
||||
|
||||
export const normalizeColorObject = (
|
||||
color: NativeColorValue,
|
||||
): ?ProcessedColorValue => {
|
||||
if ('semantic' in color) {
|
||||
// an ios semantic color
|
||||
return color;
|
||||
} else if ('dynamic' in color && color.dynamic !== undefined) {
|
||||
const normalizeColor = require('./normalizeColor');
|
||||
|
||||
// a dynamic, appearance aware color
|
||||
const dynamic = color.dynamic;
|
||||
const dynamicColor: NativeColorValue = {
|
||||
dynamic: {
|
||||
light: normalizeColor(dynamic.light),
|
||||
dark: normalizeColor(dynamic.dark),
|
||||
},
|
||||
};
|
||||
return dynamicColor;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const processColorObject = (
|
||||
color: NativeColorValue,
|
||||
): ?NativeColorValue => {
|
||||
if ('dynamic' in color && color.dynamic != null) {
|
||||
const processColor = require('./processColor');
|
||||
const dynamic = color.dynamic;
|
||||
const dynamicColor: NativeColorValue = {
|
||||
dynamic: {
|
||||
light: processColor(dynamic.light),
|
||||
dark: processColor(dynamic.dark),
|
||||
},
|
||||
};
|
||||
return dynamicColor;
|
||||
}
|
||||
return color;
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* 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 strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ColorValue} from './StyleSheetTypes';
|
||||
import {ColorAndroidPrivate} from './PlatformColorValueTypes';
|
||||
|
||||
export const ColorAndroid = (color: string): ColorValue => {
|
||||
return ColorAndroidPrivate(color);
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* 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 strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ColorValue} from './StyleSheetTypes';
|
||||
|
||||
export const ColorAndroid = (color: string): ColorValue => {
|
||||
throw new Error('ColorAndroid is not available on this platform.');
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* 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 strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ColorValue} from './StyleSheetTypes';
|
||||
import {DynamicColorIOSPrivate} from './PlatformColorValueTypes';
|
||||
|
||||
export type DynamicColorIOSTuple = {
|
||||
light: ColorValue,
|
||||
dark: ColorValue,
|
||||
};
|
||||
|
||||
export const DynamicColorIOS = (tuple: DynamicColorIOSTuple): ColorValue => {
|
||||
return DynamicColorIOSPrivate({light: tuple.light, dark: tuple.dark});
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* 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 strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ColorValue} from './StyleSheetTypes';
|
||||
|
||||
export type DynamicColorIOSTuple = {
|
||||
light: ColorValue,
|
||||
dark: ColorValue,
|
||||
};
|
||||
|
||||
export const DynamicColorIOS = (tuple: DynamicColorIOSTuple): ColorValue => {
|
||||
throw new Error('DynamicColorIOS is not available on this platform.');
|
||||
};
|
|
@ -12,7 +12,10 @@
|
|||
|
||||
const AnimatedNode = require('../Animated/src/nodes/AnimatedNode');
|
||||
|
||||
export type ColorValue = null | string;
|
||||
import type {NativeColorValue} from './PlatformColorValueTypes';
|
||||
|
||||
export type ColorValue = null | string | NativeColorValue;
|
||||
|
||||
export type ColorArrayValue = null | $ReadOnlyArray<ColorValue>;
|
||||
export type PointValue = {|
|
||||
x: number,
|
||||
|
|
|
@ -10,8 +10,16 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
const {OS} = require('../../Utilities/Platform');
|
||||
const normalizeColor = require('../normalizeColor');
|
||||
|
||||
const PlatformColorIOS = require('../PlatformColorValueTypes.ios')
|
||||
.PlatformColor;
|
||||
const DynamicColorIOS = require('../PlatformColorValueTypesIOS.ios')
|
||||
.DynamicColorIOS;
|
||||
const PlatformColorAndroid = require('../PlatformColorValueTypes.android')
|
||||
.PlatformColor;
|
||||
|
||||
describe('normalizeColor', function() {
|
||||
it('should accept only spec compliant colors', function() {
|
||||
expect(normalizeColor('#abc')).not.toBe(null);
|
||||
|
@ -128,4 +136,48 @@ describe('normalizeColor', function() {
|
|||
const normalizedColor = normalizeColor('red') || 0;
|
||||
expect(normalizeColor(normalizedColor)).toBe(normalizedColor);
|
||||
});
|
||||
|
||||
describe('iOS', () => {
|
||||
if (OS === 'ios') {
|
||||
it('should normalize iOS PlatformColor colors', () => {
|
||||
const color = PlatformColorIOS('systemRedColor');
|
||||
const normalizedColor = normalizeColor(color);
|
||||
const expectedColor = {semantic: ['systemRedColor']};
|
||||
expect(normalizedColor).toEqual(expectedColor);
|
||||
});
|
||||
|
||||
it('should normalize iOS Dynamic colors with named colors', () => {
|
||||
const color = DynamicColorIOS({light: 'black', dark: 'white'});
|
||||
const normalizedColor = normalizeColor(color);
|
||||
const expectedColor = {dynamic: {light: 'black', dark: 'white'}};
|
||||
expect(normalizedColor).toEqual(expectedColor);
|
||||
});
|
||||
|
||||
it('should normalize iOS Dynamic colors with PlatformColor colors', () => {
|
||||
const color = DynamicColorIOS({
|
||||
light: PlatformColorIOS('systemBlackColor'),
|
||||
dark: PlatformColorIOS('systemWhiteColor'),
|
||||
});
|
||||
const normalizedColor = normalizeColor(color);
|
||||
const expectedColor = {
|
||||
dynamic: {
|
||||
light: {semantic: ['systemBlackColor']},
|
||||
dark: {semantic: ['systemWhiteColor']},
|
||||
},
|
||||
};
|
||||
expect(normalizedColor).toEqual(expectedColor);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Android', () => {
|
||||
if (OS === 'android') {
|
||||
it('should normalize Android PlatformColor colors', () => {
|
||||
const color = PlatformColorAndroid('?attr/colorPrimary');
|
||||
const normalizedColor = normalizeColor(color);
|
||||
const expectedColor = {resource_paths: ['?attr/colorPrimary']};
|
||||
expect(normalizedColor).toEqual(expectedColor);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,13 @@
|
|||
const {OS} = require('../../Utilities/Platform');
|
||||
const processColor = require('../processColor');
|
||||
|
||||
const PlatformColorIOS = require('../PlatformColorValueTypes.ios')
|
||||
.PlatformColor;
|
||||
const DynamicColorIOS = require('../PlatformColorValueTypesIOS.ios')
|
||||
.DynamicColorIOS;
|
||||
const PlatformColorAndroid = require('../PlatformColorValueTypes.android')
|
||||
.PlatformColor;
|
||||
|
||||
const platformSpecific =
|
||||
OS === 'android'
|
||||
? unsigned => unsigned | 0 //eslint-disable-line no-bitwise
|
||||
|
@ -84,4 +91,33 @@ describe('processColor', () => {
|
|||
expect(colorFromString).toEqual(platformSpecific(expectedInt));
|
||||
});
|
||||
});
|
||||
|
||||
describe('iOS', () => {
|
||||
if (OS === 'ios') {
|
||||
it('should process iOS PlatformColor colors', () => {
|
||||
const color = PlatformColorIOS('systemRedColor');
|
||||
const processedColor = processColor(color);
|
||||
const expectedColor = {semantic: ['systemRedColor']};
|
||||
expect(processedColor).toEqual(expectedColor);
|
||||
});
|
||||
|
||||
it('should process iOS Dynamic colors', () => {
|
||||
const color = DynamicColorIOS({light: 'black', dark: 'white'});
|
||||
const processedColor = processColor(color);
|
||||
const expectedColor = {dynamic: {light: 0xff000000, dark: 0xffffffff}};
|
||||
expect(processedColor).toEqual(expectedColor);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Android', () => {
|
||||
if (OS === 'android') {
|
||||
it('should process Android PlatformColor colors', () => {
|
||||
const color = PlatformColorAndroid('?attr/colorPrimary');
|
||||
const processedColor = processColor(color);
|
||||
const expectedColor = {resource_paths: ['?attr/colorPrimary']};
|
||||
expect(processedColor).toEqual(expectedColor);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,13 @@
|
|||
const {OS} = require('../../Utilities/Platform');
|
||||
const processColorArray = require('../processColorArray');
|
||||
|
||||
const PlatformColorIOS = require('../PlatformColorValueTypes.ios')
|
||||
.PlatformColor;
|
||||
const DynamicColorIOS = require('../PlatformColorValueTypesIOS.ios')
|
||||
.DynamicColorIOS;
|
||||
const PlatformColorAndroid = require('../PlatformColorValueTypes.android')
|
||||
.PlatformColor;
|
||||
|
||||
const platformSpecific =
|
||||
OS === 'android'
|
||||
? unsigned => unsigned | 0 //eslint-disable-line no-bitwise
|
||||
|
@ -57,4 +64,48 @@ describe('processColorArray', () => {
|
|||
expect(colorFromNoArray).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('iOS', () => {
|
||||
if (OS === 'ios') {
|
||||
it('should convert array of iOS PlatformColor colors', () => {
|
||||
const colorFromArray = processColorArray([
|
||||
PlatformColorIOS('systemColorWhite'),
|
||||
PlatformColorIOS('systemColorBlack'),
|
||||
]);
|
||||
const expectedColorValueArray = [
|
||||
{semantic: ['systemColorWhite']},
|
||||
{semantic: ['systemColorBlack']},
|
||||
];
|
||||
expect(colorFromArray).toEqual(expectedColorValueArray);
|
||||
});
|
||||
|
||||
it('should process iOS Dynamic colors', () => {
|
||||
const colorFromArray = processColorArray([
|
||||
DynamicColorIOS({light: 'black', dark: 'white'}),
|
||||
DynamicColorIOS({light: 'white', dark: 'black'}),
|
||||
]);
|
||||
const expectedColorValueArray = [
|
||||
{dynamic: {light: 0xff000000, dark: 0xffffffff}},
|
||||
{dynamic: {light: 0xffffffff, dark: 0xff000000}},
|
||||
];
|
||||
expect(colorFromArray).toEqual(expectedColorValueArray);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Android', () => {
|
||||
if (OS === 'android') {
|
||||
it('should convert array of Android PlatformColor colors', () => {
|
||||
const colorFromArray = processColorArray([
|
||||
PlatformColorAndroid('?attr/colorPrimary'),
|
||||
PlatformColorAndroid('?colorPrimaryDark'),
|
||||
]);
|
||||
const expectedColorValueArray = [
|
||||
{resource_paths: ['?attr/colorPrimary']},
|
||||
{resource_paths: ['?colorPrimaryDark']},
|
||||
];
|
||||
expect(colorFromArray).toEqual(expectedColorValueArray);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,7 +12,12 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
function normalizeColor(color: string | number): ?number {
|
||||
import type {ColorValue} from './StyleSheetTypes';
|
||||
import type {ProcessedColorValue} from './processColor';
|
||||
|
||||
function normalizeColor(
|
||||
color: ?(ColorValue | ProcessedColorValue),
|
||||
): ?ProcessedColorValue {
|
||||
const matchers = getMatchers();
|
||||
let match;
|
||||
|
||||
|
@ -23,6 +28,21 @@ function normalizeColor(color: string | number): ?number {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (typeof color === 'object' && color != null) {
|
||||
const normalizeColorObject = require('./PlatformColorValueTypes')
|
||||
.normalizeColorObject;
|
||||
|
||||
const normalizedColorObj = normalizeColorObject(color);
|
||||
|
||||
if (normalizedColorObj != null) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof color !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ordered based on occurrences on Facebook codebase
|
||||
if ((match = matchers.hex6.exec(color))) {
|
||||
return parseInt(match[1] + 'ff', 16) >>> 0;
|
||||
|
|
|
@ -14,35 +14,48 @@ const Platform = require('../Utilities/Platform');
|
|||
|
||||
const normalizeColor = require('./normalizeColor');
|
||||
|
||||
// TODO: This is an empty object for now, just to enforce that everything using this
|
||||
// downstream is correct. This will be replaced with an import to other files
|
||||
// with a platform specific implementation. See the PR for more information
|
||||
// https://github.com/facebook/react-native/pull/27908
|
||||
opaque type NativeColorType = {};
|
||||
export type ProcessedColorValue = ?number | NativeColorType;
|
||||
import type {ColorValue} from './StyleSheetTypes';
|
||||
import type {NativeColorValue} from './PlatformColorValueTypes';
|
||||
|
||||
export type ProcessedColorValue = number | NativeColorValue;
|
||||
|
||||
/* eslint no-bitwise: 0 */
|
||||
function processColor(color?: ?(string | number)): ProcessedColorValue {
|
||||
function processColor(color?: ?(number | ColorValue)): ?ProcessedColorValue {
|
||||
if (color === undefined || color === null) {
|
||||
return color;
|
||||
}
|
||||
|
||||
let int32Color = normalizeColor(color);
|
||||
if (int32Color === null || int32Color === undefined) {
|
||||
let normalizedColor = normalizeColor(color);
|
||||
if (normalizedColor === null || normalizedColor === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof normalizedColor === 'object') {
|
||||
const processColorObject = require('./PlatformColorValueTypes')
|
||||
.processColorObject;
|
||||
|
||||
const processedColorObj = processColorObject(normalizedColor);
|
||||
|
||||
if (processedColorObj != null) {
|
||||
return processedColorObj;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof normalizedColor !== 'number') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Converts 0xrrggbbaa into 0xaarrggbb
|
||||
int32Color = ((int32Color << 24) | (int32Color >>> 8)) >>> 0;
|
||||
normalizedColor = ((normalizedColor << 24) | (normalizedColor >>> 8)) >>> 0;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
// Android use 32 bit *signed* integer to represent the color
|
||||
// We utilize the fact that bitwise operations in JS also operates on
|
||||
// signed 32 bit integers, so that we can use those to convert from
|
||||
// *unsigned* to *signed* 32bit int that way.
|
||||
int32Color = int32Color | 0x0;
|
||||
normalizedColor = normalizedColor | 0x0;
|
||||
}
|
||||
return int32Color;
|
||||
return normalizedColor;
|
||||
}
|
||||
|
||||
module.exports = processColor;
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
'use strict';
|
||||
|
||||
const processColor = require('./processColor');
|
||||
|
||||
import type {ColorValue} from './StyleSheetTypes';
|
||||
import type {ProcessedColorValue} from './processColor';
|
||||
|
||||
function processColorArray(
|
||||
colors: ?Array<string>,
|
||||
): ?Array<ProcessedColorValue> {
|
||||
colors: ?Array<ColorValue>,
|
||||
): ?Array<?ProcessedColorValue> {
|
||||
return colors == null ? null : colors.map(processColor);
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
[super drawRect:rect];
|
||||
if (!_textStorage) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
272E6B3F1BEA849E001FCF37 /* UpdatePropertiesExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */; };
|
||||
27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; };
|
||||
2DDEF0101F84BF7B00DBDF73 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2DDEF00F1F84BF7B00DBDF73 /* Images.xcassets */; };
|
||||
383889DA23A7398900D06C3E /* RCTConvert_UIColorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 383889D923A7398900D06C3E /* RCTConvert_UIColorTests.m */; };
|
||||
3D2AFAF51D646CF80089D1A3 /* legacy_image@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D2AFAF41D646CF80089D1A3 /* legacy_image@2x.png */; };
|
||||
5C60EB1C226440DB0018C04F /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5C60EB1B226440DB0018C04F /* AppDelegate.mm */; };
|
||||
5CB07C9B226467E60039471C /* RNTesterTurboModuleProvider.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5CB07C99226467E60039471C /* RNTesterTurboModuleProvider.mm */; };
|
||||
|
@ -81,6 +82,7 @@
|
|||
27F441EA1BEBE5030039B79C /* FlexibleSizeExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FlexibleSizeExampleView.h; path = RNTester/NativeExampleViews/FlexibleSizeExampleView.h; sourceTree = "<group>"; };
|
||||
2DDEF00F1F84BF7B00DBDF73 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RNTester/Images.xcassets; sourceTree = "<group>"; };
|
||||
34028D6B10F47E490042EB27 /* Pods-RNTesterUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNTesterUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RNTesterUnitTests/Pods-RNTesterUnitTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
383889D923A7398900D06C3E /* RCTConvert_UIColorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIColorTests.m; sourceTree = "<group>"; };
|
||||
3D2AFAF41D646CF80089D1A3 /* legacy_image@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "legacy_image@2x.png"; path = "RNTester/legacy_image@2x.png"; sourceTree = "<group>"; };
|
||||
5BEC8567F3741044B6A5EFC5 /* Pods-RNTester.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNTester.release.xcconfig"; path = "Pods/Target Support Files/Pods-RNTester/Pods-RNTester.release.xcconfig"; sourceTree = "<group>"; };
|
||||
5C60EB1B226440DB0018C04F /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = RNTester/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
|
@ -324,6 +326,7 @@
|
|||
E7DB20CC22B2BAA5005AC45F /* RCTComponentPropsTests.m */,
|
||||
E7DB20CA22B2BAA5005AC45F /* RCTConvert_NSURLTests.m */,
|
||||
E7DB20CE22B2BAA5005AC45F /* RCTConvert_YGValueTests.m */,
|
||||
383889D923A7398900D06C3E /* RCTConvert_UIColorTests.m */,
|
||||
E7DB20C822B2BAA5005AC45F /* RCTDevMenuTests.m */,
|
||||
E7DB20C022B2BAA4005AC45F /* RCTEventDispatcherTests.m */,
|
||||
E7DB20AF22B2BAA4005AC45F /* RCTFontTests.m */,
|
||||
|
@ -686,6 +689,7 @@
|
|||
E7DB20D322B2BAA6005AC45F /* RCTBlobManagerTests.m in Sources */,
|
||||
E7DB20DC22B2BAA6005AC45F /* RCTUIManagerTests.m in Sources */,
|
||||
E7DB20E322B2BAA6005AC45F /* RCTAllocationTests.m in Sources */,
|
||||
383889DA23A7398900D06C3E /* RCTConvert_UIColorTests.m in Sources */,
|
||||
E7DB20E622B2BAA6005AC45F /* RCTImageLoaderHelpers.m in Sources */,
|
||||
E7DB20D622B2BAA6005AC45F /* RCTFontTests.m in Sources */,
|
||||
E7DB20DB22B2BAA6005AC45F /* RCTNativeAnimatedNodesManagerTests.m in Sources */,
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
@interface RCTConvert_NSColorTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
static BOOL CGColorsAreEqual(CGColorRef color1, CGColorRef color2) {
|
||||
CGFloat rgba1[4];
|
||||
CGFloat rgba2[4];
|
||||
RCTGetRGBAColorComponents(color1, rgba1);
|
||||
RCTGetRGBAColorComponents(color2, rgba2);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (rgba1[i] != rgba2[i]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
@implementation RCTConvert_NSColorTests
|
||||
|
||||
- (void)testColor
|
||||
{
|
||||
id json = RCTJSONParse(@"{ \"semantic\": \"lightTextColor\" }", nil);
|
||||
UIColor *value = [RCTConvert UIColor:json];
|
||||
XCTAssertEqualObjects(value, [UIColor lightTextColor]);
|
||||
}
|
||||
|
||||
- (void)testColorFailure
|
||||
{
|
||||
id json = RCTJSONParse(@"{ \"semantic\": \"bogusColor\" }", nil);
|
||||
|
||||
__block NSString *errorMessage = nil;
|
||||
RCTLogFunction defaultLogFunction = RCTGetLogFunction();
|
||||
RCTSetLogFunction(^(__unused RCTLogLevel level, __unused RCTLogSource source, __unused NSString *fileName, __unused NSNumber *lineNumber, NSString *message) {
|
||||
errorMessage = message;
|
||||
});
|
||||
|
||||
UIColor *value = [RCTConvert UIColor:json];
|
||||
|
||||
RCTSetLogFunction(defaultLogFunction);
|
||||
|
||||
XCTAssertEqualObjects(value, nil);
|
||||
XCTAssertTrue([errorMessage containsString:@"labelColor"]); // the RedBox message will contain a list of the valid color names.
|
||||
}
|
||||
|
||||
- (void)testFallbackColor
|
||||
{
|
||||
id json = RCTJSONParse(@"{ \"semantic\": \"unitTestFallbackColorIOS\" }", nil);
|
||||
UIColor *value = [RCTConvert UIColor:json];
|
||||
XCTAssertTrue(CGColorsAreEqual([value CGColor], [[UIColor blueColor] CGColor]));
|
||||
}
|
||||
|
||||
- (void)testDynamicColor
|
||||
{
|
||||
// 0 == 0x00000000 == black
|
||||
// 16777215 == 0x00FFFFFF == white
|
||||
id json = RCTJSONParse(@"{ \"dynamic\": { \"light\":0, \"dark\":16777215 } }", nil);
|
||||
UIColor *value = [RCTConvert UIColor:json];
|
||||
XCTAssertNotNil(value);
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
if (@available(iOS 13.0, *)) {
|
||||
id savedTraitCollection = [UITraitCollection currentTraitCollection];
|
||||
|
||||
[UITraitCollection setCurrentTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]];
|
||||
CGFloat rgba[4];
|
||||
RCTGetRGBAColorComponents([value CGColor], rgba);
|
||||
XCTAssertEqual(rgba[0], 0);
|
||||
XCTAssertEqual(rgba[1], 0);
|
||||
XCTAssertEqual(rgba[2], 0);
|
||||
XCTAssertEqual(rgba[3], 0);
|
||||
|
||||
[UITraitCollection setCurrentTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark]];
|
||||
RCTGetRGBAColorComponents([value CGColor], rgba);
|
||||
XCTAssertEqual(rgba[0], 1);
|
||||
XCTAssertEqual(rgba[1], 1);
|
||||
XCTAssertEqual(rgba[2], 1);
|
||||
XCTAssertEqual(rgba[3], 0);
|
||||
|
||||
[UITraitCollection setCurrentTraitCollection:savedTraitCollection];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)testCompositeDynamicColor
|
||||
{
|
||||
id json = RCTJSONParse(@"{ \"dynamic\": { \"light\": { \"semantic\": \"systemRedColor\" }, \"dark\":{ \"semantic\": \"systemBlueColor\" } } }", nil);
|
||||
UIColor *value = [RCTConvert UIColor:json];
|
||||
XCTAssertNotNil(value);
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
if (@available(iOS 13.0, *)) {
|
||||
id savedTraitCollection = [UITraitCollection currentTraitCollection];
|
||||
|
||||
[UITraitCollection setCurrentTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]];
|
||||
|
||||
XCTAssertTrue(CGColorsAreEqual([value CGColor], [[UIColor systemRedColor] CGColor]));
|
||||
|
||||
[UITraitCollection setCurrentTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark]];
|
||||
|
||||
XCTAssertTrue(CGColorsAreEqual([value CGColor], [[UIColor systemBlueColor] CGColor]));
|
||||
|
||||
[UITraitCollection setCurrentTraitCollection:savedTraitCollection];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)testGenerateFallbacks
|
||||
{
|
||||
NSDictionary<NSString *, NSNumber*>* semanticColors = @{
|
||||
// https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors
|
||||
// Label Colors
|
||||
@"labelColor": @(0xFF000000),
|
||||
@"secondaryLabelColor": @(0x993c3c43),
|
||||
@"tertiaryLabelColor": @(0x4c3c3c43),
|
||||
@"quaternaryLabelColor": @(0x2d3c3c43),
|
||||
// Fill Colors
|
||||
@"systemFillColor": @(0x33787880),
|
||||
@"secondarySystemFillColor": @(0x28787880),
|
||||
@"tertiarySystemFillColor": @(0x1e767680),
|
||||
@"quaternarySystemFillColor": @(0x14747480),
|
||||
// Text Colors
|
||||
@"placeholderTextColor": @(0x4c3c3c43),
|
||||
// Standard Content Background Colors
|
||||
@"systemBackgroundColor": @(0xFFffffff),
|
||||
@"secondarySystemBackgroundColor": @(0xFFf2f2f7),
|
||||
@"tertiarySystemBackgroundColor": @(0xFFffffff),
|
||||
// Grouped Content Background Colors
|
||||
@"systemGroupedBackgroundColor": @(0xFFf2f2f7),
|
||||
@"secondarySystemGroupedBackgroundColor": @(0xFFffffff),
|
||||
@"tertiarySystemGroupedBackgroundColor": @(0xFFf2f2f7),
|
||||
// Separator Colors
|
||||
@"separatorColor": @(0x493c3c43),
|
||||
@"opaqueSeparatorColor": @(0xFFc6c6c8),
|
||||
// Link Color
|
||||
@"linkColor": @(0xFF007aff),
|
||||
// https://developer.apple.com/documentation/uikit/uicolor/standard_colors
|
||||
// Adaptable Colors
|
||||
@"systemBrownColor": @(0xFFa2845e),
|
||||
@"systemIndigoColor": @(0xFF5856d6),
|
||||
// Adaptable Gray Colors
|
||||
@"systemGray2Color": @(0xFFaeaeb2),
|
||||
@"systemGray3Color": @(0xFFc7c7cc),
|
||||
@"systemGray4Color": @(0xFFd1d1d6),
|
||||
@"systemGray5Color": @(0xFFe5e5ea),
|
||||
@"systemGray6Color": @(0xFFf2f2f7),
|
||||
};
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
id savedTraitCollection = nil;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
savedTraitCollection = [UITraitCollection currentTraitCollection];
|
||||
|
||||
[UITraitCollection setCurrentTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]];
|
||||
}
|
||||
#endif
|
||||
|
||||
for (NSString *semanticColor in semanticColors) {
|
||||
id json = RCTJSONParse([NSString stringWithFormat:@"{ \"semantic\": \"%@\" }", semanticColor], nil);
|
||||
UIColor *value = [RCTConvert UIColor:json];
|
||||
XCTAssertNotNil(value);
|
||||
|
||||
NSNumber *fallback = [semanticColors objectForKey:semanticColor];
|
||||
NSUInteger rgbValue = [fallback unsignedIntegerValue];
|
||||
NSUInteger alpha1 = ((rgbValue & 0xFF000000) >> 24);
|
||||
NSUInteger red1 = ((rgbValue & 0x00FF0000) >> 16);
|
||||
NSUInteger green1 = ((rgbValue & 0x0000FF00) >> 8);
|
||||
NSUInteger blue1 = ((rgbValue & 0x000000FF) >> 0);
|
||||
|
||||
CGFloat rgba[4];
|
||||
RCTGetRGBAColorComponents([value CGColor], rgba);
|
||||
NSUInteger red2 = rgba[0] * 255;
|
||||
NSUInteger green2 = rgba[1] * 255;
|
||||
NSUInteger blue2 = rgba[2] * 255;
|
||||
NSUInteger alpha2 = rgba[3] * 255;
|
||||
|
||||
XCTAssertEqual(red1, red2);
|
||||
XCTAssertEqual(green1, green2);
|
||||
XCTAssertEqual(blue1, blue2);
|
||||
XCTAssertEqual(alpha1, alpha2);
|
||||
}
|
||||
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[UITraitCollection setCurrentTraitCollection:savedTraitCollection];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
|
@ -12,28 +12,29 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import {Appearance} from 'react-native';
|
||||
import type {ColorValue} from '../../../Libraries/StyleSheet/StyleSheetTypes';
|
||||
|
||||
export type RNTesterTheme = {
|
||||
LabelColor: string,
|
||||
SecondaryLabelColor: string,
|
||||
TertiaryLabelColor: string,
|
||||
QuaternaryLabelColor: string,
|
||||
PlaceholderTextColor: string,
|
||||
SystemBackgroundColor: string,
|
||||
SecondarySystemBackgroundColor: string,
|
||||
TertiarySystemBackgroundColor: string,
|
||||
GroupedBackgroundColor: string,
|
||||
SecondaryGroupedBackgroundColor: string,
|
||||
TertiaryGroupedBackgroundColor: string,
|
||||
SystemFillColor: string,
|
||||
SecondarySystemFillColor: string,
|
||||
TertiarySystemFillColor: string,
|
||||
QuaternarySystemFillColor: string,
|
||||
SeparatorColor: string,
|
||||
OpaqueSeparatorColor: string,
|
||||
LinkColor: string,
|
||||
SystemPurpleColor: string,
|
||||
ToolbarColor: string,
|
||||
LabelColor: ColorValue,
|
||||
SecondaryLabelColor: ColorValue,
|
||||
TertiaryLabelColor: ColorValue,
|
||||
QuaternaryLabelColor: ColorValue,
|
||||
PlaceholderTextColor: ColorValue,
|
||||
SystemBackgroundColor: ColorValue,
|
||||
SecondarySystemBackgroundColor: ColorValue,
|
||||
TertiarySystemBackgroundColor: ColorValue,
|
||||
GroupedBackgroundColor: ColorValue,
|
||||
SecondaryGroupedBackgroundColor: ColorValue,
|
||||
TertiaryGroupedBackgroundColor: ColorValue,
|
||||
SystemFillColor: ColorValue,
|
||||
SecondarySystemFillColor: ColorValue,
|
||||
TertiarySystemFillColor: ColorValue,
|
||||
QuaternarySystemFillColor: ColorValue,
|
||||
SeparatorColor: ColorValue,
|
||||
OpaqueSeparatorColor: ColorValue,
|
||||
LinkColor: ColorValue,
|
||||
SystemPurpleColor: ColorValue,
|
||||
ToolbarColor: ColorValue,
|
||||
...
|
||||
};
|
||||
|
||||
|
|
|
@ -191,7 +191,9 @@ exports.examples = [
|
|||
paddingVertical: 2,
|
||||
color: theme.LabelColor,
|
||||
}}>
|
||||
{theme[key]}
|
||||
{typeof theme[key] === 'string'
|
||||
? theme[key]
|
||||
: JSON.stringify(theme[key])}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
import Platform from '../../../../Libraries/Utilities/Platform';
|
||||
const {
|
||||
ColorAndroid,
|
||||
DynamicColorIOS,
|
||||
PlatformColor,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} = ReactNative;
|
||||
|
||||
function PlatformColorsExample() {
|
||||
function createTable() {
|
||||
let colors = [];
|
||||
if (Platform.OS === 'ios') {
|
||||
colors = [
|
||||
// https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors
|
||||
// Label Colors
|
||||
{label: 'labelColor', color: PlatformColor('labelColor')},
|
||||
{
|
||||
label: 'secondaryLabelColor',
|
||||
color: PlatformColor('secondaryLabelColor'),
|
||||
},
|
||||
{
|
||||
label: 'tertiaryLabelColor',
|
||||
color: PlatformColor('tertiaryLabelColor'),
|
||||
},
|
||||
{
|
||||
label: 'quaternaryLabelColor',
|
||||
color: PlatformColor('quaternaryLabelColor'),
|
||||
},
|
||||
// Fill Colors
|
||||
{label: 'systemFillColor', color: PlatformColor('systemFillColor')},
|
||||
{
|
||||
label: 'secondarySystemFillColor',
|
||||
color: PlatformColor('secondarySystemFillColor'),
|
||||
},
|
||||
{
|
||||
label: 'tertiarySystemFillColor',
|
||||
color: PlatformColor('tertiarySystemFillColor'),
|
||||
},
|
||||
{
|
||||
label: 'quaternarySystemFillColor',
|
||||
color: PlatformColor('quaternarySystemFillColor'),
|
||||
},
|
||||
// Text Colors
|
||||
{
|
||||
label: 'placeholderTextColor',
|
||||
color: PlatformColor('placeholderTextColor'),
|
||||
},
|
||||
// Standard Content Background Colors
|
||||
{
|
||||
label: 'systemBackgroundColor',
|
||||
color: PlatformColor('systemBackgroundColor'),
|
||||
},
|
||||
{
|
||||
label: 'secondarySystemBackgroundColor',
|
||||
color: PlatformColor('secondarySystemBackgroundColor'),
|
||||
},
|
||||
{
|
||||
label: 'tertiarySystemBackgroundColor',
|
||||
color: PlatformColor('tertiarySystemBackgroundColor'),
|
||||
},
|
||||
// Grouped Content Background Colors
|
||||
{
|
||||
label: 'systemGroupedBackgroundColor',
|
||||
color: PlatformColor('systemGroupedBackgroundColor'),
|
||||
},
|
||||
{
|
||||
label: 'secondarySystemGroupedBackgroundColor',
|
||||
color: PlatformColor('secondarySystemGroupedBackgroundColor'),
|
||||
},
|
||||
{
|
||||
label: 'tertiarySystemGroupedBackgroundColor',
|
||||
color: PlatformColor('tertiarySystemGroupedBackgroundColor'),
|
||||
},
|
||||
// Separator Colors
|
||||
{label: 'separatorColor', color: PlatformColor('separatorColor')},
|
||||
{
|
||||
label: 'opaqueSeparatorColor',
|
||||
color: PlatformColor('opaqueSeparatorColor'),
|
||||
},
|
||||
// Link Color
|
||||
{label: 'linkColor', color: PlatformColor('linkColor')},
|
||||
// Nonadaptable Colors
|
||||
{label: 'darkTextColor', color: PlatformColor('darkTextColor')},
|
||||
{label: 'lightTextColor', color: PlatformColor('lightTextColor')},
|
||||
// https://developer.apple.com/documentation/uikit/uicolor/standard_colors
|
||||
// Adaptable Colors
|
||||
{label: 'systemBlueColor', color: PlatformColor('systemBlueColor')},
|
||||
{label: 'systemBrownColor', color: PlatformColor('systemBrownColor')},
|
||||
{label: 'systemGreenColor', color: PlatformColor('systemGreenColor')},
|
||||
{label: 'systemIndigoColor', color: PlatformColor('systemIndigoColor')},
|
||||
{label: 'systemOrangeColor', color: PlatformColor('systemOrangeColor')},
|
||||
{label: 'systemPinkColor', color: PlatformColor('systemPinkColor')},
|
||||
{label: 'systemPurpleColor', color: PlatformColor('systemPurpleColor')},
|
||||
{label: 'systemRedColor', color: PlatformColor('systemRedColor')},
|
||||
{label: 'systemTealColor', color: PlatformColor('systemTealColor')},
|
||||
{label: 'systemYellowColor', color: PlatformColor('systemYellowColor')},
|
||||
// Adaptable Gray Colors
|
||||
{label: 'systemGrayColor', color: PlatformColor('systemGrayColor')},
|
||||
{label: 'systemGray2Color', color: PlatformColor('systemGray2Color')},
|
||||
{label: 'systemGray3Color', color: PlatformColor('systemGray3Color')},
|
||||
{label: 'systemGray4Color', color: PlatformColor('systemGray4Color')},
|
||||
{label: 'systemGray5Color', color: PlatformColor('systemGray5Color')},
|
||||
{label: 'systemGray6Color', color: PlatformColor('systemGray6Color')},
|
||||
];
|
||||
} else if (Platform.OS === 'android') {
|
||||
colors = [
|
||||
{label: '?attr/colorAccent', color: PlatformColor('?attr/colorAccent')},
|
||||
{
|
||||
label: '?attr/colorBackgroundFloating',
|
||||
color: PlatformColor('?attr/colorBackgroundFloating'),
|
||||
},
|
||||
{
|
||||
label: '?attr/colorButtonNormal',
|
||||
color: PlatformColor('?attr/colorButtonNormal'),
|
||||
},
|
||||
{
|
||||
label: '?attr/colorControlActivated',
|
||||
color: PlatformColor('?attr/colorControlActivated'),
|
||||
},
|
||||
{
|
||||
label: '?attr/colorControlHighlight',
|
||||
color: PlatformColor('?attr/colorControlHighlight'),
|
||||
},
|
||||
{
|
||||
label: '?attr/colorControlNormal',
|
||||
color: PlatformColor('?attr/colorControlNormal'),
|
||||
},
|
||||
{
|
||||
label: '?android:colorError',
|
||||
color: PlatformColor('?android:colorError'),
|
||||
},
|
||||
{
|
||||
label: '?android:attr/colorError',
|
||||
color: PlatformColor('?android:attr/colorError'),
|
||||
},
|
||||
{
|
||||
label: '?attr/colorPrimary',
|
||||
color: PlatformColor('?attr/colorPrimary'),
|
||||
},
|
||||
{label: '?colorPrimaryDark', color: PlatformColor('?colorPrimaryDark')},
|
||||
{
|
||||
label: '@android:color/holo_purple',
|
||||
color: PlatformColor('@android:color/holo_purple'),
|
||||
},
|
||||
{
|
||||
label: '@android:color/holo_green_light',
|
||||
color: PlatformColor('@android:color/holo_green_light'),
|
||||
},
|
||||
{
|
||||
label: '@color/catalyst_redbox_background',
|
||||
color: PlatformColor('@color/catalyst_redbox_background'),
|
||||
},
|
||||
{
|
||||
label: '@color/catalyst_logbox_background',
|
||||
color: PlatformColor('@color/catalyst_logbox_background'),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
let table = [];
|
||||
for (let color of colors) {
|
||||
table.push(
|
||||
<View style={styles.row} key={color.label}>
|
||||
<Text style={styles.labelCell}>{color.label}</Text>
|
||||
<View
|
||||
style={{
|
||||
...styles.colorCell,
|
||||
backgroundColor: color.color,
|
||||
}}
|
||||
/>
|
||||
</View>,
|
||||
);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
return <View style={styles.column}>{createTable()}</View>;
|
||||
}
|
||||
|
||||
function FallbackColorsExample() {
|
||||
let color = {};
|
||||
if (Platform.OS === 'ios') {
|
||||
color = {
|
||||
label: "PlatformColor('bogus', 'systemGreenColor')",
|
||||
color: PlatformColor('bogus', 'systemGreenColor'),
|
||||
};
|
||||
} else if (Platform.OS === 'android') {
|
||||
color = {
|
||||
label: "PlatformColor('bogus', '@color/catalyst_redbox_background')",
|
||||
color: PlatformColor('bogus', '@color/catalyst_redbox_background'),
|
||||
};
|
||||
} else {
|
||||
throw 'Unexpected Platform.OS: ' + Platform.OS;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.column}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.labelCell}>{color.label}</Text>
|
||||
<View
|
||||
style={{
|
||||
...styles.colorCell,
|
||||
backgroundColor: color.color,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function DynamicColorsExample() {
|
||||
return Platform.OS === 'ios' ? (
|
||||
<View style={styles.column}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.labelCell}>
|
||||
DynamicColorIOS({'{\n'}
|
||||
{' '}light: 'red', dark: 'blue'{'\n'}
|
||||
{'}'})
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
...styles.colorCell,
|
||||
backgroundColor: DynamicColorIOS({light: 'red', dark: 'blue'}),
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.labelCell}>
|
||||
DynamicColorIOS({'{\n'}
|
||||
{' '}light: PlatformColor('systemBlueColor'),{'\n'}
|
||||
{' '}dark: PlatformColor('systemRedColor'),{'\n'}
|
||||
{'}'})
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
...styles.colorCell,
|
||||
backgroundColor: DynamicColorIOS({
|
||||
light: PlatformColor('systemBlueColor'),
|
||||
dark: PlatformColor('systemRedColor'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.labelCell}>Not applicable on this platform</Text>
|
||||
);
|
||||
}
|
||||
|
||||
function AndroidColorsExample() {
|
||||
return Platform.OS === 'android' ? (
|
||||
<View style={styles.column}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.labelCell}>ColorAndroid('?attr/colorAccent')</Text>
|
||||
<View
|
||||
style={{
|
||||
...styles.colorCell,
|
||||
backgroundColor: ColorAndroid('?attr/colorAccent'),
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.labelCell}>Not applicable on this platform</Text>
|
||||
);
|
||||
}
|
||||
|
||||
function VariantColorsExample() {
|
||||
return (
|
||||
<View style={styles.column}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.labelCell}>
|
||||
{Platform.OS === 'ios'
|
||||
? "DynamicColorIOS({light: 'red', dark: 'blue'})"
|
||||
: "ColorAndroid('?attr/colorAccent')"}
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
...styles.colorCell,
|
||||
backgroundColor:
|
||||
Platform.OS === 'ios'
|
||||
? DynamicColorIOS({light: 'red', dark: 'blue'})
|
||||
: ColorAndroid('?attr/colorAccent'),
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
column: {flex: 1, flexDirection: 'column'},
|
||||
row: {flex: 0.75, flexDirection: 'row'},
|
||||
labelCell: {
|
||||
flex: 1,
|
||||
alignItems: 'stretch',
|
||||
...Platform.select({
|
||||
ios: {color: PlatformColor('labelColor')},
|
||||
default: {color: 'black'},
|
||||
}),
|
||||
},
|
||||
colorCell: {flex: 0.25, alignItems: 'stretch'},
|
||||
});
|
||||
|
||||
exports.title = 'PlatformColor';
|
||||
exports.description =
|
||||
'Examples that show how PlatformColors may be used in an app.';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Platform Colors',
|
||||
render(): React.Element<any> {
|
||||
return <PlatformColorsExample />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Fallback Colors',
|
||||
render(): React.Element<any> {
|
||||
return <FallbackColorsExample />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'iOS Dynamic Colors',
|
||||
render(): React.Element<any> {
|
||||
return <DynamicColorsExample />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Android Colors',
|
||||
render(): React.Element<any> {
|
||||
return <AndroidColorsExample />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Variant Colors',
|
||||
render(): React.Element<any> {
|
||||
return <VariantColorsExample />;
|
||||
},
|
||||
},
|
||||
];
|
|
@ -200,6 +200,10 @@ const APIExamples: Array<RNTesterExample> = [
|
|||
key: 'PermissionsExampleAndroid',
|
||||
module: require('../examples/PermissionsAndroid/PermissionsExample'),
|
||||
},
|
||||
{
|
||||
key: 'PlatformColorExample',
|
||||
module: require('../examples/PlatformColor/PlatformColorExample'),
|
||||
},
|
||||
{
|
||||
key: 'PointerEventsExample',
|
||||
module: require('../examples/PointerEvents/PointerEventsExample'),
|
||||
|
|
|
@ -279,6 +279,11 @@ const APIExamples: Array<RNTesterExample> = [
|
|||
module: require('../examples/PanResponder/PanResponderExample'),
|
||||
supportsTVOS: false,
|
||||
},
|
||||
{
|
||||
key: 'PlatformColorExample',
|
||||
module: require('../examples/PlatformColor/PlatformColorExample'),
|
||||
supportsTVOS: true,
|
||||
},
|
||||
{
|
||||
key: 'PointerEventsExample',
|
||||
module: require('../examples/PointerEvents/PointerEventsExample'),
|
||||
|
|
|
@ -506,6 +506,207 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
|||
@"a", @"b", @"c", @"d", @"tx", @"ty"
|
||||
]))
|
||||
|
||||
static NSString *const RCTFallback = @"fallback";
|
||||
static NSString *const RCTFallbackARGB = @"fallback-argb";
|
||||
static NSString *const RCTSelector = @"selector";
|
||||
static NSString *const RCTIndex = @"index";
|
||||
|
||||
/** The following dictionary defines the react-native semantic colors for ios.
|
||||
* If the value for a given name is empty then the name itself
|
||||
* is used as the UIColor selector.
|
||||
* If the RCTSelector key is present then that value is used for a selector instead
|
||||
* of the key name.
|
||||
* If the given selector is not available on the running OS version then
|
||||
* the RCTFallback selector is used instead.
|
||||
* If the RCTIndex key is present then object returned from UIColor is an
|
||||
* NSArray and the object at index RCTIndex is to be used.
|
||||
*/
|
||||
static NSDictionary<NSString *, NSDictionary *>* RCTSemanticColorsMap()
|
||||
{
|
||||
static NSDictionary<NSString *, NSDictionary *> *colorMap = nil;
|
||||
if (colorMap == nil) {
|
||||
colorMap = @{
|
||||
// https://developer.apple.com/documentation/uikit/uicolor/ui_element_colors
|
||||
// Label Colors
|
||||
@"labelColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFF000000) // fallback for iOS<=12: RGBA returned by this semantic color in light mode on iOS 13
|
||||
},
|
||||
@"secondaryLabelColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0x993c3c43)
|
||||
},
|
||||
@"tertiaryLabelColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0x4c3c3c43)
|
||||
},
|
||||
@"quaternaryLabelColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0x2d3c3c43)
|
||||
},
|
||||
// Fill Colors
|
||||
@"systemFillColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0x33787880)
|
||||
},
|
||||
@"secondarySystemFillColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0x28787880)
|
||||
},
|
||||
@"tertiarySystemFillColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0x1e767680)
|
||||
},
|
||||
@"quaternarySystemFillColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0x14747480)
|
||||
},
|
||||
// Text Colors
|
||||
@"placeholderTextColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0x4c3c3c43)
|
||||
},
|
||||
// Standard Content Background Colors
|
||||
@"systemBackgroundColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFffffff)
|
||||
},
|
||||
@"secondarySystemBackgroundColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFf2f2f7)
|
||||
},
|
||||
@"tertiarySystemBackgroundColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFffffff)
|
||||
},
|
||||
// Grouped Content Background Colors
|
||||
@"systemGroupedBackgroundColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFf2f2f7)
|
||||
},
|
||||
@"secondarySystemGroupedBackgroundColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFffffff)
|
||||
},
|
||||
@"tertiarySystemGroupedBackgroundColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFf2f2f7)
|
||||
},
|
||||
// Separator Colors
|
||||
@"separatorColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0x493c3c43)
|
||||
},
|
||||
@"opaqueSeparatorColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFc6c6c8)
|
||||
},
|
||||
// Link Color
|
||||
@"linkColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFF007aff)
|
||||
},
|
||||
// Nonadaptable Colors
|
||||
@"darkTextColor": @{},
|
||||
@"lightTextColor": @{},
|
||||
// https://developer.apple.com/documentation/uikit/uicolor/standard_colors
|
||||
// Adaptable Colors
|
||||
@"systemBlueColor": @{},
|
||||
@"systemBrownColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFa2845e)
|
||||
},
|
||||
@"systemGreenColor": @{},
|
||||
@"systemIndigoColor": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFF5856d6)
|
||||
},
|
||||
@"systemOrangeColor": @{},
|
||||
@"systemPinkColor": @{},
|
||||
@"systemPurpleColor": @{},
|
||||
@"systemRedColor": @{},
|
||||
@"systemTealColor": @{},
|
||||
@"systemYellowColor": @{},
|
||||
// Adaptable Gray Colors
|
||||
@"systemGrayColor": @{},
|
||||
@"systemGray2Color": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFaeaeb2)
|
||||
},
|
||||
@"systemGray3Color": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFc7c7cc)
|
||||
},
|
||||
@"systemGray4Color": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFd1d1d6)
|
||||
},
|
||||
@"systemGray5Color": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFe5e5ea)
|
||||
},
|
||||
@"systemGray6Color": @{ // iOS 13.0
|
||||
RCTFallbackARGB: @(0xFFf2f2f7)
|
||||
},
|
||||
#if DEBUG
|
||||
// The follow exist for Unit Tests
|
||||
@"unitTestFallbackColor": @{
|
||||
RCTFallback: @"gridColor"
|
||||
},
|
||||
@"unitTestFallbackColorIOS": @{
|
||||
RCTFallback: @"blueColor"
|
||||
},
|
||||
@"unitTestFallbackColorEven": @{
|
||||
RCTSelector: @"unitTestFallbackColorEven",
|
||||
RCTIndex: @0,
|
||||
RCTFallback: @"controlAlternatingRowBackgroundColors"
|
||||
},
|
||||
@"unitTestFallbackColorOdd": @{
|
||||
RCTSelector: @"unitTestFallbackColorOdd",
|
||||
RCTIndex: @1,
|
||||
RCTFallback: @"controlAlternatingRowBackgroundColors"
|
||||
},
|
||||
#endif
|
||||
};
|
||||
}
|
||||
return colorMap;
|
||||
}
|
||||
|
||||
/** Returns a UIColor based on a semantic color name.
|
||||
* Returns nil if the semantic color name is invalid.
|
||||
*/
|
||||
static UIColor *RCTColorFromSemanticColorName(NSString *semanticColorName)
|
||||
{
|
||||
NSDictionary<NSString *, NSDictionary *> *colorMap = RCTSemanticColorsMap();
|
||||
UIColor *color = nil;
|
||||
NSDictionary<NSString *, id> *colorInfo = colorMap[semanticColorName];
|
||||
if (colorInfo) {
|
||||
NSString *semanticColorSelector = colorInfo[RCTSelector];
|
||||
if (semanticColorSelector == nil) {
|
||||
semanticColorSelector = semanticColorName;
|
||||
}
|
||||
SEL selector = NSSelectorFromString(semanticColorSelector);
|
||||
if (![UIColor respondsToSelector:selector]) {
|
||||
NSNumber *fallbackRGB = colorInfo[RCTFallbackARGB];
|
||||
if (fallbackRGB != nil) {
|
||||
RCTAssert([fallbackRGB isKindOfClass:[NSNumber class]], @"fallback ARGB is not a number");
|
||||
return [RCTConvert UIColor:fallbackRGB];
|
||||
}
|
||||
semanticColorSelector = colorInfo[RCTFallback];
|
||||
selector = NSSelectorFromString(semanticColorSelector);
|
||||
}
|
||||
RCTAssert ([UIColor respondsToSelector:selector], @"RCTUIColor does not respond to a semantic color selector.");
|
||||
Class klass = [UIColor class];
|
||||
IMP imp = [klass methodForSelector:selector];
|
||||
id (*getSemanticColorObject)(id, SEL) = (void *)imp;
|
||||
id colorObject = getSemanticColorObject(klass, selector);
|
||||
if ([colorObject isKindOfClass:[UIColor class]]) {
|
||||
color = colorObject;
|
||||
} else if ([colorObject isKindOfClass:[NSArray class]]) {
|
||||
NSArray *colors = colorObject;
|
||||
NSNumber *index = colorInfo[RCTIndex];
|
||||
RCTAssert(index, @"index should not be null");
|
||||
color = colors[[index unsignedIntegerValue]];
|
||||
} else {
|
||||
RCTAssert(false, @"selector return an unknown object type");
|
||||
}
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/** Returns an alphabetically sorted comma seperated list of the valid semantic color names
|
||||
*/
|
||||
static NSString *RCTSemanticColorNames()
|
||||
{
|
||||
NSMutableString *names = [[NSMutableString alloc] init];
|
||||
NSDictionary<NSString *, NSDictionary *> *colorMap = RCTSemanticColorsMap();
|
||||
NSArray *allKeys = [[[colorMap allKeys] mutableCopy] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
||||
|
||||
for(id key in allKeys) {
|
||||
if ([names length]) {
|
||||
[names appendString:@", "];
|
||||
}
|
||||
[names appendString:key];
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
+ (UIColor *)UIColor:(id)json
|
||||
{
|
||||
if (!json) {
|
||||
|
@ -525,6 +726,56 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
|||
CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
|
||||
CGFloat b = (argb & 0xFF) / 255.0;
|
||||
return [UIColor colorWithRed:r green:g blue:b alpha:a];
|
||||
} else if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *dictionary = json;
|
||||
id value = nil;
|
||||
if ((value = [dictionary objectForKey:@"semantic"])) {
|
||||
if ([value isKindOfClass:[NSString class]]) {
|
||||
NSString *semanticName = value;
|
||||
UIColor *color = RCTColorFromSemanticColorName(semanticName);
|
||||
if (color == nil) {
|
||||
RCTLogConvertError(json, [@"a UIColor. Expected one of the following values: " stringByAppendingString:RCTSemanticColorNames()]);
|
||||
}
|
||||
return color;
|
||||
} else if ([value isKindOfClass:[NSArray class]]) {
|
||||
for (id name in value) {
|
||||
UIColor *color = RCTColorFromSemanticColorName(name);
|
||||
if (color != nil) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
RCTLogConvertError(json, [@"a UIColor. None of the names in the array were one of the following values: " stringByAppendingString:RCTSemanticColorNames()]);
|
||||
return nil;
|
||||
}
|
||||
RCTLogConvertError(json, @"a UIColor. Expected either a single name or an array of names but got something else.");
|
||||
return nil;
|
||||
} else if ((value = [dictionary objectForKey:@"dynamic"])) {
|
||||
NSDictionary *appearances = value;
|
||||
id light = [appearances objectForKey:@"light"];
|
||||
UIColor *lightColor = [RCTConvert UIColor:light];
|
||||
id dark = [appearances objectForKey:@"dark"];
|
||||
UIColor *darkColor = [RCTConvert UIColor:dark];
|
||||
if (lightColor != nil && darkColor != nil) {
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIColor *color = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull collection) {
|
||||
return collection.userInterfaceStyle == UIUserInterfaceStyleDark ? darkColor : lightColor;
|
||||
}];
|
||||
return color;
|
||||
} else {
|
||||
#endif
|
||||
return lightColor;
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
RCTLogConvertError(json, @"a UIColor. Expected an iOS dynamic appearance aware color.");
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
RCTLogConvertError(json, @"a UIColor. Expected an iOS semantic color or dynamic appearance aware color.");
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
RCTLogConvertError(json, @"a UIColor. Did you forget to call processColor() on the JS side?");
|
||||
return nil;
|
||||
|
|
|
@ -141,6 +141,9 @@ RCT_EXTERN UIImage *__nullable RCTImageFromLocalBundleAssetURL(NSURL *imageURL);
|
|||
// Creates a new, unique temporary file path with the specified extension
|
||||
RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *__nullable extension, NSError **error);
|
||||
|
||||
// Get RGBA components of CGColor
|
||||
RCT_EXTERN void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[_Nonnull 4]);
|
||||
|
||||
// Converts a CGColor to a hex string
|
||||
RCT_EXTERN NSString *RCTColorToHexString(CGColorRef color);
|
||||
|
||||
|
|
|
@ -832,7 +832,7 @@ RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *extension, NSError **e
|
|||
return [directory stringByAppendingPathComponent:filename];
|
||||
}
|
||||
|
||||
static void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4])
|
||||
RCT_EXTERN void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4])
|
||||
{
|
||||
CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color));
|
||||
const CGFloat *components = CGColorGetComponents(color);
|
||||
|
|
|
@ -608,6 +608,17 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
|
|||
}
|
||||
}
|
||||
|
||||
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
|
||||
[super traitCollectionDidChange: previousTraitCollection];
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
if (@available(iOS 13.0, *)) {
|
||||
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
|
||||
[self.layer setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - Borders
|
||||
|
||||
- (UIColor *)backgroundColor
|
||||
|
@ -783,11 +794,22 @@ static CGFloat RCTDefaultIfNegativeTo(CGFloat defaultValue, CGFloat x) {
|
|||
// solve this, we'll need to add a container view inside the main view to
|
||||
// correctly clip the subviews.
|
||||
|
||||
CGColorRef backgroundColor;
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
if (@available(iOS 13.0, *)) {
|
||||
backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection].CGColor;
|
||||
} else {
|
||||
backgroundColor = _backgroundColor.CGColor;
|
||||
}
|
||||
#else
|
||||
backgroundColor = _backgroundColor.CGColor;
|
||||
#endif
|
||||
|
||||
if (useIOSBorderRendering) {
|
||||
layer.cornerRadius = cornerRadii.topLeft;
|
||||
layer.borderColor = borderColors.left;
|
||||
layer.borderWidth = borderInsets.left;
|
||||
layer.backgroundColor = _backgroundColor.CGColor;
|
||||
layer.backgroundColor = backgroundColor;
|
||||
layer.contents = nil;
|
||||
layer.needsDisplayOnBoundsChange = NO;
|
||||
layer.mask = nil;
|
||||
|
@ -799,7 +821,7 @@ static CGFloat RCTDefaultIfNegativeTo(CGFloat defaultValue, CGFloat x) {
|
|||
cornerRadii,
|
||||
borderInsets,
|
||||
borderColors,
|
||||
_backgroundColor.CGColor,
|
||||
backgroundColor,
|
||||
self.clipsToBounds);
|
||||
|
||||
layer.backgroundColor = NULL;
|
||||
|
|
|
@ -441,6 +441,7 @@ dependencies {
|
|||
api("com.facebook.yoga:proguard-annotations:1.14.1")
|
||||
api("javax.inject:javax.inject:1")
|
||||
api("androidx.appcompat:appcompat:1.0.2")
|
||||
api("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
|
||||
api("com.facebook.fresco:fresco:${FRESCO_VERSION}")
|
||||
api("com.facebook.fresco:imagepipeline-okhttp3:${FRESCO_VERSION}")
|
||||
api("com.facebook.soloader:soloader:${SO_LOADER_VERSION}")
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.util.TypedValue;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
|
||||
public class ColorPropConverter {
|
||||
private static final String JSON_KEY = "resource_paths";
|
||||
private static final String PREFIX_RESOURCE = "@";
|
||||
private static final String PREFIX_ATTR = "?";
|
||||
private static final String PACKAGE_DELIMITER = ":";
|
||||
private static final String PATH_DELIMITER = "/";
|
||||
private static final String ATTR = "attr";
|
||||
private static final String ATTR_SEGMENT = "attr/";
|
||||
|
||||
public static Integer getColor(Object value, Context context) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Double) {
|
||||
return ((Double) value).intValue();
|
||||
}
|
||||
|
||||
if (context == null) {
|
||||
throw new RuntimeException("Context may not be null.");
|
||||
}
|
||||
|
||||
if (value instanceof ReadableMap) {
|
||||
ReadableMap map = (ReadableMap) value;
|
||||
ReadableArray resourcePaths = map.getArray(JSON_KEY);
|
||||
|
||||
if (resourcePaths == null) {
|
||||
throw new JSApplicationCausedNativeException(
|
||||
"ColorValue: The `" + JSON_KEY + "` must be an array of color resource path strings.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < resourcePaths.size(); i++) {
|
||||
String resourcePath = resourcePaths.getString(i);
|
||||
|
||||
if (resourcePath == null || resourcePath.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isResource = resourcePath.startsWith(PREFIX_RESOURCE);
|
||||
boolean isThemeAttribute = resourcePath.startsWith(PREFIX_ATTR);
|
||||
|
||||
resourcePath = resourcePath.substring(1);
|
||||
|
||||
try {
|
||||
if (isResource) {
|
||||
return resolveResource(context, resourcePath);
|
||||
} else if (isThemeAttribute) {
|
||||
return resolveThemeAttribute(context, resourcePath);
|
||||
}
|
||||
} catch (Resources.NotFoundException exception) {
|
||||
// The resource could not be found so do nothing to allow the for loop to continue and
|
||||
// try the next fallback resource in the array. If none of the fallbacks are
|
||||
// found then the exception immediately after the for loop will be thrown.
|
||||
}
|
||||
}
|
||||
|
||||
throw new JSApplicationCausedNativeException(
|
||||
"ColorValue: None of the paths in the `"
|
||||
+ JSON_KEY
|
||||
+ "` array resolved to a color resource.");
|
||||
}
|
||||
|
||||
throw new JSApplicationCausedNativeException(
|
||||
"ColorValue: the value must be a number or Object.");
|
||||
}
|
||||
|
||||
private static int resolveResource(Context context, String resourcePath) {
|
||||
String[] pathTokens = resourcePath.split(PACKAGE_DELIMITER);
|
||||
|
||||
String packageName = context.getPackageName();
|
||||
String resource = resourcePath;
|
||||
|
||||
if (pathTokens.length > 1) {
|
||||
packageName = pathTokens[0];
|
||||
resource = pathTokens[1];
|
||||
}
|
||||
|
||||
String[] resourceTokens = resource.split(PATH_DELIMITER);
|
||||
String resourceType = resourceTokens[0];
|
||||
String resourceName = resourceTokens[1];
|
||||
|
||||
int resourceId = context.getResources().getIdentifier(resourceName, resourceType, packageName);
|
||||
|
||||
return ResourcesCompat.getColor(context.getResources(), resourceId, context.getTheme());
|
||||
}
|
||||
|
||||
private static int resolveThemeAttribute(Context context, String resourcePath) {
|
||||
String path = resourcePath.replaceAll(ATTR_SEGMENT, "");
|
||||
String[] pathTokens = path.split(PACKAGE_DELIMITER);
|
||||
|
||||
String packageName = context.getPackageName();
|
||||
String resourceName = path;
|
||||
|
||||
if (pathTokens.length > 1) {
|
||||
packageName = pathTokens[0];
|
||||
resourceName = pathTokens[1];
|
||||
}
|
||||
|
||||
int resourceId = context.getResources().getIdentifier(resourceName, ATTR, packageName);
|
||||
|
||||
TypedValue outValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
|
||||
if (theme.resolveAttribute(resourceId, outValue, true)) {
|
||||
return outValue.data;
|
||||
}
|
||||
|
||||
throw new Resources.NotFoundException();
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ package com.facebook.react.uimanager;
|
|||
|
||||
import android.view.View;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.ColorPropConverter;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.yoga.YogaConstants;
|
||||
|
@ -47,7 +48,8 @@ public abstract class BaseViewManagerDelegate<T extends View, U extends BaseView
|
|||
mViewManager.setViewState(view, (ReadableMap) value);
|
||||
break;
|
||||
case ViewProps.BACKGROUND_COLOR:
|
||||
mViewManager.setBackgroundColor(view, value == null ? 0 : ((Double) value).intValue());
|
||||
mViewManager.setBackgroundColor(
|
||||
view, value == null ? 0 : ColorPropConverter.getColor(value, view.getContext()));
|
||||
break;
|
||||
case ViewProps.BORDER_RADIUS:
|
||||
mViewManager.setBorderRadius(
|
||||
|
|
|
@ -7,9 +7,11 @@
|
|||
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.ColorPropConverter;
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
import com.facebook.react.bridge.DynamicFromObject;
|
||||
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
|
||||
|
@ -81,13 +83,13 @@ import java.util.Map;
|
|||
try {
|
||||
if (mIndex == null) {
|
||||
VIEW_MGR_ARGS[0] = viewToUpdate;
|
||||
VIEW_MGR_ARGS[1] = getValueOrDefault(value);
|
||||
VIEW_MGR_ARGS[1] = getValueOrDefault(value, viewToUpdate.getContext());
|
||||
mSetter.invoke(viewManager, VIEW_MGR_ARGS);
|
||||
Arrays.fill(VIEW_MGR_ARGS, null);
|
||||
} else {
|
||||
VIEW_MGR_GROUP_ARGS[0] = viewToUpdate;
|
||||
VIEW_MGR_GROUP_ARGS[1] = mIndex;
|
||||
VIEW_MGR_GROUP_ARGS[2] = getValueOrDefault(value);
|
||||
VIEW_MGR_GROUP_ARGS[2] = getValueOrDefault(value, viewToUpdate.getContext());
|
||||
mSetter.invoke(viewManager, VIEW_MGR_GROUP_ARGS);
|
||||
Arrays.fill(VIEW_MGR_GROUP_ARGS, null);
|
||||
}
|
||||
|
@ -105,12 +107,12 @@ import java.util.Map;
|
|||
public void updateShadowNodeProp(ReactShadowNode nodeToUpdate, Object value) {
|
||||
try {
|
||||
if (mIndex == null) {
|
||||
SHADOW_ARGS[0] = getValueOrDefault(value);
|
||||
SHADOW_ARGS[0] = getValueOrDefault(value, nodeToUpdate.getThemedContext());
|
||||
mSetter.invoke(nodeToUpdate, SHADOW_ARGS);
|
||||
Arrays.fill(SHADOW_ARGS, null);
|
||||
} else {
|
||||
SHADOW_GROUP_ARGS[0] = mIndex;
|
||||
SHADOW_GROUP_ARGS[1] = getValueOrDefault(value);
|
||||
SHADOW_GROUP_ARGS[1] = getValueOrDefault(value, nodeToUpdate.getThemedContext());
|
||||
mSetter.invoke(nodeToUpdate, SHADOW_GROUP_ARGS);
|
||||
Arrays.fill(SHADOW_GROUP_ARGS, null);
|
||||
}
|
||||
|
@ -125,7 +127,7 @@ import java.util.Map;
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract @Nullable Object getValueOrDefault(Object value);
|
||||
protected abstract @Nullable Object getValueOrDefault(Object value, Context context);
|
||||
}
|
||||
|
||||
private static class DynamicPropSetter extends PropSetter {
|
||||
|
@ -139,7 +141,7 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Object getValueOrDefault(Object value) {
|
||||
protected Object getValueOrDefault(Object value, Context context) {
|
||||
if (value instanceof Dynamic) {
|
||||
return value;
|
||||
} else {
|
||||
|
@ -163,7 +165,7 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Object getValueOrDefault(Object value) {
|
||||
protected Object getValueOrDefault(Object value, Context context) {
|
||||
// All numbers from JS are Doubles which can't be simply cast to Integer
|
||||
return value == null ? mDefaultValue : (Integer) ((Double) value).intValue();
|
||||
}
|
||||
|
@ -184,11 +186,34 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Object getValueOrDefault(Object value) {
|
||||
protected Object getValueOrDefault(Object value, Context context) {
|
||||
return value == null ? mDefaultValue : (Double) value;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ColorPropSetter extends PropSetter {
|
||||
|
||||
private final int mDefaultValue;
|
||||
|
||||
public ColorPropSetter(ReactProp prop, Method setter) {
|
||||
this(prop, setter, 0);
|
||||
}
|
||||
|
||||
public ColorPropSetter(ReactProp prop, Method setter, int defaultValue) {
|
||||
super(prop, "mixed", setter);
|
||||
mDefaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getValueOrDefault(Object value, Context context) {
|
||||
if (value == null) {
|
||||
return mDefaultValue;
|
||||
}
|
||||
|
||||
return ColorPropConverter.getColor(value, context);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BooleanPropSetter extends PropSetter {
|
||||
|
||||
private final boolean mDefaultValue;
|
||||
|
@ -199,7 +224,7 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Object getValueOrDefault(Object value) {
|
||||
protected Object getValueOrDefault(Object value, Context context) {
|
||||
boolean val = value == null ? mDefaultValue : (boolean) value;
|
||||
return val ? Boolean.TRUE : Boolean.FALSE;
|
||||
}
|
||||
|
@ -220,7 +245,7 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Object getValueOrDefault(Object value) {
|
||||
protected Object getValueOrDefault(Object value, Context context) {
|
||||
// All numbers from JS are Doubles which can't be simply cast to Float
|
||||
return value == null ? mDefaultValue : (Float) ((Double) value).floatValue();
|
||||
}
|
||||
|
@ -233,7 +258,7 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object getValueOrDefault(Object value) {
|
||||
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
||||
return (ReadableArray) value;
|
||||
}
|
||||
}
|
||||
|
@ -245,7 +270,7 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object getValueOrDefault(Object value) {
|
||||
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
||||
return (ReadableMap) value;
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +282,7 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object getValueOrDefault(Object value) {
|
||||
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
||||
return (String) value;
|
||||
}
|
||||
}
|
||||
|
@ -269,7 +294,7 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object getValueOrDefault(Object value) {
|
||||
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
||||
if (value != null) {
|
||||
return (boolean) value ? Boolean.TRUE : Boolean.FALSE;
|
||||
}
|
||||
|
@ -288,7 +313,7 @@ import java.util.Map;
|
|||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Object getValueOrDefault(Object value) {
|
||||
protected @Nullable Object getValueOrDefault(Object value, Context context) {
|
||||
if (value != null) {
|
||||
if (value instanceof Double) {
|
||||
return ((Double) value).intValue();
|
||||
|
@ -379,6 +404,9 @@ import java.util.Map;
|
|||
} else if (propTypeClass == boolean.class) {
|
||||
return new BooleanPropSetter(annotation, method, annotation.defaultBoolean());
|
||||
} else if (propTypeClass == int.class) {
|
||||
if ("Color".equals(annotation.customType())) {
|
||||
return new ColorPropSetter(annotation, method, annotation.defaultInt());
|
||||
}
|
||||
return new IntPropSetter(annotation, method, annotation.defaultInt());
|
||||
} else if (propTypeClass == float.class) {
|
||||
return new FloatPropSetter(annotation, method, annotation.defaultFloat());
|
||||
|
@ -389,6 +417,9 @@ import java.util.Map;
|
|||
} else if (propTypeClass == Boolean.class) {
|
||||
return new BoxedBooleanPropSetter(annotation, method);
|
||||
} else if (propTypeClass == Integer.class) {
|
||||
if ("Color".equals(annotation.customType())) {
|
||||
return new ColorPropSetter(annotation, method);
|
||||
}
|
||||
return new BoxedIntPropSetter(annotation, method);
|
||||
} else if (propTypeClass == ReadableArray.class) {
|
||||
return new ArrayPropSetter(annotation, method);
|
||||
|
|
|
@ -14,6 +14,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener;
|
||||
import com.facebook.react.bridge.ColorPropConverter;
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableType;
|
||||
|
@ -68,7 +69,11 @@ public class SwipeRefreshLayoutManager extends ViewGroupManager<ReactSwipeRefres
|
|||
if (colors != null) {
|
||||
int[] colorValues = new int[colors.size()];
|
||||
for (int i = 0; i < colors.size(); i++) {
|
||||
colorValues[i] = colors.getInt(i);
|
||||
if (colors.getType(i) == ReadableType.Map) {
|
||||
colorValues[i] = ColorPropConverter.getColor(colors.getMap(i), view.getContext());
|
||||
} else {
|
||||
colorValues[i] = colors.getInt(i);
|
||||
}
|
||||
}
|
||||
view.setColorSchemeColors(colorValues);
|
||||
} else {
|
||||
|
|
|
@ -469,7 +469,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
|||
markUpdated();
|
||||
}
|
||||
|
||||
@ReactProp(name = ViewProps.COLOR)
|
||||
@ReactProp(name = ViewProps.COLOR, customType = "Color")
|
||||
public void setColor(@Nullable Integer color) {
|
||||
mIsColorSet = (color != null);
|
||||
if (mIsColorSet) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import static org.mockito.Mockito.reset;
|
|||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.react.bridge.JavaOnlyMap;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
|
|
@ -12,9 +12,11 @@ import static org.mockito.Matchers.any;
|
|||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.Layout;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
|
|
15
index.js
15
index.js
|
@ -94,6 +94,9 @@ import typeof RCTNativeAppEventEmitter from './Libraries/EventEmitter/RCTNativeA
|
|||
import typeof NativeModules from './Libraries/BatchedBridge/NativeModules';
|
||||
import typeof Platform from './Libraries/Utilities/Platform';
|
||||
import typeof processColor from './Libraries/StyleSheet/processColor';
|
||||
import typeof {PlatformColor} from './Libraries/StyleSheet/PlatformColorValueTypes';
|
||||
import typeof {DynamicColorIOS} from './Libraries/StyleSheet/PlatformColorValueTypesIOS';
|
||||
import typeof {ColorAndroid} from './Libraries/StyleSheet/PlatformColorValueTypesAndroid';
|
||||
import typeof RootTagContext from './Libraries/ReactNative/RootTagContext';
|
||||
import typeof DeprecatedColorPropType from './Libraries/DeprecatedPropTypes/DeprecatedColorPropType';
|
||||
import typeof DeprecatedEdgeInsetsPropType from './Libraries/DeprecatedPropTypes/DeprecatedEdgeInsetsPropType';
|
||||
|
@ -462,6 +465,18 @@ module.exports = {
|
|||
get processColor(): processColor {
|
||||
return require('./Libraries/StyleSheet/processColor');
|
||||
},
|
||||
get PlatformColor(): PlatformColor {
|
||||
return require('./Libraries/StyleSheet/PlatformColorValueTypes')
|
||||
.PlatformColor;
|
||||
},
|
||||
get DynamicColorIOS(): DynamicColorIOS {
|
||||
return require('./Libraries/StyleSheet/PlatformColorValueTypesIOS')
|
||||
.DynamicColorIOS;
|
||||
},
|
||||
get ColorAndroid(): ColorAndroid {
|
||||
return require('./Libraries/StyleSheet/PlatformColorValueTypesAndroid')
|
||||
.ColorAndroid;
|
||||
},
|
||||
get requireNativeComponent(): <T>(
|
||||
uiViewClassName: string,
|
||||
) => HostComponent<T> {
|
||||
|
|
|
@ -275,6 +275,12 @@ type ModuleProps = $ReadOnly<{|
|
|||
color_array_optional_value: ?ColorArrayValue,
|
||||
color_array_optional_both?: ?ColorArrayValue,
|
||||
|
||||
// ProcessedColorValue props
|
||||
processed_color_required: ProcessedColorValue,
|
||||
processed_color_optional_key?: ProcessedColorValue,
|
||||
processed_color_optional_value: ?ProcessedColorValue,
|
||||
processed_color_optional_both?: ?ProcessedColorValue,
|
||||
|
||||
// PointValue props
|
||||
point_required: PointValue,
|
||||
point_optional_key?: PointValue,
|
||||
|
@ -310,7 +316,7 @@ const codegenNativeComponent = require('codegenNativeComponent');
|
|||
|
||||
import type {Int32, Double, Float, WithDefault} from 'CodegenTypes';
|
||||
import type {ImageSource} from 'ImageSource';
|
||||
import type {ColorValue, PointValue, EdgeInsetsValue} from 'StyleSheetTypes';
|
||||
import type {ColorValue, PointValue, ProcessColorValue, EdgeInsetsValue} from 'StyleSheetTypes';
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
|
@ -508,6 +514,12 @@ type ModuleProps = $ReadOnly<{|
|
|||
color_optional_value: $ReadOnly<{|prop: ?ColorValue|}>,
|
||||
color_optional_both: $ReadOnly<{|prop?: ?ColorValue|}>,
|
||||
|
||||
// ProcessedColorValue props
|
||||
processed_color_required: $ReadOnly<{|prop: ProcessedColorValue|}>,
|
||||
processed_color_optional_key: $ReadOnly<{|prop?: ProcessedColorValue|}>,
|
||||
processed_color_optional_value: $ReadOnly<{|prop: ?ProcessedColorValue|}>,
|
||||
processed_color_optional_both: $ReadOnly<{|prop?: ?ProcessedColorValue|}>,
|
||||
|
||||
// PointValue props
|
||||
point_required: $ReadOnly<{|prop: PointValue|}>,
|
||||
point_optional_key: $ReadOnly<{|prop?: PointValue|}>,
|
||||
|
|
|
@ -456,6 +456,38 @@ Object {
|
|||
"type": "ArrayTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "processed_color_required",
|
||||
"optional": false,
|
||||
"typeAnnotation": Object {
|
||||
"name": "ColorPrimitive",
|
||||
"type": "NativePrimitiveTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "processed_color_optional_key",
|
||||
"optional": true,
|
||||
"typeAnnotation": Object {
|
||||
"name": "ColorPrimitive",
|
||||
"type": "NativePrimitiveTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "processed_color_optional_value",
|
||||
"optional": true,
|
||||
"typeAnnotation": Object {
|
||||
"name": "ColorPrimitive",
|
||||
"type": "NativePrimitiveTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "processed_color_optional_both",
|
||||
"optional": true,
|
||||
"typeAnnotation": Object {
|
||||
"name": "ColorPrimitive",
|
||||
"type": "NativePrimitiveTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "point_required",
|
||||
"optional": false,
|
||||
|
@ -5480,6 +5512,74 @@ Object {
|
|||
"type": "ObjectTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "processed_color_required",
|
||||
"optional": false,
|
||||
"typeAnnotation": Object {
|
||||
"properties": Array [
|
||||
Object {
|
||||
"name": "prop",
|
||||
"optional": false,
|
||||
"typeAnnotation": Object {
|
||||
"name": "ColorPrimitive",
|
||||
"type": "NativePrimitiveTypeAnnotation",
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "ObjectTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "processed_color_optional_key",
|
||||
"optional": false,
|
||||
"typeAnnotation": Object {
|
||||
"properties": Array [
|
||||
Object {
|
||||
"name": "prop",
|
||||
"optional": true,
|
||||
"typeAnnotation": Object {
|
||||
"name": "ColorPrimitive",
|
||||
"type": "NativePrimitiveTypeAnnotation",
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "ObjectTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "processed_color_optional_value",
|
||||
"optional": false,
|
||||
"typeAnnotation": Object {
|
||||
"properties": Array [
|
||||
Object {
|
||||
"name": "prop",
|
||||
"optional": true,
|
||||
"typeAnnotation": Object {
|
||||
"name": "ColorPrimitive",
|
||||
"type": "NativePrimitiveTypeAnnotation",
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "ObjectTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "processed_color_optional_both",
|
||||
"optional": false,
|
||||
"typeAnnotation": Object {
|
||||
"properties": Array [
|
||||
Object {
|
||||
"name": "prop",
|
||||
"optional": true,
|
||||
"typeAnnotation": Object {
|
||||
"name": "ColorPrimitive",
|
||||
"type": "NativePrimitiveTypeAnnotation",
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "ObjectTypeAnnotation",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"name": "point_required",
|
||||
"optional": false,
|
||||
|
|
|
@ -94,6 +94,7 @@ function getTypeAnnotationForArray(name, typeAnnotation, defaultValue, types) {
|
|||
name: 'ImageSourcePrimitive',
|
||||
};
|
||||
case 'ColorValue':
|
||||
case 'ProcessedColorValue':
|
||||
return {
|
||||
type: 'NativePrimitiveTypeAnnotation',
|
||||
name: 'ColorPrimitive',
|
||||
|
@ -217,6 +218,7 @@ function getTypeAnnotation(
|
|||
name: 'ImageSourcePrimitive',
|
||||
};
|
||||
case 'ColorValue':
|
||||
case 'ProcessedColorValue':
|
||||
return {
|
||||
type: 'NativePrimitiveTypeAnnotation',
|
||||
name: 'ColorPrimitive',
|
||||
|
|
Загрузка…
Ссылка в новой задаче