improvement(Export): Minor UI changes

This commit is contained in:
Artem Chepurnoy 2024-12-20 13:23:57 +02:00
parent 4dfe4fc3d2
commit 27b98f8d9d
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
8 changed files with 283 additions and 34 deletions

View File

@ -977,6 +977,7 @@
<string name="exportaccount_header_title">Export items</string>
<string name="exportaccount_password_label">Archive password</string>
<string name="exportaccount_format_note">Export vault as an encrypted ZIP archive.</string>
<string name="exportaccount_attachments_note">Vault will be kept unlocked during the export process. The attachments are referenced in the vault's data. The attachments are never stored in an unencrypted form in the process.</string>
<string name="exportaccount_include_attachments_title">Export attachments</string>
<string name="exportaccount_export_button">Export</string>
@ -1210,7 +1211,9 @@
<string name="feat_item_show_barcode_title">Show as Barcode</string>
<string name="feat_item_show_barcode_text">Encode a text as different barcodes.</string>
<string name="feat_item_generator_title">Generator</string>
<string name="feat_item_generator_text">Generate passwords or usernames.</string>
<string name="feat_item_generator_text">Generate passwords, usernames, emails and keys.</string>
<string name="feat_item_export_title">Export</string>
<string name="feat_item_export_text">Export Vault's data, including attachments.</string>
<string name="datasafety_header_title">Data safety</string>
<string name="datasafety_local_section">Local data</string>

View File

