mirror of
https://github.com/twofas/2fas-android.git
synced 2024-11-24 11:19:57 +01:00
Service list item
This commit is contained in:
parent
3488c3bc3d
commit
53c0456663
@ -10,8 +10,8 @@ repositories {
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -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",
|
||||
|
@ -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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
)
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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 = {}
|
||||
)
|
||||
}
|
@ -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()
|
||||
)
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package com.twofasapp.designsystem.service
|
||||
|
||||
enum class ServiceStyle {
|
||||
Normal,
|
||||
Modal,
|
||||
Default,
|
||||
Compact,
|
||||
Edit,
|
||||
Modal,
|
||||
}
|
@ -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 = {}
|
||||
)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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")
|
||||
}
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
}
|
@ -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")
|
||||
}
|
@ -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)
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<plurals name="past_duration_seconds" tools:ignore="MissingDefaultResource">
|
||||
<item quantity="other">moments ago</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="past_duration_minutes" tools:ignore="MissingDefaultResource">
|
||||
<item quantity="one">%d minute ago</item>
|
||||
<item quantity="other">%d minutes ago</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="past_duration_hours" tools:ignore="MissingDefaultResource">
|
||||
<item quantity="one">%d hour ago</item>
|
||||
<item quantity="other">%d hours ago</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="past_duration_days" tools:ignore="MissingDefaultResource">
|
||||
<item quantity="one">%d day ago</item>
|
||||
<item quantity="other">%d days ago</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="past_duration_weeks" tools:ignore="MissingDefaultResource">
|
||||
<item quantity="one">%d week ago</item>
|
||||
<item quantity="other">%d weeks ago</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="past_duration_months" tools:ignore="MissingDefaultResource">
|
||||
<item quantity="one">%d month ago</item>
|
||||
<item quantity="other">%d months ago</item>
|
||||
</plurals>
|
||||
</resources>
|
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Loco xml export: Android string resources
|
||||
Project: 2FAS iOS
|
||||
Project: 2FAS App
|
||||
Release: Working copy
|
||||
Locale: en, English
|
||||
Exported by: rafakob
|
||||
Exported at: Wed, 28 Dec 2022 11:53:36 +0100
|
||||
Exported at: Sat, 11 Feb 2023 08:20:15 -0800
|
||||
-->
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<!-- InfoPlist.strings
|
||||
@ -160,7 +160,7 @@
|
||||
<string name="tokens__choose_method">Choose method</string>
|
||||
<string name="tokens__duplicated_private_key">Duplicated Service Key</string>
|
||||
<!-- tokens__incorrect_private_key -->
|
||||
<string name="tokens__incorrect_service_key">Incorrect service key (only numbers 2 to 7, letters)</string>
|
||||
<string name="tokens__incorrect_service_key">Incorrect service key (only numbers 2 to 7, letters), max. 512 chars long</string>
|
||||
<string name="tokens__try_again">OK, let\'s try again</string>
|
||||
<!-- Original: To capture the QR code
|
||||
tokens__point_right_2fa_code -->
|
||||
@ -646,4 +646,57 @@
|
||||
<string name="settings__recommendation">Check out this awesome two-factor authentication app from 2FAS: https://2fas.com</string>
|
||||
<string name="settings__acknowledgements">Acknowledgements</string>
|
||||
<string name="introduction__import_external_app">Import from external app</string>
|
||||
<plurals name="past_duration_seconds">
|
||||
<item quantity="one">moments ago</item>
|
||||
<item quantity="other">moments ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_minutes">
|
||||
<item quantity="one">%d minute ago</item>
|
||||
<item quantity="other">%d minutes ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_hours">
|
||||
<item quantity="one">%d hour ago</item>
|
||||
<item quantity="other">%d hours ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_days">
|
||||
<item quantity="one">%d day ago</item>
|
||||
<item quantity="other">%d days ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_weeks">
|
||||
<item quantity="one">%d week ago</item>
|
||||
<item quantity="other">%d weeks ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_months">
|
||||
<item quantity="one">%d month ago</item>
|
||||
<item quantity="other">%d months ago</item>
|
||||
</plurals>
|
||||
<string name="commons__unknown_error">Unknown error occurred! Try again!</string>
|
||||
<string name="backup__export_result_success">File successfully saved!</string>
|
||||
<string name="backup__share_result_failure">Could not share file!</string>
|
||||
<string name="backup__enter_password_dialog_title">Type in password</string>
|
||||
<string name="backup__remove_password_msg">Enter the backup password to proceed with remove.</string>
|
||||
<string name="backup__revoke_google_access_msg">Enter the backup password to proceed with revoking access to Google.</string>
|
||||
<string name="backup__synchronization_settings">Synchronization settings</string>
|
||||
<string name="backup__local_file_title">Local file</string>
|
||||
<string name="backup__drive_title">Google Drive sync</string>
|
||||
<string name="backup__delete_file_title">Delete your backup file from Google Drive?</string>
|
||||
<string name="backup__delete_file_msg">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.</string>
|
||||
<string name="backup__sync_status_waiting">Waiting for sync…</string>
|
||||
<string name="backup__sync_status_progress">Syncing…</string>
|
||||
<string name="import_backup_msg1_encrypted">You are going to import an encrypted backup file.</string>
|
||||
<string name="externalimport__choose_json_cta">Choose JSON file</string>
|
||||
<string name="externalimport__aegis_msg">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.</string>
|
||||
<string name="externalimport__raivo_msg">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.</string>
|
||||
<string name="externalimport__no_tokens_msg">However, there are no services that could be imported.</string>
|
||||
<string name="commons__try_again">Try again</string>
|
||||
<string name="commons__proceed">Proceed</string>
|
||||
<string name="externalimport__ga_title">Importing 2FA tokens from Google Authenticator app</string>
|
||||
<string name="externalimport__aegis_title">Importing 2FA tokens from Aegis app</string>
|
||||
<string name="externalimport__raivo_title">Importing 2FA tokens from Raivo app</string>
|
||||
<string name="externalimport__ga_success_msg">This QR code allows importing tokens from Google Authenticator.</string>
|
||||
<string name="externalimport__aegis_success_msg">This JSON file allows importing tokens from Aegis.</string>
|
||||
<string name="externalimport__raivo_success_msg">This JSON file allows importing tokens from Raivo.</string>
|
||||
<string name="externalimport__read_error">Could not read any tokens. Try to select a different file.</string>
|
||||
<string name="settings__gd_sync_info">Google Drive sync info</string>
|
||||
<string name="settings__gd_sync_disable_confirm">Are you sure? Without Google Drive sync, you won\'t be able to restore your tokens if you lose or reset your phone!</string>
|
||||
</resources>
|
||||
|
@ -15,7 +15,7 @@ interface Preferences {
|
||||
fun putInt(key: String, value: Int)
|
||||
fun putFloat(key: String, value: Float)
|
||||
|
||||
fun <T> observe(key: String, default: T): Flow<T>
|
||||
fun <T> observe(key: String, default: T?): Flow<T?>
|
||||
|
||||
fun delete(key: String)
|
||||
}
|
@ -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 <T> observe(key: String, default: T): Flow<T> {
|
||||
override fun <T> observe(key: String, default: T?): Flow<T?> {
|
||||
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()
|
||||
|
@ -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<List<Group>> {
|
||||
return flow { }
|
||||
return local.observeGroups().map { it.list.map { it.asDomain() } }
|
||||
}
|
||||
}
|
@ -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<ServicesRepository>() }
|
||||
|
||||
singleOf(::GroupsLocalSource)
|
||||
singleOf(::GroupsRepositoryImpl) { bind<GroupsRepository>() }
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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<String> = emptyList(),
|
||||
// val isDeleted: Boolean = false,
|
||||
//// val backupSyncStatus: BackupSyncStatus = BackupSyncStatus.NOT_SYNCED,
|
||||
@ -89,7 +88,4 @@ data class Service(
|
||||
// )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -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<GroupsEntity> {
|
||||
return preferences.observe<String>(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()
|
||||
}
|
||||
}
|
@ -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
|
||||
// )
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.twofasapp.data.services.local.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class GroupsEntity(
|
||||
val list: List<GroupEntity> = emptyList(),
|
||||
val isDefaultGroupExpanded: Boolean = true,
|
||||
)
|
@ -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,
|
||||
)
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ data class ServicesUiState(
|
||||
val isInEditMode: Boolean = false,
|
||||
val events: List<ServicesStateEvent> = listOf(),
|
||||
) {
|
||||
fun getService(id: Long): Service {
|
||||
return services.first { it.id == id }
|
||||
fun getService(id: Long): Service? {
|
||||
return services.firstOrNull() { it.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Service>,
|
||||
val groups: List<Group>,
|
||||
val isInEditMode: Boolean,
|
||||
)
|
||||
}
|
@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Loco xml export: Android string resources
|
||||
Project: 2FAS iOS
|
||||
Project: 2FAS App
|
||||
Release: Working copy
|
||||
Locale: en, English
|
||||
Exported by: rafakob
|
||||
Exported at: Wed, 28 Dec 2022 11:53:36 +0100
|
||||
Exported at: Sat, 11 Feb 2023 08:20:15 -0800
|
||||
-->
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<!-- InfoPlist.strings
|
||||
@ -160,7 +160,7 @@
|
||||
<string name="tokens__choose_method">Choose method</string>
|
||||
<string name="tokens__duplicated_private_key">Duplicated Service Key</string>
|
||||
<!-- tokens__incorrect_private_key -->
|
||||
<string name="tokens__incorrect_service_key">Incorrect service key (only numbers 2 to 7, letters)</string>
|
||||
<string name="tokens__incorrect_service_key">Incorrect service key (only numbers 2 to 7, letters), max. 512 chars long</string>
|
||||
<string name="tokens__try_again">OK, let\'s try again</string>
|
||||
<!-- Original: To capture the QR code
|
||||
tokens__point_right_2fa_code -->
|
||||
@ -646,4 +646,57 @@
|
||||
<string name="settings__recommendation">Check out this awesome two-factor authentication app from 2FAS: https://2fas.com</string>
|
||||
<string name="settings__acknowledgements">Acknowledgements</string>
|
||||
<string name="introduction__import_external_app">Import from external app</string>
|
||||
<plurals name="past_duration_seconds">
|
||||
<item quantity="one">moments ago</item>
|
||||
<item quantity="other">moments ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_minutes">
|
||||
<item quantity="one">%d minute ago</item>
|
||||
<item quantity="other">%d minutes ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_hours">
|
||||
<item quantity="one">%d hour ago</item>
|
||||
<item quantity="other">%d hours ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_days">
|
||||
<item quantity="one">%d day ago</item>
|
||||
<item quantity="other">%d days ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_weeks">
|
||||
<item quantity="one">%d week ago</item>
|
||||
<item quantity="other">%d weeks ago</item>
|
||||
</plurals>
|
||||
<plurals name="past_duration_months">
|
||||
<item quantity="one">%d month ago</item>
|
||||
<item quantity="other">%d months ago</item>
|
||||
</plurals>
|
||||
<string name="commons__unknown_error">Unknown error occurred! Try again!</string>
|
||||
<string name="backup__export_result_success">File successfully saved!</string>
|
||||
<string name="backup__share_result_failure">Could not share file!</string>
|
||||
<string name="backup__enter_password_dialog_title">Type in password</string>
|
||||
<string name="backup__remove_password_msg">Enter the backup password to proceed with remove.</string>
|
||||
<string name="backup__revoke_google_access_msg">Enter the backup password to proceed with revoking access to Google.</string>
|
||||
<string name="backup__synchronization_settings">Synchronization settings</string>
|
||||
<string name="backup__local_file_title">Local file</string>
|
||||
<string name="backup__drive_title">Google Drive sync</string>
|
||||
<string name="backup__delete_file_title">Delete your backup file from Google Drive?</string>
|
||||
<string name="backup__delete_file_msg">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.</string>
|
||||
<string name="backup__sync_status_waiting">Waiting for sync…</string>
|
||||
<string name="backup__sync_status_progress">Syncing…</string>
|
||||
<string name="import_backup_msg1_encrypted">You are going to import an encrypted backup file.</string>
|
||||
<string name="externalimport__choose_json_cta">Choose JSON file</string>
|
||||
<string name="externalimport__aegis_msg">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.</string>
|
||||
<string name="externalimport__raivo_msg">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.</string>
|
||||
<string name="externalimport__no_tokens_msg">However, there are no services that could be imported.</string>
|
||||
<string name="commons__try_again">Try again</string>
|
||||
<string name="commons__proceed">Proceed</string>
|
||||
<string name="externalimport__ga_title">Importing 2FA tokens from Google Authenticator app</string>
|
||||
<string name="externalimport__aegis_title">Importing 2FA tokens from Aegis app</string>
|
||||
<string name="externalimport__raivo_title">Importing 2FA tokens from Raivo app</string>
|
||||
<string name="externalimport__ga_success_msg">This QR code allows importing tokens from Google Authenticator.</string>
|
||||
<string name="externalimport__aegis_success_msg">This JSON file allows importing tokens from Aegis.</string>
|
||||
<string name="externalimport__raivo_success_msg">This JSON file allows importing tokens from Raivo.</string>
|
||||
<string name="externalimport__read_error">Could not read any tokens. Try to select a different file.</string>
|
||||
<string name="settings__gd_sync_info">Google Drive sync info</string>
|
||||
<string name="settings__gd_sync_disable_confirm">Are you sure? Without Google Drive sync, you won\'t be able to restore your tokens if you lose or reset your phone!</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user