From d55527a005e41f563f0cb8d45afab8bfbd7a65c1 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Thu, 16 Jul 2026 11:34:18 +0100 Subject: [PATCH] feat: update react-native to use new native color scheme properties --- .../checkoutkit/ShopifyCheckoutKitModule.java | 92 ++++++++++++------- .../ShopifyCheckoutKitModuleTest.kt | 42 +++++++++ .../ios/ShopifyCheckoutKit.swift | 29 ++++-- .../test/rct-integration-app/Podfile | 8 ++ .../AcceleratedCheckouts_SupportedTests.swift | 2 +- .../ShopifyCheckoutKitTests.swift | 27 +++++- 6 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/test/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModuleTest.kt diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java index 667b2a39e..aba7724e3 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/main/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModule.java @@ -21,7 +21,7 @@ public class ShopifyCheckoutKitModule extends NativeShopifyCheckoutKitSpec { public static Configuration checkoutConfig = new Configuration(); - private CheckoutKitDialog checkoutSheet; + private CheckoutHandle checkoutSheet; private CustomCheckoutListener checkoutListener; @@ -117,7 +117,7 @@ private void releaseCheckoutListener() { public WritableMap getConfig() { WritableMap resultConfig = Arguments.createMap(); - resultConfig.putString("colorScheme", colorSchemeToString(checkoutConfig.getColorScheme())); + resultConfig.putString("colorScheme", colorSchemeStringFor(checkoutConfig.getAppearance())); resultConfig.putString("logLevel", logLevelToString(checkoutConfig.getLogLevel())); resultConfig.putBoolean("preloading", checkoutConfig.getPreloading().getEnabled()); @@ -139,7 +139,7 @@ public void setConfig(ReadableMap config) { } if (config.hasKey("colorScheme")) { - ColorScheme colorScheme = getColorScheme(Objects.requireNonNull(config.getString("colorScheme"))); + String colorScheme = Objects.requireNonNull(config.getString("colorScheme")); ReadableMap colorsConfig = config.hasKey("colors") ? config.getMap("colors") : null; ReadableMap androidConfig = null; @@ -147,16 +147,7 @@ public void setConfig(ReadableMap config) { androidConfig = colorsConfig.getMap("android"); } - if (this.isValidColorConfig(androidConfig)) { - ColorScheme colorSchemeWithOverrides = getColors(colorScheme, androidConfig); - if (colorSchemeWithOverrides != null) { - configuration.setColorScheme(colorSchemeWithOverrides); - checkoutConfig = configuration; - return; - } - } - - configuration.setColorScheme(colorScheme); + configuration.setAppearance(appearanceFor(colorScheme, androidConfig)); } checkoutConfig = configuration; @@ -198,10 +189,45 @@ public void respondToGeolocationRequest(boolean allow) { // Private - private ColorScheme getColorScheme(String colorScheme) { + static CheckoutAppearance appearanceFor(String colorScheme, ReadableMap androidConfig) { + if ("web_default".equals(colorScheme)) { + return getStorefrontAppearance(androidConfig); + } + + ColorScheme scheme = getColorScheme(colorScheme); + + if (isValidColorConfig(androidConfig)) { + ColorScheme schemeWithOverrides = getColors(scheme, androidConfig); + if (schemeWithOverrides != null) { + return new CheckoutAppearance.App(schemeWithOverrides); + } + } + + return new CheckoutAppearance.App(scheme); + } + + private static CheckoutAppearance getStorefrontAppearance(ReadableMap androidConfig) { + CheckoutAppearance.Storefront storefront = new CheckoutAppearance.Storefront(); + + Colors colors = createColorsFromConfig(androidConfig); + if (colors == null) { + return storefront; + } + + return storefront.customize(builder -> { + builder.withWebViewBackground(colors.getWebViewBackground()); + builder.withHeaderBackground(colors.getHeaderBackground()); + builder.withHeaderFont(colors.getHeaderFont()); + builder.withProgressIndicator(colors.getProgressIndicator()); + Color closeButtonColor = colors.getCloseIconTint(); + if (closeButtonColor != null) { + builder.withCloseIconTint(closeButtonColor); + } + }); + } + + private static ColorScheme getColorScheme(String colorScheme) { switch (colorScheme) { - case "web_default": - return new ColorScheme.Web(); case "light": return new ColorScheme.Light(); case "dark": @@ -212,8 +238,11 @@ private ColorScheme getColorScheme(String colorScheme) { } } - private String colorSchemeToString(ColorScheme colorScheme) { - return colorScheme.getId(); + static String colorSchemeStringFor(CheckoutAppearance appearance) { + if (appearance instanceof CheckoutAppearance.App) { + return ((CheckoutAppearance.App) appearance).getColorScheme().getId(); + } + return "web_default"; } private LogLevel getLogLevel(String logLevel) { @@ -236,7 +265,7 @@ private String logLevelToString(LogLevel logLevel) { return "error"; } - private boolean isValidColorConfig(ReadableMap config) { + private static boolean isValidColorConfig(ReadableMap config) { if (config == null) { return false; } @@ -259,7 +288,7 @@ private boolean isValidColorConfig(ReadableMap config) { return true; } - private boolean isValidColorScheme(ColorScheme colorScheme, ReadableMap colorConfig) { + private static boolean isValidColorScheme(ColorScheme colorScheme, ReadableMap colorConfig) { if (colorConfig == null) { return false; } @@ -269,16 +298,16 @@ private boolean isValidColorScheme(ColorScheme colorScheme, ReadableMap colorCon return false; } - boolean validLight = this.isValidColorConfig(colorConfig.getMap("light")); - boolean validDark = this.isValidColorConfig(colorConfig.getMap("dark")); + boolean validLight = isValidColorConfig(colorConfig.getMap("light")); + boolean validDark = isValidColorConfig(colorConfig.getMap("dark")); return validLight && validDark; } - return this.isValidColorConfig(colorConfig); + return isValidColorConfig(colorConfig); } - private Color parseColorFromConfig(ReadableMap config, String colorKey) { + private static Color parseColorFromConfig(ReadableMap config, String colorKey) { if (config.hasKey(colorKey)) { String colorStr = config.getString(colorKey); return parseColor(colorStr); @@ -287,7 +316,7 @@ private Color parseColorFromConfig(ReadableMap config, String colorKey) { return null; } - private Colors createColorsFromConfig(ReadableMap config) { + private static Colors createColorsFromConfig(ReadableMap config) { if (config == null) { return null; } @@ -307,18 +336,19 @@ private Colors createColorsFromConfig(ReadableMap config) { // Parameter allows passing a custom drawable, we'll just support custom color // for now null, - closeButtonColor); + closeButtonColor, + null); } return null; } - private ColorScheme getColors(ColorScheme colorScheme, ReadableMap config) { - if (!this.isValidColorScheme(colorScheme, config)) { + private static ColorScheme getColors(ColorScheme colorScheme, ReadableMap config) { + if (!isValidColorScheme(colorScheme, config)) { return null; } - if (colorScheme instanceof ColorScheme.Automatic && this.isValidColorScheme(colorScheme, config)) { + if (colorScheme instanceof ColorScheme.Automatic && isValidColorScheme(colorScheme, config)) { Colors lightColors = createColorsFromConfig(config.getMap("light")); Colors darkColors = createColorsFromConfig(config.getMap("dark")); @@ -337,8 +367,6 @@ private ColorScheme getColors(ColorScheme colorScheme, ReadableMap config) { ((ColorScheme.Light) colorScheme).setColors(colors); } else if (colorScheme instanceof ColorScheme.Dark) { ((ColorScheme.Dark) colorScheme).setColors(colors); - } else if (colorScheme instanceof ColorScheme.Web) { - ((ColorScheme.Web) colorScheme).setColors(colors); } return colorScheme; } @@ -346,7 +374,7 @@ private ColorScheme getColors(ColorScheme colorScheme, ReadableMap config) { return null; } - private Color parseColor(String colorStr) { + private static Color parseColor(String colorStr) { try { colorStr = colorStr.replace("#", ""); diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/test/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModuleTest.kt b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/test/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModuleTest.kt new file mode 100644 index 000000000..1f16f6717 --- /dev/null +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/android/src/test/java/com/shopify/reactnative/checkoutkit/ShopifyCheckoutKitModuleTest.kt @@ -0,0 +1,42 @@ +package com.shopify.reactnative.checkoutkit + +import com.shopify.checkoutkit.CheckoutAppearance +import com.shopify.checkoutkit.ColorScheme +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class ShopifyCheckoutKitModuleTest { + + @Test + fun `appearanceFor maps an app color scheme to an App appearance`() { + val appearance = ShopifyCheckoutKitModule.appearanceFor("dark", null) + + assertThat(appearance).isEqualTo(CheckoutAppearance.App(ColorScheme.Dark())) + } + + @Test + fun `appearanceFor maps web_default to a Storefront appearance`() { + val appearance = ShopifyCheckoutKitModule.appearanceFor("web_default", null) + + assertThat(appearance).isInstanceOf(CheckoutAppearance.Storefront::class.java) + } + + @Test + fun `colorSchemeStringFor returns the color scheme id for an App appearance`() { + val colorScheme = ShopifyCheckoutKitModule.colorSchemeStringFor( + CheckoutAppearance.App(ColorScheme.Light()) + ) + + assertThat(colorScheme).isEqualTo("light") + } + + @Test + fun `colorSchemeStringFor represents a Storefront appearance as web_default`() { + val colorScheme = ShopifyCheckoutKitModule.colorSchemeStringFor(CheckoutAppearance.Storefront()) + + assertThat(colorScheme).isEqualTo("web_default") + } +} diff --git a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift index a4b2767f7..9798ebc8e 100644 --- a/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift +++ b/platforms/react-native/modules/@shopify/checkout-kit-react-native/ios/ShopifyCheckoutKit.swift @@ -129,18 +129,27 @@ class RCTShopifyCheckoutKit: NSObject { } } - private func getColorScheme(_ colorScheme: String) -> Configuration.ColorScheme { + private func appearanceFor(_ colorScheme: String) -> Configuration.Appearance { switch colorScheme { case "web_default": - return Configuration.ColorScheme.web - case "automatic": - return Configuration.ColorScheme.automatic + return .storefront case "light": - return Configuration.ColorScheme.light + return .app(.light) case "dark": - return Configuration.ColorScheme.dark + return .app(.dark) + case "automatic": + return .app(.automatic) default: - return Configuration.ColorScheme.automatic + return .app(.automatic) + } + } + + private func colorSchemeStringFor(_ appearance: Configuration.Appearance) -> String { + switch appearance { + case let .app(colorScheme): + return colorScheme.rawValue + case .storefront: + return "web_default" } } @@ -157,7 +166,7 @@ class RCTShopifyCheckoutKit: NSObject { } if let colorScheme = configuration["colorScheme"] as? String { - ShopifyCheckoutKit.configuration.colorScheme = getColorScheme(colorScheme) + ShopifyCheckoutKit.configuration.appearance = appearanceFor(colorScheme) } if let tintColorHex = iosConfig?["tintColor"] as? String { @@ -184,7 +193,7 @@ class RCTShopifyCheckoutKit: NSObject { @objc func getConfig() -> NSDictionary { return [ "title": ShopifyCheckoutKit.configuration.title, - "colorScheme": ShopifyCheckoutKit.configuration.colorScheme.rawValue, + "colorScheme": colorSchemeStringFor(ShopifyCheckoutKit.configuration.appearance), "preloading": ShopifyCheckoutKit.configuration.preloading.enabled, "tintColor": ShopifyCheckoutKit.configuration.tintColor, "backgroundColor": ShopifyCheckoutKit.configuration.backgroundColor, @@ -282,7 +291,7 @@ class RCTShopifyCheckoutKit: NSObject { private func logLevelToString(_ logLevel: LogLevel) -> String { switch logLevel { - case .all, .debug: + case .debug: return "debug" case .error: return "error" diff --git a/platforms/react-native/test/rct-integration-app/Podfile b/platforms/react-native/test/rct-integration-app/Podfile index d42ec1d40..79cd4305e 100644 --- a/platforms/react-native/test/rct-integration-app/Podfile +++ b/platforms/react-native/test/rct-integration-app/Podfile @@ -11,6 +11,14 @@ prepare_react_native_project! inhibit_all_warnings! target 'RCTIntegrationApp' do + use_local_sdk = ENV['USE_LOCAL_SDK'] == '1' + + if use_local_sdk + shopify_kit_path = "../../../../" + pod "ShopifyCheckoutKit", :path => shopify_kit_path + pod "ShopifyCheckoutKit/AcceleratedCheckouts", :path => shopify_kit_path + end + pod 'RNShopifyCheckoutKit', :path => '../../modules/@shopify/checkout-kit-react-native' config = use_native_modules! diff --git a/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/AcceleratedCheckouts_SupportedTests.swift b/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/AcceleratedCheckouts_SupportedTests.swift index 97d928125..7cbdb5dd0 100644 --- a/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/AcceleratedCheckouts_SupportedTests.swift +++ b/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/AcceleratedCheckouts_SupportedTests.swift @@ -34,7 +34,7 @@ class AcceleratedCheckouts_SupportedTests: XCTestCase { } private func resetCheckoutKitDefaults() { - ShopifyCheckoutKit.configuration.colorScheme = .automatic + ShopifyCheckoutKit.configuration.appearance = .storefront ShopifyCheckoutKit.configuration.closeButtonTintColor = nil } diff --git a/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/ShopifyCheckoutKitTests.swift b/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/ShopifyCheckoutKitTests.swift index d73fc06c3..42b513138 100644 --- a/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/ShopifyCheckoutKitTests.swift +++ b/platforms/react-native/test/rct-integration-app/RCTIntegrationAppTests/ShopifyCheckoutKitTests.swift @@ -18,7 +18,7 @@ class ShopifyCheckoutKitTests: XCTestCase { } private func resetShopifyCheckoutKitDefaults() { - ShopifyCheckoutKit.configuration.colorScheme = .automatic + ShopifyCheckoutKit.configuration.appearance = .storefront ShopifyCheckoutKit.configuration.closeButtonTintColor = nil ShopifyCheckoutKit.configuration.logLevel = LogLevel.error ShopifyCheckoutKit.configuration.preloading.enabled = true @@ -34,7 +34,7 @@ class ShopifyCheckoutKitTests: XCTestCase { let result = shopifyCheckoutKit.getConfig() as? [String: Any] // Verify that getConfig returned the expected result - XCTAssertEqual(result?["colorScheme"] as? String, "automatic") + XCTAssertEqual(result?["colorScheme"] as? String, "web_default") XCTAssertEqual(result?["preloading"] as? Bool, true) } @@ -52,11 +52,32 @@ class ShopifyCheckoutKitTests: XCTestCase { shopifyCheckoutKit.setConfig(configuration) - XCTAssertEqual(ShopifyCheckoutKit.configuration.colorScheme, .dark) + XCTAssertEqual(ShopifyCheckoutKit.configuration.appearance, .app(.dark)) XCTAssertEqual(ShopifyCheckoutKit.configuration.tintColor, UIColor(hex: "#FF0000")) XCTAssertEqual(ShopifyCheckoutKit.configuration.backgroundColor, UIColor(hex: "#0000FF")) } + func testConfigureWithWebDefaultUsesStorefrontAppearance() { + let configuration: [AnyHashable: Any] = [ + "colorScheme": "web_default" + ] + + shopifyCheckoutKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutKit.configuration.appearance, .storefront) + } + + func testGetConfigReturnsColorSchemeIdForAppAppearance() { + let configuration: [AnyHashable: Any] = [ + "colorScheme": "light" + ] + shopifyCheckoutKit.setConfig(configuration) + + let result = shopifyCheckoutKit.getConfig() as? [String: Any] + + XCTAssertEqual(result?["colorScheme"] as? String, "light") + } + func testConfigureWithInvalidColors() { let configuration: [AnyHashable: Any] = [ "colors": [