Skip to content

Commit

Permalink
Merge pull request #2 from smileidentity/feature/reactcompose
Browse files Browse the repository at this point in the history
React Native Products Integration
  • Loading branch information
JNdhlovu authored Oct 24, 2023
2 parents 405627d + 9cd9f2b commit 7425f22
Show file tree
Hide file tree
Showing 55 changed files with 3,505 additions and 179 deletions.
51 changes: 39 additions & 12 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import com.android.Version

buildscript {
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["SmileId_kotlinVersion"]
Expand All @@ -6,12 +8,14 @@ buildscript {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}

dependencies {
classpath "com.android.tools.build:gradle:8.1.0"
classpath "com.android.tools.build:gradle:8.1.2"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jlleitschuh.gradle:ktlint-gradle:11.6.0"
}
}

Expand All @@ -21,9 +25,8 @@ def isNewArchitectureEnabled() {

apply plugin: "com.android.library"
apply plugin: "kotlin-android"


def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
apply plugin: 'kotlin-kapt'
apply plugin: "org.jlleitschuh.gradle.ktlint"

if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
Expand All @@ -38,7 +41,7 @@ def getExtOrIntegerDefault(name) {
}

def supportsNamespace() {
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def parsed = Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger()
def minor = parsed[1].toInteger()

Expand Down Expand Up @@ -75,16 +78,25 @@ android {
}

buildFeatures {
buildConfig = true
buildConfig true
compose = true
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}

lintOptions {
disable "GradleCompatible"
}

composeOptions {
kotlinCompilerExtensionVersion '1.5.3'
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}

sourceSets {
Expand All @@ -105,18 +117,33 @@ android {
repositories {
mavenCentral()
google()
gradlePluginPortal()
}

def kotlin_version = getExtOrDefault("kotlinVersion")
def smile_id_sdk_version = getExtOrDefault("androidVersion")

dependencies {
// For < 0.71, this will be from the local maven repo
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "com.facebook.react:react-native:0.72"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core"
implementation "com.smileidentity:android-sdk:$smile_id_sdk_version"
implementation "com.jakewharton.timber:timber"
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation("androidx.navigation:navigation-compose:2.7.4")
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'


androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'


debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
}

if (isNewArchitectureEnabled()) {
Expand Down
4 changes: 2 additions & 2 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
SmileId_kotlinVersion=1.9.0
SmileId_kotlinVersion=1.9.10
SmileId_minSdkVersion=21
SmileId_targetSdkVersion=34
SmileId_compileSdkVersion=34
SmileId_ndkversion=21.4.7075529
SmileId_androidVersion=10.0.0-beta05
SmileId_androidVersion=10.0.0-beta09
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
84 changes: 78 additions & 6 deletions android/src/main/java/com/smileidentity/react/SmileIdModule.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package com.smileidentity.react

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableMap
import com.smileidentity.SmileID
import com.smileidentity.SmileIdSpec
import com.smileidentity.models.EnhancedKycRequest
import com.smileidentity.react.utils.getStringOrDefault
import com.smileidentity.react.utils.partnerParams
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch


class SmileIdModule internal constructor(context: ReactApplicationContext) :
SmileIdSpec(context) {
Expand All @@ -12,14 +22,76 @@ class SmileIdModule internal constructor(context: ReactApplicationContext) :
return NAME
}

// Example method
// See https://reactnative.dev/docs/native-modules-android
@ReactMethod
override fun multiply(a: Double, b: Double, promise: Promise) {
promise.resolve(a * b)
override fun initialize(enableCrashReporting: Boolean, useSandBox: Boolean, promise: Promise) {
SmileID.initialize(
reactApplicationContext,
enableCrashReporting = enableCrashReporting,
useSandbox = useSandBox
)
promise.resolve(null)
}

@ReactMethod
override fun doEnhancedKycAsync(product: ReadableMap, promise: Promise) = launch(
work = {
val partnerParams = product.partnerParams()
partnerParams ?: run {
throw IllegalArgumentException("partnerParams is required for enhanced kyc")
}
val country = product.getStringOrDefault("country",null) ?: run {
throw IllegalArgumentException("country is required for enhanced kyc")
}
val idType = product.getStringOrDefault("idType",null) ?: run {
throw IllegalArgumentException("idType is required for enhanced kyc")
}
val idNumber = product.getStringOrDefault("idNumber",null) ?: run {
throw IllegalArgumentException("idNumber is required for enhanced kyc")
}

val timestamp = partnerParams?.extras?.get("timestamp") ?: run {
throw IllegalArgumentException("partnerParams.timestamp is required for enhanced kyc")
}
val signature = partnerParams?.extras?.get("timestamp") ?: run {
throw IllegalArgumentException("partnerParams.signature is required for enhanced kyc")
}
SmileID.api.doEnhancedKycAsync(
EnhancedKycRequest(
country = country,
idType = idType,
idNumber = idNumber,
firstName = product.getString("firstName"),
middleName = product.getString("middleName"),
lastName = product.getString("lastName"),
dob = product.getString("dob"),
phoneNumber = product.getString("phoneNumber"),
bankCode = product.getString("bankCode"),
callbackUrl = product.getString("callbackUrl"),
partnerParams = partnerParams,
sourceSdk = "android (react-native)",
timestamp = timestamp,
signature = signature,
)
)
},
promise = promise
)


private fun <T> launch(
work: suspend () -> T,
promise: Promise,
scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
) {
val handler = CoroutineExceptionHandler { _, throwable ->
promise.reject(throwable.message)
}
scope.launch(handler) {
promise.resolve(Result.success(work()))
}
}

companion object {
const val NAME = "SmileId"
const val NAME = "SmileID"
}
}
22 changes: 18 additions & 4 deletions android/src/main/java/com/smileidentity/react/SmileIdPackage.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
package com.smileidentity.react

import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.NativeModule
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.smileidentity.react.BuildConfig
import java.util.HashMap
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.uimanager.ViewManager
import com.smileidentity.react.viewmanagers.SmileIDBVNConsentViewManager
import com.smileidentity.react.viewmanagers.SmileIDBiometricKYCViewManager
import com.smileidentity.react.viewmanagers.SmileIDDocumentVerificationViewManager
import com.smileidentity.react.viewmanagers.SmileIDSmartSelfieAuthenticationViewManager
import com.smileidentity.react.viewmanagers.SmileIDSmartSelfieEnrollmentViewManager

class SmileIdPackage : TurboReactPackage() {

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
listOf(SmileIDSmartSelfieEnrollmentViewManager(reactContext),
SmileIDSmartSelfieAuthenticationViewManager(reactContext),
SmileIDDocumentVerificationViewManager(reactContext),
SmileIDBVNConsentViewManager(reactContext),
SmileIDBiometricKYCViewManager(reactContext),
)


override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return if (name == SmileIdModule.NAME) {
SmileIdModule(reactContext)
Expand Down
91 changes: 91 additions & 0 deletions android/src/main/java/com/smileidentity/react/utils/ReactUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.smileidentity.react.utils

import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import com.smileidentity.models.IdInfo
import com.smileidentity.models.JobType
import com.smileidentity.models.PartnerParams
import com.smileidentity.util.randomUserId
import timber.log.Timber

fun ReadableMap.toMap(): Map<String, String> {
val map = mutableMapOf<String, String>()
val iterator = keySetIterator()
while (iterator.hasNextKey()) {
val key = iterator.nextKey()
val value: String = when (getType(key)) {
ReadableType.Null -> null.toString()
ReadableType.Boolean -> getBoolean(key).toString()
ReadableType.Number -> getDouble(key).toString()
ReadableType.String -> getString(key)!!
ReadableType.Map -> getMap(key)?.toMap().toString()
ReadableType.Array -> getArray(key).toString()
}
map[key] = value
}
return map
}

fun ReadableMap.idInfo(): IdInfo? {
val idInfoMap = getMapOrDefault("idInfo", null)
val country = idInfoMap?.getStringOrDefault("country", null) ?: run {
Timber.e("idInfo.country is required")
return null
}
return IdInfo(
country = country,
idType = idInfoMap.getStringOrDefault("idType", null),
idNumber = idInfoMap.getStringOrDefault("idNumber", null),
firstName = idInfoMap.getStringOrDefault("firstName", null),
middleName = idInfoMap.getStringOrDefault("middleName", null),
lastName = idInfoMap.getStringOrDefault("lastName", null),
dob = idInfoMap.getStringOrDefault("dob", null),
bankCode = idInfoMap.getStringOrDefault("bankCode", null),
entered = idInfoMap.getBoolOrDefault("entered", false),
)
}


fun ReadableMap.partnerParams(): PartnerParams? {
val partnerParams = getMapOrDefault("partnerParams", null) ?: run {
Timber.w("partnerParams is required")
return null
}
val jobTypeValue = partnerParams.getIntOrDefault("jobType", null)
val jobType = if (jobTypeValue != null) JobType.fromValue(jobTypeValue) else null
return PartnerParams(
jobType = jobType,
userId = partnerParams.getStringOrDefault("userId", null) ?: run { randomUserId() },
jobId = partnerParams.getStringOrDefault("jobId", null) ?: run { randomUserId() },
extras = partnerParams.getMapOrDefault("extras", null)?.toMap() ?: run { emptyMap() },
)
}


fun ReadableMap.getBoolOrDefault(key: String, defaultValue: Boolean): Boolean {
if (hasKey(key)) {
return getBoolean(key)
}
return defaultValue
}

fun ReadableMap.getStringOrDefault(key: String, defaultValue: String?): String? {
if (hasKey(key)) {
return getString(key)
}
return defaultValue
}

fun ReadableMap.getIntOrDefault(key: String, defaultValue: Int?): Int? {
if (hasKey(key)) {
return getInt(key)
}
return defaultValue
}

fun ReadableMap.getMapOrDefault(key: String, defaultValue: ReadableMap?): ReadableMap? {
if (hasKey(key)) {
return getMap(key)
}
return defaultValue
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.smileidentity.react.viewmanagers

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.annotations.ReactProp
import com.smileidentity.react.views.SmileIDBVNConsentScreen


@ReactModule(name = SmileIDBVNConsentViewManager.NAME)
class SmileIDBVNConsentViewManager(private val reactApplicationContext: ReactApplicationContext) :
SimpleViewManager<SmileIDBVNConsentScreen>() {
override fun getName(): String {
return NAME
}

override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
return mapOf(
"onSmileResult" to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to "onResult"
)
)
)
}

@ReactProp(name = "product")
fun setProduct(view: SmileIDBVNConsentScreen, product: ReadableMap) {
view.product = product
}

override fun createViewInstance(p0: ThemedReactContext): SmileIDBVNConsentScreen {
return SmileIDBVNConsentScreen(reactApplicationContext)
}

companion object {
const val NAME = "SmileIDBVNConsentScreenView"
}

}
Loading

0 comments on commit 7425f22

Please sign in to comment.