@@ -25,6 +25,7 @@ import com.bitwarden.vault.CipherView
2525import com.bitwarden.vault.DecryptCipherListResult
2626import com.bitwarden.vault.FolderView
2727import com.x8bit.bitwarden.data.auth.repository.AuthRepository
28+ import com.bitwarden.ui.platform.feature.cardscanner.manager.CardScanManager
2829import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
2930import com.x8bit.bitwarden.data.auth.repository.model.UserState
3031import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
@@ -55,13 +56,15 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratorResult
5556import com.x8bit.bitwarden.data.vault.manager.model.GetCipherResult
5657import com.x8bit.bitwarden.data.vault.repository.VaultRepository
5758import com.x8bit.bitwarden.data.vault.repository.model.ArchiveCipherResult
59+ import com.bitwarden.ui.platform.feature.cardscanner.util.CardScanResult
5860import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
5961import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
6062import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
6163import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
6264import com.x8bit.bitwarden.data.vault.repository.model.UnarchiveCipherResult
6365import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
6466import com.x8bit.bitwarden.data.vault.repository.model.VaultData
67+ import com.x8bit.bitwarden.ui.vault.util.detectCardBrand
6568import com.x8bit.bitwarden.ui.credentials.manager.model.CreateCredentialResult
6669import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
6770import com.x8bit.bitwarden.ui.platform.model.SnackbarRelay
@@ -125,6 +128,7 @@ class VaultAddEditViewModel @Inject constructor(
125128 private val toastManager : ToastManager ,
126129 private val authRepository : AuthRepository ,
127130 private val clipboardManager : BitwardenClipboardManager ,
131+ private val cardScanManager : CardScanManager ,
128132 private val policyManager : PolicyManager ,
129133 private val vaultRepository : VaultRepository ,
130134 private val bitwardenCredentialManager : BitwardenCredentialManager ,
@@ -178,6 +182,7 @@ class VaultAddEditViewModel @Inject constructor(
178182
179183 VaultAddEditState (
180184 isArchiveEnabled = featureFlagManager.getFeatureFlag(FlagKey .ArchiveItems ),
185+ isCardScannerEnabled = featureFlagManager.getFeatureFlag(FlagKey .CardScanner ),
181186 vaultAddEditType = vaultAddEditType,
182187 cipherType = vaultCipherType,
183188 viewState = when (vaultAddEditType) {
@@ -279,6 +284,18 @@ class VaultAddEditViewModel @Inject constructor(
279284 .onEach(::sendAction)
280285 .launchIn(viewModelScope)
281286
287+ featureFlagManager
288+ .getFeatureFlagFlow(FlagKey .CardScanner )
289+ .map { VaultAddEditAction .Internal .CardScannerFlagUpdateReceive (it) }
290+ .onEach(::sendAction)
291+ .launchIn(viewModelScope)
292+
293+ cardScanManager
294+ .cardScanResultFlow
295+ .map { VaultAddEditAction .Internal .CardScanResultReceive (it) }
296+ .onEach(::sendAction)
297+ .launchIn(viewModelScope)
298+
282299 snackbarRelayManager
283300 .getSnackbarDataFlow(SnackbarRelay .CIPHER_MOVED_TO_ORGANIZATION )
284301 .map { VaultAddEditAction .Internal .SnackbarDataReceived (it) }
@@ -1542,6 +1559,24 @@ class VaultAddEditViewModel @Inject constructor(
15421559 is VaultAddEditAction .ItemType .CardType .SecurityCodeVisibilityChange -> {
15431560 handleSecurityCodeVisibilityChange(action)
15441561 }
1562+
1563+ is VaultAddEditAction .ItemType .CardType .ScanCardClick -> {
1564+ handleScanCardClick(action)
1565+ }
1566+ }
1567+ }
1568+
1569+ private fun handleScanCardClick (
1570+ action : VaultAddEditAction .ItemType .CardType .ScanCardClick ,
1571+ ) {
1572+ if (! state.isCardScannerEnabled) return
1573+ if (action.isGranted) {
1574+ sendEvent(VaultAddEditEvent .NavigateToCardScan )
1575+ } else {
1576+ toastManager.show(
1577+ messageId = BitwardenString
1578+ .enable_camera_permission_to_use_the_scanner,
1579+ )
15451580 }
15461581 }
15471582
@@ -1653,6 +1688,14 @@ class VaultAddEditViewModel @Inject constructor(
16531688 handleArchiveItemsFlagUpdateReceive(action)
16541689 }
16551690
1691+ is VaultAddEditAction .Internal .CardScannerFlagUpdateReceive -> {
1692+ handleCardScannerFlagUpdateReceive(action)
1693+ }
1694+
1695+ is VaultAddEditAction .Internal .CardScanResultReceive -> {
1696+ handleCardScanResultReceive(action)
1697+ }
1698+
16561699 is VaultAddEditAction .Internal .DeleteCipherReceive -> handleDeleteCipherReceive(action)
16571700 is VaultAddEditAction .Internal .TotpCodeReceive -> handleVaultTotpCodeReceive(action)
16581701 is VaultAddEditAction .Internal .VaultDataReceive -> handleVaultDataReceive(action)
@@ -1870,6 +1913,41 @@ class VaultAddEditViewModel @Inject constructor(
18701913 mutableStateFlow.update { it.copy(isArchiveEnabled = action.isEnabled) }
18711914 }
18721915
1916+ private fun handleCardScannerFlagUpdateReceive (
1917+ action : VaultAddEditAction .Internal .CardScannerFlagUpdateReceive ,
1918+ ) {
1919+ mutableStateFlow.update { it.copy(isCardScannerEnabled = action.isEnabled) }
1920+ }
1921+
1922+ private fun handleCardScanResultReceive (
1923+ action : VaultAddEditAction .Internal .CardScanResultReceive ,
1924+ ) {
1925+ when (val result = action.cardScanResult) {
1926+ is CardScanResult .Success -> {
1927+ val data = result.cardScanData
1928+ updateCardContent { cardType ->
1929+ cardType.copy(
1930+ number = data.number ? : cardType.number,
1931+ cardHolderName = data.cardholderName
1932+ ? : cardType.cardHolderName,
1933+ expirationYear = data.expirationYear
1934+ ? : cardType.expirationYear,
1935+ expirationMonth = data.expirationMonth
1936+ ?.toExpirationMonth()
1937+ ? : cardType.expirationMonth,
1938+ securityCode = data.securityCode
1939+ ? : cardType.securityCode,
1940+ brand = data.number
1941+ ?.detectCardBrand()
1942+ ? : cardType.brand,
1943+ )
1944+ }
1945+ }
1946+
1947+ is CardScanResult .ScanError -> Unit
1948+ }
1949+ }
1950+
18731951 private fun handleDeleteCipherReceive (action : VaultAddEditAction .Internal .DeleteCipherReceive ) {
18741952 when (val result = action.result) {
18751953 is DeleteCipherResult .Error -> {
@@ -2404,6 +2482,24 @@ class VaultAddEditViewModel @Inject constructor(
24042482 // endregion Utility Functions
24052483}
24062484
2485+ @Suppress(" MagicNumber" )
2486+ private fun String.toExpirationMonth (): VaultCardExpirationMonth =
2487+ when (this .toIntOrNull()) {
2488+ 1 -> VaultCardExpirationMonth .JANUARY
2489+ 2 -> VaultCardExpirationMonth .FEBRUARY
2490+ 3 -> VaultCardExpirationMonth .MARCH
2491+ 4 -> VaultCardExpirationMonth .APRIL
2492+ 5 -> VaultCardExpirationMonth .MAY
2493+ 6 -> VaultCardExpirationMonth .JUNE
2494+ 7 -> VaultCardExpirationMonth .JULY
2495+ 8 -> VaultCardExpirationMonth .AUGUST
2496+ 9 -> VaultCardExpirationMonth .SEPTEMBER
2497+ 10 -> VaultCardExpirationMonth .OCTOBER
2498+ 11 -> VaultCardExpirationMonth .NOVEMBER
2499+ 12 -> VaultCardExpirationMonth .DECEMBER
2500+ else -> VaultCardExpirationMonth .SELECT
2501+ }
2502+
24072503/* *
24082504 * Represents the state for adding an item to the vault.
24092505 *
@@ -2428,6 +2524,7 @@ data class VaultAddEditState(
24282524 val defaultUriMatchType : UriMatchType ,
24292525 private val shouldShowCoachMarkTour : Boolean ,
24302526 private val isArchiveEnabled : Boolean ,
2527+ val isCardScannerEnabled : Boolean ,
24312528) : Parcelable {
24322529
24332530 /* *
@@ -3098,6 +3195,11 @@ sealed class VaultAddEditEvent {
30983195 */
30993196 data object NavigateToQrCodeScan : VaultAddEditEvent ()
31003197
3198+ /* *
3199+ * Navigate to the card scan screen.
3200+ */
3201+ data object NavigateToCardScan : VaultAddEditEvent ()
3202+
31013203 /* *
31023204 * Navigate to the manual code entry screen.
31033205 */
@@ -3698,6 +3800,13 @@ sealed class VaultAddEditAction {
36983800 * @property isVisible The new code visibility state.
36993801 */
37003802 data class SecurityCodeVisibilityChange (val isVisible : Boolean ) : CardType()
3803+
3804+ /* *
3805+ * Fired when the scan card button is clicked.
3806+ *
3807+ * @property isGranted Whether camera permission was granted.
3808+ */
3809+ data class ScanCardClick (val isGranted : Boolean ) : CardType()
37013810 }
37023811
37033812 /* *
@@ -3841,5 +3950,19 @@ sealed class VaultAddEditAction {
38413950 data class ArchiveItemsFlagUpdateReceive (
38423951 val isEnabled : Boolean ,
38433952 ) : Internal()
3953+
3954+ /* *
3955+ * Indicates that the Card Scanner flag has been updated.
3956+ */
3957+ data class CardScannerFlagUpdateReceive (
3958+ val isEnabled : Boolean ,
3959+ ) : Internal()
3960+
3961+ /* *
3962+ * Indicates that a card scan result has been received.
3963+ */
3964+ data class CardScanResultReceive (
3965+ val cardScanResult : CardScanResult ,
3966+ ) : Internal()
38443967 }
38453968}
0 commit comments