mirror of
https://github.com/twofas/2fas-android.git
synced 2025-01-05 14:05:30 +01:00
Hide codes
This commit is contained in:
parent
52a6b3267d
commit
a0b06a6fa1
@ -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,
|
||||
)
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
@ -78,7 +78,7 @@ internal fun ServiceCode(
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.formatCode(): String {
|
||||
internal fun String.formatCode(): String {
|
||||
if (isEmpty()) return ""
|
||||
|
||||
return when (this.length) {
|
||||
|
@ -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() }
|
||||
|
@ -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)
|
||||
|
@ -30,4 +30,5 @@ interface ServicesRepository {
|
||||
suspend fun addService(service: Service): Long
|
||||
fun observeAddServiceAdvancedExpanded(): Flow<Boolean>
|
||||
fun pushAddServiceAdvancedExpanded(expanded: Boolean)
|
||||
suspend fun revealService(id: Long)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ data class Service(
|
||||
val source: Source,
|
||||
val assignedDomains: List<String> = emptyList(),
|
||||
val backupSyncStatus: BackupSyncStatus,
|
||||
val revealTimestamp: Long? = null,
|
||||
) {
|
||||
data class Code(
|
||||
val current: String,
|
||||
|
@ -176,151 +176,15 @@ internal class ServicesLocalSource(
|
||||
return addServiceAdvancedExpanded
|
||||
}
|
||||
|
||||
fun pushAddServiceAdvancedExpanded(expanded: Boolean) {
|
||||
fun pushAddServiceAdvancedExpanded(expanded: Boolean) {
|
||||
addServiceAdvancedExpanded.tryEmit(expanded)
|
||||
}
|
||||
//
|
||||
// fun select(): Single<List<ServiceDto>> {
|
||||
// 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<List<ServiceDto>> {
|
||||
// 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<Long> {
|
||||
// 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(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -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<String>?,
|
||||
)
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -69,4 +69,10 @@ internal class SettingsRepositoryImpl(
|
||||
local.setAllowScreenshots(allow)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setHideCodes(hideCodes: Boolean) {
|
||||
withContext(dispatchers.io) {
|
||||
local.setHideCodes(hideCodes)
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
||||
|
@ -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<AppSettings> 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)
|
||||
}
|
||||
}
|
@ -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() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -239,6 +239,14 @@ internal class ServicesViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun reveal(service: Service) {
|
||||
launchScoped {
|
||||
servicesRepository.revealService(
|
||||
id = service.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class CombinedResult(
|
||||
val groups: List<Group>,
|
||||
val services: List<Service>,
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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) }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ private fun TrashScreen(
|
||||
iconDark = it.iconDark,
|
||||
labelText = it.labelText,
|
||||
labelColor = it.labelColor.asState(),
|
||||
revealed = true,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ class PersistenceModule : KoinModule {
|
||||
MIGRATION_6_7,
|
||||
MIGRATION_9_10,
|
||||
MIGRATION_10_11,
|
||||
MIGRATION_11_12,
|
||||
)
|
||||
|
||||
if (get<AppBuild>().isDebuggable.not()) {
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user