Skip to content

Commit

Permalink
Add new SmartSelfie UI (#337)
Browse files Browse the repository at this point in the history
* Selfie Quality Model

* Optimized model

* Maintain running average

* Add new authentication UI (#327)

* wip

* wip

* wip

* back button behavior

* bumped versions

* changes elsewhere

* Fix extraneous gzipping

* Everything wired up and some animations

* small fixes

* fix smells

* fix nav bar bug

* add animation and bump compose

* add face searching animation

* Add luminance check and animation

* make calculateLuminance way faster

* Remove commented logs

* compress avd with avocado

* tighten head pose thresholds

* remove completed todo

* add another sentry breadcrumb

* Rename to Biometric Authentication

* fix jobs list screen bug

* jobs list fix animation and make value selectable

* minor ui fix

* simplify

* add selfie quality model

* typos

* add loading indicator delay

* move to correct package

* bump versions

* add v2 models

* add new interceptors

* make callbackUrl nullable

* add fragment

* update to new endpoint

* add helper function

* remove erroneous todo

* Fix merge

* add v2 enroll endpoint

* Update CHANGELOG

* Update SmartSelfie with synchronous endpoints (#336)

* Use new synchronous endpoints

* Fix tests

* Bump Compose version

* Bump Gradle version

* Dynamic jobSuccess
  • Loading branch information
vanshg authored Apr 8, 2024
1 parent df790d1 commit b7a7c3e
Show file tree
Hide file tree
Showing 50 changed files with 1,561 additions and 302 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Release Notes

## Unreleased
## 10.1.0

* Bump Compose BOM to 2024.02.00
* Added an Offline Mode, enabled by calling `SmileID.setAllowOfflineMode(true)`. If a job is attempted while the device is offline, and offline mode has been enabled, the UI will complete successfully and the job can be submitted at a later time by calling `SmileID.submitJob(jobId)`
* Improved SmartSelfie Enrollment and Authentication times by moving to a synchronous API endpoint
* Introduce an experimental new SmartSelfie Authentication UI, available under the `SmileID.BiometricAuthentication` Composable and `BiometricAuthenticationFragment`
* Made `KEY_RESULT` constants in `Fragment`s `internal` to remove a footgun where the constant was easily confused with `KEY_REQUEST`
* Improved back button behavior on image confirmation and processing dialogs
* Bump Compose BOM to 2024.04.00
* Fixed a bug where network retries would occasionally fail

## 10.0.4
Expand Down
32 changes: 19 additions & 13 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
accompanist-permissions = "0.34.0"
android-gradle-plugin = "8.2.2"
androidx-activity = "1.8.2"
# TODO: Check if https://android-review.googlesource.com/c/platform/frameworks/support/+/2576871 has
# been merged and if so, swap the buttons in ImageCaptureConfirmationDialog
androidx-compose-bom = "2024.02.00"
androidx-compose-compiler = "1.5.9"
androidx-compose-bom = "2024.04.00"
androidx-compose-compiler = "1.5.11"
androidx-core = "1.12.0"
androidx-core-splashscreen = "1.0.1"
androidx-fragment = "1.6.2"
Expand All @@ -18,29 +16,32 @@ androidx-test-junit = "1.1.5"
androidx-test-rules = "1.5.0"
camposer = "0.4.0"
chucker = "4.0.0"
coil = "2.5.0"
coil = "2.6.0"
compose-lint-checks = "1.3.1"
coroutines = "1.8.0"
datastore = "1.0.0"
junit = "4.13.2"
kotlin = "1.9.22"
kotlin = "1.9.23"
kotlin-immutable-collections = "0.3.7"
ksp = "1.9.22-1.0.17"
ksp = "1.9.23-1.0.19"
ktlint-plugin = "12.1.0"
leakcanary = "2.13"
maven-publish = "0.27.0"
maven-publish = "0.28.0"
mlkit-code-scanner = "16.1.0"
mlkit-obj-detection = "17.0.0"
mockk = "1.13.9"
mlkit-obj-detection = "17.0.1"
mockk = "1.13.10"
moshi = "1.15.1"
moshix = "0.25.1"
moshi-lazy-adapters = "2.2"
okhttp = "4.12.0"
play-services-mlkit-face-detection = "17.1.0"
retrofit = "2.9.0"
sentry = "7.3.0"
retrofit = "2.11.0"
sentry = "7.6.0"
tflite = "2.14.0"
tflite-metadata = "0.4.4"
tflite-support = "0.4.4"
timber = "5.0.1"
truth = "1.4.1"
truth = "1.4.2"
uiautomator = "2.3.0"

[plugins]
Expand All @@ -57,6 +58,7 @@ parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotli
[libraries]
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist-permissions" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
androidx-compose-animation = { module = "androidx.compose.animation:animation-graphics" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "androidx-compose-bom" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
Expand Down Expand Up @@ -102,8 +104,12 @@ okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.
play-services-mlkit-face-detection = { module = "com.google.android.gms:play-services-mlkit-face-detection", version.ref = "play-services-mlkit-face-detection" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
retrofit-converter-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
sentry = { module = "io.sentry:sentry" }
sentry-bom = { module = "io.sentry:sentry-bom", version.ref = "sentry" }
tflite = { module = "org.tensorflow:tensorflow-lite", version.ref = "tflite" }
tflite-metadata = { group = "org.tensorflow", name = "tensorflow-lite-metadata", version.ref = "tflite-metadata" }
tflite-support = { group = "org.tensorflow", name = "tensorflow-lite-support", version.ref = "tflite-support" }
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
truth = { module = "com.google.truth:truth", version.ref = "truth" }
uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiautomator" }
2 changes: 1 addition & 1 deletion 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.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
7 changes: 7 additions & 0 deletions lib/lib.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ android {
buildFeatures {
compose = true
buildConfig = true
mlModelBinding = true
}

composeOptions {
Expand Down Expand Up @@ -147,6 +148,7 @@ dependencies {
api(libs.okhttp)
implementation(libs.retrofit)
implementation(libs.retrofit.converter.moshi)
implementation(libs.retrofit.converter.scalars)
implementation(libs.okhttp.logging.interceptor)

// Moshi is exposed in public SmileID interface, hence "api" vs "implementation"
Expand Down Expand Up @@ -187,6 +189,7 @@ dependencies {
// Jetpack Compose UI
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.text.google.fonts)
implementation(libs.androidx.compose.animation)
// Android Studio Preview support
implementation(libs.androidx.compose.ui.tooling.preview)
// Android Studio Preview support
Expand All @@ -206,6 +209,10 @@ dependencies {
// Bundled model
implementation(libs.mlkit.obj.detection)

implementation(libs.tflite)
implementation(libs.tflite.metadata)
implementation(libs.tflite.support)

testImplementation(libs.junit)
testImplementation(libs.okhttp.mockwebserver)
testImplementation(libs.coroutines.test)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class ImageCaptureConfirmationDialogTest {
val composeTestRule = createComposeRule()

@Test
@Suppress("MoveLambdaOutsideParentheses")
fun shouldInvokeConfirmationCallbackOnConfirmButtonClick() {
// given
var callbackInvoked = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class DocumentCaptureScreenTest {
composeTestRule.setContent {
permissionState = rememberPermissionState(Manifest.permission.CAMERA)
DocumentCaptureScreen(
jobId = "jobId",
side = DocumentCaptureSide.Front,
showInstructions = true,
showAttribution = true,
Expand Down Expand Up @@ -70,6 +71,7 @@ class DocumentCaptureScreenTest {
// when
composeTestRule.setContent {
DocumentCaptureScreen(
jobId = "jobId",
side = DocumentCaptureSide.Front,
showInstructions = true,
showAttribution = true,
Expand Down
13 changes: 10 additions & 3 deletions lib/src/main/java/com/smileidentity/SmileID.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ import com.smileidentity.models.UploadRequest
import com.smileidentity.networking.BiometricKycJobResultAdapter
import com.smileidentity.networking.DocumentVerificationJobResultAdapter
import com.smileidentity.networking.EnhancedDocumentVerificationJobResultAdapter
import com.smileidentity.networking.FileAdapter
import com.smileidentity.networking.FileNameAdapter
import com.smileidentity.networking.GzipRequestInterceptor
import com.smileidentity.networking.JobResultAdapter
import com.smileidentity.networking.JobTypeAdapter
import com.smileidentity.networking.PartnerParamsAdapter
import com.smileidentity.networking.SmartSelfieJobResultAdapter
import com.smileidentity.networking.SmileHeaderAuthInterceptor
import com.smileidentity.networking.SmileHeaderMetadataInterceptor
import com.smileidentity.networking.SmileIDService
import com.smileidentity.networking.StringifiedBooleanAdapter
import com.smileidentity.networking.UploadRequestConverterFactory
Expand Down Expand Up @@ -56,6 +58,7 @@ import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import timber.log.Timber

@Suppress("unused")
Expand Down Expand Up @@ -123,6 +126,8 @@ object SmileID {
.baseUrl(url)
.client(okHttpClient)
.addConverterFactory(UploadRequestConverterFactory)
// Needed for String form data. Otherwise the Moshi adapter adds extraneous quotations
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()

Expand Down Expand Up @@ -273,7 +278,7 @@ object SmileID {
@JvmStatic
suspend fun submitJob(
jobId: String,
deleteFilesOnSuccess: Boolean,
deleteFilesOnSuccess: Boolean = true,
scope: CoroutineScope = CoroutineScope(Dispatchers.IO),
exceptionHandler: ((Throwable) -> Unit)? = null,
): Job = scope.launch(
Expand Down Expand Up @@ -399,6 +404,8 @@ object SmileID {
connectTimeout(30, TimeUnit.SECONDS)
readTimeout(30, TimeUnit.SECONDS)
writeTimeout(30, TimeUnit.SECONDS)
addInterceptor(SmileHeaderAuthInterceptor)
addInterceptor(SmileHeaderMetadataInterceptor)
addInterceptor(
Interceptor { chain: Interceptor.Chain ->
// Retry on exception (network error) and 5xx
Expand Down Expand Up @@ -451,7 +458,7 @@ object SmileID {
.add(JobTypeAdapter)
.add(PartnerParamsAdapter)
.add(StringifiedBooleanAdapter)
.add(FileAdapter)
.add(FileNameAdapter)
.add(SmartSelfieJobResultAdapter)
.add(DocumentVerificationJobResultAdapter)
.add(BiometricKycJobResultAdapter)
Expand Down
39 changes: 39 additions & 0 deletions lib/src/main/java/com/smileidentity/compose/SmileIDExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@ import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Typography
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import com.smileidentity.SmileID
import com.smileidentity.compose.biometric.OrchestratedBiometricKYCScreen
import com.smileidentity.compose.biometricauthentication.OrchestratedBiometricAuthenticationScreen
import com.smileidentity.compose.consent.OrchestratedConsentScreen
import com.smileidentity.compose.consent.bvn.OrchestratedBvnConsentScreen
import com.smileidentity.compose.document.OrchestratedDocumentVerificationScreen
import com.smileidentity.compose.selfie.OrchestratedSelfieCaptureScreen
import com.smileidentity.compose.theme.colorScheme
import com.smileidentity.compose.theme.typography
import com.smileidentity.ml.SelfieQualityModel
import com.smileidentity.models.IdInfo
import com.smileidentity.models.JobType
import com.smileidentity.results.BiometricKycResult
Expand Down Expand Up @@ -432,3 +436,38 @@ fun SmileID.ConsentScreen(
)
}
}

/**
* Perform a Biometric Authentication
*
* @param userId The user ID to authenticate with the Biometric Authentication. This should be
* an ID that was previously registered via a SmartSelfie™ Enrollment
* @param modifier The modifier to apply to the layout
* @param extraPartnerParams Custom values specific to partners
* @param colorScheme The color scheme to use for the UI. This is passed in so that we show a Smile
* ID branded UI by default, but allow the user to override it if they want.
* @param typography The typography to use for the UI. This is passed in so that we show a Smile ID
* branded UI by default, but allow the user to override it if they want.
* @param onResult Callback to be invoked when the Biometric Authentication is complete.
*/
@Composable
fun SmileID.BiometricAuthentication(
userId: String,
modifier: Modifier = Modifier,
extraPartnerParams: ImmutableMap<String, String> = persistentMapOf(),
colorScheme: ColorScheme = SmileID.colorScheme,
typography: Typography = SmileID.typography,
onResult: SmileIDCallback<SmartSelfieResult>,
) {
val context = LocalContext.current
val selfieQualityModel = remember { SelfieQualityModel.newInstance(context) }
MaterialTheme(colorScheme = colorScheme, typography = typography) {
OrchestratedBiometricAuthenticationScreen(
userId = userId,
selfieQualityModel = selfieQualityModel,
extraPartnerParams = extraPartnerParams,
modifier = modifier,
onResult = onResult,
)
}
}
Loading

0 comments on commit b7a7c3e

Please sign in to comment.