Skip to content

Commit e03d2a6

Browse files
authored
Add color customization support (#17)
* Add color customization support * Add Android support * Update support for iOS and Android * Swift indentation yet again * Add automatic color support
1 parent 52aaad8 commit e03d2a6

File tree

8 files changed

+433
-105
lines changed

8 files changed

+433
-105
lines changed

modules/react-native-shopify-checkout-kit/android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ ndkVersion=23.1.7779620
55
buildToolsVersion = "33.0.0"
66

77
# Version of Shopify Checkout SDK to use with React Native
8-
SHOPIFY_CHECKOUT_SDK_VERSION=0.3.1
8+
SHOPIFY_CHECKOUT_SDK_VERSION=0.3.2

modules/react-native-shopify-checkout-kit/android/src/main/java/com/shopifycheckoutkit/ShopifyCheckoutKitModule.java

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ of this software and associated documentation files (the "Software"), to deal
2626
import android.app.Activity;
2727
import android.content.Context;
2828
import androidx.activity.ComponentActivity;
29+
import androidx.core.content.ContextCompat;
2930
import com.facebook.react.bridge.Arguments;
3031
import com.facebook.react.bridge.Promise;
3132
import com.facebook.react.bridge.ReactApplicationContext;
@@ -87,12 +88,11 @@ private ColorScheme getColorScheme(String colorScheme) {
8788
switch (colorScheme) {
8889
case "web_default":
8990
return new ColorScheme.Web();
90-
case "automatic":
91-
return new ColorScheme.Automatic();
9291
case "light":
9392
return new ColorScheme.Light();
9493
case "dark":
9594
return new ColorScheme.Dark();
95+
case "automatic":
9696
default:
9797
return new ColorScheme.Automatic();
9898
}
@@ -102,15 +102,133 @@ private String colorSchemeToString(ColorScheme colorScheme) {
102102
return colorScheme.getId();
103103
}
104104

105+
private boolean isValidColorConfig(ReadableMap config) {
106+
if (config == null) {
107+
return false;
108+
}
109+
110+
String[] colorKeys = {"backgroundColor", "spinnerColor", "headerTextColor", "headerBackgroundColor"};
111+
112+
for (String key : colorKeys) {
113+
if (!config.hasKey(key) || config.getString(key) == null || parseColor(config.getString(key)) == null) {
114+
return false;
115+
}
116+
}
117+
118+
return true;
119+
}
120+
121+
private boolean isValidColorScheme(ColorScheme colorScheme, ReadableMap colorConfig) {
122+
if (colorConfig == null) {
123+
return false;
124+
}
125+
126+
if (colorScheme instanceof ColorScheme.Automatic) {
127+
if (!colorConfig.hasKey("light") || !colorConfig.hasKey("dark")) {
128+
return false;
129+
}
130+
131+
boolean validLight = this.isValidColorConfig(colorConfig.getMap("light"));
132+
boolean validDark = this.isValidColorConfig(colorConfig.getMap("dark"));
133+
134+
return validLight && validDark;
135+
}
136+
137+
return this.isValidColorConfig(colorConfig);
138+
}
139+
140+
private Color parseColorFromConfig(ReadableMap config, String colorKey) {
141+
if (config.hasKey(colorKey)) {
142+
String colorStr = config.getString(colorKey);
143+
return parseColor(colorStr);
144+
}
145+
146+
return null;
147+
}
148+
149+
private Colors createColorsFromConfig(ReadableMap config) {
150+
if (config == null) {
151+
return null;
152+
}
153+
154+
Color webViewBackground = parseColorFromConfig(config, "backgroundColor");
155+
Color headerBackground = parseColorFromConfig(config, "headerBackgroundColor");
156+
Color headerFont = parseColorFromConfig(config, "headerTextColor");
157+
Color spinnerColor = parseColorFromConfig(config, "spinnerColor");
158+
159+
if (webViewBackground != null && spinnerColor != null && headerFont != null && headerBackground != null) {
160+
return new Colors(
161+
webViewBackground,
162+
headerBackground,
163+
headerFont,
164+
spinnerColor
165+
);
166+
}
167+
168+
return null;
169+
}
170+
171+
private ColorScheme getColors(ColorScheme colorScheme, ReadableMap config) {
172+
if (!this.isValidColorScheme(colorScheme, config)) {
173+
return null;
174+
}
175+
176+
if (colorScheme instanceof ColorScheme.Automatic && this.isValidColorScheme(colorScheme, config)) {
177+
Colors lightColors = createColorsFromConfig(config.getMap("light"));
178+
Colors darkColors = createColorsFromConfig(config.getMap("dark"));
179+
180+
if (lightColors != null && darkColors != null) {
181+
ColorScheme.Automatic automaticColorScheme = (ColorScheme.Automatic) colorScheme;
182+
automaticColorScheme.setLightColors(lightColors);
183+
automaticColorScheme.setDarkColors(darkColors);
184+
return automaticColorScheme;
185+
}
186+
}
187+
188+
Colors colors = createColorsFromConfig(config);
189+
190+
if (colors != null) {
191+
if (colorScheme instanceof ColorScheme.Light) {
192+
((ColorScheme.Light) colorScheme).setColors(colors);
193+
} else if (colorScheme instanceof ColorScheme.Dark) {
194+
((ColorScheme.Dark) colorScheme).setColors(colors);
195+
} else if (colorScheme instanceof ColorScheme.Web) {
196+
((ColorScheme.Web) colorScheme).setColors(colors);
197+
}
198+
return colorScheme;
199+
}
200+
201+
return null;
202+
}
203+
105204
@ReactMethod
106205
public void configure(ReadableMap config) {
206+
Context context = getReactApplicationContext();
207+
107208
ShopifyCheckoutKit.configure(configuration -> {
108209
if (config.hasKey("preloading")) {
109210
configuration.setPreloading(new Preloading(config.getBoolean("preloading")));
110211
}
111212

112213
if (config.hasKey("colorScheme")) {
113-
configuration.setColorScheme(getColorScheme(config.getString("colorScheme")));
214+
ColorScheme colorScheme = getColorScheme(config.getString("colorScheme"));
215+
ReadableMap colorsConfig = config.hasKey("colors") ? config.getMap("colors") : null;
216+
ReadableMap androidConfig = null;
217+
218+
if (colorsConfig != null && colorsConfig.hasKey("android")) {
219+
androidConfig = colorsConfig.getMap("android");
220+
}
221+
222+
if (androidConfig != null && this.isValidColorConfig(androidConfig)) {
223+
ColorScheme colorSchemeWithOverrides = getColors(colorScheme, androidConfig);
224+
if (colorSchemeWithOverrides != null) {
225+
configuration.setColorScheme(colorSchemeWithOverrides);
226+
checkoutConfig = configuration;
227+
return;
228+
}
229+
}
230+
231+
configuration.setColorScheme(colorScheme);
114232
}
115233

116234
checkoutConfig = configuration;
@@ -126,4 +244,22 @@ public void getConfig(Promise promise) {
126244

127245
promise.resolve(resultConfig);
128246
}
247+
248+
private Color parseColor(String colorStr) {
249+
try {
250+
colorStr = colorStr.replace("#", "");
251+
252+
long color = Long.parseLong(colorStr, 16);
253+
254+
if (colorStr.length() == 6) {
255+
// If alpha is not included, assume full opacity
256+
color = color | 0xFF000000;
257+
}
258+
259+
return new Color.SRGB((int) color);
260+
} catch (NumberFormatException e) {
261+
System.out.println("Warning: Invalid color string. Default color will be used.");
262+
return null;
263+
}
264+
}
129265
}

modules/react-native-shopify-checkout-kit/ios/ShopifyCheckoutKit.swift

Lines changed: 110 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -27,77 +27,114 @@ import UIKit
2727

2828
@objc(RCTShopifyCheckoutKit)
2929
class RCTShopifyCheckoutKit: UIViewController, CheckoutDelegate {
30-
func checkoutDidComplete() {}
31-
32-
func checkoutDidFail(error _: ShopifyCheckoutKit.CheckoutError) {}
33-
34-
func checkoutDidCancel() {
35-
DispatchQueue.main.async {
36-
if let rootViewController = UIApplication.shared.delegate?.window??.rootViewController {
37-
rootViewController.dismiss(animated: true)
38-
}
39-
}
40-
}
41-
42-
@objc func constantsToExport() -> [String: String]! {
43-
return [
44-
"version": ShopifyCheckoutKit.version
45-
]
46-
}
47-
48-
@objc func present(_ checkoutURL: String) {
49-
DispatchQueue.main.async {
50-
let sharedDelegate = UIApplication.shared.delegate
51-
52-
if let url = URL(string: checkoutURL), let rootViewController = sharedDelegate?.window??.rootViewController {
53-
ShopifyCheckoutKit.present(checkout: url, from: rootViewController, delegate: self)
54-
}
55-
}
56-
}
57-
58-
@objc func preload(_ checkoutURL: String) {
59-
DispatchQueue.main.async {
60-
if let url = URL(string: checkoutURL) {
61-
ShopifyCheckoutKit.preload(checkout: url)
62-
}
63-
}
64-
}
65-
66-
private func getColorScheme(_ colorScheme: String) -> ShopifyCheckoutKit.Configuration.ColorScheme {
67-
switch colorScheme {
68-
case "web_default":
69-
return ShopifyCheckoutKit.Configuration.ColorScheme.web
70-
case "automatic":
71-
return ShopifyCheckoutKit.Configuration.ColorScheme.automatic
72-
case "light":
73-
return ShopifyCheckoutKit.Configuration.ColorScheme.light
74-
case "dark":
75-
return ShopifyCheckoutKit.Configuration.ColorScheme.dark
76-
default:
77-
return ShopifyCheckoutKit.Configuration.ColorScheme.automatic
78-
}
79-
}
80-
81-
@objc func configure(_ configuration: [AnyHashable: Any]) {
82-
if let preloading = configuration["preloading"] as? Bool {
83-
ShopifyCheckoutKit.configuration.preloading.enabled = preloading
84-
}
85-
86-
if let colorScheme = configuration["colorScheme"] as? String {
87-
ShopifyCheckoutKit.configuration.colorScheme = getColorScheme(colorScheme)
88-
}
89-
}
90-
91-
@objc func getConfig(_ resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
92-
let config: [String: Any] = [
93-
"preloading": ShopifyCheckoutKit.configuration.preloading.enabled,
94-
"colorScheme": ShopifyCheckoutKit.configuration.colorScheme.rawValue
95-
]
96-
97-
resolve(config)
98-
}
99-
100-
@objc static func requiresMainQueueSetup() -> Bool {
101-
return true
102-
}
30+
func checkoutDidComplete() {}
31+
32+
func checkoutDidFail(error _: ShopifyCheckoutKit.CheckoutError) {}
33+
34+
func checkoutDidCancel() {
35+
DispatchQueue.main.async {
36+
if let rootViewController = UIApplication.shared.delegate?.window??.rootViewController {
37+
rootViewController.dismiss(animated: true)
38+
}
39+
}
40+
}
41+
42+
@objc func constantsToExport() -> [String: String]! {
43+
return [
44+
"version": ShopifyCheckoutKit.version
45+
]
46+
}
47+
48+
@objc func present(_ checkoutURL: String) {
49+
DispatchQueue.main.async {
50+
let sharedDelegate = UIApplication.shared.delegate
51+
52+
if let url = URL(string: checkoutURL), let rootViewController = sharedDelegate?.window??.rootViewController {
53+
ShopifyCheckoutKit.present(checkout: url, from: rootViewController, delegate: self)
54+
}
55+
}
56+
}
57+
58+
@objc func preload(_ checkoutURL: String) {
59+
DispatchQueue.main.async {
60+
if let url = URL(string: checkoutURL) {
61+
ShopifyCheckoutKit.preload(checkout: url)
62+
}
63+
}
64+
}
65+
66+
private func getColorScheme(_ colorScheme: String) -> ShopifyCheckoutKit.Configuration.ColorScheme {
67+
switch colorScheme {
68+
case "web_default":
69+
return ShopifyCheckoutKit.Configuration.ColorScheme.web
70+
case "automatic":
71+
return ShopifyCheckoutKit.Configuration.ColorScheme.automatic
72+
case "light":
73+
return ShopifyCheckoutKit.Configuration.ColorScheme.light
74+
case "dark":
75+
return ShopifyCheckoutKit.Configuration.ColorScheme.dark
76+
default:
77+
return ShopifyCheckoutKit.Configuration.ColorScheme.automatic
78+
}
79+
}
80+
81+
@objc func configure(_ configuration: [AnyHashable: Any]) {
82+
let colorConfig = configuration["colors"] as? [AnyHashable: Any]
83+
let iosConfig = colorConfig?["ios"] as? [String: String]
84+
85+
if let preloading = configuration["preloading"] as? Bool {
86+
ShopifyCheckoutKit.configuration.preloading.enabled = preloading
87+
}
88+
89+
if let colorScheme = configuration["colorScheme"] as? String {
90+
ShopifyCheckoutKit.configuration.colorScheme = getColorScheme(colorScheme)
91+
}
92+
93+
if let spinnerColorHex = iosConfig?["spinnerColor"] as? String {
94+
ShopifyCheckoutKit.configuration.spinnerColor = UIColor(hex: spinnerColorHex)
95+
}
96+
97+
if let backgroundColorHex = iosConfig?["backgroundColor"] as? String {
98+
ShopifyCheckoutKit.configuration.backgroundColor = UIColor(hex: backgroundColorHex)
99+
}
100+
}
101+
102+
@objc func getConfig(_ resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
103+
let config: [String: Any] = [
104+
"preloading": ShopifyCheckoutKit.configuration.preloading.enabled,
105+
"colorScheme": ShopifyCheckoutKit.configuration.colorScheme.rawValue
106+
]
107+
108+
resolve(config)
109+
}
110+
111+
@objc static func requiresMainQueueSetup() -> Bool {
112+
return true
113+
}
114+
}
115+
116+
extension UIColor {
117+
convenience init(hex: String) {
118+
let hexString: String = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
119+
let start = hexString.index(hexString.startIndex, offsetBy: hexString.hasPrefix("#") ? 1 : 0)
120+
let hexColor = String(hexString[start...])
121+
122+
let scanner = Scanner(string: hexColor)
123+
var hexNumber: UInt64 = 0
124+
125+
if scanner.scanHexInt64(&hexNumber) {
126+
let red = (hexNumber & 0xff0000) >> 16
127+
let green = (hexNumber & 0x00ff00) >> 8
128+
let blue = hexNumber & 0x0000ff
129+
130+
self.init(
131+
red: CGFloat(red) / 0xff,
132+
green: CGFloat(green) / 0xff,
133+
blue: CGFloat(blue) / 0xff,
134+
alpha: 1
135+
)
136+
} else {
137+
self.init(red: 0, green: 0, blue: 0, alpha: 1)
138+
}
139+
}
103140
}

0 commit comments

Comments
 (0)