aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/deploy_github_releases.yml7
-rw-r--r--AGENTS.md5
-rw-r--r--CHANGELOG.md10
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/CryptoExtensions.kt10
-rw-r--r--app/src/main/java/app/passwordstore/ui/list/ListDestination.kt2
-rw-r--r--app/src/main/java/app/passwordstore/ui/list/ListScreen.kt11
-rw-r--r--app/src/main/java/app/passwordstore/ui/main/MainActivity.kt2
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/AboutScreen.kt35
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsGraph.kt1
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsScreen.kt8
-rw-r--r--app/src/main/java/app/passwordstore/ui/setup/SshKeyScreen.kt2
-rw-r--r--app/src/main/res/drawable/ic_open_in_new_24dp.xml12
-rw-r--r--app/src/main/res/values-it/strings.xml1
-rw-r--r--app/src/main/res/values/strings.xml1
-rw-r--r--app/version.properties4
15 files changed, 85 insertions, 26 deletions
diff --git a/.github/workflows/deploy_github_releases.yml b/.github/workflows/deploy_github_releases.yml
index f78687d1..4c0c7bd9 100644
--- a/.github/workflows/deploy_github_releases.yml
+++ b/.github/workflows/deploy_github_releases.yml
@@ -37,5 +37,8 @@ jobs:
apk="HardenedPass_${TAG}.apk"
cp app/build/outputs/apk/release/app-release.apk "$apk"
sha256sum "$apk" > "$apk.sha256"
- gh release create "$TAG" "$apk" "$apk.sha256" --draft --title "$TAG" \
- --notes "See [CHANGELOG.md](https://github.com/${GITHUB_REPOSITORY}/blob/${TAG}/CHANGELOG.md)."
+ # The release notes are this version's section of the changelog.
+ awk -v head="## [${TAG#v}]" 'index($0, head) == 1 { found = 1; next } /^## \[/ { found = 0 } found' \
+ CHANGELOG.md > notes.md
+ grep -q '[^[:space:]]' notes.md || { echo "No CHANGELOG.md section for $TAG"; exit 1; }
+ gh release create "$TAG" "$apk" "$apk.sha256" --draft --title "$TAG" --notes-file notes.md
diff --git a/AGENTS.md b/AGENTS.md
index 2b81fc3f..fda92a9d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -220,5 +220,8 @@ be updated again.
`openssl enc -aes-256-cbc -md sha256 -pbkdf2`; the key is the `ENCRYPT_KEY` repository
secret. `scripts/signing-setup.sh` decrypts them, `signing-cleanup.sh` deletes them.
- Pushing a `vX.Y.Z` tag runs `deploy_github_releases.yml`: it builds the signed APK and creates
- a draft GitHub Release with `HardenedPass_vX.Y.Z.apk` and its `.sha256`.
+ a draft GitHub Release with `HardenedPass_vX.Y.Z.apk` and its `.sha256`. The notes are that
+ version's `CHANGELOG.md` section in full, so the release fails without one.
+- After publishing, rebuild the website with a push to its `production` branch: its landing page
+ reads the latest version from GitHub at build time.
- Never print, log or commit the keystore password or `ENCRYPT_KEY`.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21f1ce8a..0ec067db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,16 @@
All notable changes to this project will be documented in this file.
+## [0.3.2] - 2026-11-05
+
+### Changed
+
+- About moved from Settings to the list's menu, and its links show that they open in the browser
+
+### Fixed
+
+- The version, signing certificate and SSH public key are no longer hidden in the clipboard preview
+
## [0.3.1] - 2026-11-05
### Added
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<SettingsRoute> { 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/java/app/passwordstore/ui/setup/SshKeyScreen.kt b/app/src/main/java/app/passwordstore/ui/setup/SshKeyScreen.kt
index f93fc9be..390f1160 100644
--- a/app/src/main/java/app/passwordstore/ui/setup/SshKeyScreen.kt
+++ b/app/src/main/java/app/passwordstore/ui/setup/SshKeyScreen.kt
@@ -195,7 +195,7 @@ fun SshKeyDestination(
busy = viewModel.busy,
onImport = { if (SshKey.exists) replace = true else picker.launch(arrayOf("*/*")) },
onGenerate = onGenerate,
- onCopy = { publicKey?.let { activity.copyTextToClipboard(it) } },
+ onCopy = { publicKey?.let { activity.copyTextToClipboard(it, sensitive = false) } },
onShare = { publicKey?.let { activity.startActivity(shareKey(it)) } },
onBack = { if (!viewModel.busy) onClose() },
)
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:autoMirrored="true"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z" />
+</vector>
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 @@
<string name="about_license">Licenza</string>
<string name="about_license_text">GNU General Public License v3.0 only. Copyright degli autori di Android Password Store e dei contributori di HardenedPass.</string>
<string name="about_libraries">Licenze open source</string>
+ <string name="about_opens_browser">Si apre nel browser</string>
<string name="about_no_browser">Nessun browser trovato per aprire questo link</string>
</resources>
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 @@
<string name="about_license">License</string>
<string name="about_license_text">GNU General Public License v3.0 only. Copyright the Android Password Store authors and HardenedPass contributors.</string>
<string name="about_libraries">Open source licenses</string>
+ <string name="about_opens_browser">Opens in the browser</string>
<string name="about_no_browser">No browser found to open this link</string>
</resources>
diff --git a/app/version.properties b/app/version.properties
index 04fc45ad..e6c7d4f4 100644
--- a/app/version.properties
+++ b/app/version.properties
@@ -1,5 +1,5 @@
#
# This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY.
#
-versioning-plugin.versionCode=301
-versioning-plugin.versionName=0.3.1
+versioning-plugin.versionCode=302
+versioning-plugin.versionName=0.3.2