Skip to content

Commit

Permalink
Move new SmartSelfie UI under an experimental flag (#344)
Browse files Browse the repository at this point in the history
* Move new SmartSelfie UI under an experimental flag

Also updates all internal naming to move from biometric auth to smartselfie v2

* Update naming

* Fix URL

* Update copy

* Revert deleteFilesOnSuccess
  • Loading branch information
vanshg authored Apr 11, 2024
1 parent 257adea commit b7bade6
Show file tree
Hide file tree
Showing 14 changed files with 134 additions and 255 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

* 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`
* Introduce an experimental new SmartSelfie Authentication UI, available by passing `useExperimentalUi=true` to `SmileID.SmartSelfieAuthentication` Composable/Fragment
* 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
* Fixed a bug where network retries would occasionally fail
Expand Down
76 changes: 28 additions & 48 deletions lib/src/main/java/com/smileidentity/compose/SmileIDExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ 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.selfie.v2.OrchestratedSelfieCaptureScreenV2
import com.smileidentity.compose.theme.colorScheme
import com.smileidentity.compose.theme.typography
import com.smileidentity.ml.SelfieQualityModel
Expand Down Expand Up @@ -110,6 +110,8 @@ fun SmileID.SmartSelfieEnrollment(
* @param showAttribution Whether to show the Smile ID attribution or not on the Instructions screen
* @param showInstructions Whether to deactivate capture screen's instructions for SmartSelfie.
* @param extraPartnerParams Custom values specific to partners
* @param useExperimentalUi Whether to use the new experimental UI. Note that not all parameters are
* supported in the experimental UI.
* @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
Expand All @@ -126,23 +128,36 @@ fun SmileID.SmartSelfieAuthentication(
showAttribution: Boolean = true,
showInstructions: Boolean = true,
extraPartnerParams: ImmutableMap<String, String> = persistentMapOf(),
useExperimentalUi: Boolean = false,
colorScheme: ColorScheme = SmileID.colorScheme,
typography: Typography = SmileID.typography,
onResult: SmileIDCallback<SmartSelfieResult> = {},
) {
MaterialTheme(colorScheme = colorScheme, typography = typography) {
OrchestratedSelfieCaptureScreen(
modifier = modifier,
userId = userId,
jobId = jobId,
allowNewEnroll = allowNewEnroll,
isEnroll = false,
allowAgentMode = allowAgentMode,
showAttribution = showAttribution,
showInstructions = showInstructions,
extraPartnerParams = extraPartnerParams,
onResult = onResult,
)
if (useExperimentalUi) {
val context = LocalContext.current
val selfieQualityModel = remember { SelfieQualityModel.newInstance(context) }
OrchestratedSelfieCaptureScreenV2(
userId = userId,
selfieQualityModel = selfieQualityModel,
extraPartnerParams = extraPartnerParams,
modifier = modifier,
onResult = onResult,
)
} else {
OrchestratedSelfieCaptureScreen(
modifier = modifier,
userId = userId,
jobId = jobId,
allowNewEnroll = allowNewEnroll,
isEnroll = false,
allowAgentMode = allowAgentMode,
showAttribution = showAttribution,
showInstructions = showInstructions,
extraPartnerParams = extraPartnerParams,
onResult = onResult,
)
}
}
}

Expand Down Expand Up @@ -436,38 +451,3 @@ 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,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.smileidentity.compose.biometricauthentication
package com.smileidentity.compose.selfie.v2

import android.Manifest
import android.os.OperationCanceledException
Expand Down Expand Up @@ -67,7 +67,7 @@ import com.smileidentity.results.SmartSelfieResult
import com.smileidentity.results.SmileIDCallback
import com.smileidentity.results.SmileIDResult
import com.smileidentity.util.toast
import com.smileidentity.viewmodel.BiometricAuthenticationViewModel
import com.smileidentity.viewmodel.SmartSelfieV2ViewModel
import com.ujizin.camposer.CameraPreview
import com.ujizin.camposer.state.CamSelector
import com.ujizin.camposer.state.ImplementationMode
Expand All @@ -82,15 +82,15 @@ const val DEFAULT_CUTOUT_PROPORTION = 0.8f

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun OrchestratedBiometricAuthenticationScreen(
fun OrchestratedSelfieCaptureScreenV2(
userId: String,
selfieQualityModel: SelfieQualityModel,
modifier: Modifier = Modifier,
extraPartnerParams: ImmutableMap<String, String> = persistentMapOf(),
onResult: SmileIDCallback<SmartSelfieResult> = {},
@Suppress("UNUSED_PARAMETER") viewModel: BiometricAuthenticationViewModel = viewModel(
@Suppress("UNUSED_PARAMETER") viewModel: SmartSelfieV2ViewModel = viewModel(
initializer = {
BiometricAuthenticationViewModel(
SmartSelfieV2ViewModel(
userId = userId,
extraPartnerParams = extraPartnerParams,
selfieQualityModel = selfieQualityModel,
Expand Down Expand Up @@ -123,7 +123,7 @@ fun OrchestratedBiometricAuthenticationScreen(
dismissOnClickOutside = false,
),
) {
BiometricAuthenticationScreen(
SmartSelfieV2Screen(
modifier = modifier
.height(512.dp)
.clip(MaterialTheme.shapes.large),
Expand All @@ -138,9 +138,9 @@ fun OrchestratedBiometricAuthenticationScreen(
*/
@OptIn(ExperimentalAnimationGraphicsApi::class)
@Composable
private fun BiometricAuthenticationScreen(
private fun SmartSelfieV2Screen(
modifier: Modifier = Modifier,
viewModel: BiometricAuthenticationViewModel = viewModel(),
viewModel: SmartSelfieV2ViewModel = viewModel(),
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val cameraState = rememberCameraState()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class SmartSelfieAuthenticationFragment : Fragment() {
* @param showAttribution Whether to show the Smile ID attribution or not.
* @param showInstructions Whether to show the instructions or not.
* @param extraPartnerParams Custom values specific to partners
* @param useExperimentalUi Whether to use the new experimental UI. Note that not all
* parameters are supported in the experimental UI.
*/
@JvmStatic
@JvmOverloads
Expand All @@ -95,6 +97,7 @@ class SmartSelfieAuthenticationFragment : Fragment() {
showAttribution: Boolean = true,
showInstructions: Boolean = true,
extraPartnerParams: HashMap<String, String>? = null,
useExperimentalUi: Boolean = false,
) = SmartSelfieAuthenticationFragment().apply {
arguments = Bundle().apply {
this.userId = userId
Expand All @@ -104,6 +107,7 @@ class SmartSelfieAuthenticationFragment : Fragment() {
this.showAttribution = showAttribution
this.showInstructions = showInstructions
this.extraPartnerParams = extraPartnerParams
this.useExperimentalUi = useExperimentalUi
}
}

Expand All @@ -129,6 +133,7 @@ class SmartSelfieAuthenticationFragment : Fragment() {
showAttribution = args.showAttribution,
showInstructions = args.showInstructions,
extraPartnerParams = (args.extraPartnerParams ?: mapOf()).toImmutableMap(),
useExperimentalUi = args.useExperimentalUi,
onResult = {
setFragmentResult(KEY_REQUEST, Bundle().apply { smileIdResult = it })
},
Expand Down Expand Up @@ -172,6 +177,11 @@ private var Bundle.extraPartnerParams: HashMap<String, String>?
get() = getSerializableCompat(KEY_EXTRA_PARTNER_PARAMS)
set(value) = putSerializable(KEY_EXTRA_PARTNER_PARAMS, value)

private const val KEY_USE_EXPERIMENTAL_UI = "useExperimentalUi"
private var Bundle.useExperimentalUi: Boolean
get() = getBoolean(KEY_USE_EXPERIMENTAL_UI)
set(value) = putBoolean(KEY_USE_EXPERIMENTAL_UI, value)

private var Bundle.smileIdResult: SmileIDResult<SmartSelfieResult>
get() = getParcelableCompat(KEY_RESULT)!!
set(value) = putParcelable(KEY_RESULT, value)
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ interface SmileIDService {
@SmileHeaderMetadata
@SmileIDOptIn
@Multipart
@POST("/v2/smart-selfie-enrollment")
@POST("/v2/smart-selfie-enroll")
suspend fun doSmartSelfieEnrollment(
@Part selfieImage: MultipartBody.Part,
@Part livenessImages: List<@JvmSuppressWildcards MultipartBody.Part>,
Expand Down
4 changes: 2 additions & 2 deletions lib/src/main/java/com/smileidentity/util/FileUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ internal fun cleanupJobs(
* pending jobs, or both, depending on the scope provided. If job IDs are specified, the cleanup is restricted to those
* specific jobs; otherwise, it applies to all jobs within the specified scope.
*
* @param scope The scope of the cleanup operation, determined by the DeleteScope enum. The default is DeleteScope.CompletedJobs,
* indicating that, by default, only completed jobs are targeted for cleanup. This parameter can be adjusted to target
* @param scope The scope of the cleanup operation, determined by the DeleteScope enum. The default is DeleteScope.All,
* indicating that, by default, all jobs are targeted for cleanup. This parameter can be adjusted to target
* pending jobs or both pending and completed jobs, offering flexibility in how cleanup operations are conducted.
* @param jobIds An optional list of job IDs to specifically target for cleanup. If provided, only the files associated
* with these job IDs will be cleaned up, within the bounds of the specified scope. If null or not provided, the cleanup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ enum class SelfieHint(@DrawableRes val animation: Int) {
NeedLight(R.drawable.si_tf_light_flash),
}

data class BiometricAuthenticationUiState(
data class SmartSelfieV2UiState(
val backgroundOpacity: Float = 0.8f,
val cutoutOpacity: Float = 0.8f,
val showBorderHighlight: Boolean = false,
Expand All @@ -75,7 +75,7 @@ data class BiometricAuthenticationUiState(
)

@kotlin.OptIn(FlowPreview::class)
class BiometricAuthenticationViewModel(
class SmartSelfieV2ViewModel(
private val userId: String,
private val extraPartnerParams: ImmutableMap<String, String> = persistentMapOf(),
private val selfieQualityModel: SelfieQualityModel,
Expand All @@ -89,11 +89,11 @@ class BiometricAuthenticationViewModel(
),
private val onResult: SmileIDCallback<SmartSelfieResult>,
) : ViewModel() {
private val _uiState = MutableStateFlow(BiometricAuthenticationUiState())
private val _uiState = MutableStateFlow(SmartSelfieV2UiState())
val uiState = _uiState.asStateFlow().sample(250).stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(),
BiometricAuthenticationUiState(),
SmartSelfieV2UiState(),
)
private val livenessFiles = mutableListOf<File>()
private var selfieFile: File? = null
Expand Down
Loading

0 comments on commit b7bade6

Please sign in to comment.