From 9bf0026156e4124b667cf0ed66bf150c6c8aed36 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Thu, 5 Nov 2026 16:42:10 +0100 Subject: feat(app): move About to the list's menu and mark its links The version and certificate are no secrets, so they are copied without the sensitive flag that hid them in the clipboard preview. Rows that leave for the browser now show an icon saying so. Co-Authored-By: Claude Opus 5.5 --- .../passwordstore/ui/crypto/CryptoExtensions.kt | 10 ++++--- .../app/passwordstore/ui/list/ListDestination.kt | 2 ++ .../java/app/passwordstore/ui/list/ListScreen.kt | 11 ++++++- .../java/app/passwordstore/ui/main/MainActivity.kt | 2 ++ .../app/passwordstore/ui/settings/AboutScreen.kt | 35 ++++++++++++++++++---- .../app/passwordstore/ui/settings/SettingsGraph.kt | 1 - .../passwordstore/ui/settings/SettingsScreen.kt | 8 ----- app/src/main/res/drawable/ic_open_in_new_24dp.xml | 12 ++++++++ app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 10 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 app/src/main/res/drawable/ic_open_in_new_24dp.xml (limited to 'app/src') diff --git a/app/src/main/java/app/passwordstore/ui/crypto/CryptoExtensions.kt b/app/src/main/java/app/passwordstore/ui/crypto/CryptoExtensions.kt index 4a6813f7..626ec130 100644 --- a/app/src/main/java/app/passwordstore/ui/crypto/CryptoExtensions.kt +++ b/app/src/main/java/app/passwordstore/ui/crypto/CryptoExtensions.kt @@ -48,12 +48,14 @@ fun getLongName(fullPath: String, repositoryPath: String, basename: String): Str } } -/** Copies provided [text] to the clipboard, flagged sensitive. */ -fun FragmentActivity.copyTextToClipboard(text: String?) { +/** Copies provided [text] to the clipboard; [sensitive] text is hidden from its preview. */ +fun FragmentActivity.copyTextToClipboard(text: String?, sensitive: Boolean = true) { val clipboard = clipboard ?: return val clip = ClipData.newPlainText("pgp_handler_result_pm", text) - clip.description.extras = - PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) } + if (sensitive) { + clip.description.extras = + PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) } + } clipboard.setPrimaryClip(clip) } diff --git a/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt b/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt index 54a7e66c..97092716 100644 --- a/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt +++ b/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt @@ -73,6 +73,7 @@ fun ListDestination( onNewEntry: () -> Unit, onClone: () -> Unit, onSettings: () -> Unit, + onAbout: () -> Unit, onNoStore: () -> Unit, modifier: Modifier = Modifier, viewModel: SearchableRepositoryViewModel = hiltViewModel(), @@ -159,6 +160,7 @@ fun ListDestination( }, onGitOp = { op -> ifInitialized { runGit(op) } }, onSettings = onSettings, + onAbout = onAbout, onNew = { dialog = ListDialog.Create }, onMove = { dialog = ListDialog.Move }, onDelete = { diff --git a/app/src/main/java/app/passwordstore/ui/list/ListScreen.kt b/app/src/main/java/app/passwordstore/ui/list/ListScreen.kt index 1b359d5b..593c744b 100644 --- a/app/src/main/java/app/passwordstore/ui/list/ListScreen.kt +++ b/app/src/main/java/app/passwordstore/ui/list/ListScreen.kt @@ -91,6 +91,7 @@ fun ListScreen( onSync: () -> Unit, onGitOp: (GitOp) -> Unit, onSettings: () -> Unit, + onAbout: () -> Unit, onNew: () -> Unit, onMove: () -> Unit, onDelete: () -> Unit, @@ -133,7 +134,7 @@ fun ListScreen( SelectionBar(rows, selected, onClearSelection, onMove, onDelete, onRename, onPin) } else { Column(horizontalAlignment = Alignment.CenterHorizontally) { - SearchField(query, gitOps, onQueryChange, onGitOp, onSettings, searchFocus) + SearchField(query, gitOps, onQueryChange, onGitOp, onSettings, onAbout, searchFocus) BreadcrumbBar( crumbsFor(path), stringResource(R.string.list_store_root), @@ -238,6 +239,7 @@ private fun SearchField( onQueryChange: (String) -> Unit, onGitOp: (GitOp) -> Unit, onSettings: () -> Unit, + onAbout: () -> Unit, focus: FocusRequester, ) { val focusManager = LocalFocusManager.current @@ -282,6 +284,13 @@ private fun SearchField( onSettings() }, ) + DropdownMenuItem( + text = { Text(stringResource(R.string.about)) }, + onClick = { + menu = false + onAbout() + }, + ) } } } diff --git a/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt b/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt index f3d7d3a0..5037e646 100644 --- a/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt +++ b/app/src/main/java/app/passwordstore/ui/main/MainActivity.kt @@ -30,6 +30,7 @@ import app.passwordstore.ui.git.base.BaseGitActivity import app.passwordstore.ui.list.ListDestination import app.passwordstore.ui.list.folderMoves import app.passwordstore.ui.prompts.PromptHost +import app.passwordstore.ui.settings.AboutRoute import app.passwordstore.ui.settings.SettingsRoute import app.passwordstore.ui.settings.settingsGraph import app.passwordstore.ui.setup.GitServerRoute @@ -130,6 +131,7 @@ class MainActivity : BaseGitActivity() { onSettings = { if (nav.currentBackStackEntry?.id == entry.id) nav.navigate(SettingsRoute) }, + onAbout = { if (nav.currentBackStackEntry?.id == entry.id) nav.navigate(AboutRoute) }, onNoStore = { // The store is gone (deleted from Settings): setup starts over. if (nav.currentBackStackEntry?.id == entry.id) diff --git a/app/src/main/java/app/passwordstore/ui/settings/AboutScreen.kt b/app/src/main/java/app/passwordstore/ui/settings/AboutScreen.kt index fd47c119..4a366390 100644 --- a/app/src/main/java/app/passwordstore/ui/settings/AboutScreen.kt +++ b/app/src/main/java/app/passwordstore/ui/settings/AboutScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -110,7 +111,7 @@ fun AboutDestination( versionLine(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, BuildConfig.GIT_COMMIT), changelog = changelogUrl(BuildConfig.VERSION_NAME), fingerprint = fingerprint, - onCopy = { activity.copyTextToClipboard(it) }, + onCopy = { activity.copyTextToClipboard(it, sensitive = false) }, onOpen = { url -> try { activity.startActivity(Intent(Intent.ACTION_VIEW, url.toUri())) @@ -147,10 +148,10 @@ private fun AboutScreen( Text(stringResource(R.string.about_summary), textAlign = TextAlign.Center) } AboutRow(stringResource(R.string.about_version), version) { onCopy(version) } - AboutRow(stringResource(R.string.about_docs)) { onOpen(DOCUMENTATION) } - AboutRow(stringResource(R.string.about_changelog)) { onOpen(changelog) } - AboutRow(stringResource(R.string.about_source)) { onOpen(REPOSITORY) } - AboutRow(stringResource(R.string.about_issues)) { onOpen("$REPOSITORY/issues") } + LinkRow(stringResource(R.string.about_docs)) { onOpen(DOCUMENTATION) } + LinkRow(stringResource(R.string.about_changelog)) { onOpen(changelog) } + LinkRow(stringResource(R.string.about_source)) { onOpen(REPOSITORY) } + LinkRow(stringResource(R.string.about_issues)) { onOpen("$REPOSITORY/issues") } // White in both themes: the logo's dark outline disappears on a dark background, and Ko-fi // asks that the logo itself stays as it is. Button( @@ -198,11 +199,33 @@ private fun AboutScreen( } @Composable -private fun AboutRow(title: String, summary: String? = null, onClick: () -> Unit) { +private fun AboutRow( + title: String, + summary: String? = null, + trailing: (@Composable () -> Unit)? = null, + onClick: () -> Unit, +) { ListItem( headlineContent = { Text(title) }, supportingContent = summary?.let { { Text(it) } }, + trailingContent = trailing, modifier = Modifier.clickable(role = Role.Button, onClick = onClick), colors = rowColors(), ) } + +/** A row that leaves for the browser, and says so. */ +@Composable +private fun LinkRow(title: String, summary: String? = null, onClick: () -> Unit) { + AboutRow( + title = title, + summary = summary, + trailing = { + Icon( + painterResource(R.drawable.ic_open_in_new_24dp), + contentDescription = stringResource(R.string.about_opens_browser), + ) + }, + onClick = onClick, + ) +} diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt index 44ae06d8..ba4d158c 100644 --- a/app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt +++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt @@ -47,7 +47,6 @@ fun NavGraphBuilder.settingsGraph(activity: MainActivity, nav: NavHostController composable { entry -> SettingsScreen( onSection = { if (onTop(entry)) nav.navigate(SettingsSectionRoute(it)) }, - onAbout = { if (onTop(entry)) nav.navigate(AboutRoute) }, onBack = { close(entry) }, ) } diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt index 14895017..ed78608f 100644 --- a/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Info import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -98,7 +97,6 @@ internal fun SettingsScaffold( @Composable fun SettingsScreen( onSection: (SettingsSection) -> Unit, - onAbout: () -> Unit, onBack: () -> Unit, modifier: Modifier = Modifier, ) { @@ -111,12 +109,6 @@ fun SettingsScreen( colors = rowColors(), ) } - ListItem( - headlineContent = { Text(stringResource(R.string.about)) }, - modifier = Modifier.clickable(role = Role.Button, onClick = onAbout), - leadingContent = { Icon(Icons.Filled.Info, contentDescription = null) }, - colors = rowColors(), - ) } } diff --git a/app/src/main/res/drawable/ic_open_in_new_24dp.xml b/app/src/main/res/drawable/ic_open_in_new_24dp.xml new file mode 100644 index 00000000..b82ebe62 --- /dev/null +++ b/app/src/main/res/drawable/ic_open_in_new_24dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index f6de40bd..07f63cd9 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -393,5 +393,6 @@ Licenza GNU General Public License v3.0 only. Copyright degli autori di Android Password Store e dei contributori di HardenedPass. Licenze open source + Si apre nel browser Nessun browser trovato per aprire questo link diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 38ef2cdc..c8782fad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -427,5 +427,6 @@ License GNU General Public License v3.0 only. Copyright the Android Password Store authors and HardenedPass contributors. Open source licenses + Opens in the browser No browser found to open this link -- cgit v1.2.3