From 53c0456663bf99eb32408a7dc9e4ce756ff43a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Koby=C5=82ko?= Date: Sat, 11 Feb 2023 18:13:04 +0100 Subject: [PATCH] Service list item --- buildlogic/build.gradle.kts | 4 +- .../buildlogic/extension/KotlinAndroid.kt | 6 +- .../twofasapp/designsystem/service/Service.kt | 105 ++++++++---------- .../designsystem/service/ServiceDefault.kt | 82 ++++++++++++++ .../designsystem/service/ServiceModal.kt | 46 ++------ .../designsystem/service/ServiceNoCode.kt | 77 ------------- .../designsystem/service/ServiceNormal.kt | 104 ----------------- .../designsystem/service/ServiceStyle.kt | 5 +- .../service/ServiceWithoutCode.kt | 70 ++++++++++++ .../service/component/ServiceBadge.kt | 8 ++ .../service/component/ServiceCode.kt | 41 +++++++ .../service/component/ServiceData.kt | 59 ---------- .../service/component/ServiceImage.kt | 2 +- .../service/component/ServiceInfo.kt | 38 +++++++ .../service/component/ServiceName.kt | 31 ++++++ .../service/component/ServiceTimer.kt | 29 ++++- .../src/main/res/values/strings-duration.xml | 31 ------ core/locale/src/main/res/values/strings.xml | 59 +++++++++- .../java/com/twofasapp/storage/Preferences.kt | 2 +- .../storage/internal/PreferencesDelegate.kt | 6 +- .../data/services/GroupsRepositoryImpl.kt | 11 +- .../data/services/di/DataServicesModule.kt | 2 + .../twofasapp/data/services/domain/Group.kt | 2 +- .../twofasapp/data/services/domain/Service.kt | 6 +- .../data/services/local/GroupsLocalSource.kt | 34 ++++++ .../data/services/local/model/GroupEntity.kt | 32 ++++++ .../data/services/local/model/GroupsEntity.kt | 9 ++ .../data/services/mapper/GroupsMapper.kt | 9 ++ .../data/services/mapper/ServiceMapper.kt | 1 + .../externalimport/ui/aegis/AegisScreen.kt | 2 +- .../home/ui/services/ServicesScreen.kt | 54 +++++---- .../home/ui/services/ServicesUiState.kt | 4 +- .../home/ui/services/ServicesViewModel.kt | 5 +- .../ui/services/modal/FocusServiceModal.kt | 2 +- .../feature/trash/ui/trash/TrashScreen.kt | 29 ++--- gradle/wrapper/gradle-wrapper.properties | 2 +- resources/src/main/res/values/strings.xml | 59 +++++++++- 37 files changed, 629 insertions(+), 439 deletions(-) create mode 100644 core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceDefault.kt delete mode 100644 core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceNoCode.kt delete mode 100644 core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceNormal.kt create mode 100644 core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceWithoutCode.kt create mode 100644 core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceCode.kt create mode 100644 core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceInfo.kt create mode 100644 core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceName.kt delete mode 100644 core/locale/src/main/res/values/strings-duration.xml create mode 100644 data/services/src/main/java/com/twofasapp/data/services/local/model/GroupEntity.kt create mode 100644 data/services/src/main/java/com/twofasapp/data/services/local/model/GroupsEntity.kt create mode 100644 data/services/src/main/java/com/twofasapp/data/services/mapper/GroupsMapper.kt diff --git a/buildlogic/build.gradle.kts b/buildlogic/build.gradle.kts index 154feccf..431d03be 100644 --- a/buildlogic/build.gradle.kts +++ b/buildlogic/build.gradle.kts @@ -10,8 +10,8 @@ repositories { } java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { diff --git a/buildlogic/src/main/java/com/twofasapp/buildlogic/extension/KotlinAndroid.kt b/buildlogic/src/main/java/com/twofasapp/buildlogic/extension/KotlinAndroid.kt index b13a6352..5b28ac65 100644 --- a/buildlogic/src/main/java/com/twofasapp/buildlogic/extension/KotlinAndroid.kt +++ b/buildlogic/src/main/java/com/twofasapp/buildlogic/extension/KotlinAndroid.kt @@ -27,8 +27,8 @@ internal fun Project.applyKotlinAndroid( compileOptions { isCoreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } buildFeatures { @@ -37,7 +37,7 @@ internal fun Project.applyKotlinAndroid( } kotlinOptions { - jvmTarget = "11" + jvmTarget = "17" freeCompilerArgs = freeCompilerArgs + listOf( "-opt-in=kotlin.RequiresOptIn", diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/Service.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/Service.kt index 1638a67f..6a2e4d4b 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/Service.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/Service.kt @@ -1,82 +1,69 @@ package com.twofasapp.designsystem.service -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.twofasapp.designsystem.TwIcons import com.twofasapp.designsystem.TwTheme import com.twofasapp.designsystem.common.TwIconButton -@OptIn(ExperimentalFoundationApi::class) @Composable fun Service( state: ServiceState, style: ServiceStyle, - isInEditMode: Boolean = false, - containerColor: Color = TwTheme.color.background, modifier: Modifier = Modifier, - onClick: () -> Unit = {}, - onLongClick: () -> Unit = {}, + containerColor: Color = TwTheme.color.background, + onClick: (() -> Unit)? = null, + onLongClick: (() -> Unit)? = null, ) { - if (isInEditMode) { - Box( - modifier = modifier - .fillMaxWidth() - .clip(TwTheme.shape.roundedDefault) - .background(containerColor) - .combinedClickable( - onClick = { onClick() }, - onLongClick = { onLongClick() }, - ) - .border(1.dp, TwTheme.color.surfaceVariant, TwTheme.shape.roundedDefault) - .padding(start = 21.dp) - ) { - ServiceNoCode( - name = state.name, - info = state.info, - imageType = state.imageType, - iconLight = state.iconLight, - iconDark = state.iconDark, - labelText = state.labelText, - labelColor = state.labelColor, - imageSize = 36.dp, + when (style) { + ServiceStyle.Default -> { + ServiceDefault( + state = state, containerColor = containerColor, - modifier = Modifier, + modifier = modifier, + onClick = onClick, + onLongClick = onLongClick, + ) + } + + ServiceStyle.Modal -> { + ServiceModal( + state = state, + containerColor = containerColor, + modifier = modifier, + ) + } + + ServiceStyle.Edit -> { + ServiceWithoutCode( + state = state, + imageSize = 36.dp, + showBadge = true, + containerColor = containerColor, + modifier = modifier, ) { TwIconButton(TwIcons.DragHandle, enabled = false) } } - } else { - when (style) { - ServiceStyle.Normal -> { - ServiceNormal( - state = state, - containerColor = containerColor, - modifier = modifier, - onClick = onClick, - onLongClick = onLongClick, - ) - } - - ServiceStyle.Modal -> { - ServiceModal( - state = state, - containerColor = containerColor, - modifier = modifier, - ) - } - - ServiceStyle.Compact -> {} - } + ServiceStyle.Compact -> {} } -} \ No newline at end of file +} + + +internal val ServicePreview = ServiceState( + name = "Service Name", + info = "Additional Info", + code = "123456", + nextCode = "789987", + timer = 10, + progress = .33f, + imageType = ServiceImageType.Label, + iconLight = "", + iconDark = "", + labelText = "2F", + labelColor = Color.Red, + badgeColor = Color.Red, +) \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceDefault.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceDefault.kt new file mode 100644 index 00000000..c0350d12 --- /dev/null +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceDefault.kt @@ -0,0 +1,82 @@ +package com.twofasapp.designsystem.service + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.twofasapp.designsystem.TwTheme +import com.twofasapp.designsystem.service.component.ServiceBadge +import com.twofasapp.designsystem.service.component.ServiceCode +import com.twofasapp.designsystem.service.component.ServiceImage +import com.twofasapp.designsystem.service.component.ServiceInfo +import com.twofasapp.designsystem.service.component.ServiceName +import com.twofasapp.designsystem.service.component.ServiceTimer + +@OptIn(ExperimentalFoundationApi::class) +@Composable +internal fun ServiceDefault( + state: ServiceState, + modifier: Modifier = Modifier, + containerColor: Color = TwTheme.color.background, + onClick: (() -> Unit)? = null, + onLongClick: (() -> Unit)? = null, +) { + Row( + modifier = modifier + .fillMaxWidth() + .height(128.dp) + .background(containerColor) + .combinedClickable( + enabled = onClick != null, + onClick = { onClick?.invoke() }, + onLongClick = { onLongClick?.invoke() }, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + + ServiceBadge( + color = state.badgeColor, + ) + + ServiceImage( + type = state.imageType, + iconLight = state.iconLight, + iconDark = state.iconDark, + labelText = state.labelText, + labelColor = state.labelColor, + ) + + Column(modifier = Modifier.weight(1f)) { + ServiceName(state.name) + ServiceInfo(state.info) + ServiceCode( + code = state.code, + nextCode = state.nextCode, + ) + } + + ServiceTimer( + timer = state.timer, + progress = state.progress, + modifier = Modifier.padding(end = 12.dp), + ) + } +} + +@Preview +@Composable +private fun Preview() { + ServiceDefault(state = ServicePreview) +} diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceModal.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceModal.kt index 9fd8f20e..4f572979 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceModal.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceModal.kt @@ -1,13 +1,11 @@ package com.twofasapp.designsystem.service import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -24,8 +22,8 @@ import com.twofasapp.designsystem.service.component.ServiceTimer @Composable internal fun ServiceModal( state: ServiceState, - containerColor: Color = TwTheme.color.background, modifier: Modifier = Modifier, + containerColor: Color = TwTheme.color.background, ) { Column( modifier = modifier @@ -46,13 +44,11 @@ internal fun ServiceModal( modifier = Modifier.padding(horizontal = 16.dp) ) - Spacer(Modifier.height(16.dp)) - Row( - verticalAlignment = Alignment.CenterVertically + modifier = Modifier.padding(top = 16.dp, start = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - Spacer(Modifier.width(16.dp)) - ServiceImage( type = state.imageType, iconLight = state.iconLight, @@ -61,47 +57,23 @@ internal fun ServiceModal( labelColor = state.labelColor ) - Spacer(Modifier.width(16.dp)) - ServiceCode( code = state.code, nextCode = state.nextCode, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) - Spacer(Modifier.width(16.dp)) - ServiceTimer( timer = state.timer, - progress = state.progress + progress = state.progress, + modifier = Modifier.padding(end = 12.dp), ) - - Spacer(Modifier.width(12.dp)) } } - } - @Preview @Composable private fun Preview() { - Service( - state = ServiceState( - name = "Service Name", - info = "Additional Info", - code = "123456", - nextCode = "456789", - timer = 15, - progress = .5f, - imageType = ServiceImageType.Label, - iconLight = "Hollie", - iconDark = "Louisa", - labelText = "2F", - labelColor = Color.Red, - badgeColor = Color.Red, - ), - style = ServiceStyle.Modal, - modifier = Modifier.fillMaxWidth() - ) + ServiceModal(state = ServicePreview) } diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceNoCode.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceNoCode.kt deleted file mode 100644 index 78578322..00000000 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceNoCode.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.twofasapp.designsystem.service - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.twofasapp.designsystem.TwTheme -import com.twofasapp.designsystem.service.component.ServiceData -import com.twofasapp.designsystem.service.component.ServiceImage - -@Composable -fun ServiceNoCode( - name: String, - info: String? = null, - imageType: ServiceImageType, - iconLight: String, - iconDark: String, - labelText: String?, - labelColor: Color, - imageSize: Dp = 36.dp, - containerColor: Color = TwTheme.color.background, - modifier: Modifier = Modifier, - endContent: @Composable () -> Unit = {}, -) { - Row( - modifier = modifier - .height(64.dp) - .background(containerColor), - verticalAlignment = Alignment.CenterVertically - ) { - ServiceImage( - type = imageType, - iconLight = iconLight, - iconDark = iconDark, - labelText = labelText, - labelColor = labelColor, - size = imageSize, - ) - - Spacer(Modifier.width(16.dp)) - - ServiceData( - name = name, - info = info, - modifier = Modifier.weight(1f) - ) - - Spacer(Modifier.width(16.dp)) - - endContent() - } -} - -@Preview -@Composable -private fun Preview() { - ServiceNoCode( - name = "Service Name", - info = "Additional Info", - imageType = ServiceImageType.Label, - iconLight = "Hollie", - iconDark = "Louisa", - labelText = "2F", - labelColor = Color.Red, - modifier = Modifier.fillMaxWidth(), - endContent = {} - ) -} \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceNormal.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceNormal.kt deleted file mode 100644 index 3b38b132..00000000 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceNormal.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.twofasapp.designsystem.service - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -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.TwTheme -import com.twofasapp.designsystem.service.component.ServiceBadge -import com.twofasapp.designsystem.service.component.ServiceData -import com.twofasapp.designsystem.service.component.ServiceImage -import com.twofasapp.designsystem.service.component.ServiceTimer - -@OptIn(ExperimentalFoundationApi::class) -@Composable -internal fun ServiceNormal( - state: ServiceState, - containerColor: Color = TwTheme.color.background, - modifier: Modifier = Modifier, - onClick: () -> Unit = {}, - onLongClick: () -> Unit = {}, -) { - Row( - modifier = modifier - .fillMaxWidth() - .height(128.dp) - .clip(TwTheme.shape.roundedDefault) - .background(containerColor) - .combinedClickable( - onClick = { onClick() }, - onLongClick = { onLongClick() }, - ) - .border(1.dp, TwTheme.color.surfaceVariant, TwTheme.shape.roundedDefault), - - verticalAlignment = Alignment.CenterVertically - ) { - ServiceBadge(color = state.badgeColor) - - Spacer(Modifier.width(16.dp)) - - ServiceImage( - type = state.imageType, - iconLight = state.iconLight, - iconDark = state.iconDark, - labelText = state.labelText, - labelColor = state.labelColor - ) - - Spacer(Modifier.width(16.dp)) - - ServiceData( - name = state.name, - info = state.info, - code = state.code, - nextCode = state.code, - modifier = Modifier.weight(1f) - ) - - Spacer(Modifier.width(16.dp)) - - ServiceTimer( - timer = state.timer, - progress = state.progress - ) - - Spacer(Modifier.width(12.dp)) - } -} - - -@Preview -@Composable -private fun Preview() { - Service( - state = ServiceState( - name = "Service Name", - info = "Additional Info", - code = "123456", - nextCode = "456789", - timer = 15, - progress = .5f, - imageType = ServiceImageType.Label, - iconLight = "Hollie", - iconDark = "Louisa", - labelText = "2F", - labelColor = Color.Red, - badgeColor = Color.Red, - ), - style = ServiceStyle.Normal, - modifier = Modifier.fillMaxWidth() - ) -} diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceStyle.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceStyle.kt index 2a9624a3..e7e93c6a 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceStyle.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceStyle.kt @@ -1,7 +1,8 @@ package com.twofasapp.designsystem.service enum class ServiceStyle { - Normal, - Modal, + Default, Compact, + Edit, + Modal, } \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceWithoutCode.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceWithoutCode.kt new file mode 100644 index 00000000..73dd86f7 --- /dev/null +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/ServiceWithoutCode.kt @@ -0,0 +1,70 @@ +package com.twofasapp.designsystem.service + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.twofasapp.designsystem.TwTheme +import com.twofasapp.designsystem.service.component.ServiceBadge +import com.twofasapp.designsystem.service.component.ServiceImage +import com.twofasapp.designsystem.service.component.ServiceInfo +import com.twofasapp.designsystem.service.component.ServiceName + +@Composable +fun ServiceWithoutCode( + state: ServiceState, + modifier: Modifier = Modifier, + imageSize: Dp = 36.dp, + showBadge: Boolean = false, + containerColor: Color = TwTheme.color.background, + content: @Composable () -> Unit = {}, +) { + Row( + modifier = modifier + .height(64.dp) + .background(containerColor), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + if (showBadge) { + ServiceBadge( + color = state.badgeColor, + ) + } + + ServiceImage( + type = state.imageType, + iconLight = state.iconLight, + iconDark = state.iconDark, + labelText = state.labelText, + labelColor = state.labelColor, + size = imageSize, + ) + + Column(Modifier.weight(1f)) { + ServiceName(text = state.name) + ServiceInfo(text = state.info) + } + + content() + } +} + +@Preview +@Composable +private fun Preview() { + ServiceWithoutCode( + state = ServicePreview, + modifier = Modifier.fillMaxWidth(), + content = {} + ) +} \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceBadge.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceBadge.kt index ce3800e6..c990211d 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceBadge.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceBadge.kt @@ -7,7 +7,9 @@ import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.twofasapp.designsystem.TwTheme @Composable fun ServiceBadge(color: Color) { @@ -17,4 +19,10 @@ fun ServiceBadge(color: Color) { .width(5.dp) .background(color) ) +} + +@Preview +@Composable +private fun Preview() { + ServiceBadge(color = TwTheme.color.primary) } \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceCode.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceCode.kt new file mode 100644 index 00000000..f6ea17df --- /dev/null +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceCode.kt @@ -0,0 +1,41 @@ +package com.twofasapp.designsystem.service.component + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import com.twofasapp.designsystem.TwTheme + +@Composable +fun ServiceCode( + code: String, + nextCode: String, + modifier: Modifier = Modifier, +) { + Text( + text = code.formatCode(), + style = TwTheme.typo.h1, + color = TwTheme.color.onSurfacePrimary, + maxLines = 1, + overflow = TextOverflow.Visible, + modifier = modifier, + ) +} + +private fun String.formatCode(): String { + if (isEmpty()) return "" + + return when (this.length) { + 6 -> "${take(3)} ${takeLast(3)}" + 7 -> "${take(4)} ${takeLast(3)}" + 8 -> "${take(4)} ${takeLast(4)}" + else -> this + } +} + +@Preview +@Composable +private fun Preview() { + ServiceCode(code = "123456", nextCode = "789987") +} \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceData.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceData.kt index abc162d8..33df9b3f 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceData.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceData.kt @@ -31,68 +31,9 @@ fun ServiceData( } } -@Composable -fun ServiceName( - text: String, - style: TextStyle = TwTheme.typo.body3, - modifier: Modifier = Modifier -) { - Text( - text = text, - style = style, - color = TwTheme.color.onSurfacePrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = modifier, - ) -} -@Composable -fun ServiceInfo( - text: String?, - style: TextStyle = TwTheme.typo.body3, - modifier: Modifier = Modifier -) { - if (text.isNullOrEmpty().not()) { - Text( - text = text!!, - style = style, - color = TwTheme.color.onSurfaceSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = modifier, - ) - } else { - Spacer(Modifier.width(8.dp)) - } -} -@Composable -fun ServiceCode( - code: String, - nextCode: String, - modifier: Modifier = Modifier, -) { - Text( - text = code.formatCode(), - style = TwTheme.typo.h1, - color = TwTheme.color.onSurfacePrimary, - maxLines = 1, - overflow = TextOverflow.Visible, - modifier = modifier, - ) -} -private fun String.formatCode(): String { - if (isEmpty()) return "" - - return when (this.length) { - 6 -> "${take(3)} ${takeLast(3)}" - 7 -> "${take(4)} ${takeLast(3)}" - 8 -> "${take(4)} ${takeLast(4)}" - else -> this - } -} @Preview @Composable diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceImage.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceImage.kt index ab47e2f9..d0777327 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceImage.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceImage.kt @@ -26,8 +26,8 @@ fun ServiceImage( iconDark: String, labelText: String?, labelColor: Color, - size: Dp = 36.dp, modifier: Modifier = Modifier, + size: Dp = 36.dp, ) { Box(modifier = modifier) { when (type) { diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceInfo.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceInfo.kt new file mode 100644 index 00000000..7e7a90b6 --- /dev/null +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceInfo.kt @@ -0,0 +1,38 @@ +package com.twofasapp.designsystem.service.component + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.twofasapp.designsystem.TwTheme + +@Composable +fun ServiceInfo( + text: String?, + modifier: Modifier = Modifier, + style: TextStyle = TwTheme.typo.body3 +) { + if (text.isNullOrEmpty().not()) { + Text( + text = text!!, + style = style, + color = TwTheme.color.onSurfaceSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = modifier, + ) + } else { + Spacer(Modifier.width(8.dp)) + } +} + +@Preview +@Composable +private fun Preview() { + ServiceInfo(text = "Info") +} \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceName.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceName.kt new file mode 100644 index 00000000..f12a64f7 --- /dev/null +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceName.kt @@ -0,0 +1,31 @@ +package com.twofasapp.designsystem.service.component + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import com.twofasapp.designsystem.TwTheme + +@Composable +fun ServiceName( + text: String, + modifier: Modifier = Modifier, + style: TextStyle = TwTheme.typo.body3 +) { + Text( + text = text, + style = style, + color = TwTheme.color.onSurfacePrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = modifier, + ) +} + +@Preview +@Composable +private fun Preview() { + ServiceName(text = "Name") +} \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceTimer.kt b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceTimer.kt index bb696cdd..8dba7b11 100644 --- a/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceTimer.kt +++ b/core/designsystem/src/main/java/com/twofasapp/designsystem/service/component/ServiceTimer.kt @@ -1,12 +1,16 @@ package com.twofasapp.designsystem.service.component +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ProgressIndicatorDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.twofasapp.designsystem.TwTheme @@ -16,14 +20,33 @@ fun ServiceTimer( progress: Float, modifier: Modifier = Modifier, ) { - Box(modifier, contentAlignment = Alignment.Center) { + val progressFraction by animateFloatAsState( + targetValue = progress, + animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec + ) + + Box( + modifier = modifier, + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator( - progress = progress, + progress = progressFraction, color = TwTheme.color.onSurfacePrimary, strokeWidth = 2.dp, modifier = Modifier.size(32.dp), ) - Text(text = timer.toString(), style = TwTheme.typo.caption, color = TwTheme.color.onSurfacePrimary) + Text( + text = timer.toString(), + style = TwTheme.typo.caption, + color = TwTheme.color.onSurfacePrimary + ) } +} + +@Preview +@Composable +private fun Preview() { + ServiceTimer(timer = 10, progress = 0.33f) } \ No newline at end of file diff --git a/core/locale/src/main/res/values/strings-duration.xml b/core/locale/src/main/res/values/strings-duration.xml deleted file mode 100644 index 477c7b92..00000000 --- a/core/locale/src/main/res/values/strings-duration.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - moments ago - - - - %d minute ago - %d minutes ago - - - - %d hour ago - %d hours ago - - - - %d day ago - %d days ago - - - - %d week ago - %d weeks ago - - - - %d month ago - %d months ago - - \ No newline at end of file diff --git a/core/locale/src/main/res/values/strings.xml b/core/locale/src/main/res/values/strings.xml index dacd9941..98db338f 100644 --- a/core/locale/src/main/res/values/strings.xml +++ b/core/locale/src/main/res/values/strings.xml @@ -1,11 +1,11 @@ - Incorrect service key (only numbers 2 to 7, letters) + Incorrect service key (only numbers 2 to 7, letters), max. 512 chars long OK, let\'s try again @@ -646,4 +646,57 @@ Check out this awesome two-factor authentication app from 2FAS: https://2fas.com Acknowledgements Import from external app + + moments ago + moments ago + + + %d minute ago + %d minutes ago + + + %d hour ago + %d hours ago + + + %d day ago + %d days ago + + + %d week ago + %d weeks ago + + + %d month ago + %d months ago + + Unknown error occurred! Try again! + File successfully saved! + Could not share file! + Type in password + Enter the backup password to proceed with remove. + Enter the backup password to proceed with revoking access to Google. + Synchronization settings + Local file + Google Drive sync + Delete your backup file from Google Drive? + Google Sync will be disabled. Your tokens will remain locally, but the 2FAS app will be logged out from your Google Account on this and your synced other devices. + Waiting for sync… + Syncing… + You are going to import an encrypted backup file. + Choose JSON file + Export your accounts from Aegis to an unencrypted JSON file and upload it using the \"Choose JSON file\" button. Remember to remove the file after a successful import. + Use the \"Export OTPs to ZIP archive\" option in Raivo\'s Settings, save a ZIP file, extract it and import the JSON file using the \"Choose JSON file\" button. + However, there are no services that could be imported. + Try again + Proceed + Importing 2FA tokens from Google Authenticator app + Importing 2FA tokens from Aegis app + Importing 2FA tokens from Raivo app + This QR code allows importing tokens from Google Authenticator. + This JSON file allows importing tokens from Aegis. + This JSON file allows importing tokens from Raivo. + Could not read any tokens. Try to select a different file. + Google Drive sync info + Are you sure? Without Google Drive sync, you won\'t be able to restore your tokens if you lose or reset your phone! diff --git a/core/storage/src/main/java/com/twofasapp/storage/Preferences.kt b/core/storage/src/main/java/com/twofasapp/storage/Preferences.kt index 770d463e..851ad821 100644 --- a/core/storage/src/main/java/com/twofasapp/storage/Preferences.kt +++ b/core/storage/src/main/java/com/twofasapp/storage/Preferences.kt @@ -15,7 +15,7 @@ interface Preferences { fun putInt(key: String, value: Int) fun putFloat(key: String, value: Float) - fun observe(key: String, default: T): Flow + fun observe(key: String, default: T?): Flow fun delete(key: String) } \ No newline at end of file diff --git a/core/storage/src/main/java/com/twofasapp/storage/internal/PreferencesDelegate.kt b/core/storage/src/main/java/com/twofasapp/storage/internal/PreferencesDelegate.kt index 7f74c476..4c1ac8f7 100644 --- a/core/storage/src/main/java/com/twofasapp/storage/internal/PreferencesDelegate.kt +++ b/core/storage/src/main/java/com/twofasapp/storage/internal/PreferencesDelegate.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart import timber.log.Timber internal class PreferencesDelegate( @@ -27,8 +28,9 @@ internal class PreferencesDelegate( } } - override fun observe(key: String, default: T): Flow { + override fun observe(key: String, default: T?): Flow { return flow + .onStart { emit(key) } .filter { it == key || it == null } .map { @Suppress("UNCHECKED_CAST") @@ -38,7 +40,7 @@ internal class PreferencesDelegate( is Long -> getLong(key) as T? is Boolean -> getBoolean(key) as T? is Float -> getFloat(key) as T? - else -> throw IllegalArgumentException("Unsupported preference flow type") + else -> null } ?: default } .conflate() diff --git a/data/services/src/main/java/com/twofasapp/data/services/GroupsRepositoryImpl.kt b/data/services/src/main/java/com/twofasapp/data/services/GroupsRepositoryImpl.kt index fd6fb7bd..5675dba2 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/GroupsRepositoryImpl.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/GroupsRepositoryImpl.kt @@ -1,11 +1,16 @@ package com.twofasapp.data.services import com.twofasapp.data.services.domain.Group +import com.twofasapp.data.services.local.GroupsLocalSource +import com.twofasapp.data.services.mapper.asDomain import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map + +internal class GroupsRepositoryImpl( + private val local: GroupsLocalSource, +) : GroupsRepository { -internal class GroupsRepositoryImpl : GroupsRepository { override fun observeGroups(): Flow> { - return flow { } + return local.observeGroups().map { it.list.map { it.asDomain() } } } } \ No newline at end of file diff --git a/data/services/src/main/java/com/twofasapp/data/services/di/DataServicesModule.kt b/data/services/src/main/java/com/twofasapp/data/services/di/DataServicesModule.kt index d470ea1d..3e2b5ee8 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/di/DataServicesModule.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/di/DataServicesModule.kt @@ -4,6 +4,7 @@ import com.twofasapp.data.services.GroupsRepository import com.twofasapp.data.services.GroupsRepositoryImpl import com.twofasapp.data.services.ServicesRepository import com.twofasapp.data.services.ServicesRepositoryImpl +import com.twofasapp.data.services.local.GroupsLocalSource import com.twofasapp.data.services.local.ServicesLocalSource import com.twofasapp.data.services.otp.ServiceCodeGenerator import com.twofasapp.di.KoinModule @@ -17,6 +18,7 @@ class DataServicesModule : KoinModule { singleOf(::ServicesLocalSource) singleOf(::ServicesRepositoryImpl) { bind() } + singleOf(::GroupsLocalSource) singleOf(::GroupsRepositoryImpl) { bind() } } } \ No newline at end of file diff --git a/data/services/src/main/java/com/twofasapp/data/services/domain/Group.kt b/data/services/src/main/java/com/twofasapp/data/services/domain/Group.kt index 7cc6bc74..c339ce5f 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/domain/Group.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/domain/Group.kt @@ -1,7 +1,7 @@ package com.twofasapp.data.services.domain data class Group( - val id: String, + val id: String?, val name: String, val isExpanded: Boolean = true, val updatedAt: Long = 0, 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 d502f15c..ad4f06ad 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 @@ -10,7 +10,7 @@ data class Service( val period: Int?, val digits: Int?, val algorithm: Algorithm?, - + val groupId: String? = null, val imageType: ImageType = ImageType.IconCollection, val iconCollectionId: String, val iconLight: String, @@ -25,7 +25,6 @@ data class Service( // val otp: Otp = Otp(), //// -// val groupId: String? = null, // val assignedDomains: List = emptyList(), // val isDeleted: Boolean = false, //// val backupSyncStatus: BackupSyncStatus = BackupSyncStatus.NOT_SYNCED, @@ -89,7 +88,4 @@ data class Service( // ) - - - } diff --git a/data/services/src/main/java/com/twofasapp/data/services/local/GroupsLocalSource.kt b/data/services/src/main/java/com/twofasapp/data/services/local/GroupsLocalSource.kt index 943b24b3..bd6d5695 100644 --- a/data/services/src/main/java/com/twofasapp/data/services/local/GroupsLocalSource.kt +++ b/data/services/src/main/java/com/twofasapp/data/services/local/GroupsLocalSource.kt @@ -1,11 +1,45 @@ package com.twofasapp.data.services.local +import com.twofasapp.data.services.local.model.GroupEntity +import com.twofasapp.data.services.local.model.GroupsEntity import com.twofasapp.storage.PlainPreferences +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json internal class GroupsLocalSource( + private val json: Json, private val preferences: PlainPreferences, ) { companion object { private const val KeyGroups = "groups" } + + fun observeGroups(): Flow { + return preferences.observe(KeyGroups, null).map { value -> + value?.let { + json.decodeFromString(value) + } ?: GroupsEntity() + } + } + + fun insertGroup(groupsEntity: GroupEntity) { + val local = getGroups() + + preferences.putString( + KeyGroups, json.encodeToString( + local.copy( + list = local.list.plus(groupsEntity) + ) + ) + ) + } + + private fun getGroups(): GroupsEntity { + return preferences.getString(KeyGroups)?.let { + json.decodeFromString(it) + } ?: GroupsEntity() + } } \ No newline at end of file diff --git a/data/services/src/main/java/com/twofasapp/data/services/local/model/GroupEntity.kt b/data/services/src/main/java/com/twofasapp/data/services/local/model/GroupEntity.kt new file mode 100644 index 00000000..2f71f186 --- /dev/null +++ b/data/services/src/main/java/com/twofasapp/data/services/local/model/GroupEntity.kt @@ -0,0 +1,32 @@ +package com.twofasapp.data.services.local.model + +import kotlinx.serialization.Serializable + +@Serializable +internal data class GroupEntity( + val id: String?, + val name: String, + val isExpanded: Boolean = true, + val updatedAt: Long = 0, + val backupSyncStatus: String, +) { + +// companion object { +// fun generateNew(name: String) = GroupEntity( +// id = UUID.randomUUID().toString(), +// name = name, +// ) +// } +// +// fun isContentEqualTo(group: GroupEntity) = +// name == group.name && +// id == group.id && +// isExpanded == group.isExpanded +// +// fun toRemote() = RemoteGroup( +// id = id!!, +// name = name, +// isExpanded = isExpanded, +// updatedAt = updatedAt +// ) +} \ No newline at end of file diff --git a/data/services/src/main/java/com/twofasapp/data/services/local/model/GroupsEntity.kt b/data/services/src/main/java/com/twofasapp/data/services/local/model/GroupsEntity.kt new file mode 100644 index 00000000..051da683 --- /dev/null +++ b/data/services/src/main/java/com/twofasapp/data/services/local/model/GroupsEntity.kt @@ -0,0 +1,9 @@ +package com.twofasapp.data.services.local.model + +import kotlinx.serialization.Serializable + +@Serializable +internal data class GroupsEntity( + val list: List = emptyList(), + val isDefaultGroupExpanded: Boolean = true, +) \ No newline at end of file diff --git a/data/services/src/main/java/com/twofasapp/data/services/mapper/GroupsMapper.kt b/data/services/src/main/java/com/twofasapp/data/services/mapper/GroupsMapper.kt new file mode 100644 index 00000000..27db3e31 --- /dev/null +++ b/data/services/src/main/java/com/twofasapp/data/services/mapper/GroupsMapper.kt @@ -0,0 +1,9 @@ +package com.twofasapp.data.services.mapper + +import com.twofasapp.data.services.domain.Group +import com.twofasapp.data.services.local.model.GroupEntity + +internal fun GroupEntity.asDomain() = Group( + id = id, + name = name, +) \ 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 77ba13e0..9bcd99ad 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 @@ -18,6 +18,7 @@ internal fun ServiceEntity.asDomain(): Service { period = otpPeriod, digits = otpDigits, algorithm = otpAlgorithm?.let { Service.Algorithm.valueOf(it) }, + groupId = groupId, imageType = selectedImageType?.let { when (it) { "Brand" -> Service.ImageType.IconCollection diff --git a/feature/externalimport/src/main/java/com/twofasapp/feature/externalimport/ui/aegis/AegisScreen.kt b/feature/externalimport/src/main/java/com/twofasapp/feature/externalimport/ui/aegis/AegisScreen.kt index 2813c2ba..501ba23e 100644 --- a/feature/externalimport/src/main/java/com/twofasapp/feature/externalimport/ui/aegis/AegisScreen.kt +++ b/feature/externalimport/src/main/java/com/twofasapp/feature/externalimport/ui/aegis/AegisScreen.kt @@ -17,7 +17,7 @@ internal fun AegisRoute( ImportFileScaffold( title = stringResource(id = R.string.externalimport_aegis), image = painterResource(id = R.drawable.ic_import_aegis), - description = { ImportDescription(text = "Export your accounts from Aegis to unencrypted JSON file and upload it using \"Choose JSON file\" button. Remember to remove the file after successful import.") } + description = { ImportDescription(text = stringResource(id = R.string.externalimport__aegis_msg)) } ) { ImportFilePickerButton( text = "Choose JSON 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 741e7192..a3d7f080 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 @@ -11,6 +11,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.material3.Divider import androidx.compose.material3.Scaffold import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState @@ -133,20 +134,21 @@ private fun ServicesScreen( is ModalType.FocusService -> { val id = (modalType as ModalType.FocusService).id - - FocusServiceModal( - serviceState = uiState.getService(id).asState(), - onEditClick = { - listener.openService(activity, (modalType as ModalType.FocusService).id) - scope.launch { modalState.hide() } - }, - onCopyClick = { - activity.copyToClipboard( - uiState.getService(id).code?.current.toString() - ) - scope.launch { modalState.hide() } - } - ) + uiState.getService(id)?.asState()?.let { + FocusServiceModal( + serviceState = it, + onEditClick = { + scope.launch { modalState.hide() } + listener.openService(activity, (modalType as ModalType.FocusService).id) + }, + onCopyClick = { + scope.launch { modalState.hide() } + activity.copyToClipboard( + uiState.getService(id)?.code?.current.toString() + ) + } + ) + } } } } @@ -206,6 +208,8 @@ private fun ServicesScreen( items = uiState.services, type = { ServicesListItem.Service(it.id) } ) { service -> + Divider(color = TwTheme.color.divider) + ReorderableItem( state = reorderableState, key = service.id, @@ -215,18 +219,18 @@ private fun ServicesScreen( ) { isDragging -> Service( state = service.asState(), - style = ServiceStyle.Normal, - isInEditMode = uiState.isInEditMode, + style = if (uiState.isInEditMode) ServiceStyle.Edit else ServiceStyle.Default, modifier = Modifier - .padding(horizontal = 8.dp, vertical = 4.dp) .shadow(if (isDragging) 8.dp else 0.dp) - .run { - if (uiState.isInEditMode) { - detectReorderAfterLongPress(reorderableState) - } else { - this - } - }, + .detectReorderAfterLongPress(reorderableState), + +// .run { +// if (uiState.isInEditMode) { +// detectReorderAfterLongPress(reorderableState) +// } else { +// this +// } +// }, onClick = { modalType = ModalType.FocusService(service.id) scope.launch { modalState.show() } @@ -239,6 +243,8 @@ private fun ServicesScreen( ) } } + + item { Divider(color = TwTheme.color.divider) } } } } diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesUiState.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesUiState.kt index f3522109..78363a25 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesUiState.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/ServicesUiState.kt @@ -9,8 +9,8 @@ data class ServicesUiState( val isInEditMode: Boolean = false, val events: List = listOf(), ) { - fun getService(id: Long): Service { - return services.first { it.id == id } + fun getService(id: Long): Service? { + return services.firstOrNull() { it.id == id } } } 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 1532396a..33ad17a4 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 @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.twofasapp.data.services.GroupsRepository import com.twofasapp.data.services.ServicesRepository +import com.twofasapp.data.services.domain.Group import com.twofasapp.data.services.domain.Service import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -24,8 +25,9 @@ internal class ServicesViewModel( viewModelScope.launch { combine( servicesRepository.observeServicesTicker(), + groupsRepository.observeGroups(), isInEditMode, - ) { services, isInEditMode -> CombinedResult(services, isInEditMode) }.collect { result -> + ) { services, groups, isInEditMode -> CombinedResult(services, groups, isInEditMode) }.collect { result -> uiState.update { it.copy( @@ -52,6 +54,7 @@ internal class ServicesViewModel( data class CombinedResult( val services: List, + val groups: List, val isInEditMode: Boolean, ) } \ No newline at end of file diff --git a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/modal/FocusServiceModal.kt b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/modal/FocusServiceModal.kt index 1f9c4921..b24456ef 100644 --- a/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/modal/FocusServiceModal.kt +++ b/feature/home/src/main/java/com/twofasapp/feature/home/ui/services/modal/FocusServiceModal.kt @@ -28,7 +28,7 @@ internal fun FocusServiceModal( ModalList { SettingsLink(title = "Edit", icon = TwIcons.Edit) { onEditClick() } - SettingsLink(title = "Copy code", icon = TwIcons.Copy) { onCopyClick() } + SettingsLink(title = "Copy token", icon = TwIcons.Copy) { onCopyClick() } } } } \ No newline at end of file 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 fec600d3..a387b5c2 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 @@ -25,7 +25,8 @@ import com.twofasapp.designsystem.common.TwDropdownMenuItem import com.twofasapp.designsystem.common.TwIconButton import com.twofasapp.designsystem.common.TwTopAppBar import com.twofasapp.designsystem.service.ServiceImageType -import com.twofasapp.designsystem.service.ServiceNoCode +import com.twofasapp.designsystem.service.ServiceState +import com.twofasapp.designsystem.service.ServiceWithoutCode import com.twofasapp.locale.TwLocale import org.koin.androidx.compose.koinViewModel @@ -74,22 +75,24 @@ private fun TrashScreen( } items(services, key = { it.id }) { - ServiceNoCode( - name = it.name, - info = it.info, - imageType = when (it.imageType) { - Service.ImageType.IconCollection -> ServiceImageType.Icon - Service.ImageType.Label -> ServiceImageType.Label - }, - iconLight = it.iconLight, - iconDark = it.iconDark, - labelText = it.labelText, - labelColor = it.badgeColor.asState(), + ServiceWithoutCode( + state = ServiceState( + name = it.name, + info = it.info, + imageType = when (it.imageType) { + Service.ImageType.IconCollection -> ServiceImageType.Icon + Service.ImageType.Label -> ServiceImageType.Label + }, + iconLight = it.iconLight, + iconDark = it.iconDark, + labelText = it.labelText, + labelColor = it.badgeColor.asState(), + ), imageSize = 32.dp, modifier = Modifier .fillMaxWidth() .padding(start = 16.dp, end = 0.dp), - endContent = { + content = { var expanded by rememberSaveable { mutableStateOf(false) } TwDropdownMenu( diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae01e9eb..3f598e96 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-rc-2-bin.zip diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index dacd9941..98db338f 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -1,11 +1,11 @@ - Incorrect service key (only numbers 2 to 7, letters) + Incorrect service key (only numbers 2 to 7, letters), max. 512 chars long OK, let\'s try again @@ -646,4 +646,57 @@ Check out this awesome two-factor authentication app from 2FAS: https://2fas.com Acknowledgements Import from external app + + moments ago + moments ago + + + %d minute ago + %d minutes ago + + + %d hour ago + %d hours ago + + + %d day ago + %d days ago + + + %d week ago + %d weeks ago + + + %d month ago + %d months ago + + Unknown error occurred! Try again! + File successfully saved! + Could not share file! + Type in password + Enter the backup password to proceed with remove. + Enter the backup password to proceed with revoking access to Google. + Synchronization settings + Local file + Google Drive sync + Delete your backup file from Google Drive? + Google Sync will be disabled. Your tokens will remain locally, but the 2FAS app will be logged out from your Google Account on this and your synced other devices. + Waiting for sync… + Syncing… + You are going to import an encrypted backup file. + Choose JSON file + Export your accounts from Aegis to an unencrypted JSON file and upload it using the \"Choose JSON file\" button. Remember to remove the file after a successful import. + Use the \"Export OTPs to ZIP archive\" option in Raivo\'s Settings, save a ZIP file, extract it and import the JSON file using the \"Choose JSON file\" button. + However, there are no services that could be imported. + Try again + Proceed + Importing 2FA tokens from Google Authenticator app + Importing 2FA tokens from Aegis app + Importing 2FA tokens from Raivo app + This QR code allows importing tokens from Google Authenticator. + This JSON file allows importing tokens from Aegis. + This JSON file allows importing tokens from Raivo. + Could not read any tokens. Try to select a different file. + Google Drive sync info + Are you sure? Without Google Drive sync, you won\'t be able to restore your tokens if you lose or reset your phone!