Skip to content

Commit

Permalink
Add color customization support (#17)
Browse files Browse the repository at this point in the history
* Add color customization support

* Add Android support

* Update support for iOS and Android

* Swift indentation yet again

* Add automatic color support
  • Loading branch information
markmur authored Nov 27, 2023
1 parent 52aaad8 commit e03d2a6
Show file tree
Hide file tree
Showing 8 changed files with 433 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ ndkVersion=23.1.7779620
buildToolsVersion = "33.0.0"

# Version of Shopify Checkout SDK to use with React Native
SHOPIFY_CHECKOUT_SDK_VERSION=0.3.1
SHOPIFY_CHECKOUT_SDK_VERSION=0.3.2
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ of this software and associated documentation files (the "Software"), to deal
import android.app.Activity;
import android.content.Context;
import androidx.activity.ComponentActivity;
import androidx.core.content.ContextCompat;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
Expand Down Expand Up @@ -87,12 +88,11 @@ private ColorScheme getColorScheme(String colorScheme) {
switch (colorScheme) {
case "web_default":
return new ColorScheme.Web();
case "automatic":
return new ColorScheme.Automatic();
case "light":
return new ColorScheme.Light();
case "dark":
return new ColorScheme.Dark();
case "automatic":
default:
return new ColorScheme.Automatic();
}
Expand All @@ -102,15 +102,133 @@ private String colorSchemeToString(ColorScheme colorScheme) {
return colorScheme.getId();
}

private boolean isValidColorConfig(ReadableMap config) {
if (config == null) {
return false;
}

String[] colorKeys = {"backgroundColor", "spinnerColor", "headerTextColor", "headerBackgroundColor"};

for (String key : colorKeys) {
if (!config.hasKey(key) || config.getString(key) == null || parseColor(config.getString(key)) == null) {
return false;
}
}

return true;
}

private boolean isValidColorScheme(ColorScheme colorScheme, ReadableMap colorConfig) {
if (colorConfig == null) {
return false;
}

if (colorScheme instanceof ColorScheme.Automatic) {
if (!colorConfig.hasKey("light") || !colorConfig.hasKey("dark")) {
return false;
}

boolean validLight = this.isValidColorConfig(colorConfig.getMap("light"));
boolean validDark = this.isValidColorConfig(colorConfig.getMap("dark"));

return validLight && validDark;
}

return this.isValidColorConfig(colorConfig);
}

private Color parseColorFromConfig(ReadableMap config, String colorKey) {
if (config.hasKey(colorKey)) {
String colorStr = config.getString(colorKey);
return parseColor(colorStr);
}

return null;
}

private Colors createColorsFromConfig(ReadableMap config) {
if (config == null) {
return null;
}

Color webViewBackground = parseColorFromConfig(config, "backgroundColor");
Color headerBackground = parseColorFromConfig(config, "headerBackgroundColor");
Color headerFont = parseColorFromConfig(config, "headerTextColor");
Color spinnerColor = parseColorFromConfig(config, "spinnerColor");

if (webViewBackground != null && spinnerColor != null && headerFont != null && headerBackground != null) {
return new Colors(
webViewBackground,
headerBackground,
headerFont,
spinnerColor
);
}

return null;
}

private ColorScheme getColors(ColorScheme colorScheme, ReadableMap config) {
if (!this.isValidColorScheme(colorScheme, config)) {
return null;
}

if (colorScheme instanceof ColorScheme.Automatic && this.isValidColorScheme(colorScheme, config)) {
Colors lightColors = createColorsFromConfig(config.getMap("light"));
Colors darkColors = createColorsFromConfig(config.getMap("dark"));

if (lightColors != null && darkColors != null) {
ColorScheme.Automatic automaticColorScheme = (ColorScheme.Automatic) colorScheme;
automaticColorScheme.setLightColors(lightColors);
automaticColorScheme.setDarkColors(darkColors);
return automaticColorScheme;
}
}

Colors colors = createColorsFromConfig(config);

if (colors != null) {
if (colorScheme instanceof ColorScheme.Light) {
((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;
}

return null;
}

@ReactMethod
public void configure(ReadableMap config) {
Context context = getReactApplicationContext();

ShopifyCheckoutKit.configure(configuration -> {
if (config.hasKey("preloading")) {
configuration.setPreloading(new Preloading(config.getBoolean("preloading")));
}

if (config.hasKey("colorScheme")) {
configuration.setColorScheme(getColorScheme(config.getString("colorScheme")));
ColorScheme colorScheme = getColorScheme(config.getString("colorScheme"));
ReadableMap colorsConfig = config.hasKey("colors") ? config.getMap("colors") : null;
ReadableMap androidConfig = null;

if (colorsConfig != null && colorsConfig.hasKey("android")) {
androidConfig = colorsConfig.getMap("android");
}

if (androidConfig != null && this.isValidColorConfig(androidConfig)) {
ColorScheme colorSchemeWithOverrides = getColors(colorScheme, androidConfig);
if (colorSchemeWithOverrides != null) {
configuration.setColorScheme(colorSchemeWithOverrides);
checkoutConfig = configuration;
return;
}
}

configuration.setColorScheme(colorScheme);
}

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

promise.resolve(resultConfig);
}

private Color parseColor(String colorStr) {
try {
colorStr = colorStr.replace("#", "");

long color = Long.parseLong(colorStr, 16);

if (colorStr.length() == 6) {
// If alpha is not included, assume full opacity
color = color | 0xFF000000;
}

return new Color.SRGB((int) color);
} catch (NumberFormatException e) {
System.out.println("Warning: Invalid color string. Default color will be used.");
return null;
}
}
}
183 changes: 110 additions & 73 deletions modules/react-native-shopify-checkout-kit/ios/ShopifyCheckoutKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,77 +27,114 @@ import UIKit

@objc(RCTShopifyCheckoutKit)
class RCTShopifyCheckoutKit: UIViewController, CheckoutDelegate {
func checkoutDidComplete() {}

func checkoutDidFail(error _: ShopifyCheckoutKit.CheckoutError) {}

func checkoutDidCancel() {
DispatchQueue.main.async {
if let rootViewController = UIApplication.shared.delegate?.window??.rootViewController {
rootViewController.dismiss(animated: true)
}
}
}

@objc func constantsToExport() -> [String: String]! {
return [
"version": ShopifyCheckoutKit.version
]
}

@objc func present(_ checkoutURL: String) {
DispatchQueue.main.async {
let sharedDelegate = UIApplication.shared.delegate

if let url = URL(string: checkoutURL), let rootViewController = sharedDelegate?.window??.rootViewController {
ShopifyCheckoutKit.present(checkout: url, from: rootViewController, delegate: self)
}
}
}

@objc func preload(_ checkoutURL: String) {
DispatchQueue.main.async {
if let url = URL(string: checkoutURL) {
ShopifyCheckoutKit.preload(checkout: url)
}
}
}

private func getColorScheme(_ colorScheme: String) -> ShopifyCheckoutKit.Configuration.ColorScheme {
switch colorScheme {
case "web_default":
return ShopifyCheckoutKit.Configuration.ColorScheme.web
case "automatic":
return ShopifyCheckoutKit.Configuration.ColorScheme.automatic
case "light":
return ShopifyCheckoutKit.Configuration.ColorScheme.light
case "dark":
return ShopifyCheckoutKit.Configuration.ColorScheme.dark
default:
return ShopifyCheckoutKit.Configuration.ColorScheme.automatic
}
}

@objc func configure(_ configuration: [AnyHashable: Any]) {
if let preloading = configuration["preloading"] as? Bool {
ShopifyCheckoutKit.configuration.preloading.enabled = preloading
}

if let colorScheme = configuration["colorScheme"] as? String {
ShopifyCheckoutKit.configuration.colorScheme = getColorScheme(colorScheme)
}
}

@objc func getConfig(_ resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
let config: [String: Any] = [
"preloading": ShopifyCheckoutKit.configuration.preloading.enabled,
"colorScheme": ShopifyCheckoutKit.configuration.colorScheme.rawValue
]

resolve(config)
}

@objc static func requiresMainQueueSetup() -> Bool {
return true
}
func checkoutDidComplete() {}

func checkoutDidFail(error _: ShopifyCheckoutKit.CheckoutError) {}

func checkoutDidCancel() {
DispatchQueue.main.async {
if let rootViewController = UIApplication.shared.delegate?.window??.rootViewController {
rootViewController.dismiss(animated: true)
}
}
}

@objc func constantsToExport() -> [String: String]! {
return [
"version": ShopifyCheckoutKit.version
]
}

@objc func present(_ checkoutURL: String) {
DispatchQueue.main.async {
let sharedDelegate = UIApplication.shared.delegate

if let url = URL(string: checkoutURL), let rootViewController = sharedDelegate?.window??.rootViewController {
ShopifyCheckoutKit.present(checkout: url, from: rootViewController, delegate: self)
}
}
}

@objc func preload(_ checkoutURL: String) {
DispatchQueue.main.async {
if let url = URL(string: checkoutURL) {
ShopifyCheckoutKit.preload(checkout: url)
}
}
}

private func getColorScheme(_ colorScheme: String) -> ShopifyCheckoutKit.Configuration.ColorScheme {
switch colorScheme {
case "web_default":
return ShopifyCheckoutKit.Configuration.ColorScheme.web
case "automatic":
return ShopifyCheckoutKit.Configuration.ColorScheme.automatic
case "light":
return ShopifyCheckoutKit.Configuration.ColorScheme.light
case "dark":
return ShopifyCheckoutKit.Configuration.ColorScheme.dark
default:
return ShopifyCheckoutKit.Configuration.ColorScheme.automatic
}
}

@objc func configure(_ configuration: [AnyHashable: Any]) {
let colorConfig = configuration["colors"] as? [AnyHashable: Any]
let iosConfig = colorConfig?["ios"] as? [String: String]

if let preloading = configuration["preloading"] as? Bool {
ShopifyCheckoutKit.configuration.preloading.enabled = preloading
}

if let colorScheme = configuration["colorScheme"] as? String {
ShopifyCheckoutKit.configuration.colorScheme = getColorScheme(colorScheme)
}

if let spinnerColorHex = iosConfig?["spinnerColor"] as? String {
ShopifyCheckoutKit.configuration.spinnerColor = UIColor(hex: spinnerColorHex)
}

if let backgroundColorHex = iosConfig?["backgroundColor"] as? String {
ShopifyCheckoutKit.configuration.backgroundColor = UIColor(hex: backgroundColorHex)
}
}

@objc func getConfig(_ resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
let config: [String: Any] = [
"preloading": ShopifyCheckoutKit.configuration.preloading.enabled,
"colorScheme": ShopifyCheckoutKit.configuration.colorScheme.rawValue
]

resolve(config)
}

@objc static func requiresMainQueueSetup() -> Bool {
return true
}
}

extension UIColor {
convenience init(hex: String) {
let hexString: String = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let start = hexString.index(hexString.startIndex, offsetBy: hexString.hasPrefix("#") ? 1 : 0)
let hexColor = String(hexString[start...])

let scanner = Scanner(string: hexColor)
var hexNumber: UInt64 = 0

if scanner.scanHexInt64(&hexNumber) {
let red = (hexNumber & 0xff0000) >> 16
let green = (hexNumber & 0x00ff00) >> 8
let blue = hexNumber & 0x0000ff

self.init(
red: CGFloat(red) / 0xff,
green: CGFloat(green) / 0xff,
blue: CGFloat(blue) / 0xff,
alpha: 1
)
} else {
self.init(red: 0, green: 0, blue: 0, alpha: 1)
}
}
}
Loading

0 comments on commit e03d2a6

Please sign in to comment.