@ -569,7 +569,33 @@ private fun buildItemsFlow(
}
}
val quickActions = VaultViewItem.QuickActions(
id = "quick_actions",
actions = buildContextItems {
section {
this += ExportRoute.actionOrNull(
translator = scope,
accountId = accountId,
individual = true,
navigate = scope::navigate,
)
this += ExportRoute.actionOrNull(
translator = scope,
accountId = accountId,
individual = false,
navigate = scope::navigate,
)
}
},
)
emit(quickActions)
if (account != null) {
val mainSectionItem = VaultViewItem.Section(
id = "main.section",
)
emit(mainSectionItem)
val ff0 = VaultViewItem.Action(
id = "ciphers",
title = scope.translate(Res.string.items),

View File

@ -16,6 +16,7 @@ import com.artemchep.keyguard.feature.navigation.state.TranslatorScope
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.res.*
import com.artemchep.keyguard.ui.FlatItemAction
import com.artemchep.keyguard.ui.icons.ChevronIcon
import com.artemchep.keyguard.ui.icons.Stub
import com.artemchep.keyguard.ui.icons.icon
@ -50,13 +51,13 @@ data class ExportRoute(
translator.translate(res)
}
return FlatItemAction(
leading = kotlin.run {
val res = if (individual) {
Icons.Outlined.SaveAlt
} else {
Icons.Stub
}
icon(res)
icon = if (individual) {
Icons.Outlined.SaveAlt
} else {
Icons.Stub
},
trailing = {
ChevronIcon()
},
title = TextHolder.Value(title),
onClick = {

View File

@ -350,21 +350,33 @@ private fun ExportScreen(
)
}
Spacer(
modifier = Modifier
.height(32.dp),
)
Icon(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
imageVector = Icons.Outlined.Info,
contentDescription = null,
tint = LocalContentColor.current.combineAlpha(alpha = MediumEmphasisAlpha),
)
Spacer(
modifier = Modifier
.height(16.dp),
)
Text(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
text = stringResource(Res.string.exportaccount_format_note),
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current
.combineAlpha(alpha = MediumEmphasisAlpha),
)
ExpandedIfNotEmpty(
Unit.takeIf { attachments?.enabled == true },
) {
Column {
Spacer(
modifier = Modifier
.height(32.dp),
)
Icon(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
imageVector = Icons.Outlined.Info,
contentDescription = null,
tint = LocalContentColor.current.combineAlpha(alpha = MediumEmphasisAlpha),
)
Spacer(
modifier = Modifier
.height(16.dp),
@ -478,21 +490,7 @@ private fun ColumnScope.ExportContentOk(
)
FlatItemLayout(
leading = {
BadgedBox(
modifier = Modifier
.zIndex(20f),
badge = {
val size = attachments.size
?: return@BadgedBox
Badge(
containerColor = MaterialTheme.colorScheme.badgeContainer,
) {
Text(text = size)
}
},
) {
Icon(Icons.Outlined.KeyguardAttachment, null)
}
Icon(Icons.Outlined.KeyguardAttachment, null)
},
content = {
FlatItemTextContent(
@ -501,6 +499,11 @@ private fun ColumnScope.ExportContentOk(
text = stringResource(Res.string.exportaccount_include_attachments_title),
)
},
text = {
val size = attachments.size
?: return@FlatItemTextContent
Text(size)
},
)
},
trailing = {

View File

@ -16,6 +16,7 @@ fun VaultViewItem(
is VaultViewItem.Error -> VaultViewErrorItem(modifier, item)
is VaultViewItem.Info -> VaultViewInfoItem(modifier, item)
is VaultViewItem.Identity -> VaultViewIdentityItem(modifier, item)
is VaultViewItem.QuickActions -> VaultViewQuickActionsItem(modifier, item)
is VaultViewItem.Action -> VaultViewActionItem(modifier, item)
is VaultViewItem.Value -> VaultViewValueItem(modifier, item)
is VaultViewItem.Switch -> VaultViewSwitchItem(modifier, item)

View File

@ -0,0 +1,202 @@
package com.artemchep.keyguard.feature.home.vault.component
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
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.graphics.compositeOver
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.artemchep.keyguard.feature.home.vault.model.VaultViewItem
import com.artemchep.keyguard.feature.localization.textResource
import com.artemchep.keyguard.ui.ContextItem
import com.artemchep.keyguard.ui.DisabledEmphasisAlpha
import com.artemchep.keyguard.ui.DropdownMinWidth
import com.artemchep.keyguard.ui.FlatItemAction
import com.artemchep.keyguard.ui.FlatItemTextContent
import com.artemchep.keyguard.ui.icons.Stub
import com.artemchep.keyguard.ui.theme.combineAlpha
@Composable
fun VaultViewQuickActionsItem(
modifier: Modifier = Modifier,
item: VaultViewItem.QuickActions,
) {
Row(
modifier = modifier
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Spacer(
modifier = Modifier
.width(4.dp),
)
item.actions.forEach { i ->
HorizontalContextItem(
modifier = Modifier
.widthIn(max = DropdownMinWidth),
item = i,
)
}
}
}
@Composable
private fun HorizontalContextItem(
modifier: Modifier = Modifier,
item: ContextItem,
) = when (item) {
is ContextItem.Section -> {
Section(
modifier = modifier,
text = item.title,
)
}
is ContextItem.Custom -> {
Column(
modifier = modifier,
) {
item.content()
}
}
is FlatItemAction -> {
HorizontalFlatActionItem(
modifier = modifier,
item = item,
)
}
}
@Composable
private fun HorizontalFlatActionItem(
modifier: Modifier = Modifier,
item: FlatItemAction,
) {
val updatedOnClick by rememberUpdatedState(item.onClick)
val backgroundModifier = run {
val bg = Color.Transparent
val fg = MaterialTheme.colorScheme.surfaceColorAtElevationSemi(1.dp)
Modifier
.background(fg.compositeOver(bg))
}
Row(
modifier = modifier
// Normal items have a small vertical padding,
// so add it here as well for consistency.
.padding(
vertical = 2.dp,
)
.clip(RoundedCornerShape(16.dp))
.then(backgroundModifier)
.clickable {
updatedOnClick?.invoke()
}
.minimumInteractiveComponentSize()
.padding(
horizontal = 8.dp,
vertical = 4.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
HorizontalFlatActionContent(
action = item,
compact = true,
)
}
}
@Composable
private fun RowScope.HorizontalFlatActionContent(
action: FlatItemAction,
compact: Boolean = false,
) {
CompositionLocalProvider(
LocalContentColor provides LocalContentColor.current
.let { color ->
if (action.onClick != null) {
color
} else {
color.combineAlpha(DisabledEmphasisAlpha)
}
},
) {
if ((action.icon != null && action.icon != Icons.Stub) || action.leading != null) {
Box(
modifier = Modifier
.size(24.dp),
) {
if (action.icon != null) {
Icon(
imageVector = action.icon,
contentDescription = null,
)
}
action.leading?.invoke()
}
Spacer(
modifier = Modifier
.width(16.dp),
)
}
Column(
modifier = Modifier,
verticalArrangement = Arrangement.Center,
) {
FlatItemTextContent(
title = {
Text(
text = textResource(action.title),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
text = if (action.text != null) {
// composable
{
Text(
text = textResource(action.text),
maxLines = 2,
overflow = TextOverflow.Ellipsis,
fontSize = 13.sp,
)
}
} else {
null
},
compact = compact,
)
}
if (action.trailing != null) {
Spacer(Modifier.width(8.dp))
action.trailing.invoke()
}
}
}

View File

@ -52,6 +52,13 @@ sealed interface VaultViewItem {
companion object
}
data class QuickActions(
override val id: String,
val actions: ImmutableList<ContextItem> = persistentListOf(),
) : VaultViewItem {
companion object
}
data class Action(
override val id: String,
val elevation: Dp = 0.dp,

View File

@ -17,6 +17,7 @@ import androidx.compose.material.icons.automirrored.outlined.ShortText
import androidx.compose.material.icons.outlined.AccountBox
import androidx.compose.material.icons.outlined.CopyAll
import androidx.compose.material.icons.outlined.DataArray
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.FilterAlt
import androidx.compose.material.icons.outlined.OfflineBolt
import androidx.compose.material.icons.outlined.Password
@ -153,6 +154,11 @@ val onboardingItemsWatchtower = listOf(
)
val onboardingItemsOther = listOf(
OnboardingItem(
title = Res.string.feat_item_export_title,
text = Res.string.feat_item_export_text,
icon = Icons.Outlined.Download,
),
OnboardingItem(
title = Res.string.feat_item_multi_selection_title,
text = Res.string.feat_item_multi_selection_text,