summaryrefslogtreecommitdiffstats
path: root/app/src/main/java
diff options
context:
space:
mode:
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,