diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt b/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt new file mode 100644 index 00000000000..5850dc0ca21 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ReactPlatformColor.kt @@ -0,0 +1,23 @@ +package com.reactnativenavigation.options.params + +import com.facebook.react.bridge.ColorPropConverter +import com.facebook.react.bridge.ReadableMap +import com.reactnativenavigation.NavigationApplication + +private fun parsePlatformColor(paths: ReadableMap) = + ColorPropConverter.getColor(paths, NavigationApplication.instance) + +class ReactPlatformColor(private val paths: ReadableMap) : + Colour(parsePlatformColor(paths)) { + override fun get(): Int { + return parsePlatformColor(paths) + } + + override fun get(defaultValue: Int?): Int? { + return try { + parsePlatformColor(paths) + }catch (e:Exception){ + defaultValue + } + } +} \ No newline at end of file diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ThemeColour.kt b/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ThemeColour.kt index 5784199b150..0096cc87a20 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ThemeColour.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/params/ThemeColour.kt @@ -18,7 +18,7 @@ class NullThemeColour() : ThemeColour(NullColor(), NullColor()) { open class ThemeColour(private var lightColor: Colour, private var darkColor: Colour) { - constructor(color:Colour):this(color,color) + constructor(color: Colour) : this(color, color) private fun selectedColor() = if (isDarkMode()) darkColor else lightColor @@ -30,18 +30,23 @@ open class ThemeColour(private var lightColor: Colour, private var darkColor: Co fun hasTransparency() = selectedColor().hasTransparency() fun canApplyValue() = selectedColor().canApplyValue() - companion object{ + companion object { @JvmStatic - fun of(color:Int) = ThemeColour(Colour(color), Colour(color)) + fun of(color: Int) = ThemeColour(Colour(color), Colour(color)) + @JvmStatic - fun of(light:Int,dark:Int) = ThemeColour(Colour(light),Colour(dark)) + fun of(light: Int, dark: Int) = ThemeColour(Colour(light), Colour(dark)) @JvmStatic fun parse(context: Context, json: JSONObject?): ThemeColour { return json?.let { - ThemeColour(ColorParser.parse(context, json, LIGHT_COLOR_KEY), ColorParser.parse(context, json, DARK_COLOR_KEY)) + ThemeColour( + ColorParser.parse(context, json, LIGHT_COLOR_KEY), + ColorParser.parse(context, json, DARK_COLOR_KEY) + ) } ?: NullThemeColour() } + @JvmStatic fun transparent() = of(Color.TRANSPARENT) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/JSONParser.java b/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/JSONParser.java index 09b91606ed5..68f0195cb52 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/JSONParser.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/parsers/JSONParser.java @@ -1,5 +1,6 @@ package com.reactnativenavigation.options.parsers; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; @@ -98,7 +99,7 @@ private static Object parseNumber(ReadableArray arr, int index) { } public static WritableMap convert(JSONObject jsonObject) { - WritableMap map = new WritableNativeMap(); + WritableMap map = Arguments.createMap(); Iterator iterator = jsonObject.keys(); while (iterator.hasNext()) { @@ -125,7 +126,7 @@ public static WritableMap convert(JSONObject jsonObject) { } public static WritableArray convert(JSONArray jsonArray) { - WritableArray array = new WritableNativeArray(); + WritableArray array = Arguments.createArray(); for (int i = 0; i < jsonArray.length(); i++) { Object value = jsonArray.opt(i); diff --git a/lib/android/app/src/reactNative63/java/com/reactnativenavigation/options/parsers/ColorParser.java b/lib/android/app/src/reactNative63/java/com/reactnativenavigation/options/parsers/ColorParser.java deleted file mode 100644 index cf059ed2ba2..00000000000 --- a/lib/android/app/src/reactNative63/java/com/reactnativenavigation/options/parsers/ColorParser.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.reactnativenavigation.options.parsers; - -import android.content.Context; - -import com.facebook.react.bridge.ColorPropConverter; -import com.reactnativenavigation.options.params.Colour; -import com.reactnativenavigation.options.params.DontApplyColour; -import com.reactnativenavigation.options.params.NullColor; - -import org.json.JSONObject; - -public class ColorParser { - public static Colour parse(Context context, JSONObject json, String colorName) { - if (json.has(colorName)) { - Object color = json.opt(colorName); - if (color == null) { - return new DontApplyColour(); - } else if (color instanceof Integer) { - return new Colour(json.optInt(colorName)); - } - if (color.equals("NoColor")) { - return new DontApplyColour(); - } - Object convertedColor = JSONParser.convert(json.optJSONObject(colorName)); - Integer processedColor = ColorPropConverter.getColor(convertedColor, context); - if (processedColor != null) { - return new Colour(processedColor); - } - } - return new NullColor(); - } -} diff --git a/lib/android/app/src/reactNative63/java/com/reactnativenavigation/options/parsers/ColorParser.kt b/lib/android/app/src/reactNative63/java/com/reactnativenavigation/options/parsers/ColorParser.kt new file mode 100644 index 00000000000..857af348911 --- /dev/null +++ b/lib/android/app/src/reactNative63/java/com/reactnativenavigation/options/parsers/ColorParser.kt @@ -0,0 +1,38 @@ +package com.reactnativenavigation.options.parsers + +import android.content.Context +import com.facebook.react.bridge.ColorPropConverter +import com.reactnativenavigation.options.params.Colour +import com.reactnativenavigation.options.params.DontApplyColour +import com.reactnativenavigation.options.params.NullColor +import com.reactnativenavigation.options.params.ReactPlatformColor +import org.json.JSONObject + +object ColorParser { + private const val KEY_RESOURCE_PATHS = "resource_paths" + private const val VAL_NO_COLOR = "NoColor" + + @JvmStatic + fun parse(context: Context?, json: JSONObject, colorName: String?): Colour { + if (json.has(KEY_RESOURCE_PATHS)) { + return ReactPlatformColor(JSONParser.convert(json)) + } + return when (val color = json.opt(colorName)) { + null, VAL_NO_COLOR -> { + DontApplyColour() + } + is Int -> { + Colour(json.optInt(colorName)) + } + is JSONObject -> { + ColorPropConverter.getColor(color, context)?.let { + Colour(it) + } ?: NullColor() + } + else -> { + NullColor() + } + } + + } +} \ No newline at end of file diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/BaseTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/BaseTest.java index 8f40592464d..50b30e922ce 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/BaseTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/BaseTest.java @@ -5,6 +5,7 @@ import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Color; import android.os.Handler; import android.os.Looper; import android.util.DisplayMetrics; @@ -20,6 +21,7 @@ import org.junit.After; import org.junit.Before; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.robolectric.Robolectric; @@ -58,6 +60,8 @@ public void beforeEach() { mockConfiguration.uiMode = Configuration.UI_MODE_NIGHT_NO; when(res.getConfiguration()).thenReturn(mockConfiguration); when(NavigationApplication.instance.getResources()).thenReturn(res); + when(res.getColor(ArgumentMatchers.anyInt())).thenReturn(0x00000); + when(res.getColor(ArgumentMatchers.anyInt(),any())).thenReturn(0x00000); } public void mockStatusBarUtils(int statusBarHeight,int statusBarHeightDp, Functions.Func block) { diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/options/parsers/ColorParseTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/options/parsers/ColorParseTest.java index 74f99c8673a..136fcf8cc5c 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/options/parsers/ColorParseTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/options/parsers/ColorParseTest.java @@ -1,20 +1,53 @@ package com.reactnativenavigation.options.parsers; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.JavaOnlyArray; +import com.facebook.react.bridge.JavaOnlyMap; import com.reactnativenavigation.BaseTest; import com.reactnativenavigation.options.params.DontApplyColour; +import com.reactnativenavigation.options.params.ReactPlatformColor; +import com.reactnativenavigation.utils.StatusBarUtils; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; + +import android.app.Activity; public class ColorParseTest extends BaseTest { + Activity activity; + @Override + public void beforeEach() { + super.beforeEach(); + activity = newActivity(); + } + @Test public void nullIsParsedAsNoColor() throws JSONException { JSONObject json = new JSONObject(); json.put("color", "NoColor"); assertThat(ColorParser.parse(null, json, "color")).isInstanceOf(DontApplyColour.class); } + + @Test + public void shouldParsePlatformColors() throws JSONException { + JSONObject json = new JSONObject(); + JSONObject color = new JSONObject(); + final JSONArray jsonArray = new JSONArray(); + jsonArray.put("@color/colorPrimary"); + color.put("resource_paths", + jsonArray); + try (MockedStatic theMock = Mockito.mockStatic(Arguments.class)) { + theMock.when(Arguments::createMap).thenReturn(new JavaOnlyMap()); + theMock.when(Arguments::createArray).thenReturn(new JavaOnlyArray()); + assertThat(ColorParser.parse(activity, color, "color")).isInstanceOf(ReactPlatformColor.class); + } + } } diff --git a/lib/src/commands/OptionsProcessor.test.ts b/lib/src/commands/OptionsProcessor.test.ts index 6c65519ca4d..defc886f284 100644 --- a/lib/src/commands/OptionsProcessor.test.ts +++ b/lib/src/commands/OptionsProcessor.test.ts @@ -462,6 +462,23 @@ describe('navigation options', () => { Platform.OS = 'android'; }); + it('PlatformColor should be passed to native as is', () => { + const options: Options = { + topBar: { + background: { + color: { + // @ts-ignore + resource_paths: ['@color/textColor'], + }, + }, + }, + }; + uut.processOptions(options, CommandName.SetRoot); + expect(options).toEqual({ + topBar: { background: { color: { resource_paths: ['@color/textColor'] } } }, + }); + }); + it('processes color keys', () => { const options: Options = { statusBar: { backgroundColor: 'red' }, diff --git a/lib/src/commands/OptionsProcessor.ts b/lib/src/commands/OptionsProcessor.ts index 43d48c9a0a5..4023736c0c9 100644 --- a/lib/src/commands/OptionsProcessor.ts +++ b/lib/src/commands/OptionsProcessor.ts @@ -120,10 +120,15 @@ export class OptionsProcessor { if (value === null) { options[key] = newColorObj; } else if (value instanceof Object) { - for (let keyColor in value) { - newColorObj[keyColor] = this.colorService.toNativeColor(value[keyColor]); + if ('semantic' in value || 'resource_paths' in value) { + options[key] = value; + return; + } else { + for (let keyColor in value) { + newColorObj[keyColor] = this.colorService.toNativeColor(value[keyColor]); + } + options[key] = newColorObj; } - options[key] = newColorObj; } else { let parsedColor = this.colorService.toNativeColor(value); newColorObj.light = parsedColor; diff --git a/lib/src/interfaces/Options.ts b/lib/src/interfaces/Options.ts index 3cf8e3298d7..7f8cccfc64d 100644 --- a/lib/src/interfaces/Options.ts +++ b/lib/src/interfaces/Options.ts @@ -1,9 +1,9 @@ // tslint:disable jsdoc-format -import { ImageRequireSource, ImageSourcePropType, Insets } from 'react-native'; +import { ImageRequireSource, ImageSourcePropType, Insets, OpaqueColorValue } from 'react-native'; // TODO: Import ColorValue instead when upgrading @types/react-native to 0.63+ // Only assign PlatformColor or DynamicColorIOS as a Color symbol! -export declare type Color = string | symbol | ThemeColor | null; +export declare type Color = string | symbol | ThemeColor | OpaqueColorValue | null; type FontFamily = string; type FontStyle = 'normal' | 'italic'; type FontWeightIOS = diff --git a/playground/android/app/src/main/res/values-night/colors.xml b/playground/android/app/src/main/res/values-night/colors.xml new file mode 100644 index 00000000000..0d7bbf0c4e9 --- /dev/null +++ b/playground/android/app/src/main/res/values-night/colors.xml @@ -0,0 +1,5 @@ + + + #BA292E + #282528 + \ No newline at end of file diff --git a/playground/android/app/src/main/res/values-notnight/colors.xml b/playground/android/app/src/main/res/values-notnight/colors.xml new file mode 100644 index 00000000000..a57d4024920 --- /dev/null +++ b/playground/android/app/src/main/res/values-notnight/colors.xml @@ -0,0 +1,5 @@ + + + #000000 + #e8e8e8 + \ No newline at end of file diff --git a/playground/android/app/src/main/res/values/colors.xml b/playground/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000000..a57d4024920 --- /dev/null +++ b/playground/android/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #000000 + #e8e8e8 + \ No newline at end of file diff --git a/playground/android/app/src/main/res/values/styles.xml b/playground/android/app/src/main/res/values/styles.xml index 31533f92037..7879f972437 100644 --- a/playground/android/app/src/main/res/values/styles.xml +++ b/playground/android/app/src/main/res/values/styles.xml @@ -3,10 +3,4 @@ - - - #e8e8e8 \ No newline at end of file diff --git a/playground/ios/playground/Images.xcassets/rnnTextColor.colorset/Contents.json b/playground/ios/playground/Images.xcassets/rnnTextColor.colorset/Contents.json new file mode 100644 index 00000000000..634c45cab69 --- /dev/null +++ b/playground/ios/playground/Images.xcassets/rnnTextColor.colorset/Contents.json @@ -0,0 +1,51 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "platform" : "ios", + "reference" : "darkTextColor" + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.180", + "green" : "0.160", + "red" : "0.730" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/playground/ios/playground/LaunchScreen.storyboard b/playground/ios/playground/LaunchScreen.storyboard index d6b5e4b687e..2b8552ecb68 100644 --- a/playground/ios/playground/LaunchScreen.storyboard +++ b/playground/ios/playground/LaunchScreen.storyboard @@ -1,9 +1,10 @@ - + - + + @@ -19,26 +20,26 @@ - - + - + - + + @@ -46,4 +47,9 @@ + + + + + diff --git a/playground/src/commons/Colors.ts b/playground/src/commons/Colors.ts index 166b485cf9e..608e0f53963 100644 --- a/playground/src/commons/Colors.ts +++ b/playground/src/commons/Colors.ts @@ -1,10 +1,15 @@ +import { Platform, PlatformColor } from 'react-native'; + const colors = { background: { light: '#e8e8e8', dark: '#282528' }, barBackground: { light: 'white', dark: '#282528' }, primary: { light: '#5847ff', dark: '#BA292E' }, secondary: { light: '#FFC249', dark: '#5847ff' }, accent: { light: '#65C888', dark: '#FFA73C' }, - textColor: { light: 'black', dark: '#BA292E' }, + textColor: + Platform.OS === 'ios' + ? { light: '#5847ff', dark: '#BA292E' } + : PlatformColor('@color/textColor'), activeTextColor: { light: '#5847ff', dark: '#FFA73C' }, iconTint: { light: 'black', dark: '#BA292E' }, activeIconTint: { light: '#5847ff', dark: '#FFA73C' },