feat(app): support toggling read-only status in DecryptScreen

This commit is contained in:
Harsh Shandilya 2022-10-09 18:02:49 +05:30
parent 6d73b63435
commit c866bb9fb1
No known key found for this signature in database
2 changed files with 54 additions and 5 deletions

View file

@ -19,6 +19,8 @@ import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.passwordstore.R import app.passwordstore.R
@ -32,11 +34,23 @@ import kotlin.time.ExperimentalTime
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
/**
* Composable to show a [PasswordEntry]. It can be used for both read-only usage (decrypt screen) or
* read-write (encrypt screen) to allow sharing UI logic for both these screens and deferring all
* the cryptographic aspects to its parent.
*
* When [readOnly] is `true`, the Composable assumes that we're showcasing the provided [entry] to
* the user and does not offer any edit capabilities.
*
* When [readOnly] is `false`, the [TextField]s are rendered editable but currently do not pass up
* their "updated" state to anything. This will be changed in later commits.
*/
@OptIn(ExperimentalTime::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalTime::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun PasswordEntryScreen( fun PasswordEntryScreen(
entryName: String, entryName: String,
entry: PasswordEntry, entry: PasswordEntry,
readOnly: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Scaffold( Scaffold(
@ -61,21 +75,22 @@ fun PasswordEntryScreen(
value = entry.password!!, value = entry.password!!,
label = "Password", label = "Password",
initialVisibility = false, initialVisibility = false,
readOnly = readOnly,
modifier = Modifier.padding(bottom = 8.dp), modifier = Modifier.padding(bottom = 8.dp),
) )
} }
if (entry.hasTotp()) { if (entry.hasTotp() && readOnly) {
val totp by entry.totp.collectAsState(runBlocking { entry.totp.first() }) val totp by entry.totp.collectAsState(runBlocking { entry.totp.first() })
TextField( TextField(
value = totp.value, value = totp.value,
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = readOnly,
label = { Text("OTP (expires in ${totp.remainingTime.inWholeSeconds}s)") }, label = { Text("OTP (expires in ${totp.remainingTime.inWholeSeconds}s)") },
trailingIcon = { CopyButton({ totp.value }) }, trailingIcon = { CopyButton({ totp.value }) },
modifier = Modifier.padding(bottom = 8.dp), modifier = Modifier.padding(bottom = 8.dp),
) )
} }
if (entry.username != null) { if (entry.username != null && readOnly) {
TextField( TextField(
value = entry.username!!, value = entry.username!!,
onValueChange = {}, onValueChange = {},
@ -85,11 +100,41 @@ fun PasswordEntryScreen(
modifier = Modifier.padding(bottom = 8.dp), modifier = Modifier.padding(bottom = 8.dp),
) )
} }
ExtraContent(entry = entry, readOnly = readOnly)
} }
} }
} }
} }
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun ExtraContent(
entry: PasswordEntry,
readOnly: Boolean,
modifier: Modifier = Modifier,
) {
if (readOnly) {
entry.extraContent.forEach { (label, value) ->
TextField(
value = value,
onValueChange = {},
readOnly = true,
label = { Text(label.capitalize(Locale.current)) },
trailingIcon = { CopyButton({ value }) },
modifier = modifier.padding(bottom = 8.dp),
)
}
} else {
TextField(
value = entry.extraContentWithoutAuthData,
onValueChange = {},
readOnly = false,
label = { Text("Extra content") },
modifier = modifier,
)
}
}
@Composable @Composable
private fun CopyButton( private fun CopyButton(
textToCopy: () -> String, textToCopy: () -> String,
@ -110,7 +155,9 @@ private fun CopyButton(
@Preview @Preview
@Composable @Composable
private fun PasswordEntryPreview() { private fun PasswordEntryPreview() {
APSThemePreview { PasswordEntryScreen("Test Entry", createTestEntry()) } APSThemePreview {
PasswordEntryScreen(entryName = "Test Entry", entry = createTestEntry(), readOnly = true)
}
} }
private fun createTestEntry() = private fun createTestEntry() =
@ -121,6 +168,7 @@ private fun createTestEntry() =
|My Password |My Password
|otpauth://totp/ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30 |otpauth://totp/ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
|login: msfjarvis |login: msfjarvis
|URL: example.com
""" """
.trimMargin() .trimMargin()
.encodeToByteArray() .encodeToByteArray()

View file

@ -21,13 +21,14 @@ public fun PasswordField(
value: String, value: String,
label: String, label: String,
initialVisibility: Boolean, initialVisibility: Boolean,
readOnly: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
var visible by remember { mutableStateOf(initialVisibility) } var visible by remember { mutableStateOf(initialVisibility) }
TextField( TextField(
value = value, value = value,
onValueChange = {}, onValueChange = {},
readOnly = true, readOnly = readOnly,
label = { Text(label) }, label = { Text(label) },
visualTransformation = visualTransformation =
if (visible) VisualTransformation.None else PasswordVisualTransformation(), if (visible) VisualTransformation.None else PasswordVisualTransformation(),