summaryrefslogtreecommitdiffstats
path: root/app/src/main/java
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-04 17:35:40 +0100
committerDanilo M. <danix@danix.xyz>2026-11-04 17:35:40 +0100
commit35b2c6ab50a7b8e141f88f9ae88570ec43e6fa7b (patch)
tree3c9b391ea9852270f448f4f0aa2ef69cd3bf40f1 /app/src/main/java
parent7f2c9770c701903d6c09e1e6d553e1cd6c974bab (diff)
downloadHardenedPass-35b2c6ab50a7b8e141f88f9ae88570ec43e6fa7b.tar.gz
HardenedPass-35b2c6ab50a7b8e141f88f9ae88570ec43e6fa7b.zip
refactor(app): drop dead Fragment, view and navigation stack helpers
The Compose screens use no Fragments or view binding, so FragmentExtensions (with its four slide animators) and FragmentViewBindingDelegate have no users, nor do resolveAttribute, isPermissionGranted, launchActivity and the unused Modifier.conditional. SearchableRepositoryViewModel keeps one navigateTo: the Compose list owns the back stack, so the RecyclerView-era navigation stack, stableId, isFiltered and the unused DirectoriesOnly mode go. PasswordItem loses the parent overloads, @JvmStatic and the unread NAME extra, and the leftover sentry.properties goes too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/app/passwordstore/data/password/PasswordItem.kt26
-rw-r--r--app/src/main/java/app/passwordstore/ui/list/ListDestination.kt11
-rw-r--r--app/src/main/java/app/passwordstore/util/extensions/AndroidExtensions.kt31
-rw-r--r--app/src/main/java/app/passwordstore/util/extensions/FragmentExtensions.kt40
-rw-r--r--app/src/main/java/app/passwordstore/util/extensions/FragmentViewBindingDelegate.kt70
-rw-r--r--app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt50
6 files changed, 14 insertions, 214 deletions
diff --git a/app/src/main/java/app/passwordstore/data/password/PasswordItem.kt b/app/src/main/java/app/passwordstore/data/password/PasswordItem.kt
index 765e886d..999ad050 100644
--- a/app/src/main/java/app/passwordstore/data/password/PasswordItem.kt
+++ b/app/src/main/java/app/passwordstore/data/password/PasswordItem.kt
@@ -10,13 +10,8 @@ import app.passwordstore.ui.crypto.BasePGPActivity
import app.passwordstore.ui.main.LaunchActivity
import java.io.File
-data class PasswordItem(
- val name: String,
- val parent: PasswordItem? = null,
- val type: Char,
- val file: File,
- val rootDir: File,
-) : Comparable<PasswordItem> {
+data class PasswordItem(val name: String, val type: Char, val file: File, val rootDir: File) :
+ Comparable<PasswordItem> {
val fullPathToParent = file.absolutePath.replace(rootDir.absolutePath, "").replace(file.name, "")
@@ -41,7 +36,6 @@ data class PasswordItem(
/** Creates an [Intent] to launch this [PasswordItem] through the authentication process. */
fun createAuthEnabledIntent(context: Context): Intent {
val intent = Intent(context, LaunchActivity::class.java)
- intent.putExtra("NAME", toString())
intent.putExtra("FILE_PATH", file.absolutePath)
intent.action = LaunchActivity.ACTION_DECRYPT_PASS
return intent
@@ -52,24 +46,12 @@ data class PasswordItem(
const val TYPE_CATEGORY = 'c'
const val TYPE_PASSWORD = 'p'
- @JvmStatic
- fun newCategory(name: String, file: File, parent: PasswordItem, rootDir: File): PasswordItem {
- return PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir)
- }
-
- @JvmStatic
fun newCategory(name: String, file: File, rootDir: File): PasswordItem {
- return PasswordItem(name, null, TYPE_CATEGORY, file, rootDir)
- }
-
- @JvmStatic
- fun newPassword(name: String, file: File, parent: PasswordItem, rootDir: File): PasswordItem {
- return PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir)
+ return PasswordItem(name, TYPE_CATEGORY, file, rootDir)
}
- @JvmStatic
fun newPassword(name: String, file: File, rootDir: File): PasswordItem {
- return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir)
+ return PasswordItem(name, TYPE_PASSWORD, file, rootDir)
}
}
}
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 adae3649..a9228484 100644
--- a/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt
+++ b/app/src/main/java/app/passwordstore/ui/list/ListDestination.kt
@@ -58,10 +58,9 @@ private enum class ListDialog {
/**
* The list at [path] (relative to the store root): wires [ListScreen] to the [viewModel], the
- * folder picker, the legacy activities and the store operations. [onOpenFolder] takes a path
- * relative to the root, [onOpenEntry] an entry file; [onNewEntry] opens the editor in this folder,
- * [onClone] the git server screen for a clone, [onSettings] the settings; [onNoStore] runs when the
- * store is gone.
+ * folder picker and the store operations. [onOpenFolder] takes a path relative to the root,
+ * [onOpenEntry] an entry file; [onNewEntry] opens the editor in this folder, [onClone] the git
+ * server screen for a clone, [onSettings] the settings; [onNoStore] runs when the store is gone.
*/
@Composable
fun ListDestination(
@@ -89,7 +88,7 @@ fun ListDestination(
// Declared before the collection below on purpose: effects start in order, so the ViewModel
// already points at this folder when the first rows are built (no flash of the store root).
LaunchedEffect(path, query) {
- viewModel.navigateTo(dir, pushPreviousLocation = false)
+ viewModel.navigateTo(dir)
if (query.isNotBlank()) viewModel.search(query.trim())
}
val rows by viewModel.rows.collectAsStateWithLifecycle(ListRows())
@@ -100,7 +99,7 @@ fun ListDestination(
if (path.isNotEmpty() && !dir.isDirectory) onOpenFolder("") else viewModel.forceRefresh()
}
- // Every return to this folder (back from a child, or from a legacy activity) re-reads it.
+ // Every return to this folder (back from a child, or from another activity) re-reads it.
LifecycleResumeEffect(Unit) {
if (activity.checkStore()) refresh() else onNoStore()
resumes++
diff --git a/app/src/main/java/app/passwordstore/util/extensions/AndroidExtensions.kt b/app/src/main/java/app/passwordstore/util/extensions/AndroidExtensions.kt
index bad55de4..6528334d 100644
--- a/app/src/main/java/app/passwordstore/util/extensions/AndroidExtensions.kt
+++ b/app/src/main/java/app/passwordstore/util/extensions/AndroidExtensions.kt
@@ -8,7 +8,6 @@ package app.passwordstore.util.extensions
import android.app.KeyguardManager
import android.content.ClipboardManager
import android.content.Context
-import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
@@ -17,12 +16,8 @@ import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.PackageInfoFlags
import android.os.Build
import android.util.Base64
-import android.util.TypedValue
import android.view.View
import android.view.autofill.AutofillManager
-import androidx.activity.ComponentActivity
-import androidx.compose.ui.Modifier
-import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.fragment.app.FragmentActivity
import app.passwordstore.BuildConfig
@@ -56,13 +51,6 @@ val Context.keyguardManager: KeyguardManager
val Context.sharedPrefs: SharedPreferences
get() = getSharedPreferences("${BuildConfig.APPLICATION_ID}_preferences", 0)
-/** Resolve [attr] from the [Context]'s theme */
-fun Context.resolveAttribute(attr: Int): Int {
- val typedValue = TypedValue()
- this.theme.resolveAttribute(attr, typedValue, true)
- return typedValue.data
-}
-
/**
* Commit changes to the store from a [FragmentActivity] using a custom implementation of
* [GitOperation]
@@ -90,11 +78,6 @@ suspend fun FragmentActivity.commitChange(message: String): Result<Unit, Throwab
.execute()
}
-/** Check if [permission] has been granted to the app. */
-fun FragmentActivity.isPermissionGranted(permission: String): Boolean {
- return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
-}
-
/** Show a [Snackbar] in a [FragmentActivity]. */
fun FragmentActivity.snackbar(
view: View = findViewById(android.R.id.content),
@@ -106,11 +89,6 @@ fun FragmentActivity.snackbar(
return snackbar
}
-/** Launch an activity denoted by [clazz]. */
-fun <T : ComponentActivity> ComponentActivity.launchActivity(clazz: Class<T>) {
- startActivity(Intent(this, clazz).setAction(Intent.ACTION_VIEW))
-}
-
/** Simplifies the common `getString(key, null) ?: defaultValue` case slightly */
fun SharedPreferences.getString(key: String): String? = getString(key, null)
@@ -134,12 +112,3 @@ fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): Ap
getApplicationInfo(packageName, flags)
}
}
-
-/** Allows conditionally applying the given [modifier] if [isEnabled] is `true`. */
-fun Modifier.conditional(isEnabled: Boolean, modifier: Modifier.() -> Modifier): Modifier {
- return if (isEnabled) {
- then(modifier())
- } else {
- this
- }
-}
diff --git a/app/src/main/java/app/passwordstore/util/extensions/FragmentExtensions.kt b/app/src/main/java/app/passwordstore/util/extensions/FragmentExtensions.kt
deleted file mode 100644
index 2dd3af0c..00000000
--- a/app/src/main/java/app/passwordstore/util/extensions/FragmentExtensions.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.util.extensions
-
-import androidx.annotation.IdRes
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentManager
-import androidx.fragment.app.commit
-import app.passwordstore.R
-
-/** Check if [permission] is granted to the app. Aliases to [isPermissionGranted] internally. */
-fun Fragment.isPermissionGranted(permission: String): Boolean {
- return requireActivity().isPermissionGranted(permission)
-}
-
-/** Calls `finish()` on the enclosing [androidx.fragment.app.FragmentActivity] */
-fun Fragment.finish() = requireActivity().finish()
-
-/**
- * Perform a [commit] on this [FragmentManager] with custom animations and adding the
- * [destinationFragment] to the fragment backstack
- */
-fun FragmentManager.performTransactionWithBackStack(
- destinationFragment: Fragment,
- @IdRes containerViewId: Int = android.R.id.content,
-) {
- commit {
- addToBackStack(destinationFragment.tag)
- setCustomAnimations(
- R.animator.slide_in_left,
- R.animator.slide_out_left,
- R.animator.slide_in_right,
- R.animator.slide_out_right,
- )
- replace(containerViewId, destinationFragment)
- }
-}
diff --git a/app/src/main/java/app/passwordstore/util/extensions/FragmentViewBindingDelegate.kt b/app/src/main/java/app/passwordstore/util/extensions/FragmentViewBindingDelegate.kt
deleted file mode 100644
index 0050f2e5..00000000
--- a/app/src/main/java/app/passwordstore/util/extensions/FragmentViewBindingDelegate.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright © 2014-2024 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.util.extensions
-
-import android.view.LayoutInflater
-import android.view.View
-import androidx.appcompat.app.AppCompatActivity
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.viewbinding.ViewBinding
-import kotlin.properties.ReadOnlyProperty
-import kotlin.reflect.KProperty
-
-/**
- * Imported from
- * https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c
- */
-class FragmentViewBindingDelegate<T : ViewBinding>(
- val fragment: Fragment,
- val viewBindingFactory: (View) -> T,
-) : ReadOnlyProperty<Fragment, T> {
-
- private var binding: T? = null
-
- init {
- fragment.lifecycle.addObserver(
- object : DefaultLifecycleObserver {
- override fun onCreate(owner: LifecycleOwner) {
- fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner ->
- viewLifecycleOwner.lifecycle.addObserver(
- object : DefaultLifecycleObserver {
- override fun onDestroy(owner: LifecycleOwner) {
- binding = null
- }
- }
- )
- }
- }
- }
- )
- }
-
- override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
- val binding = binding
- if (binding != null) {
- return binding
- }
-
- val lifecycle = fragment.viewLifecycleOwner.lifecycle
- if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
- throw IllegalStateException(
- "Should not attempt to get bindings when Fragment views are destroyed."
- )
- }
-
- return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
- }
-}
-
-fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
- FragmentViewBindingDelegate(this, viewBindingFactory)
-
-inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
- crossinline bindingInflater: (LayoutInflater) -> T
-) = unsafeLazy { bindingInflater.invoke(layoutInflater) }
diff --git a/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt b/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt
index 25ea2cf7..5f35644d 100644
--- a/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt
+++ b/app/src/main/java/app/passwordstore/util/viewmodel/SearchableRepositoryViewModel.kt
@@ -6,7 +6,6 @@ package app.passwordstore.util.viewmodel
import android.app.Application
import android.content.SharedPreferences
-import android.os.Parcelable
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.AndroidViewModel
import app.passwordstore.data.password.PasswordItem
@@ -68,9 +67,6 @@ private fun PasswordItem.Companion.makeComparator(
)
}
-val PasswordItem.stableId: String
- get() = file.absolutePath
-
enum class FilterMode {
NoFilter,
StrictDomain,
@@ -85,7 +81,6 @@ enum class SearchMode {
enum class ListMode {
FilesOnly,
- DirectoriesOnly,
AllEntries,
}
@@ -171,7 +166,7 @@ constructor(
)
)
- data class SearchResult(val passwordItems: List<PasswordItem>, val isFiltered: Boolean)
+ data class SearchResult(val passwordItems: List<PasswordItem>)
val searchResult =
searchActionFlow
@@ -187,8 +182,6 @@ constructor(
when (searchAction.listMode) {
ListMode.FilesOnly ->
listResultFlow.filter { it.isFile }.flowOn(dispatcherProvider.io())
- ListMode.DirectoriesOnly ->
- listResultFlow.filter { it.isDirectory }.flowOn(dispatcherProvider.io())
ListMode.AllEntries -> listResultFlow
}
val passwordList =
@@ -244,7 +237,7 @@ constructor(
.map { it.second }
}
}
- SearchResult(passwordList, isFiltered = searchAction.filterMode != FilterMode.NoFilter)
+ SearchResult(passwordList)
}
.flowOn(dispatcherProvider.io())
@@ -288,55 +281,22 @@ constructor(
.map { result -> listRows(result.passwordItems.map { it.file }, currentDir.value) }
.flowOn(dispatcherProvider.io())
- data class NavigationStackEntry(val dir: File, val recyclerViewState: Parcelable?)
-
- private val navigationStack = ArrayDeque<NavigationStackEntry>()
-
- fun navigateTo(
- newDirectory: File = root,
- listMode: ListMode = ListMode.AllEntries,
- recyclerViewState: Parcelable? = null,
- pushPreviousLocation: Boolean = true,
- ) {
+ /** Lists [newDirectory]; the Compose list keeps the back stack (one route per folder). */
+ fun navigateTo(newDirectory: File) {
if (!newDirectory.exists()) return
require(newDirectory.isDirectory) { "Can only navigate to a directory" }
- if (pushPreviousLocation) {
- navigationStack.addFirst(NavigationStackEntry(_currentDir.value, recyclerViewState))
- }
searchActionFlow.update {
makeSearchAction(
filter = "",
baseDirectory = newDirectory,
filterMode = FilterMode.NoFilter,
searchMode = SearchMode.InCurrentDirectoryOnly,
- listMode = listMode,
+ listMode = ListMode.AllEntries,
)
}
_currentDir.update { newDirectory }
}
- val canNavigateBack
- get() = navigationStack.isNotEmpty()
-
- /**
- * Navigate back to the last location on the [navigationStack] and restore a cached scroll
- * position if possible.
- *
- * Returns the old RecyclerView's LinearLayoutManager state as a [Parcelable] if it was cached.
- */
- fun navigateBack(): Parcelable? {
- if (!canNavigateBack) return null
- val (oldDir, oldRecyclerViewState) = navigationStack.removeFirst()
- navigateTo(oldDir, pushPreviousLocation = false)
- return oldRecyclerViewState
- }
-
- fun reset() {
- navigationStack.clear()
- forceUpdateOnNextSearchAction()
- navigateTo(pushPreviousLocation = false)
- }
-
fun search(
filter: String,
baseDirectory: File? = null,