From 797f83006e8334381d0986b3530d9e59c52c3e70 Mon Sep 17 00:00:00 2001 From: basart Date: Wed, 30 Mar 2022 15:08:59 +0300 Subject: [PATCH] iOS UI customization (#4) * feat: implement ui customization ability (iOS) * update README.md * fix: remove unused code * revert yarn bootstrap script * extend types Co-authored-by: Artem Basov --- README.md | 114 +++++++++++++++++++- example/ios/Podfile.lock | 4 +- ios/Appearance.swift | 77 +++++++++++++ ios/OnfidoAuthSdk.swift | 14 ++- ios/OnfidoAuthSdk.xcodeproj/project.pbxproj | 20 +--- package.json | 1 + scripts/bootstrap.js | 29 +++++ src/types.ts | 6 ++ 8 files changed, 242 insertions(+), 23 deletions(-) create mode 100644 ios/Appearance.swift create mode 100644 scripts/bootstrap.js diff --git a/README.md b/README.md index f490ce7..403f207 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,22 @@ # react-native-onfido-auth-sdk +## Table of contents + +- [Overview](#overview) +- [Installation](#installation) +- [Usage](#usage) + - [1. Creating the SDK configuration](#1-creating-the-sdk-configuration) + - [2. Parameter details](#2-parameter-details) + - [3. Success Response](#3-success-response) + - [4. Failure Response](#4-failure-response) +- [UI Customization](#ui-customization) + - [iOS](#ui-customization-ios) + - [Android](#ui-customization-android) +- [Contributing](#contributing) +- [License](#license) + +## Overview + The Onfido Authentication SDK provides a set of screens for React Native applications to capture 3D face scans for the purpose of identity authentication. ## Installation @@ -10,6 +27,8 @@ yarn add react-native-onfido-auth-sdk ## Usage +You can launch the app with a call to `OnfidoAuth.start`. + ```js import { OnfidoAuth } from 'react-native-onfido-auth-sdk'; // ... @@ -18,11 +37,104 @@ OnfidoAuth.start({ sdkToken: 'sdkToken' }) .catch(/* ... */); ``` +### 1. Creating the SDK configuration + +Once you have an added the SDK as a dependency and you have a SDK token, you can configure the SDK: + +Example configuration: + +```js +config = { + sdkToken: 'EXAMPLE-TOKEN-123', + retryCount: 2, +} +``` + +### 2. Parameter details + +* **`sdkToken`**: Required. This is the JWT sdk token obtained by making a call to the SDK token API. +* **`retryCount`**: Optional. This value is used to set the number of repeat attempts a user can do after the first unsuccessful try. + +### 3. Success Response + +Success is when the user has reached the end of the flow. The result has the `verified`, `uuid` and `token` properties. If authentication was successful, `verified` will be `true` and `token` will be the JWT. This JWT token can be used to validate the response. + +Example: + +```js +{ + token: 'EXAMPLE-TOKEN-456', + uuid: 'uuid', + verified: true, +} +``` + +### 4. Failure Response + +The SDK will reject the promise any time the OnfidoAuth SDK exits without a success. Error messages are not in a presentable format to the end user and are not localised. + ## UI Customization ### iOS -T.B.D. +You can customize the iOS UI by adding a `customization.ios.json` file to your project at the same level as your `node_modules` directory. The file should contain a single json object with the desired keys and values. For example: + +```json +{ + "onfidoPrimaryTextColor": "", + "onfidoPrimaryTextDynamicDimmingColor": "", + + "onfidoPrimaryButtonColor": "#4BC0B1", + "onfidoPrimaryButtonDynamicDimmingColor": "", + "onfidoPrimaryButtonPressedColor": "#2EAC9C", + "onfidoPrimaryButtonDisabledColor": "", + "onfidoPrimaryButtonDisabledDynamicDimmingColor": "", + "onfidoPrimaryButtonTextColor": "#FFFFFF", + "onfidoPrimaryButtonTextDynamicDimmingColor": "", + "onfidoPrimaryButtonTextPressedColor": "", + "onfidoPrimaryButtonTextDisabledColor": "", + "onfidoPrimaryButtonTextDisabledDynamicDimmingColor": "", + + "onfidoSecondaryButtonPressedColor": "", + "onfidoDualSpinnerColor": "", + "onfidoRetryScreenOvalStrokeColor": "", + "onfidoUploadProgressFillColor": "", + + "onfidoButtonCornerRadius": 24, + + "onfidoFontRegular": "", + "onfidoFontMedium": "", + "onfidoFontBold": "" +} + +``` + +Once you've added the `customization.ios.json` to your project, you should add `customization.ios.json` file to your xcode project as bundle resource. You can create symbolic link (rather than copy paste) to prevent redundancy. Then when running on an iOS device the values will be picked up dynamically at runtime. + +Below is a description of all available keys: + +| Color | Description | +| -----|-------| +| `onfidoPrimaryTextColor` | Color of most text | +| `onfidoPrimaryTextDynamicDimmingColor` | Color of text in Dynamic Dimming mode

Dynamic Dimming mode is automatically enabled in certain lighting conditions and will include UI differences such as the background turning black | +| `onfidoPrimaryButtonColor` | Background color of buttons | +| `onfidoPrimaryButtonDynamicDimmingColor` | Background color of buttons in Dynamic Dimming mode

Dynamic Dimming mode is automatically enabled in certain lighting conditions and will include UI differences such as the background turning black | +| `onfidoPrimaryButtonPressedColor` | Background color of buttons while being pressed | +| `onfidoPrimaryButtonDisabledColor` | Background color of buttons that are disabled | +| `onfidoPrimaryButtonDisabledDynamicDimmingColor` | Background color of buttons that are disabled in Dynamic Dimming mode

Dynamic Dimming mode is automatically enabled in certain lighting conditions and will include UI differences such as the background turning black | +| `onfidoPrimaryButtonTextColor` | Color of text in buttons | +| `onfidoPrimaryButtonTextDynamicDimmingColor` | Color of text in buttons in Dynamic Dimming mode

Dynamic Dimming mode is automatically enabled in certain lighting conditions and will include UI differences such as the background turning black | +| `onfidoPrimaryButtonTextPressedColor` | Color of text in buttons while being pressed | +| `onfidoPrimaryButtonTextDisabledColor` | Color of text in buttons that are disabled | +| `onfidoPrimaryButtonTextDisabledDynamicDimmingColor` | Color of text in buttons that are disabled in Dynamic Dimming mode

Dynamic Dimming mode is automatically enabled in certain lighting conditions and will include UI differences such as the background turning black | +| `onfidoSecondaryButtonPressedColor` | Background color of secondary button (currently this is only the “Do not accept” button in the optional consent screen) | +| `onfidoDualSpinnerColor` | Color of dual spinner rotating around selfie preview | +| `onfidoRetryScreenOvalStrokeColor` | Stroke color of oval on ideal selfie image in retry screen | +| `onfidoUploadProgressFillColor` | Color of completed portion of upload progress bar | +| `onfidoButtonCornerRadius` | Corner radius of buttons (should be set to 40 maximum, otherwise will not work on some buttons) | +| `onfidoFontRegular` | Name of font to use on regular text | +| `onfidoFontMedium` | Name of font to use on medium text | +| `onfidoFontBold` | Name of font to use on bold text | ### Android You can customize the Android UI by adding a `customization.android.json` file to your project at the same level as your `node_modules` directory. The file should contain a single json object with the desired keys and values. For example: diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index e1bfcaf..4a51659 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -188,7 +188,7 @@ PODS: - React-cxxreact (= 0.63.4) - React-jsi (= 0.63.4) - React-jsinspector (0.63.4) - - react-native-onfido-auth-sdk (0.1.0): + - react-native-onfido-auth-sdk (0.2.1): - OnfidoAuth (~> 0.2.3) - React-Core - React-RCTActionSheet (0.63.4): @@ -365,7 +365,7 @@ SPEC CHECKSUMS: React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31 React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949 React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a - react-native-onfido-auth-sdk: bcb2988d9adc14b11af6a8704b90ec9796043aa0 + react-native-onfido-auth-sdk: a5700d963945f46f3ffecd42e3349cec1bed23ce React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336 React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0 diff --git a/ios/Appearance.swift b/ios/Appearance.swift new file mode 100644 index 0000000..da08aee --- /dev/null +++ b/ios/Appearance.swift @@ -0,0 +1,77 @@ +import Foundation +import OnfidoAuth + +/** + * Load appearance data from the specified file. If the file cannot be loaded, use the default values. + */ +public func loadAppearanceFromFile(filePath: String?) throws -> OnfidoAuthAppearance { + + do { + let jsonResult:Any + do { + guard let path = filePath else { return OnfidoAuthAppearance() } + let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) + jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) + } catch let e as NSError where e.code == NSFileNoSuchFileError || e.code == NSFileReadNoSuchFileError { + jsonResult = Dictionary() + } + if let jsonResult = jsonResult as? Dictionary { + return OnfidoAuthAppearance( + primaryTextColor: UIColor.from(hex: jsonResult["onfidoPrimaryTextColor"] as? String), + primaryTextDynamicDimmingColor: UIColor.from(hex: jsonResult["onfidoPrimaryTextDynamicDimmingColor"] as? String), + primaryButtonColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonColor"] as? String), + primaryButtonDynamicDimmingColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonDynamicDimmingColor"] as? String), + primaryButtonPressedColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonPressedColor"] as? String), + primaryButtonDisabledColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonDisabledColor"] as? String), + primaryButtonDisabledDynamicDimmingColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonDisabledDynamicDimmingColor"] as? String), + primaryButtonTextColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonTextColor"] as? String), + primaryButtonTextDynamicDimmingColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonTextDynamicDimmingColor"] as? String), + primaryButtonTextPressedColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonTextPressedColor"] as? String), + primaryButtonTextDisabledColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonTextDisabledColor"] as? String), + primaryButtonTextDisabledDynamicDimmingColor: UIColor.from(hex: jsonResult["onfidoPrimaryButtonTextDisabledDynamicDimmingColor"] as? String), + secondaryButtonPressedColor: UIColor.from(hex: jsonResult["onfidoSecondaryButtonPressedColor"] as? String), + dualSpinnerColor: UIColor.from(hex: jsonResult["onfidoDualSpinnerColor"] as? String), + retryScreenOvalStrokeColor: UIColor.from(hex: jsonResult["onfidoRetryScreenOvalStrokeColor"] as? String), + uploadProgressFillColor: UIColor.from(hex: jsonResult["onfidoUploadProgressFillColor"] as? String), + buttonCornerRadius: jsonResult["onfidoButtonCornerRadius"] as? CGFloat, + fontRegular: jsonResult["onfidoFontRegular"] as? String, + fontMedium: jsonResult["onfidoFontMedium"] as? String, + fontBold: jsonResult["onfidoFontBold"] as? String + ) + } else { + return OnfidoAuthAppearance() + } + } catch let error { + throw NSError(domain: "There was an error setting colors for OnfidoAuthAppearance: \(error)", code: 0) + } +} + +extension UIColor { + + static func from(hex: String?) -> UIColor? { + guard let hex = hex else { + return nil + } + + let hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines) + let scanner = Scanner(string: hexString) + + if hexString.hasPrefix("#") { + scanner.scanLocation = 1 + } + + var color: UInt32 = 0 + scanner.scanHexInt32(&color) + + let mask = 0x000000FF + let redInt = Int(color >> 16) & mask + let greenInt = Int(color >> 8) & mask + let blueInt = Int(color) & mask + + let red = CGFloat(redInt) / 255.0 + let green = CGFloat(greenInt) / 255.0 + let blue = CGFloat(blueInt) / 255.0 + + return UIColor(red: red, green: green, blue: blue, alpha: 1.0) + } +} diff --git a/ios/OnfidoAuthSdk.swift b/ios/OnfidoAuthSdk.swift index 586b992..fa334e9 100644 --- a/ios/OnfidoAuthSdk.swift +++ b/ios/OnfidoAuthSdk.swift @@ -3,12 +3,13 @@ import Foundation import OnfidoAuth -public func buildOnfidoAuthConfig(config:NSDictionary) throws -> OnfidoAuth.OnfidoAuthConfigBuilder { +public func buildOnfidoAuthConfig(config:NSDictionary, appearance: OnfidoAuthAppearance) throws -> OnfidoAuth.OnfidoAuthConfigBuilder { let sdkToken:String = config["sdkToken"] as! String var onfidoAuthConfig = OnfidoAuthConfig.builder() .withSDKToken(sdkToken) - + .withAppearance(appearance) + if let retryCount = (config["retryCount"] as? Int) { onfidoAuthConfig = onfidoAuthConfig.withRetryCount(retryCount) } @@ -76,8 +77,11 @@ class OnfidoAuthSdk: NSObject { return; } } + + let appearanceFilePath = Bundle.main.path(forResource: "customization.ios", ofType: "json") + let appearance = try loadAppearanceFromFile(filePath: appearanceFilePath) - let onfidoAuthConfig = try buildOnfidoAuthConfig(config: config) + let onfidoAuthConfig = try buildOnfidoAuthConfig(config: config, appearance: appearance) let builtOnfidoAuthConfig = try onfidoAuthConfig.build() let onfidoAuthFlow = OnfidoAuthFlow(withConfiguration: builtOnfidoAuthConfig) @@ -121,8 +125,8 @@ class OnfidoAuthSdk: NSObject { private func run(withConfig config: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { - resolve("Simulator"); - return; + resolve("Simulator"); + return; } } diff --git a/ios/OnfidoAuthSdk.xcodeproj/project.pbxproj b/ios/OnfidoAuthSdk.xcodeproj/project.pbxproj index 6ca22c2..35b8b24 100644 --- a/ios/OnfidoAuthSdk.xcodeproj/project.pbxproj +++ b/ios/OnfidoAuthSdk.xcodeproj/project.pbxproj @@ -7,10 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - - 5E555C0D2413F4C50049A1A2 /* OnfidoAuthSdk.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* OnfidoAuthSdk.m */; }; - F4FF95D7245B92E800C19C63 /* OnfidoAuthSdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D6245B92E800C19C63 /* OnfidoAuthSdk.swift */; }; - + E31D727827F3129000B2F403 /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E31D727727F3129000B2F403 /* Appearance.swift */; }; + F4FF95D7245B92E800C19C63 /* OnfidoAuthSdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D6245B92E800C19C63 /* OnfidoAuthSdk.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -27,11 +25,10 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libOnfidoAuthSdk.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libOnfidoAuthSdk.a; sourceTree = BUILT_PRODUCTS_DIR; }; - B3E7B5891CC2AC0600A0062D /* OnfidoAuthSdk.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OnfidoAuthSdk.m; sourceTree = ""; }; + E31D727727F3129000B2F403 /* Appearance.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = ""; tabWidth = 2; }; F4FF95D5245B92E700C19C63 /* OnfidoAuthSdk-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OnfidoAuthSdk-Bridging-Header.h"; sourceTree = ""; }; F4FF95D6245B92E800C19C63 /* OnfidoAuthSdk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnfidoAuthSdk.swift; sourceTree = ""; }; - /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,11 +53,10 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - + E31D727727F3129000B2F403 /* Appearance.swift */, F4FF95D6245B92E800C19C63 /* OnfidoAuthSdk.swift */, B3E7B5891CC2AC0600A0062D /* OnfidoAuthSdk.m */, F4FF95D5245B92E700C19C63 /* OnfidoAuthSdk-Bridging-Header.h */, - 134814211AA4EA7D00B7C361 /* Products */, ); sourceTree = ""; @@ -122,10 +118,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F4FF95D7245B92E800C19C63 /* OnfidoAuthSdk.swift in Sources */, - B3E7B58A1CC2AC0600A0062D /* OnfidoAuthSdk.m in Sources */, - + E31D727827F3129000B2F403 /* Appearance.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -238,11 +232,9 @@ OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = OnfidoAuthSdk; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "OnfidoAuthSdk-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - }; name = Debug; }; @@ -259,10 +251,8 @@ OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = OnfidoAuthSdk; SKIP_INSTALL = YES; - SWIFT_OBJC_BRIDGING_HEADER = "OnfidoAuthSdk-Bridging-Header.h"; SWIFT_VERSION = 5.0; - }; name = Release; }; diff --git a/package.json b/package.json index 8a9d9f2..d57041c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "release": "release-it", "example": "yarn --cwd example", "pods": "cd example && pod-install --quiet", + "bootstrap": "yarn example && yarn && yarn pods", "updateColors": "node scripts/customize_android.js" }, "keywords": [ diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js new file mode 100644 index 0000000..b2922b5 --- /dev/null +++ b/scripts/bootstrap.js @@ -0,0 +1,29 @@ +const os = require('os'); +const path = require('path'); +const child_process = require('child_process'); + +const root = path.resolve(__dirname, '..'); +const args = process.argv.slice(2); +const options = { + cwd: process.cwd(), + env: process.env, + stdio: 'inherit', + encoding: 'utf-8', +}; + +if (os.type() === 'Windows_NT') { + options.shell = true +} + +let result; + +if (process.cwd() !== root || args.length) { + // We're not in the root of the project, or additional arguments were passed + // In this case, forward the command to `yarn` + result = child_process.spawnSync('yarn', args, options); +} else { + // If `yarn` is run without arguments, perform bootstrap + result = child_process.spawnSync('yarn', ['bootstrap'], options); +} + +process.exitCode = result.status; diff --git a/src/types.ts b/src/types.ts index d752199..7345a05 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,3 +2,9 @@ export type OnfidoAuthConfig = { sdkToken: string; retryCount?: number; }; + +export type OnfidoAuthResult = { + token: string; + uuid: string; + verified: boolean; +};