diff --git a/identity/detekt-baseline.xml b/identity/detekt-baseline.xml
index 3a021ca0a6d..cf155de747a 100644
--- a/identity/detekt-baseline.xml
+++ b/identity/detekt-baseline.xml
@@ -3,28 +3,28 @@
CyclomaticComplexMethod:CameraScreenLaunchedEffect.kt$@Composable internal fun CameraScreenLaunchedEffect( identityViewModel: IdentityViewModel, identityScanViewModel: IdentityScanViewModel, verificationPage: VerificationPage, navController: NavController, cameraManager: IdentityCameraManager, onCameraReady: () -> Unit )
- CyclomaticComplexMethod:DocumenetScanScreen.kt$@Composable internal fun DocumentScanScreen( navController: NavController, identityViewModel: IdentityViewModel, identityScanViewModel: IdentityScanViewModel, frontScanType: IdentityScanState.ScanType, backScanType: IdentityScanState.ScanType?, shouldStartFromBack: Boolean, messageRes: DocumentScanMessageRes, collectedDataParamType: CollectedDataParam.Type, route: String )
CyclomaticComplexMethod:IdentityTopLevelDestination.kt$internal fun String.routeToScreenName(): String
- CyclomaticComplexMethod:IdentityViewModel.kt$IdentityViewModel$fun updateNewScanType(scanType: IdentityScanState.ScanType)
CyclomaticComplexMethod:OTPScreen.kt$@Composable internal fun OTPScreen( navController: NavController, identityViewModel: IdentityViewModel, otpViewModelFactory: ViewModelProvider.Factory = OTPViewModel.Factory( identityRepository = identityViewModel.identityRepository, verificationArgs = identityViewModel.verificationArgs ) )
- CyclomaticComplexMethod:UploadScreen.kt$@Composable internal fun UploadScreen( navController: NavController, identityViewModel: IdentityViewModel, collectedDataParamType: CollectedDataParam.Type, route: String, @StringRes titleRes: Int, @StringRes contextRes: Int, frontInfo: DocumentUploadSideInfo, backInfo: DocumentUploadSideInfo? )
+ CyclomaticComplexMethod:UploadScreen.kt$@Composable internal fun UploadScreen( navController: NavController, identityViewModel: IdentityViewModel, )
LargeClass:IdentityViewModel.kt$IdentityViewModel : AndroidViewModel
LargeClass:IdentityViewModelTest.kt$IdentityViewModelTest
LongMethod:AddressSection.kt$@Composable internal fun AddressSection( enabled: Boolean, identityViewModel: IdentityViewModel, addressCountries: List<Country>, addressNotListedText: String, navController: NavController, onAddressCollected: (Resource<RequiredInternationalAddress>) -> Unit )
LongMethod:CameraScreenLaunchedEffect.kt$@Composable internal fun CameraScreenLaunchedEffect( identityViewModel: IdentityViewModel, identityScanViewModel: IdentityScanViewModel, verificationPage: VerificationPage, navController: NavController, cameraManager: IdentityCameraManager, onCameraReady: () -> Unit )
+ LongMethod:CameraScreenLaunchedEffectLight.kt$@Composable internal fun CameraScreenLaunchedEffectLight( identityViewModel: IdentityViewModel, identityScanViewModel: IdentityScanViewModel, verificationPage: VerificationPage, navController: NavController )
LongMethod:ConfirmationScreen.kt$@Composable internal fun ConfirmationScreen( navController: NavController, identityViewModel: IdentityViewModel, verificationFlowFinishable: VerificationFlowFinishable )
LongMethod:ConsentScreen.kt$@Composable private fun SuccessUI( merchantLogoUri: Uri, consentPage: VerificationPageStaticContentConsentPage, bottomSheets: Map<String, VerificationPageStaticContentBottomSheetContent>?, visitedIndividualWelcomePage: Boolean, onConsentAgreed: () -> Unit, onConsentDeclined: () -> Unit )
LongMethod:ConsentWelcomeHeader.kt$@Composable internal fun ConsentWelcomeHeader( modifier: Modifier = Modifier, merchantLogoUri: Uri, title: String?, showLogos: Boolean = true )
LongMethod:DebugScreen.kt$@Composable internal fun CompleteWithTestDataSection( onClickSubmit: (CompleteOption) -> Unit )
LongMethod:DebugScreen.kt$@Composable internal fun DebugScreen( navController: NavController, identityViewModel: IdentityViewModel, verificationFlowFinishable: VerificationFlowFinishable )
LongMethod:DocSelectionScreen.kt$@Composable internal fun DocSelectionScreen( navController: NavController, identityViewModel: IdentityViewModel, cameraPermissionEnsureable: CameraPermissionEnsureable )
- LongMethod:DocumenetScanScreen.kt$@Composable internal fun DocumentScanScreen( navController: NavController, identityViewModel: IdentityViewModel, identityScanViewModel: IdentityScanViewModel, frontScanType: IdentityScanState.ScanType, backScanType: IdentityScanState.ScanType?, shouldStartFromBack: Boolean, messageRes: DocumentScanMessageRes, collectedDataParamType: CollectedDataParam.Type, route: String )
+ LongMethod:DocumentScanScreen.kt$@Composable internal fun DocumentScanScreen( navController: NavController, identityViewModel: IdentityViewModel, identityScanViewModel: IdentityScanViewModel )
+ LongMethod:DocumentScanScreen.kt$@Composable private fun ScanScreen( newDisplayState: IdentityScanState?, documentScannerState: IdentityScanViewModel.State, targetScanType: IdentityScanState.ScanType?, identityScanViewModel: IdentityScanViewModel, identityViewModel: IdentityViewModel, lifecycleOwner: LifecycleOwner, cameraManager: IdentityCameraManager, onContinueClick: () -> Unit )
LongMethod:ErrorScreen.kt$@Composable internal fun ErrorScreen( identityViewModel: IdentityViewModel, title: String, modifier: Modifier = Modifier, message1: String? = null, message2: String? = null, topButton: ErrorScreenButton? = null, bottomButton: ErrorScreenButton? = null, )
LongMethod:IDDetectorAnalyzer.kt$IDDetectorAnalyzer$override suspend fun analyze( data: AnalyzerInput, state: IdentityScanState ): AnalyzerOutput
LongMethod:IDNumberSection.kt$@Composable internal fun IDNumberSection( enabled: Boolean, idNumberCountries: List<Country>, countryNotListedText: String, navController: NavController, onIdNumberCollected: (Resource<IdNumberParam>) -> Unit )
LongMethod:IdentityActivity.kt$IdentityActivity$@ExperimentalMaterialApi override fun onCreate(savedInstanceState: Bundle?)
LongMethod:IdentityNavGraph.kt$@Composable @ExperimentalMaterialApi internal fun IdentityNavGraph( navController: NavHostController = rememberNavController(), identityViewModel: IdentityViewModel, fallbackUrlLauncher: FallbackUrlLauncher, appSettingsOpenable: AppSettingsOpenable, cameraPermissionEnsureable: CameraPermissionEnsureable, verificationFlowFinishable: VerificationFlowFinishable, identityScanViewModelFactory: IdentityScanViewModel.IdentityScanViewModelFactory, onTopBarNavigationClick: () -> Unit, topBarState: IdentityTopBarState, onNavControllerCreated: (NavController) -> Unit )
- LongMethod:IdentityViewModel.kt$IdentityViewModel$internal fun uploadScanResult( result: IdentityAggregator.FinalResult, verificationPage: VerificationPage, targetScanType: IdentityScanState.ScanType? )
+ LongMethod:IdentityViewModel.kt$IdentityViewModel$internal fun uploadScanResult( result: IdentityAggregator.FinalResult, verificationPage: VerificationPage )
LongMethod:IdentityViewModel.kt$IdentityViewModel$private fun uploadDocumentImagesAndNotify( imageFile: File, filePurpose: StripeFilePurpose, uploadMethod: UploadMethod, scores: List<Float>? = null, isHighRes: Boolean, isFront: Boolean, scanType: IdentityScanState.ScanType, compressionQuality: Float )
LongMethod:IdentityViewModel.kt$IdentityViewModel$suspend fun postVerificationPageDataForDocSelection( type: CollectedDataParam.Type, navController: NavController, viewLifecycleOwner: LifecycleOwner, cameraPermissionEnsureable: CameraPermissionEnsureable )
LongMethod:IdentityViewModelTest.kt$IdentityViewModelTest$private fun testUploadDocumentScanSuccessResult(isFront: Boolean)
@@ -35,7 +35,7 @@
LongMethod:OTPScreen.kt$@Composable private fun OTPViewStateEffect( viewState: OTPViewState?, navController: NavController, identityViewModel: IdentityViewModel, viewModel: OTPViewModel, focusRequester: FocusRequester )
LongMethod:SelfieScreen.kt$@Composable internal fun SelfieScanScreen( navController: NavController, identityViewModel: IdentityViewModel, identityScanViewModel: IdentityScanViewModel, )
LongMethod:SelfieScreen.kt$@Composable private fun ResultView( displayState: IdentityScanState, allowImageCollectionHtml: String, isSubmittingSelfie: Boolean, allowImageCollection: Boolean, navController: NavController, onAllowImageCollectionChanged: (Boolean) -> Unit )
- LongMethod:UploadScreen.kt$@Composable internal fun UploadScreen( navController: NavController, identityViewModel: IdentityViewModel, collectedDataParamType: CollectedDataParam.Type, route: String, @StringRes titleRes: Int, @StringRes contextRes: Int, frontInfo: DocumentUploadSideInfo, backInfo: DocumentUploadSideInfo? )
+ LongMethod:UploadScreen.kt$@Composable internal fun UploadScreen( navController: NavController, identityViewModel: IdentityViewModel, )
MagicNumber:DefaultIdentityIO.kt$DefaultIdentityIO$100
MagicNumber:DefaultIdentityIO.kt$DefaultIdentityIO$5
MagicNumber:DobParam.kt$DobParam.Companion$4
diff --git a/identity/src/main/java/com/stripe/android/identity/analytics/IdentityAnalyticsRequestFactory.kt b/identity/src/main/java/com/stripe/android/identity/analytics/IdentityAnalyticsRequestFactory.kt
index 71a3e9b7b1e..e2355d43297 100644
--- a/identity/src/main/java/com/stripe/android/identity/analytics/IdentityAnalyticsRequestFactory.kt
+++ b/identity/src/main/java/com/stripe/android/identity/analytics/IdentityAnalyticsRequestFactory.kt
@@ -156,13 +156,8 @@ internal class IdentityAnalyticsRequestFactory @Inject constructor(
)
)
- fun cameraPermissionGranted(
- scanType: IdentityScanState.ScanType
- ) = requestFactory.createRequest(
- eventName = EVENT_CAMERA_PERMISSION_GRANTED,
- additionalParams = additionalParamWithEventMetadata(
- PARAM_SCAN_TYPE to scanType.toParam()
- )
+ fun cameraPermissionGranted() = requestFactory.createRequest(
+ eventName = EVENT_CAMERA_PERMISSION_GRANTED
)
fun documentTimeout(
@@ -247,21 +242,15 @@ internal class IdentityAnalyticsRequestFactory @Inject constructor(
private fun IdentityScanState.ScanType.toParam(): String =
when (this) {
- IdentityScanState.ScanType.ID_FRONT -> ID
- IdentityScanState.ScanType.ID_BACK -> ID
- IdentityScanState.ScanType.PASSPORT -> PASSPORT
- IdentityScanState.ScanType.DL_FRONT -> DRIVER_LICENSE
- IdentityScanState.ScanType.DL_BACK -> DRIVER_LICENSE
+ IdentityScanState.ScanType.DOC_FRONT -> DOC_FRONT
+ IdentityScanState.ScanType.DOC_BACK -> DOC_BACK
IdentityScanState.ScanType.SELFIE -> SELFIE
}
private fun IdentityScanState.ScanType.toSide(): String =
when (this) {
- IdentityScanState.ScanType.ID_FRONT -> FRONT
- IdentityScanState.ScanType.ID_BACK -> BACK
- IdentityScanState.ScanType.PASSPORT -> FRONT
- IdentityScanState.ScanType.DL_FRONT -> FRONT
- IdentityScanState.ScanType.DL_BACK -> BACK
+ IdentityScanState.ScanType.DOC_FRONT -> FRONT
+ IdentityScanState.ScanType.DOC_BACK -> BACK
else -> {
throw IllegalArgumentException("Unknown type: $this")
}
@@ -271,8 +260,8 @@ internal class IdentityAnalyticsRequestFactory @Inject constructor(
const val CLIENT_ID = "mobile-identity-sdk"
const val ORIGIN = "stripe-identity-android"
const val ID = "id"
- const val PASSPORT = "passport"
- const val DRIVER_LICENSE = "driver_license"
+ const val DOC_FRONT = "doc_front"
+ const val DOC_BACK = "doc_front"
const val SELFIE = "selfie"
const val FRONT = "front"
const val BACK = "back"
@@ -334,14 +323,10 @@ internal class IdentityAnalyticsRequestFactory @Inject constructor(
const val SCREEN_NAME_CONSENT = "consent"
const val SCREEN_NAME_DOC_SELECT = "document_select"
- const val SCREEN_NAME_LIVE_CAPTURE_PASSPORT = "live_capture_passport"
- const val SCREEN_NAME_LIVE_CAPTURE_ID = "live_capture_id"
- const val SCREEN_NAME_LIVE_CAPTURE_DRIVER_LICENSE = "live_capture_driver_license"
- const val SCREEN_NAME_FILE_UPLOAD_PASSPORT = "file_upload_passport"
- const val SCREEN_NAME_FILE_UPLOAD_ID = "file_upload_id"
- const val SCREEN_NAME_FILE_UPLOAD_DRIVER_LICENSE = "file_upload_driver_license"
const val SCREEN_NAME_SELFIE_WARMUP = "selfie_warmup"
const val SCREEN_NAME_SELFIE = "selfie"
+ const val SCREEN_NAME_LIVE_CAPTURE = "live_capture"
+ const val SCREEN_NAME_FILE_UPLOAD = "file_upload"
const val SCREEN_NAME_CONFIRMATION = "confirmation"
const val SCREEN_NAME_ERROR = "error"
const val SCREEN_NAME_INDIVIDUAL = "individual"
diff --git a/identity/src/main/java/com/stripe/android/identity/navigation/CouldNotCaptureDestination.kt b/identity/src/main/java/com/stripe/android/identity/navigation/CouldNotCaptureDestination.kt
index 5d0440a91f2..866c32645c1 100644
--- a/identity/src/main/java/com/stripe/android/identity/navigation/CouldNotCaptureDestination.kt
+++ b/identity/src/main/java/com/stripe/android/identity/navigation/CouldNotCaptureDestination.kt
@@ -3,42 +3,30 @@ package com.stripe.android.identity.navigation
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavType
import androidx.navigation.navArgument
-import com.stripe.android.identity.states.IdentityScanState
internal class CouldNotCaptureDestination(
- scanType: IdentityScanState.ScanType,
- requireLiveCapture: Boolean
+ fromSelfie: Boolean
) : IdentityTopLevelDestination() {
override val destinationRoute = ROUTE
override val routeWithArgs = destinationRoute.withParams(
- ARG_COULD_NOT_CAPTURE_SCAN_TYPE to scanType,
- ARG_REQUIRE_LIVE_CAPTURE to requireLiveCapture
+ ARG_FROM_SELFIE to fromSelfie
)
companion object {
const val COULD_NOT_CAPTURE = "CouldNotCapture"
- const val ARG_COULD_NOT_CAPTURE_SCAN_TYPE = "scanType"
- const val ARG_REQUIRE_LIVE_CAPTURE = "requireLiveCapture"
+ const val ARG_FROM_SELFIE = "fromSelfie"
val ROUTE = object : DestinationRoute() {
override val routeBase = COULD_NOT_CAPTURE
override val arguments = listOf(
- navArgument(ARG_COULD_NOT_CAPTURE_SCAN_TYPE) {
- type = NavType.EnumType(IdentityScanState.ScanType::class.java)
- },
- navArgument(ARG_REQUIRE_LIVE_CAPTURE) {
+ navArgument(ARG_FROM_SELFIE) {
type = NavType.BoolType
}
)
}
- fun couldNotCaptureScanType(backStackEntry: NavBackStackEntry) =
- backStackEntry.arguments?.getSerializable(
- ARG_COULD_NOT_CAPTURE_SCAN_TYPE
- ) as IdentityScanState.ScanType
-
- fun requireLiveCapture(backStackEntry: NavBackStackEntry) =
- backStackEntry.getBooleanArgument(ARG_REQUIRE_LIVE_CAPTURE)
+ fun fromSelfie(backStackEntry: NavBackStackEntry) =
+ backStackEntry.getBooleanArgument(ARG_FROM_SELFIE)
}
}
diff --git a/identity/src/main/java/com/stripe/android/identity/navigation/DocumentUploadDestination.kt b/identity/src/main/java/com/stripe/android/identity/navigation/DocumentUploadDestination.kt
new file mode 100644
index 00000000000..3e4e2488a88
--- /dev/null
+++ b/identity/src/main/java/com/stripe/android/identity/navigation/DocumentUploadDestination.kt
@@ -0,0 +1,14 @@
+package com.stripe.android.identity.navigation
+
+internal object DocumentUploadDestination : IdentityTopLevelDestination(
+ popUpToParam = PopUpToParam(
+ route = DocSelectionDestination.ROUTE.route,
+ inclusive = false
+ )
+) {
+ val ROUTE = object : DestinationRoute() {
+ override val routeBase = UPLOAD
+ }
+
+ override val destinationRoute = ROUTE
+}
diff --git a/identity/src/main/java/com/stripe/android/identity/navigation/IdentityNavGraph.kt b/identity/src/main/java/com/stripe/android/identity/navigation/IdentityNavGraph.kt
index dfc7bb2195e..9465de2cbc3 100644
--- a/identity/src/main/java/com/stripe/android/identity/navigation/IdentityNavGraph.kt
+++ b/identity/src/main/java/com/stripe/android/identity/navigation/IdentityNavGraph.kt
@@ -35,19 +35,13 @@ import com.stripe.android.identity.VerificationFlowFinishable
import com.stripe.android.identity.analytics.IdentityAnalyticsRequestFactory
import com.stripe.android.identity.networking.models.CollectedDataParam
import com.stripe.android.identity.networking.models.CollectedDataParam.Companion.getDisplayName
-import com.stripe.android.identity.networking.models.CollectedDataParam.Companion.toUploadDestination
-import com.stripe.android.identity.states.IdentityScanState
-import com.stripe.android.identity.states.IdentityScanState.Companion.toScanDestination
-import com.stripe.android.identity.states.IdentityScanState.Companion.toUploadDestination
import com.stripe.android.identity.ui.BottomSheet
import com.stripe.android.identity.ui.ConfirmationScreen
import com.stripe.android.identity.ui.ConsentScreen
import com.stripe.android.identity.ui.CountryNotListedScreen
import com.stripe.android.identity.ui.DebugScreen
import com.stripe.android.identity.ui.DocSelectionScreen
-import com.stripe.android.identity.ui.DocumentScanMessageRes
import com.stripe.android.identity.ui.DocumentScanScreen
-import com.stripe.android.identity.ui.DocumentUploadSideInfo
import com.stripe.android.identity.ui.ErrorScreen
import com.stripe.android.identity.ui.ErrorScreenButton
import com.stripe.android.identity.ui.IdentityTopAppBar
@@ -128,50 +122,18 @@ internal fun IdentityNavGraph(
cameraPermissionEnsureable = cameraPermissionEnsureable
)
}
- screen(IDScanDestination.ROUTE) {
+ screen(DocumentScanDestination.ROUTE) {
val identityScanViewModel: IdentityScanViewModel =
viewModel(factory = identityScanViewModelFactory)
ScanDestinationEffect(
lifecycleOwner = it,
identityScanViewModel = identityScanViewModel
)
- DocumentScanScreenContent(
+ DocumentScanScreen(
navController = navController,
identityViewModel = identityViewModel,
- identityScanViewModel = identityScanViewModel,
- backStackEntry = it,
- route = IDScanDestination.ROUTE.route
- )
- }
- screen(DriverLicenseScanDestination.ROUTE) {
- val identityScanViewModel: IdentityScanViewModel =
- viewModel(factory = identityScanViewModelFactory)
- ScanDestinationEffect(
- lifecycleOwner = it,
identityScanViewModel = identityScanViewModel
)
- DocumentScanScreenContent(
- navController = navController,
- identityViewModel = identityViewModel,
- identityScanViewModel = identityScanViewModel,
- backStackEntry = it,
- route = DriverLicenseScanDestination.ROUTE.route
- )
- }
- screen(PassportScanDestination.ROUTE) {
- val identityScanViewModel: IdentityScanViewModel =
- viewModel(factory = identityScanViewModelFactory)
- ScanDestinationEffect(
- lifecycleOwner = it,
- identityScanViewModel = identityScanViewModel
- )
- DocumentScanScreenContent(
- navController = navController,
- identityViewModel = identityViewModel,
- identityScanViewModel = identityScanViewModel,
- backStackEntry = it,
- route = PassportScanDestination.ROUTE.route
- )
}
screen(SelfieWarmupDestination.ROUTE) {
SelfieWarmupScreen(
@@ -192,47 +154,10 @@ internal fun IdentityNavGraph(
identityScanViewModel = identityScanViewModel
)
}
- screen(IDUploadDestination.ROUTE) {
- LaunchedEffect(Unit) {
- identityViewModel.updateImageHandlerScanTypes(
- IdentityScanState.ScanType.ID_FRONT,
- IdentityScanState.ScanType.ID_BACK
- )
- }
- DocumentUploadScreenContent(
- navController = navController,
- identityViewModel = identityViewModel,
- backStackEntry = it,
- route = IDUploadDestination.ROUTE.route
- )
- }
- screen(DriverLicenseUploadDestination.ROUTE) {
- LaunchedEffect(Unit) {
- identityViewModel.updateImageHandlerScanTypes(
- IdentityScanState.ScanType.DL_FRONT,
- IdentityScanState.ScanType.DL_BACK
- )
- }
- DocumentUploadScreenContent(
+ screen(DocumentUploadDestination.ROUTE) {
+ UploadScreen(
navController = navController,
identityViewModel = identityViewModel,
- backStackEntry = it,
- route = DriverLicenseUploadDestination.ROUTE.route
- )
- }
- screen(PassportUploadDestination.ROUTE) {
- LaunchedEffect(Unit) {
- identityViewModel.updateImageHandlerScanTypes(
- IdentityScanState.ScanType.PASSPORT,
- null
- )
- }
- DocumentUploadScreenContent(
- navController = navController,
- identityViewModel = identityViewModel,
- backStackEntry = it,
- route = PassportUploadDestination.ROUTE.route,
- hasBack = false
)
}
screen(IndividualDestination.ROUTE) {
@@ -284,7 +209,7 @@ internal fun IdentityNavGraph(
IdentityAnalyticsRequestFactory.SCREEN_NAME_ERROR
)
navController.navigateTo(
- collectedDataParamType.toUploadDestination()
+ DocumentUploadDestination
)
}
} else {
@@ -302,20 +227,19 @@ internal fun IdentityNavGraph(
)
}
screen(CouldNotCaptureDestination.ROUTE) {
- val scanType = CouldNotCaptureDestination.couldNotCaptureScanType(it)
- val requireLiveCapture = CouldNotCaptureDestination.requireLiveCapture(it)
+ val fromSelfie = CouldNotCaptureDestination.fromSelfie(it)
ErrorScreen(
identityViewModel = identityViewModel,
title = stringResource(id = R.string.stripe_could_not_capture_title),
message1 = stringResource(id = R.string.stripe_could_not_capture_body1),
- message2 = if (scanType == IdentityScanState.ScanType.SELFIE) {
+ message2 = if (fromSelfie) {
null
} else {
stringResource(
R.string.stripe_could_not_capture_body2
)
},
- topButton = if (scanType == IdentityScanState.ScanType.SELFIE) {
+ topButton = if (fromSelfie) {
null
} else {
ErrorScreenButton(
@@ -325,7 +249,7 @@ internal fun IdentityNavGraph(
IdentityAnalyticsRequestFactory.SCREEN_NAME_ERROR
)
navController.navigateTo(
- scanType.toUploadDestination()
+ DocumentUploadDestination
)
}
},
@@ -337,7 +261,11 @@ internal fun IdentityNavGraph(
IdentityAnalyticsRequestFactory.SCREEN_NAME_ERROR
)
navController.navigateTo(
- scanType.toScanDestination()
+ if (fromSelfie) {
+ SelfieDestination
+ } else {
+ DocumentScanDestination
+ }
)
}
)
@@ -399,76 +327,6 @@ internal fun IdentityNavGraph(
}
}
-@Composable
-private fun DocumentScanScreenContent(
- navController: NavController,
- identityViewModel: IdentityViewModel,
- identityScanViewModel: IdentityScanViewModel,
- backStackEntry: NavBackStackEntry,
- route: String
-) {
- DocumentScanScreen(
- navController = navController,
- identityViewModel = identityViewModel,
- identityScanViewModel = identityScanViewModel,
- frontScanType = DocumentScanDestination.frontScanType(backStackEntry),
- backScanType = DocumentScanDestination.backScanType(backStackEntry),
- shouldStartFromBack = DocumentScanDestination.shouldStartFromBack(backStackEntry),
- messageRes = DocumentScanMessageRes(
- DocumentScanDestination.frontTitleStringRes(backStackEntry),
- DocumentScanDestination.backTitleStringRes(backStackEntry),
- DocumentScanDestination.frontMessageStringRes(backStackEntry),
- DocumentScanDestination.backMessageStringRes(backStackEntry),
- ),
- collectedDataParamType = DocumentScanDestination.collectedDataParamType(backStackEntry),
- route = route,
- )
-}
-
-@Composable
-private fun DocumentUploadScreenContent(
- navController: NavController,
- identityViewModel: IdentityViewModel,
- backStackEntry: NavBackStackEntry,
- route: String,
- hasBack: Boolean = true
-) {
- UploadScreen(
- navController = navController,
- identityViewModel = identityViewModel,
- collectedDataParamType = DocumentUploadDestination.collectedDataParamType(backStackEntry),
- route = route,
- titleRes = DocumentUploadDestination.titleRes(backStackEntry),
- contextRes = DocumentUploadDestination.contextRes(backStackEntry),
- frontInfo = DocumentUploadSideInfo(
- descriptionRes = DocumentUploadDestination.frontDescriptionRes(
- backStackEntry
- ),
- checkmarkContentDescriptionRes =
- DocumentUploadDestination.frontCheckMarkDescriptionRes(
- backStackEntry
- ),
- scanType = DocumentUploadDestination.frontScanType(backStackEntry)
- ),
- backInfo =
- if (hasBack) {
- DocumentUploadSideInfo(
- descriptionRes =
- DocumentUploadDestination.backDescriptionRes(
- backStackEntry
- ),
- checkmarkContentDescriptionRes =
- DocumentUploadDestination.backCheckMarkDescriptionRes(
- backStackEntry
- ),
- scanType = DocumentUploadDestination.backScanType(backStackEntry)
- )
- } else {
- null
- }
- )
-}
-
@ExperimentalMaterialApi
/**
* Built a composable screen with ModalBottomSheetLayout
diff --git a/identity/src/main/java/com/stripe/android/identity/navigation/IdentityTopLevelDestination.kt b/identity/src/main/java/com/stripe/android/identity/navigation/IdentityTopLevelDestination.kt
index 05f360ff08a..d78f46c0ffb 100644
--- a/identity/src/main/java/com/stripe/android/identity/navigation/IdentityTopLevelDestination.kt
+++ b/identity/src/main/java/com/stripe/android/identity/navigation/IdentityTopLevelDestination.kt
@@ -91,18 +91,10 @@ internal fun String.routeToScreenName(): String = when (this) {
IdentityAnalyticsRequestFactory.SCREEN_NAME_CONSENT
DocSelectionDestination.ROUTE.route ->
IdentityAnalyticsRequestFactory.SCREEN_NAME_DOC_SELECT
- IDScanDestination.ROUTE.route ->
- IdentityAnalyticsRequestFactory.SCREEN_NAME_LIVE_CAPTURE_ID
- PassportScanDestination.ROUTE.route ->
- IdentityAnalyticsRequestFactory.SCREEN_NAME_LIVE_CAPTURE_PASSPORT
- DriverLicenseScanDestination.ROUTE.route ->
- IdentityAnalyticsRequestFactory.SCREEN_NAME_LIVE_CAPTURE_DRIVER_LICENSE
- IDUploadDestination.ROUTE.route ->
- IdentityAnalyticsRequestFactory.SCREEN_NAME_FILE_UPLOAD_ID
- PassportUploadDestination.ROUTE.route ->
- IdentityAnalyticsRequestFactory.SCREEN_NAME_FILE_UPLOAD_PASSPORT
- DriverLicenseUploadDestination.ROUTE.route ->
- IdentityAnalyticsRequestFactory.SCREEN_NAME_FILE_UPLOAD_DRIVER_LICENSE
+ DocumentScanDestination.ROUTE.route ->
+ IdentityAnalyticsRequestFactory.SCREEN_NAME_LIVE_CAPTURE
+ DocumentUploadDestination.ROUTE.route ->
+ IdentityAnalyticsRequestFactory.SCREEN_NAME_FILE_UPLOAD
SelfieDestination.ROUTE.route ->
IdentityAnalyticsRequestFactory.SCREEN_NAME_SELFIE
ConfirmationDestination.ROUTE.route ->
@@ -132,17 +124,9 @@ internal fun String.routeToRequirement(): List = when (this) {
listOf(Requirement.BIOMETRICCONSENT)
DocSelectionDestination.ROUTE.route ->
listOf(Requirement.IDDOCUMENTTYPE)
- IDUploadDestination.ROUTE.route ->
+ DocumentScanDestination.ROUTE.route ->
listOf(Requirement.IDDOCUMENTFRONT, Requirement.IDDOCUMENTBACK)
- PassportUploadDestination.ROUTE.route ->
- listOf(Requirement.IDDOCUMENTFRONT, Requirement.IDDOCUMENTBACK)
- DriverLicenseUploadDestination.ROUTE.route ->
- listOf(Requirement.IDDOCUMENTFRONT, Requirement.IDDOCUMENTBACK)
- IDScanDestination.ROUTE.route ->
- listOf(Requirement.IDDOCUMENTFRONT, Requirement.IDDOCUMENTBACK)
- PassportScanDestination.ROUTE.route ->
- listOf(Requirement.IDDOCUMENTFRONT, Requirement.IDDOCUMENTBACK)
- DriverLicenseScanDestination.ROUTE.route ->
+ DocumentUploadDestination.ROUTE.route ->
listOf(Requirement.IDDOCUMENTFRONT, Requirement.IDDOCUMENTBACK)
SelfieDestination.ROUTE.route ->
listOf(Requirement.FACE)
diff --git a/identity/src/main/java/com/stripe/android/identity/navigation/NavControllerExt.kt b/identity/src/main/java/com/stripe/android/identity/navigation/NavControllerExt.kt
index a4b38875eac..8a393846da3 100644
--- a/identity/src/main/java/com/stripe/android/identity/navigation/NavControllerExt.kt
+++ b/identity/src/main/java/com/stripe/android/identity/navigation/NavControllerExt.kt
@@ -94,12 +94,8 @@ internal fun NavController.navigateToFinalErrorScreen(
* Route of all screens that collect front/back of a document.
*/
private val DOCUMENT_UPLOAD_ROUTES = setOf(
- IDUploadDestination.ROUTE.route,
- DriverLicenseUploadDestination.ROUTE.route,
- PassportUploadDestination.ROUTE.route,
- IDScanDestination.ROUTE.route,
- DriverLicenseScanDestination.ROUTE.route,
- PassportScanDestination.ROUTE.route
+ DocumentScanDestination.ROUTE.route,
+ DocumentUploadDestination.ROUTE.route
)
/**
@@ -141,7 +137,9 @@ internal suspend fun NavController.navigateOnVerificationPageData(
onMissingOtp()
} else if (verificationPageData.isMissingConsent()) {
navigateTo(ConsentDestination)
- } else if (verificationPageData.isMissingDocType()) {
+ }
+ // TODO - remove this when backend removes docType
+ else if (verificationPageData.isMissingDocType()) {
navigateTo(DocSelectionDestination)
} else if (verificationPageData.isMissingFront()) {
onMissingFront()
diff --git a/identity/src/main/java/com/stripe/android/identity/navigation/ScanDestinations.kt b/identity/src/main/java/com/stripe/android/identity/navigation/ScanDestinations.kt
index 1983de9257a..605862cc2a9 100644
--- a/identity/src/main/java/com/stripe/android/identity/navigation/ScanDestinations.kt
+++ b/identity/src/main/java/com/stripe/android/identity/navigation/ScanDestinations.kt
@@ -1,173 +1,10 @@
package com.stripe.android.identity.navigation
-import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.lifecycle.LifecycleOwner
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.stripe.android.identity.R
-import com.stripe.android.identity.networking.models.CollectedDataParam
-import com.stripe.android.identity.states.IdentityScanState
import com.stripe.android.identity.viewmodel.IdentityScanViewModel
-internal abstract class DocumentScanDestination(
- shouldStartFromBack: Boolean = false,
- shouldPopUpToDocSelection: Boolean = false,
- frontScanType: IdentityScanState.ScanType,
- backScanType: IdentityScanState.ScanType,
- @StringRes frontTitleStringRes: Int,
- @StringRes backTitleStringRes: Int = INVALID_STRING_RES,
- @StringRes frontMessageStringRes: Int,
- @StringRes backMessageStringRes: Int = INVALID_STRING_RES,
- collectedDataParamType: CollectedDataParam.Type
-) : IdentityTopLevelDestination(
- popUpToParam = if (shouldPopUpToDocSelection) {
- PopUpToParam(
- route = DocSelectionDestination.ROUTE.route,
- inclusive = false
- )
- } else {
- null
- }
-) {
- override val routeWithArgs by lazy {
- destinationRoute.withParams(
- ARG_SHOULD_START_FROM_BACK to shouldStartFromBack,
- ARG_FRONT_SCAN_TYPE to frontScanType,
- ARG_BACK_SCAN_TYPE to backScanType,
- ARG_FRONT_TITLE_STRING_RES to frontTitleStringRes,
- ARG_BACK_TITLE_STRING_RES to backTitleStringRes,
- ARG_FRONT_MESSAGE_STRING_RES to frontMessageStringRes,
- ARG_BACK_MESSAGE_STRING_RES to backMessageStringRes,
- ARG_COLLECTED_DATA_PARAM_TYPE to collectedDataParamType
- )
- }
-
- internal companion object {
- fun shouldStartFromBack(backStackEntry: NavBackStackEntry) =
- backStackEntry.getBooleanArgument(ARG_SHOULD_START_FROM_BACK)
-
- fun frontScanType(backStackEntry: NavBackStackEntry) =
- backStackEntry.arguments?.getSerializable(ARG_FRONT_SCAN_TYPE) as IdentityScanState.ScanType
-
- fun backScanType(backStackEntry: NavBackStackEntry) =
- backStackEntry.arguments?.getSerializable(ARG_BACK_SCAN_TYPE) as? IdentityScanState.ScanType
-
- fun frontTitleStringRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_FRONT_TITLE_STRING_RES)
-
- fun backTitleStringRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_BACK_TITLE_STRING_RES)
-
- fun frontMessageStringRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_FRONT_MESSAGE_STRING_RES)
-
- fun backMessageStringRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_BACK_MESSAGE_STRING_RES)
-
- fun collectedDataParamType(backStackEntry: NavBackStackEntry) =
- backStackEntry.arguments?.getSerializable(ARG_COLLECTED_DATA_PARAM_TYPE) as CollectedDataParam.Type
- }
-}
-
-internal class DocumentScanDestinationRoute(
- override val routeBase: String
-) :
- IdentityTopLevelDestination.DestinationRoute() {
- override val arguments = mutableListOf(
- navArgument(ARG_SHOULD_START_FROM_BACK) {
- type = NavType.BoolType
- },
- navArgument(ARG_FRONT_SCAN_TYPE) {
- type = NavType.EnumType(IdentityScanState.ScanType::class.java)
- },
- navArgument(ARG_BACK_SCAN_TYPE) {
- type = NavType.EnumType(IdentityScanState.ScanType::class.java)
- },
- navArgument(ARG_FRONT_TITLE_STRING_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_BACK_TITLE_STRING_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_FRONT_MESSAGE_STRING_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_BACK_MESSAGE_STRING_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_COLLECTED_DATA_PARAM_TYPE) {
- type = NavType.EnumType(CollectedDataParam.Type::class.java)
- }
- )
-}
-
-internal class PassportScanDestination(
- shouldStartFromBack: Boolean = false,
- shouldPopUpToDocSelection: Boolean = false
-) : DocumentScanDestination(
- shouldStartFromBack = shouldStartFromBack,
- shouldPopUpToDocSelection = shouldPopUpToDocSelection,
- frontScanType = IdentityScanState.ScanType.PASSPORT,
- backScanType = IdentityScanState.ScanType.PASSPORT,
- frontTitleStringRes = R.string.stripe_passport,
- frontMessageStringRes = R.string.stripe_position_passport,
- collectedDataParamType = CollectedDataParam.Type.PASSPORT
-) {
- override val destinationRoute = ROUTE
-
- companion object {
- private const val PASSPORT_SCAN = "PassportScan"
- val ROUTE = DocumentScanDestinationRoute(routeBase = PASSPORT_SCAN)
- }
-}
-
-internal class IDScanDestination(
- shouldStartFromBack: Boolean = false,
- shouldPopUpToDocSelection: Boolean = false
-) : DocumentScanDestination(
- shouldStartFromBack = shouldStartFromBack,
- shouldPopUpToDocSelection = shouldPopUpToDocSelection,
- frontScanType = IdentityScanState.ScanType.ID_FRONT,
- backScanType = IdentityScanState.ScanType.ID_BACK,
- frontTitleStringRes = R.string.stripe_front_of_id,
- backTitleStringRes = R.string.stripe_back_of_id,
- frontMessageStringRes = R.string.stripe_position_id_front,
- backMessageStringRes = R.string.stripe_position_id_back,
- collectedDataParamType = CollectedDataParam.Type.IDCARD
-) {
- override val destinationRoute = ROUTE
-
- companion object {
- private const val ID_SCAN = "IDScan"
- val ROUTE = DocumentScanDestinationRoute(routeBase = ID_SCAN)
- }
-}
-
-internal class DriverLicenseScanDestination(
- shouldStartFromBack: Boolean = false,
- shouldPopUpToDocSelection: Boolean = false,
-) : DocumentScanDestination(
- shouldStartFromBack = shouldStartFromBack,
- shouldPopUpToDocSelection = shouldPopUpToDocSelection,
- frontScanType = IdentityScanState.ScanType.DL_FRONT,
- backScanType = IdentityScanState.ScanType.DL_BACK,
- frontTitleStringRes = R.string.stripe_front_of_dl,
- backTitleStringRes = R.string.stripe_back_of_dl,
- frontMessageStringRes = R.string.stripe_position_dl_front,
- backMessageStringRes = R.string.stripe_position_dl_back,
- collectedDataParamType = CollectedDataParam.Type.DRIVINGLICENSE
-) {
- override val destinationRoute = ROUTE
-
- companion object {
- private const val DRIVE_LICENSE_SCAN = "DriverLicenseScan"
- val ROUTE = DocumentScanDestinationRoute(routeBase = DRIVE_LICENSE_SCAN)
- }
-}
-
@Composable
internal fun ScanDestinationEffect(
lifecycleOwner: LifecycleOwner,
@@ -194,13 +31,19 @@ internal object SelfieDestination : IdentityTopLevelDestination(
override val destinationRoute = ROUTE
}
+internal object DocumentScanDestination : IdentityTopLevelDestination(
+ popUpToParam = PopUpToParam(
+ route = DocSelectionDestination.ROUTE.route,
+ inclusive = false
+ )
+) {
+ val ROUTE = object : DestinationRoute() {
+ override val routeBase = SCAN
+ }
+
+ override val destinationRoute = ROUTE
+}
+
internal const val SELFIE = "Selfie"
-internal const val ARG_SHOULD_START_FROM_BACK = "startFromBack"
-internal const val ARG_FRONT_SCAN_TYPE = "frontScanType"
-internal const val ARG_BACK_SCAN_TYPE = "backScanType"
-internal const val ARG_FRONT_TITLE_STRING_RES = "frontTitleStringRes"
-internal const val ARG_BACK_TITLE_STRING_RES = "backTitleStringRes"
-internal const val ARG_FRONT_MESSAGE_STRING_RES = "frontMessageStringRes"
-internal const val ARG_BACK_MESSAGE_STRING_RES = "backMessageStringRes"
-internal const val ARG_COLLECTED_DATA_PARAM_TYPE = "collectedDataParamType"
-internal const val INVALID_STRING_RES = -1
+internal const val SCAN = "Scan"
+internal const val UPLOAD = "Upload"
diff --git a/identity/src/main/java/com/stripe/android/identity/navigation/UploadDestinations.kt b/identity/src/main/java/com/stripe/android/identity/navigation/UploadDestinations.kt
deleted file mode 100644
index e14e62746a1..00000000000
--- a/identity/src/main/java/com/stripe/android/identity/navigation/UploadDestinations.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-package com.stripe.android.identity.navigation
-
-import androidx.annotation.StringRes
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.stripe.android.identity.R
-import com.stripe.android.identity.networking.models.CollectedDataParam
-import com.stripe.android.identity.states.IdentityScanState
-
-internal abstract class DocumentUploadDestination(
- shouldPopUpToDocSelection: Boolean,
- frontScanType: IdentityScanState.ScanType,
- backScanType: IdentityScanState.ScanType,
- @StringRes titleRes: Int,
- @StringRes contextRes: Int,
- @StringRes frontDescriptionRes: Int,
- @StringRes frontCheckMarkDescriptionRes: Int,
- @StringRes backDescriptionRes: Int = INVALID_STRING_RES,
- @StringRes backCheckMarkDescriptionRes: Int = INVALID_STRING_RES,
- collectedDataParamType: CollectedDataParam.Type
-) : IdentityTopLevelDestination(
- popUpToParam = if (shouldPopUpToDocSelection) {
- PopUpToParam(
- route = DocSelectionDestination.ROUTE.route,
- inclusive = false
- )
- } else {
- null
- }
-) {
- override val routeWithArgs by lazy {
- destinationRoute.withParams(
- ARG_FRONT_SCAN_TYPE to frontScanType,
- ARG_BACK_SCAN_TYPE to backScanType,
- ARG_TITLE_RES to titleRes,
- ARG_CONTEXT_RES to contextRes,
- ARG_FRONT_DESCRIPTION_RES to frontDescriptionRes,
- ARG_FRONT_CHECK_MARK_DESCRIPTION_RES to frontCheckMarkDescriptionRes,
- ARG_BACK_DESCRIPTION_RES to backDescriptionRes,
- ARG_BACK_CHECK_MARK_DESCRIPTION_RES to backCheckMarkDescriptionRes,
- ARG_COLLECTED_DATA_PARAM_TYPE to collectedDataParamType
- )
- }
-
- companion object {
- const val PASSPORT_UPLOAD = "PassportUpload"
- val ROUTE = object : DestinationRoute() {
- override val routeBase = PASSPORT_UPLOAD
- }
-
- fun frontScanType(backStackEntry: NavBackStackEntry) =
- backStackEntry.arguments?.getSerializable(ARG_FRONT_SCAN_TYPE) as IdentityScanState.ScanType
-
- fun backScanType(backStackEntry: NavBackStackEntry) =
- backStackEntry.arguments?.getSerializable(ARG_BACK_SCAN_TYPE) as IdentityScanState.ScanType
-
- fun titleRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_TITLE_RES)
-
- fun contextRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_CONTEXT_RES)
-
- fun frontDescriptionRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_FRONT_DESCRIPTION_RES)
-
- fun frontCheckMarkDescriptionRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_FRONT_CHECK_MARK_DESCRIPTION_RES)
-
- fun backDescriptionRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_BACK_DESCRIPTION_RES)
-
- fun backCheckMarkDescriptionRes(backStackEntry: NavBackStackEntry) =
- backStackEntry.getIntArgument(ARG_BACK_CHECK_MARK_DESCRIPTION_RES)
-
- fun collectedDataParamType(backStackEntry: NavBackStackEntry) =
- backStackEntry.arguments?.getSerializable(ARG_COLLECTED_DATA_PARAM_TYPE) as CollectedDataParam.Type
- }
-}
-
-internal class DocumentUploadDestinationRoute(
- override val routeBase: String
-) : IdentityTopLevelDestination.DestinationRoute() {
- override val arguments = mutableListOf(
- navArgument(ARG_FRONT_SCAN_TYPE) {
- type = NavType.EnumType(IdentityScanState.ScanType::class.java)
- },
- navArgument(ARG_BACK_SCAN_TYPE) {
- type = NavType.EnumType(IdentityScanState.ScanType::class.java)
- },
- navArgument(ARG_TITLE_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_CONTEXT_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_FRONT_DESCRIPTION_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_FRONT_CHECK_MARK_DESCRIPTION_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_BACK_DESCRIPTION_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_BACK_CHECK_MARK_DESCRIPTION_RES) {
- type = NavType.IntType
- },
- navArgument(ARG_COLLECTED_DATA_PARAM_TYPE) {
- type = NavType.EnumType(CollectedDataParam.Type::class.java)
- }
- )
-}
-
-internal class PassportUploadDestination(
- shouldPopUpToDocSelection: Boolean = false,
-) : DocumentUploadDestination(
- shouldPopUpToDocSelection = shouldPopUpToDocSelection,
- frontScanType = IdentityScanState.ScanType.PASSPORT,
- backScanType = IdentityScanState.ScanType.PASSPORT,
- titleRes = R.string.stripe_upload_your_photo_id,
- contextRes = R.string.stripe_file_upload_content_passport,
- frontDescriptionRes = R.string.stripe_passport,
- frontCheckMarkDescriptionRes = R.string.stripe_passport_selected,
- collectedDataParamType = CollectedDataParam.Type.PASSPORT
-) {
- override val destinationRoute = ROUTE
-
- companion object {
- private const val PASSPORT_UPLOAD = "PassportUpload"
- val ROUTE = DocumentUploadDestinationRoute(routeBase = PASSPORT_UPLOAD)
- }
-}
-
-internal class IDUploadDestination(
- shouldPopUpToDocSelection: Boolean = false
-) : DocumentUploadDestination(
- shouldPopUpToDocSelection = shouldPopUpToDocSelection,
- frontScanType = IdentityScanState.ScanType.ID_FRONT,
- backScanType = IdentityScanState.ScanType.ID_BACK,
- titleRes = R.string.stripe_upload_your_photo_id,
- contextRes = R.string.stripe_file_upload_content_id,
- frontDescriptionRes = R.string.stripe_front_of_id,
- frontCheckMarkDescriptionRes = R.string.stripe_front_of_id_selected,
- backDescriptionRes = R.string.stripe_back_of_id,
- backCheckMarkDescriptionRes = R.string.stripe_back_of_id_selected,
- collectedDataParamType = CollectedDataParam.Type.IDCARD
-) {
- override val destinationRoute = ROUTE
-
- companion object {
- private const val ID_UPLOAD = "IDUpload"
- val ROUTE = DocumentUploadDestinationRoute(routeBase = ID_UPLOAD)
- }
-}
-
-internal class DriverLicenseUploadDestination(
- shouldPopUpToDocSelection: Boolean = false
-) : DocumentUploadDestination(
- shouldPopUpToDocSelection = shouldPopUpToDocSelection,
- frontScanType = IdentityScanState.ScanType.DL_FRONT,
- backScanType = IdentityScanState.ScanType.DL_BACK,
- titleRes = R.string.stripe_upload_your_photo_id,
- contextRes = R.string.stripe_file_upload_content_dl,
- frontDescriptionRes = R.string.stripe_front_of_dl,
- frontCheckMarkDescriptionRes = R.string.stripe_front_of_dl_selected,
- backDescriptionRes = R.string.stripe_back_of_dl,
- backCheckMarkDescriptionRes = R.string.stripe_back_of_dl_selected,
- collectedDataParamType = CollectedDataParam.Type.DRIVINGLICENSE
-) {
- override val destinationRoute = ROUTE
-
- companion object {
- private const val DRIVE_LICENSE_UPLOAD = "DriverLicenseUpload"
- val ROUTE = DocumentUploadDestinationRoute(routeBase = DRIVE_LICENSE_UPLOAD)
- }
-}
-
-internal const val ARG_TITLE_RES = "titleRes"
-internal const val ARG_CONTEXT_RES = "contextRes"
-internal const val ARG_FRONT_DESCRIPTION_RES = "frontDescriptionRes"
-internal const val ARG_FRONT_CHECK_MARK_DESCRIPTION_RES = "frontCheckMarkDescriptionRes"
-internal const val ARG_BACK_DESCRIPTION_RES = "backDescriptionRes"
-internal const val ARG_BACK_CHECK_MARK_DESCRIPTION_RES = "backCheckMarkDescriptionRes"
diff --git a/identity/src/main/java/com/stripe/android/identity/networking/models/CollectedDataParam.kt b/identity/src/main/java/com/stripe/android/identity/networking/models/CollectedDataParam.kt
index 3b2fbee7ea6..1b974712a74 100644
--- a/identity/src/main/java/com/stripe/android/identity/networking/models/CollectedDataParam.kt
+++ b/identity/src/main/java/com/stripe/android/identity/networking/models/CollectedDataParam.kt
@@ -5,12 +5,6 @@ import android.os.Parcelable
import com.stripe.android.core.networking.toMap
import com.stripe.android.identity.R
import com.stripe.android.identity.ml.IDDetectorAnalyzer
-import com.stripe.android.identity.navigation.DriverLicenseScanDestination
-import com.stripe.android.identity.navigation.DriverLicenseUploadDestination
-import com.stripe.android.identity.navigation.IDScanDestination
-import com.stripe.android.identity.navigation.IDUploadDestination
-import com.stripe.android.identity.navigation.PassportScanDestination
-import com.stripe.android.identity.navigation.PassportUploadDestination
import com.stripe.android.identity.networking.UploadedResult
import com.stripe.android.identity.ui.DRIVING_LICENSE_KEY
import com.stripe.android.identity.ui.ID_CARD_KEY
@@ -85,7 +79,6 @@ internal data class CollectedDataParam(
).toMap()
fun createFromFrontUploadedResultsForAutoCapture(
- type: Type,
frontHighResResult: UploadedResult,
frontLowResResult: UploadedResult
): CollectedDataParam =
@@ -106,12 +99,10 @@ internal data class CollectedDataParam(
"front low res image id is null"
},
uploadMethod = DocumentUploadParam.UploadMethod.AUTOCAPTURE
- ),
- idDocumentType = type
+ )
)
fun createFromBackUploadedResultsForAutoCapture(
- type: Type,
backHighResResult: UploadedResult,
backLowResResult: UploadedResult
): CollectedDataParam =
@@ -132,8 +123,7 @@ internal data class CollectedDataParam(
"back low res image id is null"
},
uploadMethod = DocumentUploadParam.UploadMethod.AUTOCAPTURE
- ),
- idDocumentType = type
+ )
)
fun createForSelfie(
@@ -234,47 +224,20 @@ internal data class CollectedDataParam(
return requirements
}
- fun Type.toUploadDestination(
- shouldPopUpToDocSelection: Boolean = false
- ) = when (this) {
- Type.IDCARD -> IDUploadDestination(shouldPopUpToDocSelection = shouldPopUpToDocSelection)
- Type.DRIVINGLICENSE -> DriverLicenseUploadDestination(shouldPopUpToDocSelection = shouldPopUpToDocSelection)
- Type.PASSPORT -> PassportUploadDestination(shouldPopUpToDocSelection = shouldPopUpToDocSelection)
- else -> throw java.lang.IllegalStateException("Invalid CollectedDataParam.Type")
- }
- fun Type.toScanDestination(
- shouldStartFromBack: Boolean = false,
- shouldPopUpToDocSelection: Boolean = false
- ) = when (this) {
- Type.IDCARD -> IDScanDestination(
- shouldStartFromBack,
- shouldPopUpToDocSelection
- )
-
- Type.PASSPORT -> PassportScanDestination(
- shouldStartFromBack,
- shouldPopUpToDocSelection
- )
-
- Type.DRIVINGLICENSE -> DriverLicenseScanDestination(
- shouldStartFromBack,
- shouldPopUpToDocSelection
- )
-
- else -> throw IllegalStateException("Invalid CollectedDataParam.Type")
- }
-
fun Type.getDisplayName(context: Context) =
when (this) {
Type.IDCARD -> {
context.getString(R.string.stripe_id_card)
}
+
Type.DRIVINGLICENSE -> {
context.getString(R.string.stripe_driver_license)
}
+
Type.PASSPORT -> {
context.getString(R.string.stripe_passport)
}
+
else -> throw java.lang.IllegalStateException("Invalid CollectedDataParam.Type")
}
}
diff --git a/identity/src/main/java/com/stripe/android/identity/networking/models/Requirement.kt b/identity/src/main/java/com/stripe/android/identity/networking/models/Requirement.kt
index 70f0f1d7154..90fb710e81b 100644
--- a/identity/src/main/java/com/stripe/android/identity/networking/models/Requirement.kt
+++ b/identity/src/main/java/com/stripe/android/identity/networking/models/Requirement.kt
@@ -4,16 +4,12 @@ import android.content.Context
import com.stripe.android.identity.navigation.ConfirmationDestination
import com.stripe.android.identity.navigation.ConsentDestination
import com.stripe.android.identity.navigation.DocSelectionDestination
-import com.stripe.android.identity.navigation.DriverLicenseScanDestination
-import com.stripe.android.identity.navigation.DriverLicenseUploadDestination
-import com.stripe.android.identity.navigation.IDScanDestination
-import com.stripe.android.identity.navigation.IDUploadDestination
+import com.stripe.android.identity.navigation.DocumentScanDestination
+import com.stripe.android.identity.navigation.DocumentUploadDestination
import com.stripe.android.identity.navigation.IdentityTopLevelDestination
import com.stripe.android.identity.navigation.IndividualDestination
import com.stripe.android.identity.navigation.IndividualWelcomeDestination
import com.stripe.android.identity.navigation.OTPDestination
-import com.stripe.android.identity.navigation.PassportScanDestination
-import com.stripe.android.identity.navigation.PassportUploadDestination
import com.stripe.android.identity.navigation.SelfieDestination
import com.stripe.android.identity.navigation.finalErrorDestination
import kotlinx.serialization.SerialName
@@ -55,14 +51,6 @@ internal enum class Requirement {
PHONE_OTP;
internal companion object {
- private val SCAN_UPLOAD_ROUTE_SET = setOf(
- DriverLicenseUploadDestination.ROUTE,
- IDUploadDestination.ROUTE,
- PassportUploadDestination.ROUTE,
- DriverLicenseScanDestination.ROUTE,
- IDScanDestination.ROUTE,
- PassportScanDestination.ROUTE
- )
val INDIVIDUAL_REQUIREMENT_SET = setOf(
NAME,
@@ -86,15 +74,13 @@ internal enum class Requirement {
}
IDDOCUMENTBACK -> {
- SCAN_UPLOAD_ROUTE_SET.any {
- it.route == fromRoute
- }
+ fromRoute == DocumentScanDestination.ROUTE.route ||
+ fromRoute == DocumentUploadDestination.ROUTE.route
}
IDDOCUMENTFRONT -> {
- SCAN_UPLOAD_ROUTE_SET.any {
- it.route == fromRoute
- }
+ fromRoute == DocumentScanDestination.ROUTE.route ||
+ fromRoute == DocumentUploadDestination.ROUTE.route
}
IDDOCUMENTTYPE -> {
diff --git a/identity/src/main/java/com/stripe/android/identity/states/IDDetectorTransitioner.kt b/identity/src/main/java/com/stripe/android/identity/states/IDDetectorTransitioner.kt
index eb937b4e643..f89d91965d0 100644
--- a/identity/src/main/java/com/stripe/android/identity/states/IDDetectorTransitioner.kt
+++ b/identity/src/main/java/com/stripe/android/identity/states/IDDetectorTransitioner.kt
@@ -268,10 +268,10 @@ internal class IDDetectorTransitioner(
* Note: the ML model will output ID_FRONT or ID_BACK for both ID and Driver License.
*/
private fun Category.matchesScanType(scanType: ScanType): Boolean {
- return this == Category.ID_BACK && scanType == ScanType.ID_BACK ||
- this == Category.ID_FRONT && scanType == ScanType.ID_FRONT ||
- this == Category.ID_BACK && scanType == ScanType.DL_BACK ||
- this == Category.ID_FRONT && scanType == ScanType.DL_FRONT ||
- this == Category.PASSPORT && scanType == ScanType.PASSPORT
+ return this == Category.ID_BACK && scanType == ScanType.DOC_BACK ||
+ this == Category.ID_FRONT && scanType == ScanType.DOC_FRONT ||
+ this == Category.ID_BACK && scanType == ScanType.DOC_BACK ||
+ this == Category.ID_FRONT && scanType == ScanType.DOC_FRONT ||
+ this == Category.PASSPORT && scanType == ScanType.DOC_FRONT
}
}
diff --git a/identity/src/main/java/com/stripe/android/identity/states/IdentityScanState.kt b/identity/src/main/java/com/stripe/android/identity/states/IdentityScanState.kt
index f1eb6f1ddc6..6eee7e76119 100644
--- a/identity/src/main/java/com/stripe/android/identity/states/IdentityScanState.kt
+++ b/identity/src/main/java/com/stripe/android/identity/states/IdentityScanState.kt
@@ -5,13 +5,6 @@ import com.stripe.android.camera.framework.time.ClockMark
import com.stripe.android.camera.scanui.ScanState
import com.stripe.android.identity.ml.AnalyzerInput
import com.stripe.android.identity.ml.AnalyzerOutput
-import com.stripe.android.identity.navigation.DriverLicenseScanDestination
-import com.stripe.android.identity.navigation.DriverLicenseUploadDestination
-import com.stripe.android.identity.navigation.IDScanDestination
-import com.stripe.android.identity.navigation.IDUploadDestination
-import com.stripe.android.identity.navigation.PassportScanDestination
-import com.stripe.android.identity.navigation.PassportUploadDestination
-import com.stripe.android.identity.navigation.SelfieDestination
/**
* States during scanning a document.
@@ -26,11 +19,8 @@ internal sealed class IdentityScanState(
* Type of documents being scanned
*/
enum class ScanType {
- ID_FRONT,
- ID_BACK,
- DL_FRONT,
- DL_BACK,
- PASSPORT,
+ DOC_FRONT,
+ DOC_BACK,
SELFIE
}
@@ -135,59 +125,11 @@ internal sealed class IdentityScanState(
internal companion object {
fun ScanType.isFront() =
- this == ScanType.ID_FRONT || this == ScanType.DL_FRONT || this == ScanType.PASSPORT
+ this == ScanType.DOC_FRONT
fun ScanType.isBack() =
- this == ScanType.ID_BACK || this == ScanType.DL_BACK
+ this == ScanType.DOC_BACK
fun ScanType?.isNullOrFront() = this == null || this.isFront()
-
- fun ScanType.toUploadDestination() =
- when (this) {
- ScanType.ID_FRONT ->
- IDUploadDestination(true)
- ScanType.ID_BACK ->
- IDUploadDestination(true)
- ScanType.DL_FRONT ->
- DriverLicenseUploadDestination(true)
- ScanType.DL_BACK ->
- DriverLicenseUploadDestination(true)
- ScanType.PASSPORT ->
- PassportUploadDestination(true)
- ScanType.SELFIE -> {
- throw IllegalArgumentException("SELFIE doesn't support upload")
- }
- }
-
- fun ScanType.toScanDestination() =
- when (this) {
- ScanType.ID_FRONT ->
- IDScanDestination(
- shouldStartFromBack = false,
- shouldPopUpToDocSelection = true
- )
- ScanType.ID_BACK ->
- IDScanDestination(
- shouldStartFromBack = true,
- shouldPopUpToDocSelection = true
- )
- ScanType.DL_FRONT ->
- DriverLicenseScanDestination(
- shouldStartFromBack = false,
- shouldPopUpToDocSelection = true
- )
- ScanType.DL_BACK ->
- DriverLicenseScanDestination(
- shouldStartFromBack = true,
- shouldPopUpToDocSelection = true
- )
- ScanType.PASSPORT ->
- PassportScanDestination(
- shouldStartFromBack = false,
- shouldPopUpToDocSelection = true
- )
- ScanType.SELFIE ->
- SelfieDestination
- }
}
}
diff --git a/identity/src/main/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffect.kt b/identity/src/main/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffect.kt
index db8d986a466..b31e6f07167 100644
--- a/identity/src/main/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffect.kt
+++ b/identity/src/main/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffect.kt
@@ -113,8 +113,7 @@ internal fun CameraScreenLaunchedEffect(
}
identityViewModel.uploadScanResult(
finalResult,
- verificationPage,
- identityScanViewModel.targetScanTypeFlow.value
+ verificationPage
)
}
// Transition to CouldNotCaptureDestination
@@ -136,15 +135,7 @@ internal fun CameraScreenLaunchedEffect(
navController.navigateTo(
CouldNotCaptureDestination(
- scanType = requireNotNull(identityScanViewModel.targetScanTypeFlow.value),
- requireLiveCapture =
- if (identityScanViewModel.targetScanTypeFlow.value
- != IdentityScanState.ScanType.SELFIE
- ) {
- verificationPage.documentCapture.requireLiveCapture
- } else {
- false
- }
+ fromSelfie = finalResult.result is FaceDetectorOutput
)
)
}
diff --git a/identity/src/main/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffectLight.kt b/identity/src/main/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffectLight.kt
new file mode 100644
index 00000000000..08a6d98a412
--- /dev/null
+++ b/identity/src/main/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffectLight.kt
@@ -0,0 +1,106 @@
+package com.stripe.android.identity.ui
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.NavController
+import com.stripe.android.identity.analytics.IdentityAnalyticsRequestFactory
+import com.stripe.android.identity.ml.FaceDetectorOutput
+import com.stripe.android.identity.ml.IDDetectorOutput
+import com.stripe.android.identity.navigation.CouldNotCaptureDestination
+import com.stripe.android.identity.navigation.navigateTo
+import com.stripe.android.identity.networking.models.VerificationPage
+import com.stripe.android.identity.states.IdentityScanState
+import com.stripe.android.identity.states.IdentityScanState.Companion.isBack
+import com.stripe.android.identity.states.IdentityScanState.Companion.isFront
+import com.stripe.android.identity.viewmodel.IdentityScanViewModel
+import com.stripe.android.identity.viewmodel.IdentityViewModel
+import kotlinx.coroutines.launch
+
+/**
+ * [CameraScreenLaunchedEffect] without checking pageAndModelFiles from IdentityViewModel.
+ */
+@Composable
+internal fun CameraScreenLaunchedEffectLight(
+ identityViewModel: IdentityViewModel,
+ identityScanViewModel: IdentityScanViewModel,
+ verificationPage: VerificationPage,
+ navController: NavController
+) {
+ val lifecycleOwner = LocalLifecycleOwner.current
+ LaunchedEffect(identityScanViewModel) {
+ // Handles interim result - track FPS
+ identityScanViewModel.interimResults.observe(lifecycleOwner) {
+ identityViewModel.fpsTracker.trackFrame()
+ }
+
+ // Handles final result - upload if success, transition to CouldNotCapture if timeout
+ identityScanViewModel.finalResult.observe(lifecycleOwner) { finalResult ->
+ lifecycleOwner.lifecycleScope.launch {
+ identityViewModel.fpsTracker.reportAndReset(
+ if (finalResult.result is FaceDetectorOutput) {
+ IdentityAnalyticsRequestFactory.TYPE_SELFIE
+ } else {
+ IdentityAnalyticsRequestFactory.TYPE_DOCUMENT
+ }
+ )
+ }
+
+ // Upload success result
+ if (finalResult.identityState is IdentityScanState.Finished) {
+ when (finalResult.result) {
+ is FaceDetectorOutput -> {
+ identityViewModel.updateAnalyticsState { oldState ->
+ oldState.copy(selfieModelScore = finalResult.result.resultScore)
+ }
+ }
+ is IDDetectorOutput -> {
+ if (finalResult.identityState.type.isFront()) {
+ identityViewModel.updateAnalyticsState { oldState ->
+ oldState.copy(
+ docFrontModelScore = finalResult.result.resultScore,
+ docFrontBlurScore = finalResult.result.blurScore
+ )
+ }
+ } else if (finalResult.identityState.type.isBack()) {
+ identityViewModel.updateAnalyticsState { oldState ->
+ oldState.copy(
+ docBackModelScore = finalResult.result.resultScore,
+ docBackBlurScore = finalResult.result.blurScore
+ )
+ }
+ }
+ }
+ }
+ identityViewModel.uploadScanResult(
+ finalResult,
+ verificationPage
+ )
+ }
+ // Transition to CouldNotCaptureDestination
+ else if (finalResult.identityState is IdentityScanState.TimeOut) {
+ when (finalResult.result) {
+ is FaceDetectorOutput -> {
+ identityViewModel.sendAnalyticsRequest(
+ identityViewModel.identityAnalyticsRequestFactory.selfieTimeout()
+ )
+ }
+ is IDDetectorOutput -> {
+ identityViewModel.sendAnalyticsRequest(
+ identityViewModel.identityAnalyticsRequestFactory.documentTimeout(
+ scanType = finalResult.identityState.type
+ )
+ )
+ }
+ }
+
+ navController.navigateTo(
+ CouldNotCaptureDestination(
+ fromSelfie = finalResult.result is FaceDetectorOutput
+ )
+ )
+ }
+ }
+ }
+}
diff --git a/identity/src/main/java/com/stripe/android/identity/ui/DocumenetScanScreen.kt b/identity/src/main/java/com/stripe/android/identity/ui/DocumenetScanScreen.kt
deleted file mode 100644
index 25c9cf960af..00000000000
--- a/identity/src/main/java/com/stripe/android/identity/ui/DocumenetScanScreen.kt
+++ /dev/null
@@ -1,336 +0,0 @@
-package com.stripe.android.identity.ui
-
-import androidx.annotation.StringRes
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.testTag
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.navigation.NavController
-import com.stripe.android.camera.scanui.CameraView
-import com.stripe.android.identity.R
-import com.stripe.android.identity.camera.DocumentScanCameraManager
-import com.stripe.android.identity.camera.IdentityCameraManager
-import com.stripe.android.identity.navigation.navigateToErrorScreenWithDefaultValues
-import com.stripe.android.identity.navigation.routeToScreenName
-import com.stripe.android.identity.networking.Resource
-import com.stripe.android.identity.networking.models.CollectedDataParam
-import com.stripe.android.identity.states.IdentityScanState
-import com.stripe.android.identity.states.IdentityScanState.Companion.isFront
-import com.stripe.android.identity.states.IdentityScanState.Companion.isNullOrFront
-import com.stripe.android.identity.utils.startScanning
-import com.stripe.android.identity.viewmodel.IdentityScanViewModel
-import com.stripe.android.identity.viewmodel.IdentityViewModel
-import kotlinx.coroutines.launch
-
-internal const val CONTINUE_BUTTON_TAG = "Continue"
-internal const val SCAN_TITLE_TAG = "Title"
-internal const val SCAN_MESSAGE_TAG = "Message"
-internal const val CHECK_MARK_TAG = "CheckMark"
-internal const val VIEW_FINDER_ASPECT_RATIO = 1.5f
-
-internal data class DocumentScanMessageRes(
- @StringRes
- val frontTitleStringRes: Int,
- @StringRes
- val backTitleStringRes: Int,
- @StringRes
- val frontMessageStringRes: Int,
- @StringRes
- val backMessageStringRes: Int
-)
-
-@Composable
-internal fun DocumentScanScreen(
- navController: NavController,
- identityViewModel: IdentityViewModel,
- identityScanViewModel: IdentityScanViewModel,
- frontScanType: IdentityScanState.ScanType,
- backScanType: IdentityScanState.ScanType?,
- shouldStartFromBack: Boolean,
- messageRes: DocumentScanMessageRes,
- collectedDataParamType: CollectedDataParam.Type,
- route: String
-) {
- val changedDisplayState by identityScanViewModel.displayStateChangedFlow.collectAsState()
- val newDisplayState by remember {
- derivedStateOf {
- changedDisplayState?.first
- }
- }
- val verificationPageState by identityViewModel.verificationPage.observeAsState(Resource.loading())
- val context = LocalContext.current
- val coroutineScope = rememberCoroutineScope()
-
- CheckVerificationPageAndCompose(
- verificationPageResource = verificationPageState,
- onError = {
- identityViewModel.errorCause.postValue(it)
- navController.navigateToErrorScreenWithDefaultValues(context)
- }
- ) { verificationPage ->
- val cameraManager = remember {
- DocumentScanCameraManager(
- context = context
- ) { cause ->
- identityViewModel.sendAnalyticsRequest(
- identityViewModel.identityAnalyticsRequestFactory.cameraError(
- scanType = frontScanType,
- throwable = IllegalStateException(cause)
- )
- )
- }
- }
-
- val lifecycleOwner = LocalLifecycleOwner.current
-
- val targetScanType by identityScanViewModel.targetScanTypeFlow.collectAsState()
-
- val title = if (targetScanType.isNullOrFront()) {
- stringResource(id = messageRes.frontTitleStringRes)
- } else {
- stringResource(id = messageRes.backTitleStringRes)
- }
-
- val message = when (newDisplayState) {
- is IdentityScanState.Finished -> stringResource(id = R.string.stripe_scanned)
- is IdentityScanState.Found -> stringResource(id = R.string.stripe_hold_still)
- is IdentityScanState.Initial -> {
- if (targetScanType.isNullOrFront()) {
- stringResource(id = messageRes.frontMessageStringRes)
- } else {
- stringResource(id = messageRes.backMessageStringRes)
- }
- }
-
- is IdentityScanState.Satisfied -> stringResource(id = R.string.stripe_scanned)
- is IdentityScanState.TimeOut -> ""
- is IdentityScanState.Unsatisfied -> ""
- null -> {
- if (targetScanType.isNullOrFront()) {
- stringResource(id = messageRes.frontMessageStringRes)
- } else {
- stringResource(id = messageRes.backMessageStringRes)
- }
- }
- }
-
- LaunchedEffect(newDisplayState) {
- when (newDisplayState) {
- null -> {
- cameraManager.toggleInitial()
- }
- is IdentityScanState.Initial -> {
- cameraManager.toggleInitial()
- }
- is IdentityScanState.Found -> {
- cameraManager.toggleFound()
- }
- is IdentityScanState.Finished -> {
- identityScanViewModel.stopScan(lifecycleOwner)
- cameraManager.toggleFinished()
- }
- else -> {} // no-op
- }
- }
-
- LaunchedEffect(Unit) {
- if (shouldStartFromBack) {
- identityViewModel.resetDocumentUploadedState()
- }
- }
-
- CameraScreenLaunchedEffect(
- identityViewModel = identityViewModel,
- identityScanViewModel = identityScanViewModel,
- verificationPage = verificationPage,
- navController = navController,
- cameraManager = cameraManager
- ) {
- if (shouldStartFromBack) {
- startScanning(
- scanType = requireNotNull(backScanType) {
- "$backScanType should not be null when trying to scan from back"
- },
- identityViewModel = identityViewModel,
- identityScanViewModel = identityScanViewModel,
- lifecycleOwner = lifecycleOwner
- )
- } else {
- startScanning(
- scanType = frontScanType,
- identityViewModel = identityViewModel,
- identityScanViewModel = identityScanViewModel,
- lifecycleOwner = lifecycleOwner
- )
- }
- }
-
- ScreenTransitionLaunchedEffect(
- identityViewModel = identityViewModel,
- scanType = frontScanType,
- screenName = route.routeToScreenName()
- )
-
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(
- vertical = dimensionResource(id = R.dimen.stripe_page_vertical_margin),
- horizontal = dimensionResource(id = R.dimen.stripe_page_horizontal_margin)
- )
- ) {
- var loadingButtonState by remember(newDisplayState) {
- mutableStateOf(
- if (newDisplayState is IdentityScanState.Finished) {
- LoadingButtonState.Idle
- } else {
- LoadingButtonState.Disabled
- }
- )
- }
- Column(
- modifier = Modifier
- .weight(1f)
- .verticalScroll(rememberScrollState())
- ) {
- Text(
- text = title,
- modifier = Modifier
- .fillMaxWidth()
- .semantics {
- testTag = SCAN_TITLE_TAG
- },
- fontSize = 24.sp,
- fontWeight = FontWeight.Bold
- )
- Text(
- text = message,
- modifier = Modifier
- .fillMaxWidth()
- .height(100.dp)
- .padding(
- top = dimensionResource(id = R.dimen.stripe_item_vertical_margin),
- bottom = 48.dp
- )
- .semantics {
- testTag = SCAN_MESSAGE_TAG
- },
- maxLines = 3,
- overflow = TextOverflow.Ellipsis
- )
- CameraViewFinder(newDisplayState, cameraManager)
- }
- LoadingButton(
- modifier = Modifier.testTag(CONTINUE_BUTTON_TAG),
- text = stringResource(id = R.string.stripe_kontinue).uppercase(),
- state = loadingButtonState
- ) {
- loadingButtonState = LoadingButtonState.Loading
-
- coroutineScope.launch {
- identityViewModel.collectDataForDocumentScanScreen(
- navController = navController,
- isFront = requireNotNull(targetScanType) {
- "targetScanType is still null"
- }.isFront(),
- collectedDataParamType = collectedDataParamType,
- route = route
- ) {
- startScanning(
- scanType = requireNotNull(backScanType) {
- "backScanType is null while still missing back"
- },
- identityViewModel = identityViewModel,
- identityScanViewModel = identityScanViewModel,
- lifecycleOwner = lifecycleOwner
- )
- }
- }
- }
- }
- }
-}
-
-@Composable
-private fun CameraViewFinder(
- newScanState: IdentityScanState?,
- cameraManager: IdentityCameraManager
-) {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .aspectRatio(VIEW_FINDER_ASPECT_RATIO)
- .clip(RoundedCornerShape(dimensionResource(id = R.dimen.stripe_view_finder_corner_radius)))
- ) {
- AndroidView(
- modifier = Modifier.fillMaxSize(),
- factory = {
- CameraView(
- it,
- CameraView.ViewFinderType.ID,
- R.drawable.stripe_viewfinder_border_initial
- )
- },
- update =
- {
- cameraManager.onCameraViewUpdate(it)
- }
- )
- if (newScanState is IdentityScanState.Finished) {
- Box(
- modifier = Modifier
- .fillMaxSize()
- .background(
- colorResource(id = R.color.stripe_check_mark_background)
- )
- .testTag(CHECK_MARK_TAG)
- ) {
- Image(
- modifier = Modifier
- .fillMaxSize()
- .padding(60.dp),
- painter = painterResource(id = R.drawable.stripe_check_mark),
- contentDescription = stringResource(id = R.string.stripe_check_mark),
- colorFilter = ColorFilter.tint(MaterialTheme.colors.primary),
- )
- }
- }
- }
-}
diff --git a/identity/src/main/java/com/stripe/android/identity/ui/DocumentScanScreen.kt b/identity/src/main/java/com/stripe/android/identity/ui/DocumentScanScreen.kt
new file mode 100644
index 00000000000..76c32341598
--- /dev/null
+++ b/identity/src/main/java/com/stripe/android/identity/ui/DocumentScanScreen.kt
@@ -0,0 +1,349 @@
+package com.stripe.android.identity.ui
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTag
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.LifecycleOwner
+import androidx.navigation.NavController
+import com.stripe.android.camera.scanui.CameraView
+import com.stripe.android.identity.R
+import com.stripe.android.identity.analytics.IdentityAnalyticsRequestFactory.Companion.SCREEN_NAME_LIVE_CAPTURE
+import com.stripe.android.identity.camera.DocumentScanCameraManager
+import com.stripe.android.identity.camera.IdentityCameraManager
+import com.stripe.android.identity.states.IdentityScanState
+import com.stripe.android.identity.states.IdentityScanState.Companion.isFront
+import com.stripe.android.identity.states.IdentityScanState.Companion.isNullOrFront
+import com.stripe.android.identity.utils.startScanning
+import com.stripe.android.identity.viewmodel.IdentityScanViewModel
+import com.stripe.android.identity.viewmodel.IdentityViewModel
+import kotlinx.coroutines.launch
+
+internal const val CONTINUE_BUTTON_TAG = "Continue"
+internal const val SCAN_TITLE_TAG = "Title"
+internal const val SCAN_MESSAGE_TAG = "Message"
+internal const val CHECK_MARK_TAG = "CheckMark"
+internal const val VIEW_FINDER_ASPECT_RATIO = 1.5f
+
+@Composable
+internal fun DocumentScanScreen(
+ navController: NavController,
+ identityViewModel: IdentityViewModel,
+ identityScanViewModel: IdentityScanViewModel
+) {
+ val changedDisplayState by identityScanViewModel.displayStateChangedFlow.collectAsState()
+ val newDisplayState by remember {
+ derivedStateOf {
+ changedDisplayState?.first
+ }
+ }
+ val context = LocalContext.current
+ val coroutineScope = rememberCoroutineScope()
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val cameraManager = remember {
+ DocumentScanCameraManager(
+ context = context
+ ) { cause ->
+ identityViewModel.sendAnalyticsRequest(
+ identityViewModel.identityAnalyticsRequestFactory.cameraError(
+ scanType = IdentityScanState.ScanType.DOC_FRONT,
+ throwable = IllegalStateException(cause)
+ )
+ )
+ }
+ }
+
+ CheckVerificationPageModelFilesAndCompose(
+ identityViewModel = identityViewModel,
+ navController = navController
+ ) { pageAndModelFiles ->
+
+ val targetScanType by identityScanViewModel.targetScanTypeFlow.collectAsState()
+
+ ScreenTransitionLaunchedEffect(
+ identityViewModel = identityViewModel,
+ screenName = SCREEN_NAME_LIVE_CAPTURE
+ )
+
+ // run once to initialize
+ LaunchedEffect(Unit) {
+ identityScanViewModel.initializeScanFlowAndUpdateState(pageAndModelFiles, cameraManager)
+ }
+
+ val documentScannerState by identityScanViewModel.scannerState.collectAsState()
+
+ when (documentScannerState) {
+ IdentityScanViewModel.State.Initial -> {
+ LoadingScreen()
+ }
+
+ IdentityScanViewModel.State.Initializing -> {
+ LoadingScreen()
+ }
+ // TODO(IDPROD-6555) - separate Initialized, Scanning and Scanned
+ else -> {
+ // TODO(IDPROD-6555) Remove this and handle toggling inside IdentityScanViewModel
+ LaunchedEffect(newDisplayState) {
+ when (newDisplayState) {
+ null -> {
+ cameraManager.toggleInitial()
+ }
+
+ is IdentityScanState.Initial -> {
+ cameraManager.toggleInitial()
+ }
+
+ is IdentityScanState.Found -> {
+ cameraManager.toggleFound()
+ }
+
+ is IdentityScanState.Finished -> {
+ identityScanViewModel.stopScan(lifecycleOwner)
+ cameraManager.toggleFinished()
+ }
+
+ else -> {} // no-op
+ }
+ }
+ CameraScreenLaunchedEffectLight(
+ identityViewModel = identityViewModel,
+ identityScanViewModel = identityScanViewModel,
+ verificationPage = pageAndModelFiles.page,
+ navController = navController
+ )
+
+ ScanScreen(
+ newDisplayState,
+ documentScannerState,
+ targetScanType,
+ identityScanViewModel,
+ identityViewModel,
+ lifecycleOwner,
+ cameraManager
+ ) {
+ coroutineScope.launch {
+ identityViewModel.collectDataForDocumentScanScreen(
+ navController = navController,
+ isFront = requireNotNull(targetScanType) {
+ "targetScanType is still null"
+ }.isFront()
+ ) {
+ startScanning(
+ scanType = IdentityScanState.ScanType.DOC_BACK,
+ identityViewModel = identityViewModel,
+ identityScanViewModel = identityScanViewModel,
+ lifecycleOwner = lifecycleOwner
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ScanScreen(
+ newDisplayState: IdentityScanState?,
+ documentScannerState: IdentityScanViewModel.State,
+ targetScanType: IdentityScanState.ScanType?,
+ identityScanViewModel: IdentityScanViewModel,
+ identityViewModel: IdentityViewModel,
+ lifecycleOwner: LifecycleOwner,
+ cameraManager: IdentityCameraManager,
+ onContinueClick: () -> Unit
+) {
+ val collectedData by identityViewModel.collectedData.collectAsState()
+
+ LaunchedEffect(Unit) {
+ val shouldStartFromBack = collectedData.idDocumentFront != null
+ // start scan - this is only triggered once
+ if (shouldStartFromBack) {
+ startScanning(
+ scanType = IdentityScanState.ScanType.DOC_BACK,
+ identityViewModel = identityViewModel,
+ identityScanViewModel = identityScanViewModel,
+ lifecycleOwner = lifecycleOwner
+ )
+ } else {
+ startScanning(
+ scanType = IdentityScanState.ScanType.DOC_FRONT,
+ identityViewModel = identityViewModel,
+ identityScanViewModel = identityScanViewModel,
+ lifecycleOwner = lifecycleOwner
+ )
+ }
+ }
+
+ val title = if (targetScanType.isNullOrFront()) {
+ stringResource(id = R.string.stripe_front_of_id)
+ } else {
+ stringResource(id = R.string.stripe_back_of_id)
+ }
+
+ val message = when (newDisplayState) {
+ is IdentityScanState.Finished -> stringResource(id = R.string.stripe_scanned)
+ is IdentityScanState.Found -> stringResource(id = R.string.stripe_hold_still)
+ is IdentityScanState.Initial -> {
+ if (targetScanType.isNullOrFront()) {
+ stringResource(id = R.string.stripe_position_id_front)
+ } else {
+ stringResource(id = R.string.stripe_position_id_back)
+ }
+ }
+
+ is IdentityScanState.Satisfied -> stringResource(id = R.string.stripe_scanned)
+ is IdentityScanState.TimeOut -> ""
+ is IdentityScanState.Unsatisfied -> ""
+ null -> {
+ if (targetScanType.isNullOrFront()) {
+ stringResource(id = R.string.stripe_position_id_front)
+ } else {
+ stringResource(id = R.string.stripe_position_id_back)
+ }
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(
+ vertical = dimensionResource(id = R.dimen.stripe_page_vertical_margin),
+ horizontal = dimensionResource(id = R.dimen.stripe_page_horizontal_margin)
+ )
+ ) {
+ var loadingButtonState by remember(documentScannerState) {
+ mutableStateOf(
+ if (documentScannerState is IdentityScanViewModel.State.Scanned) {
+ LoadingButtonState.Idle
+ } else {
+ LoadingButtonState.Disabled
+ }
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Text(
+ text = title,
+ modifier = Modifier
+ .fillMaxWidth()
+ .semantics {
+ testTag = SCAN_TITLE_TAG
+ },
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Bold
+ )
+ Text(
+ text = message,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(100.dp)
+ .padding(
+ top = dimensionResource(id = R.dimen.stripe_item_vertical_margin),
+ bottom = 48.dp
+ )
+ .semantics {
+ testTag = SCAN_MESSAGE_TAG
+ },
+ maxLines = 3,
+ overflow = TextOverflow.Ellipsis
+ )
+ CameraViewFinder(newDisplayState, cameraManager)
+ }
+ LoadingButton(
+ modifier = Modifier.testTag(CONTINUE_BUTTON_TAG),
+ text = stringResource(id = R.string.stripe_kontinue).uppercase(),
+ state = loadingButtonState
+ ) {
+ loadingButtonState = LoadingButtonState.Loading
+ onContinueClick()
+ }
+ }
+}
+
+@Composable
+private fun CameraViewFinder(
+ newScanState: IdentityScanState?,
+ cameraManager: IdentityCameraManager
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .aspectRatio(VIEW_FINDER_ASPECT_RATIO)
+ .clip(RoundedCornerShape(dimensionResource(id = R.dimen.stripe_view_finder_corner_radius)))
+ ) {
+ AndroidView(
+ modifier = Modifier.fillMaxSize(),
+ factory = {
+ CameraView(
+ it,
+ CameraView.ViewFinderType.ID,
+ R.drawable.stripe_viewfinder_border_initial
+ )
+ },
+ update =
+ {
+ cameraManager.onCameraViewUpdate(it)
+ }
+ )
+ if (newScanState is IdentityScanState.Finished) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ colorResource(id = R.color.stripe_check_mark_background)
+ )
+ .testTag(CHECK_MARK_TAG)
+ ) {
+ Image(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(60.dp),
+ painter = painterResource(id = R.drawable.stripe_check_mark),
+ contentDescription = stringResource(id = R.string.stripe_check_mark),
+ colorFilter = ColorFilter.tint(MaterialTheme.colors.primary),
+ )
+ }
+ }
+ }
+}
diff --git a/identity/src/main/java/com/stripe/android/identity/ui/LoadingScreen.kt b/identity/src/main/java/com/stripe/android/identity/ui/LoadingScreen.kt
index ff4f8b787c4..19422ed26b5 100644
--- a/identity/src/main/java/com/stripe/android/identity/ui/LoadingScreen.kt
+++ b/identity/src/main/java/com/stripe/android/identity/ui/LoadingScreen.kt
@@ -19,6 +19,7 @@ import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
+import com.stripe.android.core.exception.InvalidResponseException
import com.stripe.android.identity.R
import com.stripe.android.identity.navigation.navigateToErrorScreenWithDefaultValues
import com.stripe.android.identity.networking.Resource
@@ -90,3 +91,31 @@ internal fun CheckVerificationPageAndCompose(
}
}
}
+
+@Composable
+internal fun CheckVerificationPageModelFilesAndCompose(
+ identityViewModel: IdentityViewModel,
+ navController: NavController,
+ onSuccess: @Composable (IdentityViewModel.PageAndModelFiles) -> Unit
+) {
+ val verificationPageState by identityViewModel.pageAndModelFiles.observeAsState(Resource.loading())
+ val context = LocalContext.current
+ when (verificationPageState.status) {
+ Status.SUCCESS -> {
+ onSuccess(requireNotNull(verificationPageState.data))
+ }
+ Status.LOADING -> {
+ LoadingScreen()
+ } // no-op
+ Status.IDLE -> {} // no-op
+ Status.ERROR -> {
+ identityViewModel.errorCause.postValue(
+ InvalidResponseException(
+ cause = verificationPageState.throwable,
+ message = verificationPageState.message
+ )
+ )
+ navController.navigateToErrorScreenWithDefaultValues(context)
+ }
+ }
+}
diff --git a/identity/src/main/java/com/stripe/android/identity/ui/UploadScreen.kt b/identity/src/main/java/com/stripe/android/identity/ui/UploadScreen.kt
index c22dc511dce..dff529202ae 100644
--- a/identity/src/main/java/com/stripe/android/identity/ui/UploadScreen.kt
+++ b/identity/src/main/java/com/stripe/android/identity/ui/UploadScreen.kt
@@ -1,6 +1,5 @@
package com.stripe.android.identity.ui
-import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -49,13 +48,12 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.navigation.NavController
import com.stripe.android.identity.R
+import com.stripe.android.identity.analytics.IdentityAnalyticsRequestFactory.Companion.SCREEN_NAME_FILE_UPLOAD
+import com.stripe.android.identity.navigation.DocumentUploadDestination
import com.stripe.android.identity.navigation.navigateToFinalErrorScreen
-import com.stripe.android.identity.navigation.routeToScreenName
import com.stripe.android.identity.networking.Resource
import com.stripe.android.identity.networking.Status
-import com.stripe.android.identity.networking.models.CollectedDataParam
import com.stripe.android.identity.networking.models.Requirement
-import com.stripe.android.identity.states.IdentityScanState
import com.stripe.android.identity.viewmodel.IdentityViewModel
import com.stripe.android.uicore.text.dimensionResourceSp
import kotlinx.coroutines.launch
@@ -71,24 +69,10 @@ internal enum class UploadMethod {
TAKE_PHOTO, CHOOSE_PHOTO
}
-internal data class DocumentUploadSideInfo(
- @StringRes
- val descriptionRes: Int,
- @StringRes
- val checkmarkContentDescriptionRes: Int,
- val scanType: IdentityScanState.ScanType
-)
-
@Composable
internal fun UploadScreen(
navController: NavController,
identityViewModel: IdentityViewModel,
- collectedDataParamType: CollectedDataParam.Type,
- route: String,
- @StringRes titleRes: Int,
- @StringRes contextRes: Int,
- frontInfo: DocumentUploadSideInfo,
- backInfo: DocumentUploadSideInfo?
) {
val localContext = LocalContext.current
val verificationState by identityViewModel.verificationPage.observeAsState(Resource.loading())
@@ -113,8 +97,6 @@ internal fun UploadScreen(
launch {
identityViewModel.collectDataForDocumentUploadScreen(
navController,
- collectedDataParamType,
- route,
isFront = true
)
}
@@ -122,8 +104,6 @@ internal fun UploadScreen(
launch {
identityViewModel.collectDataForDocumentUploadScreen(
navController,
- collectedDataParamType,
- route,
isFront = false
)
}
@@ -131,8 +111,7 @@ internal fun UploadScreen(
ScreenTransitionLaunchedEffect(
identityViewModel = identityViewModel,
- screenName = route.routeToScreenName(),
- scanType = frontInfo.scanType
+ screenName = SCREEN_NAME_FILE_UPLOAD
)
Column(
@@ -148,7 +127,7 @@ internal fun UploadScreen(
.testTag(SCROLLABLE_COLUMN_TAG)
) {
Text(
- text = stringResource(id = titleRes),
+ text = stringResource(id = R.string.stripe_upload_your_photo_id),
fontSize = dimensionResourceSp(id = R.dimen.stripe_upload_title_text_size),
modifier = Modifier.padding(
@@ -156,7 +135,7 @@ internal fun UploadScreen(
)
)
Text(
- text = stringResource(id = contextRes),
+ text = stringResource(id = R.string.stripe_file_upload_content_id),
modifier = Modifier.padding(
bottom = 32.dp
)
@@ -181,13 +160,13 @@ internal fun UploadScreen(
var shouldShowFrontDialog by remember { mutableStateOf(false) }
SingleSideUploadRow(
modifier = Modifier.testTag(FRONT_ROW_TAG),
+ isFront = true,
uploadUiState = frontUploadedUiState,
- uploadInfo = frontInfo,
) { shouldShowFrontDialog = true }
if (shouldShowFrontDialog) {
UploadImageDialog(
- uploadInfo = frontInfo,
+ isFront = true,
shouldShowTakePhoto = cameraPermissionGranted,
shouldShowChoosePhoto = shouldShowChoosePhoto,
onPhotoSelected = { uploadMethod ->
@@ -195,6 +174,7 @@ internal fun UploadScreen(
UploadMethod.TAKE_PHOTO -> {
identityViewModel.imageHandler.takePhotoFront(localContext)
}
+
UploadMethod.CHOOSE_PHOTO -> {
identityViewModel.imageHandler.chooseImageFront()
}
@@ -213,11 +193,10 @@ internal fun UploadScreen(
true
}
// Otherwise show back when all the follows are true
- // * backInfo is not null - e.g not a passport scan where backInfo is null
// * collectedData.idDocumentFront not null - front is already scanned
// * missing BACK - front already scanned and server returns missing back
else {
- backInfo != null && collectedData.idDocumentFront != null && missings.contains(
+ collectedData.idDocumentFront != null && missings.contains(
Requirement.IDDOCUMENTBACK
)
}
@@ -248,13 +227,13 @@ internal fun UploadScreen(
}
SingleSideUploadRow(
modifier = Modifier.testTag(BACK_ROW_TAG),
+ isFront = false,
uploadUiState = backUploadedUiState,
- uploadInfo = requireNotNull(backInfo),
) { shouldShowBackDialog = true }
if (shouldShowBackDialog) {
UploadImageDialog(
- uploadInfo = backInfo,
+ isFront = false,
shouldShowTakePhoto = cameraPermissionGranted,
shouldShowChoosePhoto = shouldShowChoosePhoto,
onPhotoSelected = { uploadMethod ->
@@ -262,6 +241,7 @@ internal fun UploadScreen(
UploadMethod.TAKE_PHOTO -> {
identityViewModel.imageHandler.takePhotoBack(localContext)
}
+
UploadMethod.CHOOSE_PHOTO -> {
identityViewModel.imageHandler.chooseImageBack()
}
@@ -296,7 +276,7 @@ internal fun UploadScreen(
coroutineScope.launch {
identityViewModel.navigateToSelfieOrSubmit(
navController,
- route
+ DocumentUploadDestination.ROUTE.route
)
}
}
@@ -306,7 +286,7 @@ internal fun UploadScreen(
@Composable
internal fun UploadImageDialog(
- uploadInfo: DocumentUploadSideInfo,
+ isFront: Boolean,
shouldShowTakePhoto: Boolean,
shouldShowChoosePhoto: Boolean,
onPhotoSelected: (UploadMethod) -> Unit,
@@ -333,7 +313,9 @@ internal fun UploadImageDialog(
start = 24.dp,
end = 24.dp
),
- text = getTitleFromScanType(scanType = uploadInfo.scanType),
+ text = stringResource(
+ id = if (isFront) R.string.stripe_front_of_id else R.string.stripe_back_of_id
+ ),
style = MaterialTheme.typography.subtitle1,
fontWeight = FontWeight.Bold
)
@@ -388,30 +370,6 @@ private fun DialogListItem(
}
}
-@Composable
-private fun getTitleFromScanType(scanType: IdentityScanState.ScanType): String {
- return when (scanType) {
- IdentityScanState.ScanType.ID_FRONT -> {
- stringResource(R.string.stripe_front_of_id)
- }
- IdentityScanState.ScanType.ID_BACK -> {
- stringResource(R.string.stripe_back_of_id)
- }
- IdentityScanState.ScanType.DL_FRONT -> {
- stringResource(R.string.stripe_front_of_dl)
- }
- IdentityScanState.ScanType.DL_BACK -> {
- stringResource(R.string.stripe_back_of_dl)
- }
- IdentityScanState.ScanType.PASSPORT -> {
- stringResource(R.string.stripe_passport)
- }
- else -> {
- throw java.lang.IllegalArgumentException("invalid scan type: $scanType")
- }
- }
-}
-
private enum class DocumentUploadUIState {
Idle, Loading, Done
}
@@ -419,8 +377,8 @@ private enum class DocumentUploadUIState {
@Composable
private fun SingleSideUploadRow(
modifier: Modifier = Modifier,
+ isFront: Boolean,
uploadUiState: DocumentUploadUIState,
- uploadInfo: DocumentUploadSideInfo,
onSelectButtonClicked: () -> Unit
) {
Row(
@@ -431,7 +389,7 @@ private fun SingleSideUploadRow(
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
- text = stringResource(id = uploadInfo.descriptionRes),
+ text = stringResource(id = if (isFront) R.string.stripe_front_of_id else R.string.stripe_back_of_id),
modifier = Modifier.align(CenterVertically)
)
when (uploadUiState) {
@@ -442,6 +400,7 @@ private fun SingleSideUploadRow(
Text(text = stringResource(id = R.string.stripe_select).uppercase())
}
}
+
DocumentUploadUIState.Loading -> {
CircularProgressIndicator(
modifier = Modifier
@@ -450,10 +409,13 @@ private fun SingleSideUploadRow(
strokeWidth = 3.dp
)
}
+
DocumentUploadUIState.Done -> {
Image(
painter = painterResource(id = R.drawable.stripe_check_mark),
- contentDescription = stringResource(id = uploadInfo.checkmarkContentDescriptionRes),
+ contentDescription = stringResource(
+ id = if (isFront) R.string.stripe_front_of_id_selected else R.string.stripe_back_of_id_selected
+ ),
modifier = Modifier
.height(18.dp)
.width(18.dp)
diff --git a/identity/src/main/java/com/stripe/android/identity/utils/IdentityImageHandler.kt b/identity/src/main/java/com/stripe/android/identity/utils/IdentityImageHandler.kt
index 80e6ba542aa..53969fee15d 100644
--- a/identity/src/main/java/com/stripe/android/identity/utils/IdentityImageHandler.kt
+++ b/identity/src/main/java/com/stripe/android/identity/utils/IdentityImageHandler.kt
@@ -4,7 +4,6 @@ import android.content.Context
import android.net.Uri
import androidx.activity.result.ActivityResultCaller
import androidx.lifecycle.SavedStateHandle
-import com.stripe.android.identity.states.IdentityScanState
import javax.inject.Inject
/**
@@ -18,53 +17,30 @@ internal class IdentityImageHandler @Inject constructor(
private lateinit var frontImageChooser: ImageChooser
private lateinit var backImageChooser: ImageChooser
- // [ScanType] for front and back, might be updated when current screen changes.
- private var frontScanType: IdentityScanState.ScanType? = null
- private var backScanType: IdentityScanState.ScanType? = null
-
fun registerActivityResultCaller(
activityResultCaller: ActivityResultCaller,
savedStateHandle: SavedStateHandle,
- onFrontPhotoTaken: (Uri, IdentityScanState.ScanType?) -> Unit,
- onBackPhotoTaken: (Uri, IdentityScanState.ScanType?) -> Unit,
- onFrontImageChosen: (Uri, IdentityScanState.ScanType?) -> Unit,
- onBackImageChosen: (Uri, IdentityScanState.ScanType?) -> Unit
+ onFrontPhotoTaken: (Uri) -> Unit,
+ onBackPhotoTaken: (Uri) -> Unit,
+ onFrontImageChosen: (Uri) -> Unit,
+ onBackImageChosen: (Uri) -> Unit
) {
- frontScanType = savedStateHandle.get(FRONT_SCAN_TYPE)
- backScanType = savedStateHandle.get(BACK_SCAN_TYPE)
-
frontPhotoTaker = PhotoTaker(
activityResultCaller,
identityIO,
- {
- onFrontPhotoTaken(it, frontScanType)
- },
+ onFrontPhotoTaken,
savedStateHandle,
FRONT_PHOTO_URI
)
backPhotoTaker = PhotoTaker(
activityResultCaller,
identityIO,
- {
- onBackPhotoTaken(it, backScanType)
- },
+ onBackPhotoTaken,
savedStateHandle,
BACK_PHOTO_URI
)
- frontImageChooser = ImageChooser(activityResultCaller) {
- onFrontImageChosen(it, frontScanType)
- }
- backImageChooser = ImageChooser(activityResultCaller) {
- onBackImageChosen(it, backScanType)
- }
- }
-
- fun updateScanTypes(
- frontScanType: IdentityScanState.ScanType,
- backScanType: IdentityScanState.ScanType?
- ) {
- this.frontScanType = frontScanType
- this.backScanType = backScanType
+ frontImageChooser = ImageChooser(activityResultCaller, onFrontImageChosen)
+ backImageChooser = ImageChooser(activityResultCaller, onBackImageChosen)
}
/**
@@ -102,7 +78,5 @@ internal class IdentityImageHandler @Inject constructor(
companion object {
const val FRONT_PHOTO_URI = "front_photo_uri"
const val BACK_PHOTO_URI = "back_photo_uri"
- const val FRONT_SCAN_TYPE = "front_scan_type"
- const val BACK_SCAN_TYPE = "back_scan_type"
}
}
diff --git a/identity/src/main/java/com/stripe/android/identity/viewmodel/IdentityScanViewModel.kt b/identity/src/main/java/com/stripe/android/identity/viewmodel/IdentityScanViewModel.kt
index e2a8fa8dd37..bfbd36ea30d 100644
--- a/identity/src/main/java/com/stripe/android/identity/viewmodel/IdentityScanViewModel.kt
+++ b/identity/src/main/java/com/stripe/android/identity/viewmodel/IdentityScanViewModel.kt
@@ -8,10 +8,13 @@ import androidx.lifecycle.viewModelScope
import com.stripe.android.camera.scanui.util.asRect
import com.stripe.android.core.injection.UIContext
import com.stripe.android.identity.analytics.ModelPerformanceTracker
+import com.stripe.android.identity.camera.DocumentScanCameraManager
+import com.stripe.android.identity.camera.IdentityAggregator
import com.stripe.android.identity.camera.IdentityCameraManager
import com.stripe.android.identity.states.IdentityScanState
import com.stripe.android.identity.states.LaplacianBlurDetector
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -24,6 +27,22 @@ internal class IdentityScanViewModel(
@UIContext private val uiContext: CoroutineContext
) :
CameraViewModel(modelPerformanceTracker, laplacianBlurDetector, uiContext) {
+ internal sealed class State {
+ object Initial : State()
+ object Initializing : State()
+ object Initialized : State()
+ object Scanning : State()
+ object Scanned : State()
+ }
+
+ override suspend fun onResult(result: IdentityAggregator.FinalResult) {
+ super.onResult(result)
+ _scannerInitializedState.update { State.Scanned }
+ }
+
+ private val _scannerInitializedState: MutableStateFlow = MutableStateFlow(State.Initial)
+
+ val scannerState: StateFlow = _scannerInitializedState
/**
* StateFlow to keep track of current target scan type.
@@ -40,6 +59,7 @@ internal class IdentityScanViewModel(
scanType: IdentityScanState.ScanType,
lifecycleOwner: LifecycleOwner
) {
+ _scannerInitializedState.update { State.Scanning }
targetScanTypeFlow.update { scanType }
cameraManager.requireCameraAdapter().bindToLifecycle(lifecycleOwner)
scanState = null
@@ -60,6 +80,25 @@ internal class IdentityScanViewModel(
cameraManager.requireCameraAdapter().unbindFromLifecycle(lifecycleOwner)
}
+ /**
+ * Initialize scanner, including [identityScanFlow] and [cameraManager].
+ */
+ fun initializeScanFlowAndUpdateState(
+ pageAndModelFiles: IdentityViewModel.PageAndModelFiles,
+ cameraManager: DocumentScanCameraManager
+ ) {
+ _scannerInitializedState.update { State.Initializing }
+ initializeScanFlow(
+ pageAndModelFiles.page,
+ idDetectorModelFile = pageAndModelFiles.idDetectorFile,
+ faceDetectorModelFile = pageAndModelFiles.faceDetectorFile
+ )
+ this.cameraManager = cameraManager
+ _scannerInitializedState.update {
+ State.Initialized
+ }
+ }
+
internal class IdentityScanViewModelFactory @Inject constructor(
private val context: Context,
private val modelPerformanceTracker: ModelPerformanceTracker,
diff --git a/identity/src/main/java/com/stripe/android/identity/viewmodel/IdentityViewModel.kt b/identity/src/main/java/com/stripe/android/identity/viewmodel/IdentityViewModel.kt
index dd1ec8dcbf9..19a8a8bdccf 100644
--- a/identity/src/main/java/com/stripe/android/identity/viewmodel/IdentityViewModel.kt
+++ b/identity/src/main/java/com/stripe/android/identity/viewmodel/IdentityViewModel.kt
@@ -33,12 +33,15 @@ import com.stripe.android.identity.analytics.ScreenTracker
import com.stripe.android.identity.camera.IdentityAggregator
import com.stripe.android.identity.injection.IdentityActivitySubcomponent
import com.stripe.android.identity.ml.BoundingBox
+import com.stripe.android.identity.ml.Category
import com.stripe.android.identity.ml.FaceDetectorAnalyzer
import com.stripe.android.identity.ml.FaceDetectorOutput
import com.stripe.android.identity.ml.IDDetectorOutput
import com.stripe.android.identity.navigation.CameraPermissionDeniedDestination
import com.stripe.android.identity.navigation.ConfirmationDestination
import com.stripe.android.identity.navigation.DocSelectionDestination
+import com.stripe.android.identity.navigation.DocumentScanDestination
+import com.stripe.android.identity.navigation.DocumentUploadDestination
import com.stripe.android.identity.navigation.ErrorDestination
import com.stripe.android.identity.navigation.IdentityTopLevelDestination
import com.stripe.android.identity.navigation.IndividualDestination
@@ -64,8 +67,6 @@ import com.stripe.android.identity.networking.models.CollectedDataParam
import com.stripe.android.identity.networking.models.CollectedDataParam.Companion.clearData
import com.stripe.android.identity.networking.models.CollectedDataParam.Companion.collectedRequirements
import com.stripe.android.identity.networking.models.CollectedDataParam.Companion.mergeWith
-import com.stripe.android.identity.networking.models.CollectedDataParam.Companion.toScanDestination
-import com.stripe.android.identity.networking.models.CollectedDataParam.Companion.toUploadDestination
import com.stripe.android.identity.networking.models.DocumentUploadParam
import com.stripe.android.identity.networking.models.DocumentUploadParam.UploadMethod
import com.stripe.android.identity.networking.models.Requirement
@@ -374,14 +375,6 @@ internal class IdentityViewModel constructor(
*/
val errorCause = MutableLiveData()
- /**
- * Reset document uploaded state to loading state.
- */
- internal fun resetDocumentUploadedState() {
- _documentFrontUploadedState.updateStateAndSave { SingleSideDocumentUploadState() }
- _documentBackUploadedState.updateStateAndSave { SingleSideDocumentUploadState() }
- }
-
/**
* Reset selfie uploaded state to loading state.
*/
@@ -427,8 +420,7 @@ internal class IdentityViewModel constructor(
*/
internal fun uploadScanResult(
result: IdentityAggregator.FinalResult,
- verificationPage: VerificationPage,
- targetScanType: IdentityScanState.ScanType?
+ verificationPage: VerificationPage
) {
when (result.result) {
is IDDetectorOutput -> {
@@ -436,16 +428,28 @@ internal class IdentityViewModel constructor(
val boundingBox = result.result.boundingBox
val scores = result.result.allScores
- val isFront = when (targetScanType) {
- IdentityScanState.ScanType.ID_FRONT -> true
- IdentityScanState.ScanType.ID_BACK -> false
- IdentityScanState.ScanType.DL_FRONT -> true
- IdentityScanState.ScanType.DL_BACK -> false
- // passport is always uploaded as front
- IdentityScanState.ScanType.PASSPORT -> true
+ val isFront: Boolean
+ val targetScanType: IdentityScanState.ScanType
+
+ when (result.result.category) {
+ Category.PASSPORT -> {
+ isFront = true
+ targetScanType = IdentityScanState.ScanType.DOC_FRONT
+ }
+
+ Category.ID_FRONT -> {
+ isFront = true
+ targetScanType = IdentityScanState.ScanType.DOC_FRONT
+ }
+
+ Category.ID_BACK -> {
+ isFront = false
+ targetScanType = IdentityScanState.ScanType.DOC_BACK
+ }
+
else -> {
- Log.e(TAG, "incorrect targetScanType: $targetScanType")
- throw IllegalStateException("incorrect targetScanType: $targetScanType")
+ Log.e(TAG, "incorrect category: ${result.result.category}")
+ throw IllegalStateException("incorrect targetScanType: ${result.result.category}")
}
}
// upload high res
@@ -1308,41 +1312,20 @@ internal class IdentityViewModel constructor(
fun updateNewScanType(scanType: IdentityScanState.ScanType) {
updateAnalyticsState { oldState ->
when (scanType) {
- IdentityScanState.ScanType.ID_FRONT -> {
+ IdentityScanState.ScanType.DOC_FRONT -> {
oldState.copy(
docFrontRetryTimes =
oldState.docFrontRetryTimes?.let { it + 1 } ?: 1
)
}
- IdentityScanState.ScanType.ID_BACK -> {
+ IdentityScanState.ScanType.DOC_BACK -> {
oldState.copy(
docBackRetryTimes =
oldState.docBackRetryTimes?.let { it + 1 } ?: 1
)
}
- IdentityScanState.ScanType.DL_FRONT -> {
- oldState.copy(
- docFrontRetryTimes =
- oldState.docFrontRetryTimes?.let { it + 1 } ?: 1
- )
- }
-
- IdentityScanState.ScanType.DL_BACK -> {
- oldState.copy(
- docBackRetryTimes =
- oldState.docBackRetryTimes?.let { it + 1 } ?: 1
- )
- }
-
- IdentityScanState.ScanType.PASSPORT -> {
- oldState.copy(
- docFrontRetryTimes =
- oldState.docFrontRetryTimes?.let { it + 1 } ?: 1
- )
- }
-
IdentityScanState.ScanType.SELFIE -> {
oldState.copy(
selfieRetryTimes =
@@ -1367,16 +1350,14 @@ internal class IdentityViewModel constructor(
cameraPermissionEnsureable.ensureCameraPermission(
onCameraReady = {
sendAnalyticsRequest(
- identityAnalyticsRequestFactory.cameraPermissionGranted(
- type.toAnalyticsScanType()
- )
+ identityAnalyticsRequestFactory.cameraPermissionGranted()
)
_cameraPermissionGranted.update { true }
idDetectorModelFile.observe(viewLifecycleOwner) { modelResource ->
when (modelResource.status) {
// model ready, camera permission is granted -> navigate to scan
Status.SUCCESS -> {
- navController.navigateTo(type.toScanDestination())
+ navController.navigateTo(DocumentScanDestination)
}
// model not ready, camera permission is granted -> navigate to manual capture
Status.ERROR -> {
@@ -1396,7 +1377,7 @@ internal class IdentityViewModel constructor(
)
} else {
navController.navigateTo(
- type.toUploadDestination()
+ DocumentUploadDestination
)
}
}
@@ -1536,22 +1517,15 @@ internal class IdentityViewModel constructor(
val destinationWhenMissingBack =
when (failedUploadMethod) {
UploadMethod.AUTOCAPTURE -> {
- failedDocumentType.toScanDestination(
- shouldStartFromBack = true,
- shouldPopUpToDocSelection = true
- )
+ DocumentScanDestination
}
UploadMethod.FILEUPLOAD -> {
- failedDocumentType.toUploadDestination(
- shouldPopUpToDocSelection = true
- )
+ DocumentUploadDestination
}
UploadMethod.MANUALCAPTURE -> {
- failedDocumentType.toUploadDestination(
- shouldPopUpToDocSelection = true
- )
+ DocumentUploadDestination
}
}
return collectedDataParamWithForceConfirm to destinationWhenMissingBack
@@ -1592,8 +1566,6 @@ internal class IdentityViewModel constructor(
suspend fun collectDataForDocumentScanScreen(
navController: NavController,
isFront: Boolean,
- collectedDataParamType: CollectedDataParam.Type,
- route: String,
onMissingBack: () -> Unit
) {
if (isFront) {
@@ -1607,17 +1579,16 @@ internal class IdentityViewModel constructor(
getApplication()
)
} else if (uploadedState.isUploaded()) {
+ val route = DocumentScanDestination.ROUTE.route
postVerificationPageDataAndMaybeNavigate(
navController = navController,
collectedDataParam = if (isFront) {
CollectedDataParam.createFromFrontUploadedResultsForAutoCapture(
- type = collectedDataParamType,
frontHighResResult = requireNotNull(uploadedState.highResResult.data),
frontLowResResult = requireNotNull(uploadedState.lowResResult.data)
)
} else {
CollectedDataParam.createFromBackUploadedResultsForAutoCapture(
- type = collectedDataParamType,
backHighResResult = requireNotNull(uploadedState.highResResult.data),
backLowResResult = requireNotNull(uploadedState.lowResResult.data)
)
@@ -1641,8 +1612,6 @@ internal class IdentityViewModel constructor(
*/
suspend fun collectDataForDocumentUploadScreen(
navController: NavController,
- collectedDataParamType: CollectedDataParam.Type,
- route: String,
isFront: Boolean
) {
if (isFront) {
@@ -1661,10 +1630,9 @@ internal class IdentityViewModel constructor(
"front uploaded file id is null"
},
uploadMethod = requireNotNull(front.uploadMethod)
- ),
- idDocumentType = collectedDataParamType
+ )
),
- fromRoute = route
+ fromRoute = DocumentUploadDestination.ROUTE.route
)
}
}
@@ -1685,10 +1653,9 @@ internal class IdentityViewModel constructor(
"back uploaded file id is null"
},
uploadMethod = requireNotNull(back.uploadMethod)
- ),
- idDocumentType = collectedDataParamType
+ )
),
- fromRoute = route
+ fromRoute = DocumentUploadDestination.ROUTE.route
)
}
}
@@ -1766,40 +1733,40 @@ internal class IdentityViewModel constructor(
imageHandler.registerActivityResultCaller(
activityResultCaller,
savedStateHandle,
- onFrontPhotoTaken = { uri, scanType ->
+ onFrontPhotoTaken = { uri ->
uploadManualResult(
uri = uri,
isFront = true,
docCapturePage = requireNotNull(verificationPage.value?.data).documentCapture,
uploadMethod = UploadMethod.MANUALCAPTURE,
- scanType = requireNotNull(scanType)
+ scanType = IdentityScanState.ScanType.DOC_FRONT
)
},
- onBackPhotoTaken = { uri, scanType ->
+ onBackPhotoTaken = { uri ->
uploadManualResult(
uri = uri,
isFront = false,
docCapturePage = requireNotNull(verificationPage.value?.data).documentCapture,
uploadMethod = UploadMethod.MANUALCAPTURE,
- scanType = requireNotNull(scanType)
+ scanType = IdentityScanState.ScanType.DOC_BACK
)
},
- onFrontImageChosen = { uri, scanType ->
+ onFrontImageChosen = { uri ->
uploadManualResult(
uri = uri,
isFront = true,
docCapturePage = requireNotNull(verificationPage.value?.data).documentCapture,
uploadMethod = UploadMethod.FILEUPLOAD,
- scanType = requireNotNull(scanType)
+ scanType = IdentityScanState.ScanType.DOC_FRONT
)
},
- onBackImageChosen = { uri, scanType ->
+ onBackImageChosen = { uri ->
uploadManualResult(
uri = uri,
isFront = false,
docCapturePage = requireNotNull(verificationPage.value?.data).documentCapture,
uploadMethod = UploadMethod.FILEUPLOAD,
- scanType = requireNotNull(scanType)
+ scanType = IdentityScanState.ScanType.DOC_BACK
)
}
)
@@ -1809,19 +1776,10 @@ internal class IdentityViewModel constructor(
_visitedIndividualWelcomeScreen.updateStateAndSave { true }
}
- fun updateImageHandlerScanTypes(
- frontScanType: IdentityScanState.ScanType,
- backScanType: IdentityScanState.ScanType?
- ) {
- savedStateHandle[IdentityImageHandler.FRONT_SCAN_TYPE] = frontScanType
- savedStateHandle[IdentityImageHandler.BACK_SCAN_TYPE] = backScanType
- imageHandler.updateScanTypes(frontScanType, backScanType)
- }
-
private fun CollectedDataParam.Type.toAnalyticsScanType() = when (this) {
- CollectedDataParam.Type.DRIVINGLICENSE -> IdentityScanState.ScanType.DL_FRONT
- CollectedDataParam.Type.IDCARD -> IdentityScanState.ScanType.ID_FRONT
- CollectedDataParam.Type.PASSPORT -> IdentityScanState.ScanType.PASSPORT
+ CollectedDataParam.Type.DRIVINGLICENSE -> IdentityScanState.ScanType.DOC_FRONT
+ CollectedDataParam.Type.IDCARD -> IdentityScanState.ScanType.DOC_FRONT
+ CollectedDataParam.Type.PASSPORT -> IdentityScanState.ScanType.DOC_FRONT
else -> throw IllegalStateException("Invalid CollectedDataParam.Type")
}
diff --git a/identity/src/test/java/com/stripe/android/identity/states/IDDetectorTransitionerTest.kt b/identity/src/test/java/com/stripe/android/identity/states/IDDetectorTransitionerTest.kt
index 78a94dc19cd..723602b71bd 100644
--- a/identity/src/test/java/com/stripe/android/identity/states/IDDetectorTransitionerTest.kt
+++ b/identity/src/test/java/com/stripe/android/identity/states/IDDetectorTransitionerTest.kt
@@ -37,7 +37,7 @@ internal class IDDetectorTransitionerTest {
transitioner.timeoutAt = mockNeverTimeoutClockMark
val foundState = IdentityScanState.Found(
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner,
mockReachedStateAt
)
@@ -64,7 +64,7 @@ internal class IDDetectorTransitionerTest {
transitioner.timeoutAt = mockNeverTimeoutClockMark
val foundState = IdentityScanState.Found(
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner,
mockReachedStateAt
)
@@ -94,7 +94,7 @@ internal class IDDetectorTransitionerTest {
transitioner.timeoutAt = mockNeverTimeoutClockMark
val mockFoundState = mock().also {
- whenever(it.type).thenReturn(ScanType.ID_FRONT)
+ whenever(it.type).thenReturn(ScanType.DOC_FRONT)
whenever(it.reachedStateAt).thenReturn(mockReachedStateAt)
whenever(it.transitioner).thenReturn(transitioner)
}
@@ -121,7 +121,7 @@ internal class IDDetectorTransitionerTest {
assertThat(resultState).isInstanceOf(IdentityScanState.Satisfied::class.java)
assertThat((resultState as IdentityScanState.Satisfied).type).isEqualTo(
- ScanType.ID_FRONT
+ ScanType.DOC_FRONT
)
}
@@ -142,7 +142,7 @@ internal class IDDetectorTransitionerTest {
whenever(mockReachedStateAt.elapsedSince()).thenReturn((timeRequired - 10).milliseconds)
val foundState = IdentityScanState.Found(
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner,
mockReachedStateAt
)
@@ -186,7 +186,7 @@ internal class IDDetectorTransitionerTest {
// never meets required time
whenever(mockReachedStateAt.elapsedSince()).thenReturn((timeRequired - 10).milliseconds)
val mockFoundState = mock().also {
- whenever(it.type).thenReturn(ScanType.ID_FRONT)
+ whenever(it.type).thenReturn(ScanType.DOC_FRONT)
whenever(it.reachedStateAt).thenReturn(mockReachedStateAt)
whenever(it.transitioner).thenReturn(transitioner)
}
@@ -222,9 +222,9 @@ internal class IDDetectorTransitionerTest {
assertThat(resultState).isInstanceOf(IdentityScanState.Unsatisfied::class.java)
assertThat((resultState as IdentityScanState.Unsatisfied).reason).isEqualTo(
- "Type ${Category.ID_BACK} doesn't match ${ScanType.ID_FRONT}"
+ "Type ${Category.ID_BACK} doesn't match ${ScanType.DOC_FRONT}"
)
- assertThat(resultState.type).isEqualTo(ScanType.ID_FRONT)
+ assertThat(resultState.type).isEqualTo(ScanType.DOC_FRONT)
}
@Test
@@ -243,7 +243,7 @@ internal class IDDetectorTransitionerTest {
// never meets required time
whenever(mockReachedStateAt.elapsedSince()).thenReturn((timeRequired - 10).milliseconds)
val mockFoundState = mock().also {
- whenever(it.type).thenReturn(ScanType.ID_FRONT)
+ whenever(it.type).thenReturn(ScanType.DOC_FRONT)
whenever(it.reachedStateAt).thenReturn(mockReachedStateAt)
whenever(it.transitioner).thenReturn(transitioner)
}
@@ -280,7 +280,7 @@ internal class IDDetectorTransitionerTest {
)
assertThat(resultState).isInstanceOf(IdentityScanState.Satisfied::class.java)
- assertThat(resultState.type).isEqualTo(ScanType.ID_FRONT)
+ assertThat(resultState.type).isEqualTo(ScanType.DOC_FRONT)
}
@Test
@@ -292,7 +292,7 @@ internal class IDDetectorTransitionerTest {
transitioner.timeoutAt = mockAlwaysTimeoutClockMark
val initialState = IdentityScanState.Initial(
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner
)
@@ -314,7 +314,7 @@ internal class IDDetectorTransitionerTest {
transitioner.timeoutAt = mockNeverTimeoutClockMark
val initialState = IdentityScanState.Initial(
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner
)
@@ -336,7 +336,7 @@ internal class IDDetectorTransitionerTest {
transitioner.timeoutAt = mockNeverTimeoutClockMark
val initialState = IdentityScanState.Initial(
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner
)
@@ -365,7 +365,7 @@ internal class IDDetectorTransitionerTest {
assertThat(
transitioner.transitionFromSatisfied(
IdentityScanState.Satisfied(
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner,
reachedStateAt = mockReachAtClockMark
),
@@ -390,7 +390,7 @@ internal class IDDetectorTransitionerTest {
val satisfiedState =
IdentityScanState.Satisfied(
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner,
reachedStateAt = mockReachAtClockMark
)
@@ -413,7 +413,7 @@ internal class IDDetectorTransitionerTest {
transitioner.transitionFromUnsatisfied(
IdentityScanState.Unsatisfied(
"reason",
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner
),
mock(),
@@ -438,7 +438,7 @@ internal class IDDetectorTransitionerTest {
val unsatisfiedState =
IdentityScanState.Unsatisfied(
"reason",
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitioner,
reachedStateAt = mockReachAtClockMark
)
@@ -471,7 +471,7 @@ internal class IDDetectorTransitionerTest {
val unsatisfiedState =
IdentityScanState.Unsatisfied(
"reason",
- ScanType.ID_FRONT,
+ ScanType.DOC_FRONT,
transitionerSpy,
reachedStateAt = mockReachAtClockMark
)
diff --git a/identity/src/test/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffectTest.kt b/identity/src/test/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffectTest.kt
index a907a391e58..a21a9315af6 100644
--- a/identity/src/test/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffectTest.kt
+++ b/identity/src/test/java/com/stripe/android/identity/ui/CameraScreenLaunchedEffectTest.kt
@@ -188,7 +188,7 @@ class CameraScreenLaunchedEffectTest {
@Test
fun verifyIDDetectorFrontFinishedResult() {
- targetScanFlow.update { IdentityScanState.ScanType.ID_FRONT }
+ targetScanFlow.update { IdentityScanState.ScanType.DOC_FRONT }
finalResult.postValue(
IdentityAggregator.FinalResult(
frame = mock(),
@@ -200,7 +200,7 @@ class CameraScreenLaunchedEffectTest {
blurScore = ID_FRONT_BLUR_SCORE
),
identityState = IdentityScanState.Finished(
- type = IdentityScanState.ScanType.ID_FRONT,
+ type = IdentityScanState.ScanType.DOC_FRONT,
transitioner = mock()
)
)
@@ -225,7 +225,7 @@ class CameraScreenLaunchedEffectTest {
@Test
fun verifyIDDetectorFrontTimeOutResult() {
- targetScanFlow.update { IdentityScanState.ScanType.ID_FRONT }
+ targetScanFlow.update { IdentityScanState.ScanType.DOC_FRONT }
finalResult.postValue(
IdentityAggregator.FinalResult(
frame = mock(),
@@ -237,7 +237,7 @@ class CameraScreenLaunchedEffectTest {
blurScore = ID_FRONT_BLUR_SCORE
),
identityState = IdentityScanState.TimeOut(
- type = IdentityScanState.ScanType.ID_FRONT,
+ type = IdentityScanState.ScanType.DOC_FRONT,
transitioner = mock()
)
)
@@ -251,7 +251,7 @@ class CameraScreenLaunchedEffectTest {
}
verify(mockIdentityAnalyticsRequestFactory).documentTimeout(
- eq(IdentityScanState.ScanType.ID_FRONT)
+ eq(IdentityScanState.ScanType.DOC_FRONT)
)
verify(mockNavController).navigate(
argWhere {
@@ -264,7 +264,7 @@ class CameraScreenLaunchedEffectTest {
@Test
fun verifyIDDetectorBackFinishedResult() {
- targetScanFlow.update { IdentityScanState.ScanType.ID_BACK }
+ targetScanFlow.update { IdentityScanState.ScanType.DOC_BACK }
finalResult.postValue(
IdentityAggregator.FinalResult(
frame = mock(),
@@ -276,7 +276,7 @@ class CameraScreenLaunchedEffectTest {
blurScore = ID_BACK_BLUR_SCORE
),
identityState = IdentityScanState.Finished(
- type = IdentityScanState.ScanType.ID_BACK,
+ type = IdentityScanState.ScanType.DOC_BACK,
transitioner = mock()
)
)
@@ -301,7 +301,7 @@ class CameraScreenLaunchedEffectTest {
@Test
fun verifyIDDetectorBackTimeOutResult() {
- targetScanFlow.update { IdentityScanState.ScanType.ID_BACK }
+ targetScanFlow.update { IdentityScanState.ScanType.DOC_BACK }
finalResult.postValue(
IdentityAggregator.FinalResult(
frame = mock(),
@@ -313,7 +313,7 @@ class CameraScreenLaunchedEffectTest {
blurScore = 1.0f
),
identityState = IdentityScanState.TimeOut(
- type = IdentityScanState.ScanType.ID_BACK,
+ type = IdentityScanState.ScanType.DOC_BACK,
transitioner = mock()
)
)
@@ -327,7 +327,7 @@ class CameraScreenLaunchedEffectTest {
}
verify(mockIdentityAnalyticsRequestFactory).documentTimeout(
- eq(IdentityScanState.ScanType.ID_BACK)
+ eq(IdentityScanState.ScanType.DOC_BACK)
)
verify(mockNavController).navigate(
argWhere {
diff --git a/identity/src/test/java/com/stripe/android/identity/ui/DocumentScanScreenTest.kt b/identity/src/test/java/com/stripe/android/identity/ui/DocumentScanScreenTest.kt
index a7cf1a44b26..b39a48c57fd 100644
--- a/identity/src/test/java/com/stripe/android/identity/ui/DocumentScanScreenTest.kt
+++ b/identity/src/test/java/com/stripe/android/identity/ui/DocumentScanScreenTest.kt
@@ -10,15 +10,13 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onChildAt
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
-import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.MediatorLiveData
import androidx.navigation.NavController
import androidx.test.core.app.ApplicationProvider
import com.stripe.android.identity.R
import com.stripe.android.identity.TestApplication
-import com.stripe.android.identity.navigation.IDScanDestination
import com.stripe.android.identity.networking.Resource
import com.stripe.android.identity.networking.models.CollectedDataParam
-import com.stripe.android.identity.networking.models.VerificationPage
import com.stripe.android.identity.states.IdentityScanState
import com.stripe.android.identity.viewmodel.IdentityScanViewModel
import com.stripe.android.identity.viewmodel.IdentityViewModel
@@ -34,7 +32,6 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
-import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@@ -49,30 +46,43 @@ class DocumentScanScreenTest {
private val context = ApplicationProvider.getApplicationContext()
private val mockNavController = mock()
- private val verificationPageLiveData =
- MutableLiveData(Resource.success(mock()))
private val targetScanTypeFlow = MutableStateFlow(null)
private val displayStateChangedFlow =
MutableStateFlow?>(null)
-
+ private val scannerStateFlow = MutableStateFlow(IdentityScanViewModel.State.Initial)
+ private val collectedDataFlow = MutableStateFlow(CollectedDataParam())
private val mockIdentityViewModel = mock {
- on { verificationPage } doReturn verificationPageLiveData
- on { pageAndModelFiles } doReturn mock()
on { identityAnalyticsRequestFactory } doReturn mock()
on { workContext } doReturn UnconfinedTestDispatcher()
on { screenTracker } doReturn mock()
+ on { fpsTracker } doReturn mock()
+ on { pageAndModelFiles } doReturn MediatorLiveData>(
+ Resource.success(
+ IdentityViewModel.PageAndModelFiles(mock(), mock(), null)
+ )
+ )
+ on { collectedData } doReturn collectedDataFlow
}
private val mockIdentityScanViewModel = mock {
on { targetScanTypeFlow } doReturn targetScanTypeFlow
on { displayStateChangedFlow } doReturn displayStateChangedFlow
on { interimResults } doReturn mock()
on { finalResult } doReturn mock()
+ on { scannerState } doReturn scannerStateFlow
+ }
+
+ @Test
+ fun verifyLoading() {
+ testDocumentScanScreen(
+ scannerState = IdentityScanViewModel.State.Initial
+ ) {
+ onNodeWithTag(LOADING_SCREEN_TAG).assertExists()
+ }
}
@Test
fun verifyNullState() {
testDocumentScanScreen {
- verify(mockIdentityViewModel, times(0)).resetDocumentUploadedState()
onNodeWithTag(SCAN_TITLE_TAG).assertTextEquals(context.getString(R.string.stripe_front_of_id))
onNodeWithTag(SCAN_MESSAGE_TAG).assertTextEquals(context.getString(R.string.stripe_position_id_front))
onNodeWithTag(CHECK_MARK_TAG).assertDoesNotExist()
@@ -83,7 +93,6 @@ class DocumentScanScreenTest {
@Test
fun verifyNullStateWithShouldStartFromBack() {
testDocumentScanScreen(shouldStartFromBack = true) {
- verify(mockIdentityViewModel).resetDocumentUploadedState()
onNodeWithTag(SCAN_TITLE_TAG).assertTextEquals(context.getString(R.string.stripe_front_of_id))
onNodeWithTag(SCAN_MESSAGE_TAG).assertTextEquals(context.getString(R.string.stripe_position_id_front))
onNodeWithTag(CHECK_MARK_TAG).assertDoesNotExist()
@@ -95,7 +104,7 @@ class DocumentScanScreenTest {
fun verifyInitialStateWithFrontType() {
testDocumentScanScreen(
displayState = mock(),
- targetScanType = IdentityScanState.ScanType.ID_FRONT
+ targetScanType = IdentityScanState.ScanType.DOC_FRONT
) {
onNodeWithTag(SCAN_TITLE_TAG).assertTextEquals(context.getString(R.string.stripe_front_of_id))
onNodeWithTag(SCAN_MESSAGE_TAG).assertTextEquals(context.getString(R.string.stripe_position_id_front))
@@ -108,7 +117,7 @@ class DocumentScanScreenTest {
fun verifyInitialStateWithBackType() {
testDocumentScanScreen(
displayState = mock(),
- targetScanType = IdentityScanState.ScanType.ID_BACK
+ targetScanType = IdentityScanState.ScanType.DOC_BACK
) {
onNodeWithTag(SCAN_TITLE_TAG).assertTextEquals(context.getString(R.string.stripe_back_of_id))
onNodeWithTag(SCAN_MESSAGE_TAG).assertTextEquals(context.getString(R.string.stripe_position_id_back))
@@ -121,7 +130,8 @@ class DocumentScanScreenTest {
fun verifyFinishedState() {
testDocumentScanScreen(
displayState = mock(),
- targetScanType = IdentityScanState.ScanType.ID_FRONT
+ targetScanType = IdentityScanState.ScanType.DOC_FRONT,
+ scannerState = IdentityScanViewModel.State.Scanned
) {
verify(mockIdentityScanViewModel).stopScan(any())
onNodeWithTag(SCAN_TITLE_TAG).assertTextEquals(context.getString(R.string.stripe_front_of_id))
@@ -134,8 +144,6 @@ class DocumentScanScreenTest {
verify(mockIdentityViewModel).collectDataForDocumentScanScreen(
eq(mockNavController),
eq(true),
- eq(CollectedDataParam.Type.IDCARD),
- eq(IDScanDestination.ROUTE.route),
any()
)
}
@@ -146,28 +154,28 @@ class DocumentScanScreenTest {
displayState: IdentityScanState? = null,
targetScanType: IdentityScanState.ScanType? = null,
shouldStartFromBack: Boolean = false,
+ scannerState: IdentityScanViewModel.State = IdentityScanViewModel.State.Initialized,
testBlock: ComposeContentTestRule.() -> Unit = {}
) {
targetScanTypeFlow.update { targetScanType }
displayState?.let {
displayStateChangedFlow.update { displayState to mock() }
}
+ collectedDataFlow.update {
+ if (shouldStartFromBack) {
+ CollectedDataParam(
+ idDocumentFront = mock()
+ )
+ } else {
+ CollectedDataParam()
+ }
+ }
+ scannerStateFlow.update { scannerState }
composeTestRule.setContent {
DocumentScanScreen(
navController = mockNavController,
identityViewModel = mockIdentityViewModel,
identityScanViewModel = mockIdentityScanViewModel,
- frontScanType = IdentityScanState.ScanType.ID_FRONT,
- backScanType = IdentityScanState.ScanType.ID_BACK,
- shouldStartFromBack = shouldStartFromBack,
- messageRes = DocumentScanMessageRes(
- R.string.stripe_front_of_id,
- R.string.stripe_back_of_id,
- R.string.stripe_position_id_front,
- R.string.stripe_position_id_back
- ),
- collectedDataParamType = CollectedDataParam.Type.IDCARD,
- route = IDScanDestination.ROUTE.route
)
}
with(composeTestRule, testBlock)
diff --git a/identity/src/test/java/com/stripe/android/identity/ui/UploadScreenTest.kt b/identity/src/test/java/com/stripe/android/identity/ui/UploadScreenTest.kt
index 74fae40fc5b..48135c24532 100644
--- a/identity/src/test/java/com/stripe/android/identity/ui/UploadScreenTest.kt
+++ b/identity/src/test/java/com/stripe/android/identity/ui/UploadScreenTest.kt
@@ -10,16 +10,14 @@ import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.lifecycle.MutableLiveData
import androidx.navigation.NavController
-import com.stripe.android.identity.R
import com.stripe.android.identity.TestApplication
-import com.stripe.android.identity.navigation.IDUploadDestination
+import com.stripe.android.identity.navigation.DocumentUploadDestination
import com.stripe.android.identity.networking.Resource
import com.stripe.android.identity.networking.SingleSideDocumentUploadState
import com.stripe.android.identity.networking.models.CollectedDataParam
import com.stripe.android.identity.networking.models.Requirement
import com.stripe.android.identity.networking.models.VerificationPage
import com.stripe.android.identity.networking.models.VerificationPageStaticContentDocumentCapturePage
-import com.stripe.android.identity.states.IdentityScanState
import com.stripe.android.identity.utils.IdentityImageHandler
import com.stripe.android.identity.viewmodel.IdentityViewModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -80,7 +78,7 @@ class UploadScreenTest {
@Test
fun `when front is not uploaded, front upload UI is enabled and back UI is not visible`() {
- testUploadScreen(hasBack = false) {
+ testUploadScreen {
onNodeWithTag(FRONT_ROW_TAG).assertExists()
onNodeWithTag(BACK_ROW_TAG).assertDoesNotExist()
onNodeWithTag(UPLOAD_SCREEN_CONTINUE_BUTTON_TAG).onChildAt(0).assertIsNotEnabled()
@@ -138,7 +136,7 @@ class UploadScreenTest {
runBlocking {
verify(mockIdentityViewModel).navigateToSelfieOrSubmit(
same(mockNavController),
- eq(IDUploadDestination.ROUTE.route)
+ eq(DocumentUploadDestination.ROUTE.route)
)
}
}
@@ -187,32 +185,12 @@ class UploadScreenTest {
}
private fun testUploadScreen(
- hasBack: Boolean = true,
testBlock: ComposeContentTestRule.() -> Unit
) {
composeTestRule.setContent {
UploadScreen(
navController = mockNavController,
- identityViewModel = mockIdentityViewModel,
- collectedDataParamType = CollectedDataParam.Type.IDCARD,
- route = IDUploadDestination.ROUTE.route,
- titleRes = R.string.stripe_file_upload,
- contextRes = R.string.stripe_file_upload_content_dl,
- frontInfo = DocumentUploadSideInfo(
- descriptionRes = R.string.stripe_front_of_dl,
- checkmarkContentDescriptionRes = R.string.stripe_front_of_dl_selected,
- scanType = FRONT_SCAN_TYPE
- ),
- backInfo =
- if (hasBack) {
- DocumentUploadSideInfo(
- descriptionRes = R.string.stripe_back_of_dl,
- checkmarkContentDescriptionRes = R.string.stripe_back_of_dl_selected,
- scanType = BACK_SCAN_TYPE
- )
- } else {
- null
- }
+ identityViewModel = mockIdentityViewModel
)
}
with(composeTestRule, testBlock)
@@ -224,11 +202,7 @@ class UploadScreenTest {
) {
composeTestRule.setContent {
UploadImageDialog(
- uploadInfo = DocumentUploadSideInfo(
- descriptionRes = R.string.stripe_front_of_dl,
- checkmarkContentDescriptionRes = R.string.stripe_front_of_dl_selected,
- scanType = FRONT_SCAN_TYPE
- ),
+ isFront = true,
shouldShowTakePhoto = true,
shouldShowChoosePhoto = shouldShowChoosePhoto,
onPhotoSelected = onPhotoSelected,
@@ -240,8 +214,6 @@ class UploadScreenTest {
}
private companion object {
- val FRONT_SCAN_TYPE = IdentityScanState.ScanType.ID_FRONT
- val BACK_SCAN_TYPE = IdentityScanState.ScanType.ID_BACK
val UPLOADED_STATE = SingleSideDocumentUploadState(
highResResult = Resource.success(mock()),
lowResResult = Resource.success(mock())
diff --git a/identity/src/test/java/com/stripe/android/identity/viewmodel/IdentityViewModelTest.kt b/identity/src/test/java/com/stripe/android/identity/viewmodel/IdentityViewModelTest.kt
index 6941b806c37..df07370042c 100644
--- a/identity/src/test/java/com/stripe/android/identity/viewmodel/IdentityViewModelTest.kt
+++ b/identity/src/test/java/com/stripe/android/identity/viewmodel/IdentityViewModelTest.kt
@@ -34,11 +34,13 @@ import com.stripe.android.identity.analytics.ScreenTracker
import com.stripe.android.identity.camera.IdentityAggregator
import com.stripe.android.identity.ml.AnalyzerInput
import com.stripe.android.identity.ml.BoundingBox
+import com.stripe.android.identity.ml.Category
import com.stripe.android.identity.ml.FaceDetectorOutput
import com.stripe.android.identity.ml.IDDetectorOutput
import com.stripe.android.identity.navigation.ConfirmationDestination
import com.stripe.android.identity.navigation.ConsentDestination
import com.stripe.android.identity.navigation.DocSelectionDestination
+import com.stripe.android.identity.navigation.DocumentScanDestination
import com.stripe.android.identity.navigation.ErrorDestination
import com.stripe.android.identity.navigation.IdentityTopLevelDestination
import com.stripe.android.identity.navigation.SelfieWarmupDestination
@@ -49,7 +51,6 @@ import com.stripe.android.identity.networking.Resource
import com.stripe.android.identity.networking.SingleSideDocumentUploadState
import com.stripe.android.identity.networking.UploadedResult
import com.stripe.android.identity.networking.models.CollectedDataParam
-import com.stripe.android.identity.networking.models.CollectedDataParam.Companion.toScanDestination
import com.stripe.android.identity.networking.models.DocumentUploadParam
import com.stripe.android.identity.networking.models.Requirement
import com.stripe.android.identity.networking.models.VerificationPage
@@ -177,7 +178,6 @@ internal class IdentityViewModelTest {
@Test
fun `resetDocumentUploadedState does reset _documentUploadedState`() {
- viewModel.resetDocumentUploadedState()
assertThat(viewModel.documentFrontUploadedState.value).isEqualTo(
SingleSideDocumentUploadState()
)
@@ -221,8 +221,7 @@ internal class IdentityViewModelTest {
mockUploadSuccess()
viewModel.uploadScanResult(
FINAL_FACE_DETECTOR_RESULT,
- mockVerificationPage,
- IdentityScanState.ScanType.SELFIE
+ mockVerificationPage
)
listOf(
@@ -249,8 +248,7 @@ internal class IdentityViewModelTest {
mockUploadFailure()
viewModel.uploadScanResult(
FINAL_FACE_DETECTOR_RESULT,
- mockVerificationPage,
- IdentityScanState.ScanType.SELFIE
+ mockVerificationPage
)
verify(mockIdentityAnalyticsRequestFactory, times(0)).imageUpload(
anyOrNull(),
@@ -322,12 +320,12 @@ internal class IdentityViewModelTest {
viewModel.updateAnalyticsState { oldState ->
oldState.copy(
- scanType = IdentityScanState.ScanType.ID_FRONT
+ scanType = IdentityScanState.ScanType.DOC_FRONT
)
}
assertThat(viewModel.analyticsState.value.scanType).isEqualTo(
- IdentityScanState.ScanType.ID_FRONT
+ IdentityScanState.ScanType.DOC_FRONT
)
assertThat(viewModel.analyticsState.value.requireSelfie).isNull()
assertThat(viewModel.analyticsState.value.docFrontUploadType).isNull()
@@ -339,7 +337,7 @@ internal class IdentityViewModelTest {
}
assertThat(viewModel.analyticsState.value.scanType).isEqualTo(
- IdentityScanState.ScanType.ID_FRONT
+ IdentityScanState.ScanType.DOC_FRONT
)
assertThat(viewModel.analyticsState.value.requireSelfie).isEqualTo(
false
@@ -353,7 +351,7 @@ internal class IdentityViewModelTest {
}
assertThat(viewModel.analyticsState.value.scanType).isEqualTo(
- IdentityScanState.ScanType.ID_FRONT
+ IdentityScanState.ScanType.DOC_FRONT
)
assertThat(viewModel.analyticsState.value.requireSelfie).isEqualTo(
false
@@ -486,7 +484,7 @@ internal class IdentityViewModelTest {
@Test
fun `forceConfirm front - missingBack - navigate to back`() =
- testForceConfirm(VERIFICATION_PAGE_DATA_MISSING_BACK) { targetDestination, failedCollectedDataParam ->
+ testForceConfirm(VERIFICATION_PAGE_DATA_MISSING_BACK) { failedCollectedDataParam ->
// fulfilling front, should post with force confirm front
viewModel.postVerificationPageDataForForceConfirm(
requirementToForceConfirm = Requirement.IDDOCUMENTFRONT,
@@ -508,14 +506,14 @@ internal class IdentityViewModelTest {
)
verify(mockController).navigate(
- eq(targetDestination.routeWithArgs),
+ eq(DocumentScanDestination.routeWithArgs),
any Unit>()
)
}
@Test
fun `forceConfirm back - missingSelfie - navigate to selfie warmup`() =
- testForceConfirm(VERIFICATION_PAGE_DATA_MISSING_SELFIE) { _, failedCollectedDataParam ->
+ testForceConfirm(VERIFICATION_PAGE_DATA_MISSING_SELFIE) { failedCollectedDataParam ->
// fulfilling back, should post with force confirm bcak
viewModel.postVerificationPageDataForForceConfirm(
requirementToForceConfirm = Requirement.IDDOCUMENTBACK,
@@ -544,7 +542,7 @@ internal class IdentityViewModelTest {
@Test
fun `forceConfirm back - noMissing - submit`() =
- testForceConfirm(CORRECT_WITH_SUBMITTED_SUCCESS_VERIFICATION_PAGE_DATA) { _, failedCollectedDataParam ->
+ testForceConfirm(CORRECT_WITH_SUBMITTED_SUCCESS_VERIFICATION_PAGE_DATA) { failedCollectedDataParam ->
whenever(
mockIdentityRepository.postVerificationPageSubmit(
@@ -904,7 +902,7 @@ internal class IdentityViewModelTest {
isFront,
DOCUMENT_CAPTURE,
DocumentUploadParam.UploadMethod.FILEUPLOAD,
- IdentityScanState.ScanType.DL_FRONT
+ IdentityScanState.ScanType.DOC_FRONT
)
verify(mockIdentityIO).resizeUriAndCreateFileToUpload(
@@ -946,13 +944,12 @@ internal class IdentityViewModelTest {
mockUploadSuccess()
viewModel.uploadScanResult(
- FINAL_ID_DETECTOR_RESULT,
- mockVerificationPage,
if (isFront) {
- IdentityScanState.ScanType.ID_FRONT
+ FINAL_ID_DETECTOR_RESULT_FRONT
} else {
- IdentityScanState.ScanType.ID_BACK
- }
+ FINAL_ID_DETECTOR_RESULT_BACK
+ },
+ mockVerificationPage
)
// high res upload
@@ -1098,7 +1095,7 @@ internal class IdentityViewModelTest {
isFront,
DOCUMENT_CAPTURE,
DocumentUploadParam.UploadMethod.FILEUPLOAD,
- IdentityScanState.ScanType.DL_FRONT
+ IdentityScanState.ScanType.DOC_FRONT
)
verify(mockIdentityAnalyticsRequestFactory, times(0)).imageUpload(
@@ -1126,16 +1123,16 @@ internal class IdentityViewModelTest {
private fun testForceConfirm(
verificationPageDataResponse: VerificationPageData,
- paramsCallback: suspend (IdentityTopLevelDestination, CollectedDataParam) -> Unit
+ paramsCallback: suspend (CollectedDataParam) -> Unit
) = runBlocking {
// mock failed scanning front of driver license
val failedDocumentType = CollectedDataParam.Type.DRIVINGLICENSE
- // failed front, now fulfilled, target destination should be failed back
- val targetDestination = failedDocumentType.toScanDestination(
- shouldStartFromBack = true,
- shouldPopUpToDocSelection = true
- )
+// // failed front, now fulfilled, target destination should be failed back
+// val targetDestination = failedDocumentType.toScanDestination(
+// shouldStartFromBack = true,
+// shouldPopUpToDocSelection = true
+// )
val failedCollectedDataParam =
CollectedDataParam(
@@ -1162,7 +1159,6 @@ internal class IdentityViewModelTest {
)
).thenReturn(verificationPageDataResponse)
paramsCallback(
- targetDestination,
failedCollectedDataParam
)
}
@@ -1229,7 +1225,24 @@ internal class IdentityViewModelTest {
val INPUT_BITMAP = mock()
val BOUNDING_BOX = mock()
val ALL_SCORES = listOf(1f, 2f, 3f)
- val FINAL_ID_DETECTOR_RESULT = IdentityAggregator.FinalResult(
+ val FINAL_ID_DETECTOR_RESULT_FRONT = IdentityAggregator.FinalResult(
+ frame = AnalyzerInput(
+ CameraPreviewImage(
+ INPUT_BITMAP,
+ mock()
+ ),
+ mock()
+ ),
+ result = IDDetectorOutput(
+ boundingBox = BOUNDING_BOX,
+ category = Category.ID_FRONT,
+ resultScore = 0.8f,
+ allScores = ALL_SCORES,
+ blurScore = 1.0f
+ ),
+ identityState = mock()
+ )
+ val FINAL_ID_DETECTOR_RESULT_BACK = IdentityAggregator.FinalResult(
frame = AnalyzerInput(
CameraPreviewImage(
INPUT_BITMAP,
@@ -1239,7 +1252,7 @@ internal class IdentityViewModelTest {
),
result = IDDetectorOutput(
boundingBox = BOUNDING_BOX,
- category = mock(),
+ category = Category.ID_BACK,
resultScore = 0.8f,
allScores = ALL_SCORES,
blurScore = 1.0f