From a0b06a6fa1c45907d82d1db08cdfce5d43e07f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=C2=A0Koby=C5=82ko?= Date: Fri, 21 Jul 2023 15:59:05 +0200 Subject: [PATCH] Hide codes --- .../designsystem/service/DsService.kt | 165 +++++++--- .../designsystem/service/DsServiceModal.kt | 97 ++++-- .../designsystem/service/ServiceState.kt | 2 + .../designsystem/service/atoms/ServiceCode.kt | 2 +- .../designsystem/service/atoms/ServiceHotp.kt | 5 +- .../main/java/com/twofasapp/locale/Strings.kt | 2 + .../data/services/ServicesRepository.kt | 1 + .../data/services/ServicesRepositoryImpl.kt | 6 + .../twofasapp/data/services/domain/Service.kt | 1 + .../services/local/ServicesLocalSource.kt | 154 +--------- .../services/local/model/ServiceEntity.kt | 1 + .../data/services/mapper/ServiceMapper.kt | 6 +- .../data/session/SettingsRepository.kt | 1 + .../data/session/SettingsRepositoryImpl.kt | 6 + .../data/session/domain/AppSettings.kt | 1 + .../data/session/local/SettingsLocalSource.kt | 7 + .../appsettings/ui/AppSettingsScreen.kt | 22 +- .../appsettings/ui/AppSettingsViewModel.kt | 6 + .../home/ui/services/ServicesScreen.kt | 18 +- .../home/ui/services/ServicesViewModel.kt | 8 + .../feature/home/ui/services/StateMapper.kt | 3 +- .../add/success/AddServiceSuccessScreen.kt | 3 +- .../add/success/AddServiceSuccessUiState.kt | 1 + .../add/success/AddServiceSuccessViewModel.kt | 13 +- .../ui/services/focus/FocusServiceModal.kt | 8 +- .../ui/services/focus/FocusServiceUiState.kt | 1 + .../services/focus/FocusServiceViewModel.kt | 13 +- .../feature/trash/ui/trash/TrashScreen.kt | 1 + .../12.json | 284 ++++++++++++++++++ .../com/twofasapp/persistence/AppDatabase.kt | 8 +- .../persistence/PersistenceModule.kt | 1 + .../services/data/ServicesLocalDataImpl.kt | 4 +- .../data/converter/ServiceConverter.kt | 1 + 33 files changed, 616 insertions(+), 236 deletions(-) create mode 100644 persistence/schemas/com.twofasapp.persistence.AppDatabase/12.json diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/DsService.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/DsService.kt index e345f707..eb7d930a 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/DsService.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/DsService.kt @@ -4,18 +4,29 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.TweenSpec import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Divider +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -34,6 +45,7 @@ import com.twofasapp.designsystem.service.atoms.ServiceName import com.twofasapp.designsystem.service.atoms.ServiceTextDefaults import com.twofasapp.designsystem.service.atoms.ServiceTextStyle import com.twofasapp.designsystem.service.atoms.ServiceTimer +import com.twofasapp.designsystem.service.atoms.formatCode internal const val ServiceExpireTransitionThreshold = 5 @@ -62,12 +74,14 @@ fun DsService( style: ServiceStyle = ServiceStyle.Default, editMode: Boolean = false, showNextCode: Boolean = false, + hideCodes: Boolean = false, containerColor: Color = TwTheme.color.background, dragHandleVisible: Boolean = true, dragModifier: Modifier = Modifier, onClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null, onIncrementCounterClick: (() -> Unit)? = null, + onRevealClick: (() -> Unit)? = null, ) { val textStyles: ServiceTextStyle = when (style) { ServiceStyle.Default -> ServiceTextDefaults.default() @@ -129,39 +143,78 @@ fun DsService( modifier = Modifier.fillMaxWidth(), ) - if (editMode.not()) { - ServiceCode( - code = state.code, - nextCode = state.nextCode, - timer = state.timer, - nextCodeVisible = state.isNextCodeEnabled(showNextCode), - nextCodeGravity = when (style) { - ServiceStyle.Default -> NextCodeGravity.Below - ServiceStyle.Compact -> NextCodeGravity.End - }, - animateColor = state.authType == ServiceAuthType.Totp, - textStyles = textStyles, - modifier = Modifier.fillMaxWidth(), - ) + if (editMode.not() && (state.revealed || hideCodes.not() || style == ServiceStyle.Default)) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Max) + ) { + ServiceCode( + code = state.code, + nextCode = state.nextCode, + timer = state.timer, + nextCodeVisible = state.isNextCodeEnabled(showNextCode), + nextCodeGravity = when (style) { + ServiceStyle.Default -> NextCodeGravity.Below + ServiceStyle.Compact -> NextCodeGravity.End + }, + animateColor = state.authType == ServiceAuthType.Totp, + textStyles = textStyles, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .alpha(if (state.revealed || hideCodes.not()) 1f else 0f), + ) + + if (state.revealed.not() && hideCodes) { + HiddenDots( + formattedCode = state.code.formatCode(), + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + ) + } + } } } if (editMode.not()) { - when (state.authType) { - ServiceAuthType.Totp -> { - ServiceTimer( - timer = state.timer, - progress = state.progress, - textStyles = textStyles, - dimens = dimens, - modifier = Modifier.padding(end = 12.dp) - ) - } - ServiceAuthType.Hotp -> { - ServiceHotp( - enabled = state.hotpCounterEnabled, - onClick = { onIncrementCounterClick?.invoke() } + if (state.revealed || hideCodes.not()) { + when (state.authType) { + ServiceAuthType.Totp -> { + ServiceTimer( + timer = state.timer, + progress = state.progress, + textStyles = textStyles, + dimens = dimens, + modifier = Modifier.padding(end = 20.dp) + ) + } + + ServiceAuthType.Hotp -> { + ServiceHotp( + enabled = state.hotpCounterEnabled, + onClick = { onIncrementCounterClick?.invoke() }, + modifier = Modifier.padding(end = 4.dp) + ) + } + } + } else { + Box( + Modifier + .padding(end = 7.dp) + .size(56.dp) + .clip(CircleShape) + .clickable { onRevealClick?.invoke() } + ) { + Icon( + painter = TwIcons.Eye, + contentDescription = null, + modifier = Modifier + .size(24.dp) + .align(Alignment.Center), + tint = TwTheme.color.iconTint ) } } @@ -171,7 +224,35 @@ fun DsService( TwIconButton( painter = TwIcons.DragHandle, enabled = false, - modifier = dragModifier, + modifier = Modifier + .padding(end = 8.dp) + .then(dragModifier), + ) + } + } + } +} + +@Composable +internal fun HiddenDots( + modifier: Modifier = Modifier, + formattedCode: String, +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + + formattedCode.map { + if (it.isWhitespace()) { + Spacer(modifier = Modifier.width(2.dp)) + } else { + Box( + modifier = Modifier + .padding(horizontal = 4.dp) + .size(8.dp) + .background(TwTheme.color.onSurfacePrimary, CircleShape) ) } } @@ -181,20 +262,29 @@ fun DsService( @Preview @Composable private fun PreviewDefault() { - DsService(state = ServicePreview) -} - -@Preview -@Composable -private fun PreviewDefaultHotp() { - DsService(state = ServicePreview.copy(authType = ServiceAuthType.Hotp)) + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + DsService(state = ServicePreview) + DsService(state = ServicePreview.copy(timer = 3), showNextCode = true, hideCodes = true) + DsService(state = ServicePreview.copy(timer = 3, revealed = false), showNextCode = true, hideCodes = true) + DsService(state = ServicePreview.copy(authType = ServiceAuthType.Hotp)) + } } @Preview @Composable private fun PreviewCompact() { - DsService(state = ServicePreview, style = ServiceStyle.Compact) + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + DsService(state = ServicePreview, style = ServiceStyle.Compact) + DsService(state = ServicePreview.copy(timer = 3), style = ServiceStyle.Compact, showNextCode = true, hideCodes = true) + DsService(state = ServicePreview.copy(revealed = false), style = ServiceStyle.Compact, hideCodes = true) + } } @Preview @@ -218,4 +308,5 @@ internal val ServicePreview = ServiceState( labelText = "2F", labelColor = Color.Red, badgeColor = Color.Red, + revealed = true, ) \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/DsServiceModal.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/DsServiceModal.kt index 6521574f..0376c659 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/DsServiceModal.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/DsServiceModal.kt @@ -1,18 +1,28 @@ package com.twofasapp.designsystem.service import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.twofasapp.designsystem.TwIcons import com.twofasapp.designsystem.TwTheme import com.twofasapp.designsystem.service.atoms.ServiceCode import com.twofasapp.designsystem.service.atoms.ServiceHotp @@ -21,14 +31,17 @@ import com.twofasapp.designsystem.service.atoms.ServiceInfo import com.twofasapp.designsystem.service.atoms.ServiceName import com.twofasapp.designsystem.service.atoms.ServiceTextDefaults import com.twofasapp.designsystem.service.atoms.ServiceTimer +import com.twofasapp.designsystem.service.atoms.formatCode @Composable fun DsServiceModal( state: ServiceState, showNextCode: Boolean = false, + hideCodes: Boolean = false, modifier: Modifier = Modifier, containerColor: Color = TwTheme.color.background, onIncrementCounterClick: (() -> Unit)? = null, + onRevealClick: (() -> Unit)? = null, ) { val textStyles = ServiceTextDefaults.modal() @@ -71,30 +84,66 @@ fun DsServiceModal( labelColor = state.labelColor ) - ServiceCode( - code = state.code, - nextCode = state.nextCode, - timer = state.timer, - nextCodeVisible = state.isNextCodeEnabled(showNextCode), - animateColor = state.authType == ServiceAuthType.Totp, - modifier = Modifier.weight(1f), - textStyles = textStyles, - ) + Box( + modifier = Modifier + .weight(1f) + .height(IntrinsicSize.Max) + ) { - when (state.authType) { - ServiceAuthType.Totp -> { - ServiceTimer( - timer = state.timer, - progress = state.progress, - textStyles = textStyles, - modifier = Modifier.padding(end = 12.dp) + ServiceCode( + code = state.code, + nextCode = state.nextCode, + timer = state.timer, + nextCodeVisible = state.isNextCodeEnabled(showNextCode), + animateColor = state.authType == ServiceAuthType.Totp, + modifier = Modifier + .fillMaxWidth() + .alpha(if (state.revealed || hideCodes.not()) 1f else 0f), + textStyles = textStyles, + ) + + if (state.revealed.not() && hideCodes) { + HiddenDots( + formattedCode = state.code.formatCode(), + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() ) } + } - ServiceAuthType.Hotp -> { - ServiceHotp( - enabled = state.hotpCounterEnabled, - onClick = { onIncrementCounterClick?.invoke() } + if (state.revealed || hideCodes.not()) { + when (state.authType) { + ServiceAuthType.Totp -> { + ServiceTimer( + timer = state.timer, + progress = state.progress, + textStyles = textStyles, + modifier = Modifier.padding(end = 12.dp) + ) + } + + ServiceAuthType.Hotp -> { + ServiceHotp( + enabled = state.hotpCounterEnabled, + onClick = { onIncrementCounterClick?.invoke() } + ) + } + } + } else { + Box( + Modifier + .size(56.dp) + .clip(CircleShape) + .clickable { onRevealClick?.invoke() } + ) { + Icon( + painter = TwIcons.Eye, + contentDescription = null, + modifier = Modifier + .size(24.dp) + .align(Alignment.Center), + tint = TwTheme.color.iconTint ) } } @@ -105,5 +154,11 @@ fun DsServiceModal( @Preview @Composable private fun Preview() { - DsServiceModal(state = ServicePreview) + DsServiceModal(state = ServicePreview.copy(revealed = true), hideCodes = true) +} + +@Preview +@Composable +private fun PreviewHidden() { + DsServiceModal(state = ServicePreview.copy(revealed = false), hideCodes = true) } diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceState.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceState.kt index 6315d423..ecbca3ac 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceState.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceState.kt @@ -21,6 +21,7 @@ data class ServiceState( val labelText: String?, val labelColor: Color, val badgeColor: Color = Color.Unspecified, + val revealed: Boolean, ) { fun copyToClipboard(activity: Activity, showNextToken: Boolean) { @@ -62,6 +63,7 @@ data class ServiceState( labelText = null, labelColor = Color.Unspecified, badgeColor = Color.Unspecified, + revealed = true, ) } } \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/atoms/ServiceCode.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/atoms/ServiceCode.kt index edd720e4..a1b0b614 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/atoms/ServiceCode.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/atoms/ServiceCode.kt @@ -78,7 +78,7 @@ internal fun ServiceCode( } } -private fun String.formatCode(): String { +internal fun String.formatCode(): String { if (isEmpty()) return "" return when (this.length) { diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/atoms/ServiceHotp.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/atoms/ServiceHotp.kt index 28bfd40a..d42f267d 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/atoms/ServiceHotp.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/atoms/ServiceHotp.kt @@ -18,10 +18,11 @@ import com.twofasapp.designsystem.common.TwIcon @Composable internal fun ServiceHotp( enabled: Boolean = true, - onClick: () -> Unit = {} + onClick: () -> Unit = {}, + modifier: Modifier = Modifier ) { - Box(modifier = Modifier + Box(modifier = modifier .size(56.dp) .clip(CircleShape) .clickable(enabled) { onClick() } diff --git a/core/locale/src/main/java/com/twofasapp/locale/Strings.kt b/core/locale/src/main/java/com/twofasapp/locale/Strings.kt index d2c3774d..7cccf394 100644 --- a/core/locale/src/main/java/com/twofasapp/locale/Strings.kt +++ b/core/locale/src/main/java/com/twofasapp/locale/Strings.kt @@ -139,6 +139,8 @@ class Strings(c: Context) { val settingsShowBackupNoticeConfirmBody = c.getString(R.string.settings__gd_sync_disable_confirm) val settingsSendCrashes = c.getString(R.string.settings__enable_crashlytics) val settingsSendCrashesBody = c.getString(R.string.settings__enable_crashlytics_description) + val settingsHideCodes = c.getString(R.string.settings__hide_tokens_title) + val settingsHideCodesBody = c.getString(R.string.settings__hide_tokens_description) val backupSyncNotice = c.getString(R.string.backup__reminder_msg) val backupSyncCta = c.getString(R.string.backup__reminder_cta) diff --git a/data/services/src/main/java/com/twofasapp/data/services/ServicesRepository.kt b/data/services/src/main/java/com/twofasapp/data/services/ServicesRepository.kt index b4203fe6..a95195a4 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/ServicesRepository.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/ServicesRepository.kt @@ -30,4 +30,5 @@ interface ServicesRepository { suspend fun addService(service: Service): Long fun observeAddServiceAdvancedExpanded(): Flow fun pushAddServiceAdvancedExpanded(expanded: Boolean) + suspend fun revealService(id: Long) } \ No newline at end of file diff --git a/data/services/src/main/java/com/twofasapp/data/services/ServicesRepositoryImpl.kt b/data/services/src/main/java/com/twofasapp/data/services/ServicesRepositoryImpl.kt index 7b25a21d..ee950f76 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/ServicesRepositoryImpl.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/ServicesRepositoryImpl.kt @@ -286,4 +286,10 @@ internal class ServicesRepositoryImpl( override fun pushAddServiceAdvancedExpanded(expanded: Boolean) { local.pushAddServiceAdvancedExpanded(expanded) } + + override suspend fun revealService(id: Long) { + withContext(dispatchers.io) { + local.revealService(id) + } + } } \ No newline at end of file diff --git a/data/services/src/main/java/com/twofasapp/data/services/domain/Service.kt b/data/services/src/main/java/com/twofasapp/data/services/domain/Service.kt index e446f3ff..a8941577 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/domain/Service.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/domain/Service.kt @@ -31,6 +31,7 @@ data class Service( val source: Source, val assignedDomains: List = emptyList(), val backupSyncStatus: BackupSyncStatus, + val revealTimestamp: Long? = null, ) { data class Code( val current: String, diff --git a/data/services/src/main/java/com/twofasapp/data/services/local/ServicesLocalSource.kt b/data/services/src/main/java/com/twofasapp/data/services/local/ServicesLocalSource.kt index a9a360eb..b41af45c 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/local/ServicesLocalSource.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/local/ServicesLocalSource.kt @@ -176,151 +176,15 @@ internal class ServicesLocalSource( return addServiceAdvancedExpanded } - fun pushAddServiceAdvancedExpanded(expanded: Boolean) { + fun pushAddServiceAdvancedExpanded(expanded: Boolean) { addServiceAdvancedExpanded.tryEmit(expanded) } -// -// fun select(): Single> { -// return dao.legacySelect() -// .map { list -> -// list.map { local -> -// ServiceDto( -// id = local.id, -// name = local.name, -// secret = local.secret, -// authType = local.authType?.let { ServiceDto.AuthType.valueOf(it) } ?: ServiceDto.AuthType.TOTP, -// otpLabel = local.otpLabel, -// otpAccount = local.otpAccount, -// otpIssuer = local.otpIssuer, -// otpDigits = local.otpDigits, -// otpPeriod = local.otpPeriod, -// otpAlgorithm = local.otpAlgorithm, -// hotpCounter = local.hotpCounter, -// backupSyncStatus = BackupSyncStatus.valueOf(local.backupSyncStatus), -// updatedAt = local.updatedAt, -// badge = local.badgeColor?.let { ServiceDto.Badge(Tint.valueOf(it)) }, -// selectedImageType = local.selectedImageType?.let { -// when (it) { -// "Brand" -> ServiceDto.ImageType.IconCollection -// "Label" -> ServiceDto.ImageType.Label -// else -> ServiceDto.ImageType.IconCollection -// } -// } ?: ServiceDto.ImageType.IconCollection, -// labelText = local.labelText, -// labelBackgroundColor = local.labelBackgroundColor?.let { color -> Tint.valueOf(color) }, -// iconCollectionId = local.iconCollectionId ?: ServiceIcons.defaultCollectionId, -// groupId = local.groupId, -// isDeleted = local.isDeleted, -// assignedDomains = local.assignedDomains.orEmpty(), -// serviceTypeId = local.serviceTypeId, -// source = local.source?.let { ServiceDto.Source.valueOf(it) } ?: ServiceDto.Source.Manual -// ) -// } -// } -// } -// -// fun observe(): Flowable> { -// return dao.legacyObserve() -// .map { list -> -// list.map { local -> -// ServiceDto( -// id = local.id, -// name = local.name, -// secret = local.secret, -// authType = local.authType?.let { ServiceDto.AuthType.valueOf(it) } ?: ServiceDto.AuthType.TOTP, -// otpLabel = local.otpLabel, -// otpAccount = local.otpAccount, -// otpIssuer = local.otpIssuer, -// otpDigits = local.otpDigits, -// otpPeriod = local.otpPeriod, -// otpAlgorithm = local.otpAlgorithm, -// hotpCounter = local.hotpCounter, -// backupSyncStatus = BackupSyncStatus.valueOf(local.backupSyncStatus), -// updatedAt = local.updatedAt, -// badge = local.badgeColor?.let { ServiceDto.Badge(Tint.valueOf(it)) }, -// selectedImageType = local.selectedImageType?.let { -// when (it) { -// "Brand" -> ServiceDto.ImageType.IconCollection -// "Label" -> ServiceDto.ImageType.Label -// else -> ServiceDto.ImageType.IconCollection -// } -// } ?: ServiceDto.ImageType.IconCollection, -// labelText = local.labelText, -// labelBackgroundColor = local.labelBackgroundColor?.let { color -> Tint.valueOf(color) }, -// iconCollectionId = local.iconCollectionId ?: ServiceIcons.defaultCollectionId, -// groupId = local.groupId, -// isDeleted = local.isDeleted, -// assignedDomains = local.assignedDomains.orEmpty(), -// serviceTypeId = local.serviceTypeId, -// source = local.source?.let { ServiceDto.Source.valueOf(it) } ?: ServiceDto.Source.Manual -// ) -// } -// } -// } -// -// fun insertService(service: ServiceDto): Single { -// Timber.d("InsertService: $service") -// return dao.legacyInsert( -// ServiceEntity( -// id = 0, -// name = service.name, -// secret = service.secret.removeWhiteCharacters(), -// serviceTypeId = service.serviceTypeId, -// iconCollectionId = service.iconCollectionId, -// source = service.source.name, -// otpLink = service.otpLink, -// otpLabel = service.otpLabel, -// otpAccount = service.otpAccount, -// otpIssuer = service.otpIssuer, -// otpDigits = service.getDigits(), -// otpPeriod = service.getPeriod(), -// otpAlgorithm = service.getAlgorithm(), -// backupSyncStatus = service.backupSyncStatus.name, -// updatedAt = service.updatedAt, -// badgeColor = service.badge?.color?.name, -// selectedImageType = service.selectedImageType.name, -// labelText = service.labelText, -// labelBackgroundColor = service.labelBackgroundColor?.name, -// groupId = service.groupId, -// isDeleted = service.isDeleted, -// authType = service.authType.name, -// hotpCounter = service.hotpCounter, -// assignedDomains = service.assignedDomains -// ) -// ) -// } -// -// fun updateService(vararg services: ServiceDto): Completable { -// Timber.d("UpdateServices: ${services.toList()}") -// return dao.legacyUpdate( -// *services.map { -// ServiceEntity( -// id = it.id, -// name = it.name, -// secret = it.secret, -// serviceTypeId = it.serviceTypeId, -// iconCollectionId = it.iconCollectionId, -// source = it.source.name, -// otpLink = it.otpLink, -// otpLabel = it.otpLabel, -// otpAccount = it.otpAccount, -// otpIssuer = it.otpIssuer, -// otpDigits = it.getDigits(), -// otpPeriod = it.getPeriod(), -// otpAlgorithm = it.getAlgorithm(), -// backupSyncStatus = it.backupSyncStatus.name, -// updatedAt = it.updatedAt, -// badgeColor = it.badge?.color?.name, -// selectedImageType = it.selectedImageType.name, -// labelText = it.labelText, -// labelBackgroundColor = it.labelBackgroundColor?.name, -// groupId = it.groupId, -// isDeleted = it.isDeleted, -// authType = it.authType.name, -// hotpCounter = it.hotpCounter, -// assignedDomains = it.assignedDomains, -// ) -// }.toTypedArray() -// ) -// } + + suspend fun revealService(id: Long) { + dao.update( + dao.select(id).copy( + revealTimestamp = System.currentTimeMillis(), + ) + ) + } } \ No newline at end of file diff --git a/data/services/src/main/java/com/twofasapp/data/services/local/model/ServiceEntity.kt b/data/services/src/main/java/com/twofasapp/data/services/local/model/ServiceEntity.kt index c0f046bd..a37c0f34 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/local/model/ServiceEntity.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/local/model/ServiceEntity.kt @@ -32,5 +32,6 @@ data class ServiceEntity( @ColumnInfo(name = "authType") val authType: String?, @ColumnInfo(name = "hotpCounter") val hotpCounter: Int?, @ColumnInfo(name = "hotpCounterTimestamp") val hotpCounterTimestamp: Long?, + @ColumnInfo(name = "revealTimestamp") val revealTimestamp: Long?, @ColumnInfo(name = "assignedDomains") val assignedDomains: List?, ) \ No newline at end of file diff --git a/data/services/src/main/java/com/twofasapp/data/services/mapper/ServiceMapper.kt b/data/services/src/main/java/com/twofasapp/data/services/mapper/ServiceMapper.kt index e60291dc..2850d9ee 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/mapper/ServiceMapper.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/mapper/ServiceMapper.kt @@ -47,7 +47,8 @@ internal fun ServiceEntity.asDomain(): Service { updatedAt = updatedAt, source = source?.let { Service.Source.valueOf(it) } ?: Service.Source.Manual, assignedDomains = assignedDomains.orEmpty(), - backupSyncStatus = BackupSyncStatus.valueOf(backupSyncStatus) + backupSyncStatus = BackupSyncStatus.valueOf(backupSyncStatus), + revealTimestamp = revealTimestamp, ) } @@ -77,7 +78,8 @@ internal fun Service.asEntity(): ServiceEntity { authType = authType.name, hotpCounter = hotpCounter, hotpCounterTimestamp = hotpCounterTimestamp, - assignedDomains = assignedDomains + assignedDomains = assignedDomains, + revealTimestamp = revealTimestamp, ) } diff --git a/data/session/src/main/java/com/twofasapp/data/session/SettingsRepository.kt b/data/session/src/main/java/com/twofasapp/data/session/SettingsRepository.kt index f381a1c5..643cec20 100644 --- a/data/session/src/main/java/com/twofasapp/data/session/SettingsRepository.kt +++ b/data/session/src/main/java/com/twofasapp/data/session/SettingsRepository.kt @@ -17,4 +17,5 @@ interface SettingsRepository { suspend fun setShowBackupNotice(showBackupNotice: Boolean) suspend fun setSendCrashLogs(sendCrashLogs: Boolean) suspend fun setAllowScreenshots(allow: Boolean) + suspend fun setHideCodes(hideCodes: Boolean) } \ No newline at end of file diff --git a/data/session/src/main/java/com/twofasapp/data/session/SettingsRepositoryImpl.kt b/data/session/src/main/java/com/twofasapp/data/session/SettingsRepositoryImpl.kt index 3e687238..41621c35 100644 --- a/data/session/src/main/java/com/twofasapp/data/session/SettingsRepositoryImpl.kt +++ b/data/session/src/main/java/com/twofasapp/data/session/SettingsRepositoryImpl.kt @@ -69,4 +69,10 @@ internal class SettingsRepositoryImpl( local.setAllowScreenshots(allow) } } + + override suspend fun setHideCodes(hideCodes: Boolean) { + withContext(dispatchers.io) { + local.setHideCodes(hideCodes) + } + } } \ No newline at end of file diff --git a/data/session/src/main/java/com/twofasapp/data/session/domain/AppSettings.kt b/data/session/src/main/java/com/twofasapp/data/session/domain/AppSettings.kt index 90a02bfd..5828e8e8 100644 --- a/data/session/src/main/java/com/twofasapp/data/session/domain/AppSettings.kt +++ b/data/session/src/main/java/com/twofasapp/data/session/domain/AppSettings.kt @@ -9,4 +9,5 @@ data class AppSettings( val selectedTheme: SelectedTheme = SelectedTheme.Auto, val servicesStyle: ServicesStyle = ServicesStyle.Default, val servicesSort: ServicesSort = ServicesSort.Manual, + val hideCodes: Boolean = false, ) diff --git a/data/session/src/main/java/com/twofasapp/data/session/local/SettingsLocalSource.kt b/data/session/src/main/java/com/twofasapp/data/session/local/SettingsLocalSource.kt index 3307a135..35fdcd65 100644 --- a/data/session/src/main/java/com/twofasapp/data/session/local/SettingsLocalSource.kt +++ b/data/session/src/main/java/com/twofasapp/data/session/local/SettingsLocalSource.kt @@ -25,6 +25,7 @@ internal class SettingsLocalSource( private const val KeyAutoFocusSearch = "autoFocusSearch" private const val KeySendCrashLogs = "sendCrashLogs" private const val KeyAllowScreenshots = "allowScreenshots" + private const val KeyHideCodes = "hideCodes" } private val appSettingsFlow: MutableStateFlow by lazy { @@ -49,6 +50,7 @@ internal class SettingsLocalSource( selectedTheme = preferences.getString(KeySelectedTheme)?.let { SelectedTheme.valueOf(it) } ?: SelectedTheme.Auto, servicesStyle = preferences.getString(KeyServicesStyle)?.let { ServicesStyle.valueOf(it) } ?: ServicesStyle.Default, servicesSort = preferences.getString(KeyServicesSort)?.let { ServicesSort.valueOf(it) } ?: ServicesSort.Manual, + hideCodes = preferences.getBoolean(KeyHideCodes) ?: false, ) } @@ -91,4 +93,9 @@ internal class SettingsLocalSource( appSettingsFlow.update { it.copy(allowScreenshots = allow) } preferences.putBoolean(KeyAllowScreenshots, allow) } + + fun setHideCodes(hideCodes: Boolean) { + appSettingsFlow.update { it.copy(hideCodes = hideCodes) } + preferences.putBoolean(KeyHideCodes, hideCodes) + } } \ No newline at end of file diff --git a/feature/appsettings/src/main/java/com/twofasapp/feature/appsettings/ui/AppSettingsScreen.kt b/feature/appsettings/src/main/java/com/twofasapp/feature/appsettings/ui/AppSettingsScreen.kt index 48b15ef8..47586b9e 100644 --- a/feature/appsettings/src/main/java/com/twofasapp/feature/appsettings/ui/AppSettingsScreen.kt +++ b/feature/appsettings/src/main/java/com/twofasapp/feature/appsettings/ui/AppSettingsScreen.kt @@ -40,6 +40,7 @@ internal fun AppSettingsRoute( onShowNextTokenToggle = { viewModel.toggleShowNextToken() }, onShowBackupNoticeToggle = { viewModel.toggleShowBackupNotice() }, onAutoFocusSearchToggle = { viewModel.toggleAutoFocusSearch() }, + onHideCodesToggle = { viewModel.toggleHideTokens() } ) } @@ -52,6 +53,7 @@ private fun AppSettingsScreen( onShowNextTokenToggle: () -> Unit, onShowBackupNoticeToggle: () -> Unit, onAutoFocusSearchToggle: () -> Unit, + onHideCodesToggle: () -> Unit, ) { val activity = LocalContext.currentActivity var showThemeDialog by remember { mutableStateOf(false) } @@ -92,26 +94,27 @@ private fun AppSettingsScreen( item { SettingsSwitch( title = TwLocale.strings.settingsShowNextCode, - checked = uiState.appSettings.showNextCode, - onCheckedChange = { onShowNextTokenToggle() }, subtitle = TwLocale.strings.settingsShowNextCodeBody, icon = TwIcons.NextToken, + checked = uiState.appSettings.showNextCode, + onCheckedChange = { onShowNextTokenToggle() }, ) } item { SettingsSwitch( title = TwLocale.strings.settingsAutoFocusSearch, - checked = uiState.appSettings.autoFocusSearch, - onCheckedChange = { onAutoFocusSearchToggle() }, subtitle = TwLocale.strings.settingsAutoFocusSearchBody, icon = TwIcons.Search, + checked = uiState.appSettings.autoFocusSearch, + onCheckedChange = { onAutoFocusSearchToggle() }, ) } item { SettingsSwitch( title = TwLocale.strings.settingsShowBackupNotice, + icon = TwIcons.CloudOff, checked = uiState.appSettings.showBackupNotice, onCheckedChange = { checked -> if (checked.not()) { @@ -120,7 +123,16 @@ private fun AppSettingsScreen( onShowBackupNoticeToggle() } }, - icon = TwIcons.CloudOff, + ) + } + + item { + SettingsSwitch( + title = TwLocale.strings.settingsHideCodes, + subtitle = TwLocale.strings.settingsHideCodesBody, + icon = TwIcons.Eye, + checked = uiState.appSettings.hideCodes, + onCheckedChange = { onHideCodesToggle() }, ) } } diff --git a/feature/appsettings/src/main/java/com/twofasapp/feature/appsettings/ui/AppSettingsViewModel.kt b/feature/appsettings/src/main/java/com/twofasapp/feature/appsettings/ui/AppSettingsViewModel.kt index 77467c40..ed331e25 100644 --- a/feature/appsettings/src/main/java/com/twofasapp/feature/appsettings/ui/AppSettingsViewModel.kt +++ b/feature/appsettings/src/main/java/com/twofasapp/feature/appsettings/ui/AppSettingsViewModel.kt @@ -64,4 +64,10 @@ internal class AppSettingsViewModel( fun consumeEvent(event: AppSettingsUiEvent) { uiState.update { it.copy(events = it.events.minus(event)) } } + + fun toggleHideTokens() { + launchScoped { + settingsRepository.setHideCodes(uiState.value.appSettings.hideCodes.not()) + } + } } \ No newline at end of file diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesScreen.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesScreen.kt index 360c7169..a8e6c1d1 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesScreen.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesScreen.kt @@ -1,6 +1,5 @@ package com.twofasapp.feature.home.ui.services -import android.Manifest import androidx.activity.compose.BackHandler import androidx.compose.animation.Animatable import androidx.compose.animation.animateContentSize @@ -47,7 +46,6 @@ import com.twofasapp.data.services.domain.Service import com.twofasapp.data.session.domain.ServicesSort import com.twofasapp.data.session.domain.ServicesStyle import com.twofasapp.designsystem.TwTheme -import com.twofasapp.designsystem.common.RequestPermission import com.twofasapp.designsystem.common.TwEmptyScreen import com.twofasapp.designsystem.common.TwOutlinedButton import com.twofasapp.designsystem.common.isScrollingUp @@ -108,7 +106,8 @@ internal fun ServicesRoute( onSearchFocusChange = { viewModel.searchFocused(it) }, onOpenBackupClick = { listener.openBackup(activity) }, onDismissSyncReminderClick = { viewModel.dismissSyncReminder() }, - onIncrementHotpCounterClick = { viewModel.incrementHotpCounter(it) } + onIncrementHotpCounterClick = { viewModel.incrementHotpCounter(it) }, + onRevealClick = { viewModel.reveal(it) } ) } @@ -135,6 +134,7 @@ private fun ServicesScreen( onOpenBackupClick: () -> Unit = {}, onDismissSyncReminderClick: () -> Unit = {}, onIncrementHotpCounterClick: (Service) -> Unit = {}, + onRevealClick: (Service) -> Unit = {}, ) { val focusRequester = remember { FocusRequester() } @@ -281,7 +281,7 @@ private fun ServicesScreen( isVisible = uiState.isLoading.not(), isExtendedVisible = uiState.totalServices == 0, isNormalVisible = reorderableState.listState.isScrollingUp(), - onClick = { listener.openAddServiceModal() }, + onClick = { listener.openAddServiceModal() }, ) }, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) @@ -432,6 +432,7 @@ private fun ServicesScreen( }, editMode = uiState.isInEditMode, showNextCode = uiState.appSettings.showNextCode, + hideCodes = uiState.appSettings.hideCodes, containerColor = if (recentlyAddedService == service.id) { serviceContainerColorBlinking.value } else { @@ -439,16 +440,13 @@ private fun ServicesScreen( }, dragHandleVisible = uiState.appSettings.servicesSort == ServicesSort.Manual, dragModifier = Modifier.detectReorder(state = reorderableState), - onClick = { - state.copyToClipboard(activity, uiState.appSettings.showNextCode) - }, + onClick = { state.copyToClipboard(activity, uiState.appSettings.showNextCode) }, onLongClick = { keyboardController?.hide() listener.openFocusServiceModal(service.id) }, - onIncrementCounterClick = { - onIncrementHotpCounterClick(service) - } + onIncrementCounterClick = { onIncrementHotpCounterClick(service) }, + onRevealClick = { onRevealClick(service) } ) } } diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesViewModel.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesViewModel.kt index 1cbdc566..29d2381a 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesViewModel.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesViewModel.kt @@ -239,6 +239,14 @@ internal class ServicesViewModel( } } + fun reveal(service: Service) { + launchScoped { + servicesRepository.revealService( + id = service.id, + ) + } + } + data class CombinedResult( val groups: List, val services: List, diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/StateMapper.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/StateMapper.kt index 37662bb4..b916b50b 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/StateMapper.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/StateMapper.kt @@ -31,7 +31,8 @@ fun Service.asState(): ServiceState { iconDark = iconDark, labelText = labelText, labelColor = labelColor.asState(), - badgeColor = badgeColor.asState() + badgeColor = badgeColor.asState(), + revealed = revealTimestamp?.let { it + 10000L > System.currentTimeMillis() } ?: false, ) } diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessScreen.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessScreen.kt index 9bdf2732..a7c9f0d6 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessScreen.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -71,8 +70,10 @@ internal fun AddServiceSuccessScreen( DsServiceModal( state = service.asState(), showNextCode = uiState.showNextCode, + hideCodes = uiState.hideCodes, containerColor = TwTheme.color.surface, onIncrementCounterClick = { viewModel.incrementHotpCounter(service) }, + onRevealClick = { viewModel.reveal(service) } ) } diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessUiState.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessUiState.kt index 39597be3..41047fad 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessUiState.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessUiState.kt @@ -5,4 +5,5 @@ import com.twofasapp.data.services.domain.Service internal data class AddServiceSuccessUiState( val service: Service? = null, val showNextCode: Boolean = false, + val hideCodes: Boolean = false, ) diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessViewModel.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessViewModel.kt index 1813e25e..c0ce48d4 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessViewModel.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/add/success/AddServiceSuccessViewModel.kt @@ -9,7 +9,6 @@ import com.twofasapp.data.services.domain.Service import com.twofasapp.data.session.SettingsRepository import com.twofasapp.feature.home.ui.services.add.NavArg import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.update internal class AddServiceSuccessViewModel( @@ -35,9 +34,13 @@ internal class AddServiceSuccessViewModel( launchScoped { settingsRepository.observeAppSettings() - .distinctUntilChangedBy { it.showNextCode } .collect { settings -> - uiState.update { it.copy(showNextCode = settings.showNextCode) } + uiState.update { + it.copy( + showNextCode = settings.showNextCode, + hideCodes = settings.hideCodes, + ) + } } } } @@ -45,4 +48,8 @@ internal class AddServiceSuccessViewModel( fun incrementHotpCounter(service: Service) { launchScoped { servicesRepository.incrementHotpCounter(service) } } + + fun reveal(service: Service) { + launchScoped { servicesRepository.revealService(service.id) } + } } diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceModal.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceModal.kt index e903b37b..350fa7f2 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceModal.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceModal.kt @@ -49,7 +49,8 @@ fun FocusServiceModal( iconDark = "", labelText = null, labelColor = Color.Unspecified, - badgeColor = Color.Unspecified + badgeColor = Color.Unspecified, + revealed = true, ) Modal { @@ -59,11 +60,12 @@ fun FocusServiceModal( DsServiceModal( state = serviceState, showNextCode = uiState.showNextCode, + hideCodes = uiState.hideCodes, containerColor = TwTheme.color.surface, - onIncrementCounterClick = { viewModel.incrementCounter() } + onIncrementCounterClick = { viewModel.incrementCounter() }, + onRevealClick = { viewModel.reveal() } ) - SettingsDivider() ModalList { diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceUiState.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceUiState.kt index df0ca4af..2b46e688 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceUiState.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceUiState.kt @@ -5,4 +5,5 @@ import com.twofasapp.data.services.domain.Service internal data class FocusServiceUiState( val service: Service? = null, val showNextCode: Boolean = false, + val hideCodes: Boolean = false, ) diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceViewModel.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceViewModel.kt index 25eaa27b..90a7811c 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceViewModel.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/focus/FocusServiceViewModel.kt @@ -7,7 +7,6 @@ import com.twofasapp.common.ktx.launchScoped import com.twofasapp.data.services.ServicesRepository import com.twofasapp.data.session.SettingsRepository import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update @@ -31,9 +30,13 @@ class FocusServiceViewModel( launchScoped { settingsRepository.observeAppSettings() - .distinctUntilChangedBy { it.showNextCode } .collect { settings -> - uiState.update { it.copy(showNextCode = settings.showNextCode) } + uiState.update { + it.copy( + showNextCode = settings.showNextCode, + hideCodes = settings.hideCodes, + ) + } } } } @@ -43,4 +46,8 @@ class FocusServiceViewModel( launchScoped { servicesRepository.incrementHotpCounter(it) } } } + + fun reveal() { + launchScoped { servicesRepository.revealService(serviceId) } + } } diff --git a/feature/trash/src/main/java/com/twofasapp/feature/trash/ui/trash/TrashScreen.kt b/feature/trash/src/main/java/com/twofasapp/feature/trash/ui/trash/TrashScreen.kt index f2f81a20..7eef7044 100644 --- a/feature/trash/src/main/java/com/twofasapp/feature/trash/ui/trash/TrashScreen.kt +++ b/feature/trash/src/main/java/com/twofasapp/feature/trash/ui/trash/TrashScreen.kt @@ -81,6 +81,7 @@ private fun TrashScreen( iconDark = it.iconDark, labelText = it.labelText, labelColor = it.labelColor.asState(), + revealed = true, ), modifier = Modifier .fillMaxWidth() diff --git a/persistence/schemas/com.twofasapp.persistence.AppDatabase/12.json b/persistence/schemas/com.twofasapp.persistence.AppDatabase/12.json new file mode 100644 index 00000000..98cd659b --- /dev/null +++ b/persistence/schemas/com.twofasapp.persistence.AppDatabase/12.json @@ -0,0 +1,284 @@ +{ + "formatVersion": 1, + "database": { + "version": 12, + "identityHash": "adbe89b487f593074103911e789d6bf3", + "entities": [ + { + "tableName": "local_services", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `secret` TEXT NOT NULL, `serviceTypeId` TEXT, `iconCollectionId` TEXT, `source` TEXT, `otpLink` TEXT, `otpLabel` TEXT, `otpAccount` TEXT, `otpIssuer` TEXT, `otpDigits` INTEGER, `otpPeriod` INTEGER, `otpAlgorithm` TEXT, `backupSyncStatus` TEXT NOT NULL, `updatedAt` INTEGER NOT NULL, `badgeColor` TEXT, `selectedImageType` TEXT, `labelText` TEXT, `labelBackgroundColor` TEXT, `groupId` TEXT, `isDeleted` INTEGER, `authType` TEXT, `hotpCounter` INTEGER, `hotpCounterTimestamp` INTEGER, `revealTimestamp` INTEGER, `assignedDomains` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secret", + "columnName": "secret", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceTypeId", + "columnName": "serviceTypeId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "iconCollectionId", + "columnName": "iconCollectionId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "otpLink", + "columnName": "otpLink", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "otpLabel", + "columnName": "otpLabel", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "otpAccount", + "columnName": "otpAccount", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "otpIssuer", + "columnName": "otpIssuer", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "otpDigits", + "columnName": "otpDigits", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "otpPeriod", + "columnName": "otpPeriod", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "otpAlgorithm", + "columnName": "otpAlgorithm", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "backupSyncStatus", + "columnName": "backupSyncStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updatedAt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "badgeColor", + "columnName": "badgeColor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "selectedImageType", + "columnName": "selectedImageType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "labelText", + "columnName": "labelText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "labelBackgroundColor", + "columnName": "labelBackgroundColor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "groupId", + "columnName": "groupId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isDeleted", + "columnName": "isDeleted", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "authType", + "columnName": "authType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "hotpCounter", + "columnName": "hotpCounter", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hotpCounterTimestamp", + "columnName": "hotpCounterTimestamp", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "revealTimestamp", + "columnName": "revealTimestamp", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "assignedDomains", + "columnName": "assignedDomains", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "paired_browsers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `extensionPublicKey` TEXT NOT NULL, `pairedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extensionPublicKey", + "columnName": "extensionPublicKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pairedAt", + "columnName": "pairedAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `category` TEXT NOT NULL, `link` TEXT NOT NULL, `message` TEXT NOT NULL, `publishTime` INTEGER NOT NULL, `push` INTEGER NOT NULL, `platform` TEXT NOT NULL, `isRead` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "link", + "columnName": "link", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "publishTime", + "columnName": "publishTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "push", + "columnName": "push", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "platform", + "columnName": "platform", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "isRead", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'adbe89b487f593074103911e789d6bf3')" + ] + } +} \ No newline at end of file diff --git a/persistence/src/main/java/com/twofasapp/persistence/AppDatabase.kt b/persistence/src/main/java/com/twofasapp/persistence/AppDatabase.kt index 7b4c301a..45bab224 100644 --- a/persistence/src/main/java/com/twofasapp/persistence/AppDatabase.kt +++ b/persistence/src/main/java/com/twofasapp/persistence/AppDatabase.kt @@ -36,7 +36,7 @@ import java.text.Normalizer @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { companion object { - const val DB_VERSION = 11 + const val DB_VERSION = 12 } abstract fun serviceDao(): ServiceDao @@ -244,6 +244,12 @@ val MIGRATION_10_11 = object : Migration(10, 11) { } } +val MIGRATION_11_12 = object : Migration(11, 12) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE local_services ADD COLUMN revealTimestamp INTEGER") + } +} + private fun Cursor.long(column: String): Long { return getLong(columnNames.indexOf(column)) } diff --git a/persistence/src/main/java/com/twofasapp/persistence/PersistenceModule.kt b/persistence/src/main/java/com/twofasapp/persistence/PersistenceModule.kt index 87e0fc10..ea5b881d 100644 --- a/persistence/src/main/java/com/twofasapp/persistence/PersistenceModule.kt +++ b/persistence/src/main/java/com/twofasapp/persistence/PersistenceModule.kt @@ -34,6 +34,7 @@ class PersistenceModule : KoinModule { MIGRATION_6_7, MIGRATION_9_10, MIGRATION_10_11, + MIGRATION_11_12, ) if (get().isDebuggable.not()) { diff --git a/services/src/main/java/com/twofasapp/services/data/ServicesLocalDataImpl.kt b/services/src/main/java/com/twofasapp/services/data/ServicesLocalDataImpl.kt index 41b4dc34..89e2d2bc 100644 --- a/services/src/main/java/com/twofasapp/services/data/ServicesLocalDataImpl.kt +++ b/services/src/main/java/com/twofasapp/services/data/ServicesLocalDataImpl.kt @@ -140,7 +140,8 @@ internal class ServicesLocalDataImpl( authType = service.authType.name, hotpCounter = service.hotpCounter, hotpCounterTimestamp = null, - assignedDomains = service.assignedDomains + assignedDomains = service.assignedDomains, + revealTimestamp = null, ) ).also { Timber.d("InsertService: Inserted with id $it") @@ -188,6 +189,7 @@ internal class ServicesLocalDataImpl( hotpCounter = it.hotpCounter, hotpCounterTimestamp = null, assignedDomains = it.assignedDomains, + revealTimestamp = null, ) }.toTypedArray() ) diff --git a/services/src/main/java/com/twofasapp/services/data/converter/ServiceConverter.kt b/services/src/main/java/com/twofasapp/services/data/converter/ServiceConverter.kt index ccd1a5d3..935d6462 100644 --- a/services/src/main/java/com/twofasapp/services/data/converter/ServiceConverter.kt +++ b/services/src/main/java/com/twofasapp/services/data/converter/ServiceConverter.kt @@ -68,6 +68,7 @@ internal fun Service.toEntity() = com.twofasapp.data.services.local.model.Servic hotpCounter = otp.hotpCounter, hotpCounterTimestamp = null, assignedDomains = assignedDomains, + revealTimestamp = null, ) internal fun Service.toDeprecatedDto() = ServiceDto(