mirror of
https://github.com/bitwarden/android.git
synced 2026-05-12 14:51:15 -05:00
[BWA-99] fix: Show next code only in last 10 seconds; fix flow caching bug
- Cache getLocalVerificationCodesFlow() as a class property so handleShowNextTotpCodeReceive reads from the already-subscribed flow instead of a new unsubscribed instance that always returns DataState.Loading (which caused the next code to never appear) - Add 10-second threshold: next code is only shown when timeLeftSeconds is at or below 10 seconds remaining in the current period - Update tests for the threshold (timeLeftSeconds = 5 / 10 / 11 cases)
This commit is contained in:
@@ -70,6 +70,10 @@ class ItemListingViewModel @Inject constructor(
|
||||
),
|
||||
) {
|
||||
|
||||
// Cached once so that handleShowNextTotpCodeReceive can read `.value` from the
|
||||
// already-subscribed flow rather than creating a new unsubscribed instance.
|
||||
private val localCodesFlow = authenticatorRepository.getLocalVerificationCodesFlow()
|
||||
|
||||
init {
|
||||
settingsRepository
|
||||
.authenticatorAlertThresholdSecondsFlow
|
||||
@@ -84,7 +88,7 @@ class ItemListingViewModel @Inject constructor(
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
combine(
|
||||
flow = authenticatorRepository.getLocalVerificationCodesFlow(),
|
||||
flow = localCodesFlow,
|
||||
flow2 = authenticatorRepository.sharedCodesStateFlow,
|
||||
ItemListingAction.Internal::AuthCodesUpdated,
|
||||
)
|
||||
@@ -467,10 +471,10 @@ class ItemListingViewModel @Inject constructor(
|
||||
mutableStateFlow.update {
|
||||
it.copy(showNextTotpCode = action.showNextTotpCode)
|
||||
}
|
||||
// Re-derive the displayed list so that the next code is shown/hidden immediately
|
||||
// when the user toggles the setting.
|
||||
// Immediately re-derive the displayed list using the cached subscribed flow so that
|
||||
// next-code visibility changes take effect without waiting for the next 1-second tick.
|
||||
val codesUpdate = ItemListingAction.Internal.AuthCodesUpdated(
|
||||
localCodes = authenticatorRepository.getLocalVerificationCodesFlow().value,
|
||||
localCodes = localCodesFlow.value,
|
||||
sharedCodesState = authenticatorRepository.sharedCodesStateFlow.value,
|
||||
)
|
||||
handleAuthenticatorDataReceive(codesUpdate)
|
||||
|
||||
@@ -5,12 +5,15 @@ import com.bitwarden.authenticator.data.authenticator.repository.model.Authentic
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.model.SharedVerificationCodesState
|
||||
import com.bitwarden.authenticator.ui.platform.components.listitem.model.VerificationCodeDisplayItem
|
||||
|
||||
private const val NEXT_CODE_SHOW_THRESHOLD_SECONDS = 10
|
||||
|
||||
/**
|
||||
* Converts [VerificationCodeItem] to a [VerificationCodeDisplayItem].
|
||||
*
|
||||
* @param showNextCode When `true`, the upcoming code is mapped to
|
||||
* [VerificationCodeDisplayItem.nextAuthCode]. When `false`, [VerificationCodeDisplayItem.nextAuthCode]
|
||||
* is always `null`.
|
||||
* [VerificationCodeDisplayItem.nextAuthCode] for items within the last
|
||||
* [NEXT_CODE_SHOW_THRESHOLD_SECONDS] seconds of their validity period. When `false`,
|
||||
* [VerificationCodeDisplayItem.nextAuthCode] is always `null`.
|
||||
*/
|
||||
fun VerificationCodeItem.toDisplayItem(
|
||||
alertThresholdSeconds: Int,
|
||||
@@ -30,7 +33,11 @@ fun VerificationCodeItem.toDisplayItem(
|
||||
periodSeconds = periodSeconds,
|
||||
alertThresholdSeconds = alertThresholdSeconds,
|
||||
authCode = code,
|
||||
nextAuthCode = if (showNextCode) nextCode else null,
|
||||
nextAuthCode = if (showNextCode && timeLeftSeconds <= NEXT_CODE_SHOW_THRESHOLD_SECONDS) {
|
||||
nextCode
|
||||
} else {
|
||||
null
|
||||
},
|
||||
showOverflow = showOverflow,
|
||||
favorite = (source as? AuthenticatorItem.Source.Local)?.isFavorite ?: false,
|
||||
showMoveToBitwarden = when (source) {
|
||||
|
||||
@@ -582,7 +582,7 @@ class ItemListingViewModelTest : BaseViewModelTest() {
|
||||
val verificationItem = VerificationCodeItem(
|
||||
code = "123456",
|
||||
periodSeconds = 60,
|
||||
timeLeftSeconds = 430,
|
||||
timeLeftSeconds = 5,
|
||||
issueTime = 35L,
|
||||
id = "1",
|
||||
issuer = "issuer",
|
||||
|
||||
@@ -146,8 +146,9 @@ class VerificationCodeItemExtensionsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toDisplayItem should set nextAuthCode to nextCode when showNextCode is true`() {
|
||||
val item = createMockVerificationCodeItem(number = 1, nextCode = "654321")
|
||||
@Suppress("MaxLineLength")
|
||||
fun `toDisplayItem should set nextAuthCode when showNextCode is true and timeLeftSeconds is within threshold`() {
|
||||
val item = createMockVerificationCodeItem(number = 1, nextCode = "654321", timeLeftSeconds = 5)
|
||||
val result = item.toDisplayItem(
|
||||
alertThresholdSeconds = 7,
|
||||
sharedVerificationCodesState = SharedVerificationCodesState.Error,
|
||||
@@ -157,9 +158,39 @@ class VerificationCodeItemExtensionsTest {
|
||||
assertEquals("654321", result.nextAuthCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `toDisplayItem should set nextAuthCode when showNextCode is true and timeLeftSeconds equals threshold`() {
|
||||
val item = createMockVerificationCodeItem(number = 1, nextCode = "654321", timeLeftSeconds = 10)
|
||||
val result = item.toDisplayItem(
|
||||
alertThresholdSeconds = 7,
|
||||
sharedVerificationCodesState = SharedVerificationCodesState.Error,
|
||||
showOverflow = true,
|
||||
showNextCode = true,
|
||||
)
|
||||
assertEquals("654321", result.nextAuthCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `toDisplayItem should set nextAuthCode to null when showNextCode is true but timeLeftSeconds exceeds threshold`() {
|
||||
val item = createMockVerificationCodeItem(number = 1, nextCode = "654321", timeLeftSeconds = 11)
|
||||
val result = item.toDisplayItem(
|
||||
alertThresholdSeconds = 7,
|
||||
sharedVerificationCodesState = SharedVerificationCodesState.Error,
|
||||
showOverflow = true,
|
||||
showNextCode = true,
|
||||
)
|
||||
assertEquals(null, result.nextAuthCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toDisplayItem should set nextAuthCode to null when showNextCode is false`() {
|
||||
val item = createMockVerificationCodeItem(number = 1, nextCode = "654321")
|
||||
val item = createMockVerificationCodeItem(
|
||||
number = 1,
|
||||
nextCode = "654321",
|
||||
timeLeftSeconds = 5,
|
||||
)
|
||||
val result = item.toDisplayItem(
|
||||
alertThresholdSeconds = 7,
|
||||
sharedVerificationCodesState = SharedVerificationCodesState.Error,
|
||||
@@ -172,7 +203,7 @@ class VerificationCodeItemExtensionsTest {
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `toDisplayItem should set nextAuthCode to null when showNextCode true but nextCode is null`() {
|
||||
val item = createMockVerificationCodeItem(number = 1, nextCode = null)
|
||||
val item = createMockVerificationCodeItem(number = 1, nextCode = null, timeLeftSeconds = 5)
|
||||
val result = item.toDisplayItem(
|
||||
alertThresholdSeconds = 7,
|
||||
sharedVerificationCodesState = SharedVerificationCodesState.Error,
|
||||
|
||||
Reference in New Issue
Block a user