Handle errors in Placeholders gracefully

This commit is contained in:
Artem Chepurnoy 2024-01-06 19:01:17 +02:00
parent d569154ede
commit cf00f116a0
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
4 changed files with 118 additions and 32 deletions

View File

@ -170,6 +170,7 @@ fun VaultViewUriItem(
UrlOverrideItem( UrlOverrideItem(
title = override.title, title = override.title,
text = override.text, text = override.text,
error = override.error,
onClick = { onClick = {
selectedDropdown = updatedDropdownState.value selectedDropdown = updatedDropdownState.value
}, },
@ -205,6 +206,7 @@ private fun UrlOverrideItem(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: String, title: String,
text: String, text: String,
error: Boolean = false,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
Surface( Surface(
@ -224,6 +226,11 @@ private fun UrlOverrideItem(
), ),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
val contentColor = if (error) {
MaterialTheme.colorScheme.error
} else {
LocalContentColor.current
}
Box( Box(
modifier = Modifier modifier = Modifier
.size(16.dp), .size(16.dp),
@ -254,7 +261,7 @@ private fun UrlOverrideItem(
.widthIn(max = 128.dp) .widthIn(max = 128.dp)
.alignByBaseline(), .alignByBaseline(),
text = text, text = text,
color = LocalContentColor.current color = contentColor
.combineAlpha(MediumEmphasisAlpha), .combineAlpha(MediumEmphasisAlpha),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,

View File

@ -221,6 +221,7 @@ sealed interface VaultViewItem {
data class Override( data class Override(
val title: String, val title: String,
val text: String, val text: String,
val error: Boolean,
/** /**
* List of the callable actions appended * List of the callable actions appended
* to the item. * to the item.

View File

@ -1,8 +1,13 @@
package com.artemchep.keyguard.feature.home.vault.screen package com.artemchep.keyguard.feature.home.vault.screen
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Android import androidx.compose.material.icons.outlined.Android
@ -11,6 +16,8 @@ import androidx.compose.material.icons.outlined.Call
import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.outlined.ContentCopy
import androidx.compose.material.icons.outlined.Directions import androidx.compose.material.icons.outlined.Directions
import androidx.compose.material.icons.outlined.Email import androidx.compose.material.icons.outlined.Email
import androidx.compose.material.icons.outlined.Error
import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material.icons.outlined.Key import androidx.compose.material.icons.outlined.Key
import androidx.compose.material.icons.outlined.Launch import androidx.compose.material.icons.outlined.Launch
import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.outlined.Link
@ -20,7 +27,10 @@ import androidx.compose.material.icons.outlined.Save
import androidx.compose.material.icons.outlined.Terminal import androidx.compose.material.icons.outlined.Terminal
import androidx.compose.material.icons.outlined.Textsms import androidx.compose.material.icons.outlined.Textsms
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -30,9 +40,11 @@ import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import arrow.core.Either
import arrow.core.getOrElse import arrow.core.getOrElse
import com.artemchep.keyguard.AppMode import com.artemchep.keyguard.AppMode
import com.artemchep.keyguard.android.downloader.journal.room.DownloadInfoEntity2 import com.artemchep.keyguard.android.downloader.journal.room.DownloadInfoEntity2
import com.artemchep.keyguard.common.exception.Readable
import com.artemchep.keyguard.common.io.attempt import com.artemchep.keyguard.common.io.attempt
import com.artemchep.keyguard.common.io.bind import com.artemchep.keyguard.common.io.bind
import com.artemchep.keyguard.common.io.launchIn import com.artemchep.keyguard.common.io.launchIn
@ -148,6 +160,7 @@ import com.artemchep.keyguard.feature.home.vault.util.cipherTrashAction
import com.artemchep.keyguard.feature.home.vault.util.cipherViewPasswordHistoryAction import com.artemchep.keyguard.feature.home.vault.util.cipherViewPasswordHistoryAction
import com.artemchep.keyguard.feature.justdeleteme.directory.JustDeleteMeServiceViewRoute import com.artemchep.keyguard.feature.justdeleteme.directory.JustDeleteMeServiceViewRoute
import com.artemchep.keyguard.feature.largetype.LargeTypeRoute import com.artemchep.keyguard.feature.largetype.LargeTypeRoute
import com.artemchep.keyguard.feature.loading.getErrorReadableMessage
import com.artemchep.keyguard.feature.navigation.NavigationIntent import com.artemchep.keyguard.feature.navigation.NavigationIntent
import com.artemchep.keyguard.feature.navigation.state.RememberStateFlowScope import com.artemchep.keyguard.feature.navigation.state.RememberStateFlowScope
import com.artemchep.keyguard.feature.navigation.state.TranslatorScope import com.artemchep.keyguard.feature.navigation.state.TranslatorScope
@ -164,6 +177,7 @@ import com.artemchep.keyguard.platform.util.isRelease
import com.artemchep.keyguard.res.Res import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.ContextItem import com.artemchep.keyguard.ui.ContextItem
import com.artemchep.keyguard.ui.FlatItemAction import com.artemchep.keyguard.ui.FlatItemAction
import com.artemchep.keyguard.ui.MediumEmphasisAlpha
import com.artemchep.keyguard.ui.buildContextItems import com.artemchep.keyguard.ui.buildContextItems
import com.artemchep.keyguard.ui.colorizePassword import com.artemchep.keyguard.ui.colorizePassword
import com.artemchep.keyguard.ui.icons.ChevronIcon import com.artemchep.keyguard.ui.icons.ChevronIcon
@ -174,6 +188,8 @@ import com.artemchep.keyguard.ui.icons.iconSmall
import com.artemchep.keyguard.ui.selection.SelectionHandle import com.artemchep.keyguard.ui.selection.SelectionHandle
import com.artemchep.keyguard.ui.selection.selectionHandle import com.artemchep.keyguard.ui.selection.selectionHandle
import com.artemchep.keyguard.ui.text.annotate import com.artemchep.keyguard.ui.text.annotate
import com.artemchep.keyguard.ui.theme.Dimens
import com.artemchep.keyguard.ui.theme.combineAlpha
import com.artemchep.keyguard.ui.totp.formatCode2 import com.artemchep.keyguard.ui.totp.formatCode2
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -266,9 +282,13 @@ private class Holder(
) { ) {
data class Override( data class Override(
val override: DGlobalUrlOverride, val override: DGlobalUrlOverride,
val contentOrException: Either<Throwable, Content>,
) {
data class Content(
val uri: String, val uri: String,
val info: List<LinkInfo>, val info: List<LinkInfo>,
) )
}
} }
@Composable @Composable
@ -494,8 +514,11 @@ fun vaultViewScreenState(
info = extra, info = extra,
) )
} }
else -> { else -> {
val newUriString = uri.uri.placeholderFormat(placeholders) val newUriString = kotlin.runCatching {
uri.uri.placeholderFormat(placeholders)
}.getOrElse { uri.uri }
val newUri = uri.copy(uri = newUriString) val newUri = uri.copy(uri = newUriString)
// Process URL overrides // Process URL overrides
@ -510,6 +533,7 @@ fun vaultViewScreenState(
.matches(newUriString) .matches(newUriString)
} }
.map { override -> .map { override ->
val contentOrException = Either.catch {
val command = override.command val command = override.command
.placeholderFormat( .placeholderFormat(
placeholders = urlOverridePlaceholders, placeholders = urlOverridePlaceholders,
@ -520,12 +544,16 @@ fun vaultViewScreenState(
match = DSecret.Uri.MatchType.Exact, match = DSecret.Uri.MatchType.Exact,
), ),
) )
Holder.Override( Holder.Override.Content(
override = override,
uri = command, uri = command,
info = extra, info = extra,
) )
} }
Holder.Override(
override = override,
contentOrException = contentOrException,
)
}
val extra = extractors.process(newUri) val extra = extractors.process(newUri)
Holder( Holder(
@ -1841,24 +1869,73 @@ private suspend fun RememberStateFlowScope.createUriItem(
): VaultViewItem.Uri { ): VaultViewItem.Uri {
val overrides = holder val overrides = holder
.overrides .overrides
.map { .map { data ->
val command = it.uri val title = data.override.name
data.contentOrException.fold(
ifLeft = { e ->
val text = getErrorReadableMessage(
e,
translator = this,
)
val dropdown = buildContextItems {
this += ContextItem.Custom {
Column(
modifier = Modifier
.padding(
horizontal = Dimens.horizontalPadding,
vertical = 8.dp,
),
) {
Icon(
imageVector = Icons.Outlined.ErrorOutline,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
)
Spacer(
modifier = Modifier
.height(12.dp),
)
Text(
text = translate(Res.strings.error_failed_format_placeholder),
style = MaterialTheme.typography.labelLarge,
)
Text(
text = text,
style = MaterialTheme.typography.labelMedium,
color = LocalContentColor.current
.combineAlpha(MediumEmphasisAlpha),
)
}
}
}
VaultViewItem.Uri.Override(
title = title,
text = text,
error = true,
dropdown = dropdown,
)
},
ifRight = { content ->
val text = content.uri
val dropdown = createUriItemContextItems( val dropdown = createUriItemContextItems(
canEdit = false, canEdit = false,
cipherUnsecureUrlCheck = cipherUnsecureUrlCheck, cipherUnsecureUrlCheck = cipherUnsecureUrlCheck,
cipherUnsecureUrlAutoFix = cipherUnsecureUrlAutoFix, cipherUnsecureUrlAutoFix = cipherUnsecureUrlAutoFix,
getJustDeleteMeByUrl = getJustDeleteMeByUrl, getJustDeleteMeByUrl = getJustDeleteMeByUrl,
executeCommand = executeCommand, executeCommand = executeCommand,
uri = command, uri = content.uri,
info = it.info, info = content.info,
cipherId = cipherId, cipherId = cipherId,
copy = copy, copy = copy,
) )
VaultViewItem.Uri.Override( VaultViewItem.Uri.Override(
title = it.override.name, title = title,
text = command, text = text,
error = false,
dropdown = dropdown, dropdown = dropdown,
) )
},
)
} }
val uri = holder.uri val uri = holder.uri

View File

@ -395,6 +395,7 @@
<string name="error_failed_generate_otp_code">Failed to generate OTP code</string> <string name="error_failed_generate_otp_code">Failed to generate OTP code</string>
<string name="error_failed_create_passkey">Failed to create a passkey</string> <string name="error_failed_create_passkey">Failed to create a passkey</string>
<string name="error_failed_use_passkey">Failed to authorize a request</string> <string name="error_failed_use_passkey">Failed to authorize a request</string>
<string name="error_failed_format_placeholder">Failed to format the placeholder</string>
<!-- Title of the 'Show as Barcode' dialog --> <!-- Title of the 'Show as Barcode' dialog -->
<string name="barcodetype_title">Barcode</string> <string name="barcodetype_title">Barcode</string>