@@ -19,6 +19,8 @@ import androidx.compose.ui.platform.LocalClipboardManager
1919import androidx.compose.ui.res.painterResource
2020import androidx.compose.ui.res.stringResource
2121import androidx.compose.ui.text.AnnotatedString
22+ import androidx.compose.ui.text.capitalize
23+ import androidx.compose.ui.text.intl.Locale
2224import androidx.compose.ui.tooling.preview.Preview
2325import androidx.compose.ui.unit.dp
2426import app.passwordstore.R
@@ -32,11 +34,23 @@ import kotlin.time.ExperimentalTime
3234import kotlinx.coroutines.flow.first
3335import kotlinx.coroutines.runBlocking
3436
37+ /* *
38+ * Composable to show a [PasswordEntry]. It can be used for both read-only usage (decrypt screen) or
39+ * read-write (encrypt screen) to allow sharing UI logic for both these screens and deferring all
40+ * the cryptographic aspects to its parent.
41+ *
42+ * When [readOnly] is `true`, the Composable assumes that we're showcasing the provided [entry] to
43+ * the user and does not offer any edit capabilities.
44+ *
45+ * When [readOnly] is `false`, the [TextField]s are rendered editable but currently do not pass up
46+ * their "updated" state to anything. This will be changed in later commits.
47+ */
3548@OptIn(ExperimentalTime ::class , ExperimentalMaterial3Api ::class )
3649@Composable
3750fun PasswordEntryScreen (
3851 entryName : String ,
3952 entry : PasswordEntry ,
53+ readOnly : Boolean ,
4054 modifier : Modifier = Modifier ,
4155) {
4256 Scaffold (
@@ -61,21 +75,22 @@ fun PasswordEntryScreen(
6175 value = entry.password!! ,
6276 label = " Password" ,
6377 initialVisibility = false ,
78+ readOnly = readOnly,
6479 modifier = Modifier .padding(bottom = 8 .dp),
6580 )
6681 }
67- if (entry.hasTotp()) {
82+ if (entry.hasTotp() && readOnly ) {
6883 val totp by entry.totp.collectAsState(runBlocking { entry.totp.first() })
6984 TextField (
7085 value = totp.value,
7186 onValueChange = {},
72- readOnly = true ,
87+ readOnly = readOnly ,
7388 label = { Text (" OTP (expires in ${totp.remainingTime.inWholeSeconds} s)" ) },
7489 trailingIcon = { CopyButton ({ totp.value }) },
7590 modifier = Modifier .padding(bottom = 8 .dp),
7691 )
7792 }
78- if (entry.username != null ) {
93+ if (entry.username != null && readOnly ) {
7994 TextField (
8095 value = entry.username!! ,
8196 onValueChange = {},
@@ -85,11 +100,41 @@ fun PasswordEntryScreen(
85100 modifier = Modifier .padding(bottom = 8 .dp),
86101 )
87102 }
103+ ExtraContent (entry = entry, readOnly = readOnly)
88104 }
89105 }
90106 }
91107}
92108
109+ @Composable
110+ @OptIn(ExperimentalMaterial3Api ::class )
111+ private fun ExtraContent (
112+ entry : PasswordEntry ,
113+ readOnly : Boolean ,
114+ modifier : Modifier = Modifier ,
115+ ) {
116+ if (readOnly) {
117+ entry.extraContent.forEach { (label, value) ->
118+ TextField (
119+ value = value,
120+ onValueChange = {},
121+ readOnly = true ,
122+ label = { Text (label.capitalize(Locale .current)) },
123+ trailingIcon = { CopyButton ({ value }) },
124+ modifier = modifier.padding(bottom = 8 .dp),
125+ )
126+ }
127+ } else {
128+ TextField (
129+ value = entry.extraContentWithoutAuthData,
130+ onValueChange = {},
131+ readOnly = false ,
132+ label = { Text (" Extra content" ) },
133+ modifier = modifier,
134+ )
135+ }
136+ }
137+
93138@Composable
94139private fun CopyButton (
95140 textToCopy : () -> String ,
@@ -110,7 +155,9 @@ private fun CopyButton(
110155@Preview
111156@Composable
112157private fun PasswordEntryPreview () {
113- APSThemePreview { PasswordEntryScreen (" Test Entry" , createTestEntry()) }
158+ APSThemePreview {
159+ PasswordEntryScreen (entryName = " Test Entry" , entry = createTestEntry(), readOnly = true )
160+ }
114161}
115162
116163private fun createTestEntry () =
@@ -121,6 +168,7 @@ private fun createTestEntry() =
121168 |My Password
122169 |otpauth://totp/ACME%20Co:john@example.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
123170 |login: msfjarvis
171+ |URL: example.com
124172 """
125173 .trimMargin()
126174 .encodeToByteArray()
0 commit comments