Skip to content

Commit 397c994

Browse files
committed
Replace cardholder name scan with post-scan focus
Cardholder name OCR detection is unreliable. Remove name from scan results and instead focus the name field after a successful scan so the user can type it manually.
1 parent 6b99c14 commit 397c994

6 files changed

Lines changed: 76 additions & 46 deletions

File tree

app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCardItems.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import androidx.compose.runtime.mutableStateOf
1010
import androidx.compose.runtime.saveable.rememberSaveable
1111
import androidx.compose.runtime.setValue
1212
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.focus.FocusRequester
14+
import androidx.compose.ui.focus.focusRequester
1315
import androidx.compose.ui.platform.LocalResources
1416
import androidx.compose.ui.platform.testTag
1517
import androidx.compose.ui.res.stringResource
@@ -36,6 +38,7 @@ import kotlinx.collections.immutable.toImmutableList
3638
fun LazyListScope.vaultAddEditCardItems(
3739
cardState: VaultAddEditState.ViewState.Content.ItemType.Card,
3840
isCardScannerEnabled: Boolean,
41+
cardHolderNameFocusRequester: FocusRequester,
3942
onScanCardClick: () -> Unit,
4043
cardHandlers: VaultAddEditCardTypeHandlers,
4144
) {
@@ -74,7 +77,8 @@ fun LazyListScope.vaultAddEditCardItems(
7477
cardStyle = CardStyle.Top(),
7578
modifier = Modifier
7679
.fillMaxWidth()
77-
.standardHorizontalMargin(),
80+
.standardHorizontalMargin()
81+
.focusRequester(cardHolderNameFocusRequester),
7882
)
7983
}
8084
item {

app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditItemContent.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.compose.runtime.Composable
1313
import androidx.compose.runtime.mutableStateOf
1414
import androidx.compose.runtime.saveable.rememberSaveable
1515
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.focus.FocusRequester
1617
import androidx.compose.ui.platform.testTag
1718
import androidx.compose.ui.res.stringResource
1819
import androidx.compose.ui.unit.dp
@@ -52,6 +53,7 @@ fun CoachMarkScope<AddEditItemCoachMark>.VaultAddEditContent(
5253
cardItemTypeHandlers: VaultAddEditCardTypeHandlers,
5354
sshKeyItemTypeHandlers: VaultAddEditSshKeyTypeHandlers,
5455
isCardScannerEnabled: Boolean,
56+
cardHolderNameFocusRequester: FocusRequester,
5557
modifier: Modifier = Modifier,
5658
lazyListState: LazyListState,
5759
permissionsManager: PermissionsManager,
@@ -241,6 +243,7 @@ fun CoachMarkScope<AddEditItemCoachMark>.VaultAddEditContent(
241243
vaultAddEditCardItems(
242244
cardState = state.type,
243245
isCardScannerEnabled = isCardScannerEnabled,
246+
cardHolderNameFocusRequester = cardHolderNameFocusRequester,
244247
onScanCardClick = {
245248
if (permissionsManager.checkPermission(Manifest.permission.CAMERA)) {
246249
cardItemTypeHandlers.onScanCardClick(true)

app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
2727
import androidx.compose.runtime.setValue
2828
import androidx.compose.ui.Alignment
2929
import androidx.compose.ui.Modifier
30+
import androidx.compose.ui.focus.FocusRequester
3031
import androidx.compose.ui.input.nestedscroll.nestedScroll
3132
import androidx.compose.ui.platform.testTag
3233
import androidx.compose.ui.res.painterResource
@@ -122,6 +123,7 @@ fun VaultAddEditScreen(
122123
lazyListState = lazyListState,
123124
orderedList = AddEditItemCoachMark.entries,
124125
)
126+
val cardHolderNameFocusRequester = remember { FocusRequester() }
125127
val scope = rememberCoroutineScope()
126128
val snackbarHostState = rememberBitwardenSnackbarHostState()
127129
EventsEffect(viewModel = viewModel) { event ->
@@ -199,6 +201,10 @@ fun VaultAddEditScreen(
199201
is VaultAddEditEvent.NavigateToPremium -> {
200202
intentManager.launchUri(uri = event.uri.toUri())
201203
}
204+
205+
VaultAddEditEvent.FocusCardHolderName -> {
206+
cardHolderNameFocusRequester.requestFocus()
207+
}
202208
}
203209
}
204210

@@ -399,6 +405,7 @@ fun VaultAddEditScreen(
399405
cardItemTypeHandlers = cardItemTypeHandlers,
400406
sshKeyItemTypeHandlers = sshKeyItemTypeHandlers,
401407
isCardScannerEnabled = state.isCardScannerEnabled,
408+
cardHolderNameFocusRequester = cardHolderNameFocusRequester,
402409
lazyListState = lazyListState,
403410
onPreviousCoachMark = {
404411
coroutineScope.launch {

app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1928,8 +1928,6 @@ class VaultAddEditViewModel @Inject constructor(
19281928
updateCardContent { cardType ->
19291929
cardType.copy(
19301930
number = data.number ?: cardType.number,
1931-
cardHolderName = data.cardholderName
1932-
?: cardType.cardHolderName,
19331931
expirationYear = data.expirationYear
19341932
?: cardType.expirationYear,
19351933
expirationMonth = data.expirationMonth
@@ -1942,6 +1940,7 @@ class VaultAddEditViewModel @Inject constructor(
19421940
?: cardType.brand,
19431941
)
19441942
}
1943+
sendEvent(VaultAddEditEvent.FocusCardHolderName)
19451944
}
19461945

19471946
is CardScanResult.ScanError -> Unit
@@ -3244,6 +3243,11 @@ sealed class VaultAddEditEvent {
32443243
* Navigate the user to the learn more help page
32453244
*/
32463245
data object NavigateToLearnMore : VaultAddEditEvent()
3246+
3247+
/**
3248+
* Focus the cardholder name field after a successful card scan.
3249+
*/
3250+
data object FocusCardHolderName : VaultAddEditEvent()
32473251
}
32483252

32493253
/**

app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5142,7 +5142,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
51425142
}
51435143

51445144
@Test
5145-
fun `CardScanResultReceive with Success should update card fields`() =
5145+
fun `CardScanResultReceive with Success should update card fields and focus name`() =
51465146
runTest {
51475147
val viewModel = createAddVaultItemViewModel(
51485148
savedStateHandle = createSavedStateHandleWithState(
@@ -5158,31 +5158,40 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
51585158
vaultItemCipherType = VaultItemCipherType.CARD,
51595159
),
51605160
)
5161-
mutableCardScanResultFlow.tryEmit(
5162-
CardScanResult.Success(
5163-
cardScanData = CardScanData(
5161+
viewModel.eventFlow.test {
5162+
mutableCardScanResultFlow.tryEmit(
5163+
CardScanResult.Success(
5164+
cardScanData = CardScanData(
5165+
number = "4111111111111111",
5166+
expirationMonth = "12",
5167+
expirationYear = "2025",
5168+
securityCode = "123",
5169+
),
5170+
),
5171+
)
5172+
val expectedCard = VaultAddEditState
5173+
.ViewState
5174+
.Content
5175+
.ItemType
5176+
.Card(
51645177
number = "4111111111111111",
5165-
expirationMonth = "12",
51665178
expirationYear = "2025",
5167-
cardholderName = "JOHN DOE",
5179+
expirationMonth = VaultCardExpirationMonth.DECEMBER,
51685180
securityCode = "123",
5169-
),
5170-
),
5171-
)
5172-
val content = viewModel.stateFlow.value.viewState
5173-
as VaultAddEditState.ViewState.Content
5174-
val cardType = content.type
5175-
as VaultAddEditState.ViewState.Content.ItemType.Card
5176-
assertEquals("4111111111111111", cardType.number)
5177-
assertEquals("JOHN DOE", cardType.cardHolderName)
5178-
assertEquals("2025", cardType.expirationYear)
5179-
assertEquals(VaultCardExpirationMonth.DECEMBER, cardType.expirationMonth)
5180-
assertEquals("123", cardType.securityCode)
5181-
assertEquals(VaultCardBrand.VISA, cardType.brand)
5181+
brand = VaultCardBrand.VISA,
5182+
)
5183+
val content = viewModel.stateFlow.value.viewState
5184+
as VaultAddEditState.ViewState.Content
5185+
assertEquals(expectedCard, content.type)
5186+
assertEquals(
5187+
VaultAddEditEvent.FocusCardHolderName,
5188+
awaitItem(),
5189+
)
5190+
}
51825191
}
51835192

51845193
@Test
5185-
fun `CardScanResultReceive with ScanError should not change state`() =
5194+
fun `CardScanResultReceive with ScanError should not change state or focus`() =
51865195
runTest {
51875196
val initialCardState = VaultAddEditState
51885197
.ViewState
@@ -5199,12 +5208,13 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
51995208
vaultItemCipherType = VaultItemCipherType.CARD,
52005209
),
52015210
)
5202-
mutableCardScanResultFlow.tryEmit(CardScanResult.ScanError())
5203-
val content = viewModel.stateFlow.value.viewState
5204-
as VaultAddEditState.ViewState.Content
5205-
val cardType = content.type
5206-
as VaultAddEditState.ViewState.Content.ItemType.Card
5207-
assertEquals(initialCardState, cardType)
5211+
viewModel.eventFlow.test {
5212+
mutableCardScanResultFlow.tryEmit(CardScanResult.ScanError())
5213+
val content = viewModel.stateFlow.value.viewState
5214+
as VaultAddEditState.ViewState.Content
5215+
assertEquals(initialCardState, content.type)
5216+
expectNoEvents()
5217+
}
52085218
}
52095219

52105220
@Test
@@ -5323,23 +5333,26 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
53235333
vaultItemCipherType = VaultItemCipherType.CARD,
53245334
),
53255335
)
5326-
mutableCardScanResultFlow.tryEmit(
5327-
CardScanResult.Success(
5328-
cardScanData = CardScanData(
5329-
number = "4111111111111111",
5336+
viewModel.eventFlow.test {
5337+
mutableCardScanResultFlow.tryEmit(
5338+
CardScanResult.Success(
5339+
cardScanData = CardScanData(
5340+
number = "4111111111111111",
5341+
),
53305342
),
5331-
),
5332-
)
5333-
val content = viewModel.stateFlow.value.viewState
5334-
as VaultAddEditState.ViewState.Content
5335-
val cardType = content.type
5336-
as VaultAddEditState.ViewState.Content.ItemType.Card
5337-
assertEquals("4111111111111111", cardType.number)
5338-
assertEquals("EXISTING NAME", cardType.cardHolderName)
5339-
assertEquals("2030", cardType.expirationYear)
5340-
assertEquals(VaultCardExpirationMonth.JUNE, cardType.expirationMonth)
5341-
assertEquals("999", cardType.securityCode)
5342-
assertEquals(VaultCardBrand.VISA, cardType.brand)
5343+
)
5344+
val expectedCard = initialCard.copy(
5345+
number = "4111111111111111",
5346+
brand = VaultCardBrand.VISA,
5347+
)
5348+
val content = viewModel.stateFlow.value.viewState
5349+
as VaultAddEditState.ViewState.Content
5350+
assertEquals(expectedCard, content.type)
5351+
assertEquals(
5352+
VaultAddEditEvent.FocusCardHolderName,
5353+
awaitItem(),
5354+
)
5355+
}
53435356
}
53445357

53455358
//region Helper functions

app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/cardscanner/CardScanViewModelTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,5 @@ private val CARD_SCAN_DATA = CardScanData(
115115
number = "4111111111111111",
116116
expirationMonth = "12",
117117
expirationYear = "2025",
118-
cardholderName = "JOHN DOE",
119118
securityCode = "123",
120119
)

0 commit comments

Comments
 (0)