Skip to content

Commit

Permalink
Change color of countdown indicator when period nears expiration
Browse files Browse the repository at this point in the history
  • Loading branch information
SaintPatrck committed Mar 28, 2024
1 parent 0a73a17 commit c7a0d18
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ interface SettingsDiskSource {
*/
val appThemeFlow: Flow<AppTheme>

fun storeAlertThresholdSeconds(thresholdSeconds: Int)

fun getAlertThresholdSeconds(): Int

fun getAlertThresholdSecondsFlow(): Flow<Int>

/**
* Clears all the settings data for the given user.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.onSubscription
private const val APP_THEME_KEY = "$BASE_KEY:theme"
private const val SCREEN_CAPTURE_ALLOW_KEY = "$BASE_KEY:screenCaptureAllowed"
private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "$BASE_KEY:accountBiometricIntegrityValid"
private const val ALERT_THRESHOLD_SECONDS_KEY = "$BASE_KEY:alertThresholdSeconds"

/**
* Primary implementation of [SettingsDiskSource].
Expand All @@ -25,6 +26,9 @@ class SettingsDiskSourceImpl(
private val mutableScreenCaptureAllowedFlowMap =
mutableMapOf<String, MutableSharedFlow<Boolean?>>()

private val mutableAlertThresholdSecondsFlow =
bufferedMutableSharedFlow<Int>()

override var appTheme: AppTheme
get() = getString(key = APP_THEME_KEY)
?.let { storedValue ->
Expand All @@ -43,6 +47,21 @@ class SettingsDiskSourceImpl(
get() = mutableAppThemeFlow
.onSubscription { emit(appTheme) }

override fun storeAlertThresholdSeconds(thresholdSeconds: Int) {
putInt(
ALERT_THRESHOLD_SECONDS_KEY,
thresholdSeconds
)
mutableAlertThresholdSecondsFlow.tryEmit(thresholdSeconds)
}

override fun getAlertThresholdSeconds(): Int {
return getInt(ALERT_THRESHOLD_SECONDS_KEY, default = 7) ?: 7
}

override fun getAlertThresholdSecondsFlow(): Flow<Int> = mutableAlertThresholdSecondsFlow
.onSubscription { emit(getAlertThresholdSeconds()) }

override fun clearData(userId: String) {
storeScreenCaptureAllowed(userId = userId, isScreenCaptureAllowed = null)
removeWithPrefix(prefix = "${ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY}_$userId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ interface SettingsRepository {
* Tracks changes to the [AppTheme].
*/
val appThemeStateFlow: StateFlow<AppTheme>

var authenticatorAlertThresholdSeconds : Int

val authenticatorAlertThresholdSecondsFlow: StateFlow<Int>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.appearance
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/**
Expand All @@ -20,6 +21,8 @@ class SettingsRepositoryImpl(

override var appTheme: AppTheme by settingsDiskSource::appTheme

override var authenticatorAlertThresholdSeconds = settingsDiskSource.getAlertThresholdSeconds()

override val appThemeStateFlow: StateFlow<AppTheme>
get() = settingsDiskSource
.appThemeFlow
Expand All @@ -28,4 +31,15 @@ class SettingsRepositoryImpl(
started = SharingStarted.Eagerly,
initialValue = settingsDiskSource.appTheme,
)

override val authenticatorAlertThresholdSecondsFlow: StateFlow<Int>
get() = settingsDiskSource
.getAlertThresholdSecondsFlow()
.map { it }
.stateIn(
scope = unconfinedScope,
started = SharingStarted.Eagerly,
initialValue = settingsDiskSource.getAlertThresholdSeconds(),
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.authenticator.R
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.authenticator.ui.platform.components.appbar.BitwardenTopAppBar
import com.x8bit.bitwarden.authenticator.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.authenticator.ui.platform.components.field.BitwardenTextFieldWithActions
import com.x8bit.bitwarden.authenticator.ui.platform.components.icon.BitwardenIconButtonWithResource
import com.x8bit.bitwarden.authenticator.ui.platform.components.indicator.BitwardenCircularCountdownIndicator
Expand Down Expand Up @@ -123,20 +124,32 @@ fun ItemContent(
) {
LazyColumn(modifier = modifier) {

item {
BitwardenTextField(
modifier = Modifier.padding(horizontal = 16.dp),
label = stringResource(id = R.string.name),
value = viewState.itemData.name,
onValueChange = {},
readOnly = true,
singleLine = true,
)
}

item {
Spacer(modifier = Modifier.height(8.dp))
BitwardenTextFieldWithActions(
label = stringResource(id = R.string.verification_code_totp),
value = viewState.totpCodeItemData.verificationCode
value = viewState.itemData.totpCodeItemData?.verificationCode.orEmpty()
.chunked(AUTH_CODE_SPACING_INTERVAL)
.joinToString(" "),
onValueChange = { },
readOnly = true,
singleLine = true,
actions = {
BitwardenCircularCountdownIndicator(
timeLeftSeconds = viewState.totpCodeItemData.timeLeftSeconds,
periodSeconds = viewState.totpCodeItemData.periodSeconds,
timeLeftSeconds = viewState.itemData.totpCodeItemData?.timeLeftSeconds ?: 0,
periodSeconds = viewState.itemData.totpCodeItemData?.periodSeconds ?: 0,
alertThresholdSeconds = viewState.itemData.alertThresholdSeconds
)
BitwardenIconButtonWithResource(
iconRes = IconResource(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import com.bitwarden.core.CipherView
import com.x8bit.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRepository
import com.x8bit.bitwarden.authenticator.data.authenticator.repository.model.DeleteItemResult
import com.x8bit.bitwarden.authenticator.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.authenticator.data.platform.repository.model.DataState
import com.x8bit.bitwarden.authenticator.data.platform.repository.util.combineDataStates
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.item.model.ItemData
Expand All @@ -28,6 +29,7 @@ private const val KEY_STATE = "state"
@HiltViewModel
class ItemViewModel @Inject constructor(
authenticatorRepository: AuthenticatorRepository,
settingsRepository: SettingsRepository,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<ItemState, ItemEvent, ItemAction>(
initialState = savedStateHandle[KEY_STATE] ?: ItemState(
Expand All @@ -40,21 +42,28 @@ class ItemViewModel @Inject constructor(
init {
combine(
authenticatorRepository.getItemStateFlow(state.itemId),
authenticatorRepository.getAuthCodeFlow(state.itemId)
) { itemState, authCodeState ->
val totpData = authCodeState.data?.let {
TotpCodeItemData(
periodSeconds = it.periodSeconds,
timeLeftSeconds = it.timeLeftSeconds,
totpCode = it.totpCode,
verificationCode = it.code
)
}
authenticatorRepository.getAuthCodeFlow(state.itemId),
settingsRepository.authenticatorAlertThresholdSecondsFlow,
) { itemState, authCodeState, alertThresholdSeconds ->

ItemAction.Internal.ItemDataReceive(
item = itemState.data,
itemDataState = combineDataStates(itemState, authCodeState) { item, _ ->
itemDataState = combineDataStates(
itemState,
authCodeState,
) { item, authCode ->
val totpData = authCode?.let {
TotpCodeItemData(
periodSeconds = it.periodSeconds,
timeLeftSeconds = it.timeLeftSeconds,
totpCode = it.totpCode,
verificationCode = it.code
)
}

ItemData(
accountName = itemState.data?.name.orEmpty(),
name = item?.name.orEmpty(),
alertThresholdSeconds = alertThresholdSeconds,
totpCodeItemData = totpData
)
}
Expand Down Expand Up @@ -122,7 +131,11 @@ class ItemViewModel @Inject constructor(
it.copy(
itemId = action.item?.id.orEmpty(),
viewState = ItemState.ViewState.Content(
totpCodeItemData = totpItemData
itemData = ItemData(
name = action.item?.name.orEmpty(),
alertThresholdSeconds = action.itemDataState.data?.alertThresholdSeconds ?: 0,
totpCodeItemData = totpItemData,
)
)
)
}
Expand Down Expand Up @@ -167,7 +180,7 @@ data class ItemState(
*/
@Parcelize
data class Content(
val totpCodeItemData: TotpCodeItemData,
val itemData: ItemData,
) : ViewState()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.parcelize.Parcelize
*/
@Parcelize
data class ItemData(
val accountName: String,
val name: String,
val alertThresholdSeconds: Int,
val totpCodeItemData: TotpCodeItemData?,
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,20 @@ fun ItemListingScreen(
LazyColumn {
items(currentState.itemList) {
VaultVerificationCodeItem(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
startIcon = it.startIcon,
label = it.label,
supportingLabel = it.supportingLabel,
timeLeftSeconds = it.timeLeftSeconds,
periodSeconds = it.periodSeconds,
alertThresholdSeconds = it.alertThresholdSeconds,
authCode = it.authCode,
onCopyClick = { /*TODO*/ },
onItemClick = {
onNavigateToItemScreen(it.id)
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
}
}
Expand Down
Loading

0 comments on commit c7a0d18

Please sign in to comment.