improvement(Export): Minor UI changes
This commit is contained in:
parent
4dfe4fc3d2
commit
27b98f8d9d
@ -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>
|
||||
|
@ -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),
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user