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

View File

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

View File

@ -1,8 +1,13 @@
package com.artemchep.keyguard.feature.home.vault.screen
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
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.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
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.Directions
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.Launch
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.Textsms
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.font.FontFamily
import androidx.compose.ui.unit.dp
import arrow.core.Either
import arrow.core.getOrElse
import com.artemchep.keyguard.AppMode
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.bind
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.justdeleteme.directory.JustDeleteMeServiceViewRoute
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.state.RememberStateFlowScope
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.ui.ContextItem
import com.artemchep.keyguard.ui.FlatItemAction
import com.artemchep.keyguard.ui.MediumEmphasisAlpha
import com.artemchep.keyguard.ui.buildContextItems
import com.artemchep.keyguard.ui.colorizePassword
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.text.annotate
import com.artemchep.keyguard.ui.theme.Dimens
import com.artemchep.keyguard.ui.theme.combineAlpha
import com.artemchep.keyguard.ui.totp.formatCode2
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
@ -266,9 +282,13 @@ private class Holder(
) {
data class Override(
val override: DGlobalUrlOverride,
val uri: String,
val info: List<LinkInfo>,
)
val contentOrException: Either<Throwable, Content>,
) {
data class Content(
val uri: String,
val info: List<LinkInfo>,
)
}
}
@Composable
@ -494,8 +514,11 @@ fun vaultViewScreenState(
info = extra,
)
}
else -> {
val newUriString = uri.uri.placeholderFormat(placeholders)
val newUriString = kotlin.runCatching {
uri.uri.placeholderFormat(placeholders)
}.getOrElse { uri.uri }
val newUri = uri.copy(uri = newUriString)
// Process URL overrides
@ -510,20 +533,25 @@ fun vaultViewScreenState(
.matches(newUriString)
}
.map { override ->
val command = override.command
.placeholderFormat(
placeholders = urlOverridePlaceholders,
val contentOrException = Either.catch {
val command = override.command
.placeholderFormat(
placeholders = urlOverridePlaceholders,
)
val extra = extractors.process(
DSecret.Uri(
uri = command,
match = DSecret.Uri.MatchType.Exact,
),
)
val extra = extractors.process(
DSecret.Uri(
Holder.Override.Content(
uri = command,
match = DSecret.Uri.MatchType.Exact,
),
)
info = extra,
)
}
Holder.Override(
override = override,
uri = command,
info = extra,
contentOrException = contentOrException,
)
}
@ -1841,23 +1869,72 @@ private suspend fun RememberStateFlowScope.createUriItem(
): VaultViewItem.Uri {
val overrides = holder
.overrides
.map {
val command = it.uri
val dropdown = createUriItemContextItems(
canEdit = false,
cipherUnsecureUrlCheck = cipherUnsecureUrlCheck,
cipherUnsecureUrlAutoFix = cipherUnsecureUrlAutoFix,
getJustDeleteMeByUrl = getJustDeleteMeByUrl,
executeCommand = executeCommand,
uri = command,
info = it.info,
cipherId = cipherId,
copy = copy,
)
VaultViewItem.Uri.Override(
title = it.override.name,
text = command,
dropdown = dropdown,
.map { data ->
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(
canEdit = false,
cipherUnsecureUrlCheck = cipherUnsecureUrlCheck,
cipherUnsecureUrlAutoFix = cipherUnsecureUrlAutoFix,
getJustDeleteMeByUrl = getJustDeleteMeByUrl,
executeCommand = executeCommand,
uri = content.uri,
info = content.info,
cipherId = cipherId,
copy = copy,
)
VaultViewItem.Uri.Override(
title = title,
text = text,
error = false,
dropdown = dropdown,
)
},
)
}

View File

@ -395,6 +395,7 @@
<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_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 -->
<string name="barcodetype_title">Barcode</string>