mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-09-01 06:45:19 +00:00
refactor: use a ViewModel to pass around decryption passphrase
This commit is contained in:
@@ -11,6 +11,7 @@ import android.content.IntentSender
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.autofill.AutofillManager
|
import android.view.autofill.AutofillManager
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@@ -21,6 +22,7 @@ import app.passwordstore.util.autofill.AutofillPreferences
|
|||||||
import app.passwordstore.util.autofill.AutofillResponseBuilder
|
import app.passwordstore.util.autofill.AutofillResponseBuilder
|
||||||
import app.passwordstore.util.autofill.DirectoryStructure
|
import app.passwordstore.util.autofill.DirectoryStructure
|
||||||
import app.passwordstore.util.extensions.asLog
|
import app.passwordstore.util.extensions.asLog
|
||||||
|
import app.passwordstore.util.viewmodel.PasswordViewModel
|
||||||
import com.github.androidpasswordstore.autofillparser.AutofillAction
|
import com.github.androidpasswordstore.autofillparser.AutofillAction
|
||||||
import com.github.androidpasswordstore.autofillparser.Credentials
|
import com.github.androidpasswordstore.autofillparser.Credentials
|
||||||
import com.github.michaelbull.result.getOrElse
|
import com.github.michaelbull.result.getOrElse
|
||||||
@@ -77,6 +79,7 @@ class AutofillDecryptActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val viewModel: PasswordViewModel by viewModels()
|
||||||
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
|
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
|
||||||
@Inject lateinit var repository: CryptoRepository
|
@Inject lateinit var repository: CryptoRepository
|
||||||
|
|
||||||
@@ -105,10 +108,8 @@ class AutofillDecryptActivity : AppCompatActivity() {
|
|||||||
val dialog = PasswordDialog()
|
val dialog = PasswordDialog()
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
dialog.password.collectLatest { value ->
|
viewModel.password.collectLatest { value ->
|
||||||
if (value != null) {
|
decrypt(File(filePath), clientState, action, value)
|
||||||
decrypt(File(filePath), clientState, action, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import app.passwordstore.R
|
import app.passwordstore.R
|
||||||
import app.passwordstore.data.crypto.CryptoRepository
|
import app.passwordstore.data.crypto.CryptoRepository
|
||||||
@@ -22,6 +23,7 @@ import app.passwordstore.util.extensions.unsafeLazy
|
|||||||
import app.passwordstore.util.extensions.viewBinding
|
import app.passwordstore.util.extensions.viewBinding
|
||||||
import app.passwordstore.util.settings.Constants
|
import app.passwordstore.util.settings.Constants
|
||||||
import app.passwordstore.util.settings.PreferenceKeys
|
import app.passwordstore.util.settings.PreferenceKeys
|
||||||
|
import app.passwordstore.util.viewmodel.PasswordViewModel
|
||||||
import com.github.michaelbull.result.Err
|
import com.github.michaelbull.result.Err
|
||||||
import com.github.michaelbull.result.Ok
|
import com.github.michaelbull.result.Ok
|
||||||
import com.github.michaelbull.result.runCatching
|
import com.github.michaelbull.result.runCatching
|
||||||
@@ -45,6 +47,7 @@ import logcat.logcat
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class DecryptActivity : BasePgpActivity() {
|
class DecryptActivity : BasePgpActivity() {
|
||||||
|
|
||||||
|
private val viewModel: PasswordViewModel by viewModels()
|
||||||
private val binding by viewBinding(DecryptLayoutBinding::inflate)
|
private val binding by viewBinding(DecryptLayoutBinding::inflate)
|
||||||
private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
|
private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
|
||||||
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
|
@Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
|
||||||
@@ -67,7 +70,7 @@ class DecryptActivity : BasePgpActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
askPassphrase(isError = false)
|
askPassphrase()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
@@ -138,34 +141,36 @@ class DecryptActivity : BasePgpActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun askPassphrase(isError: Boolean) {
|
private fun askPassphrase() {
|
||||||
if (retries < MAX_RETRIES) {
|
if (retries < MAX_RETRIES) {
|
||||||
retries += 1
|
retries += 1
|
||||||
} else {
|
} else {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
val dialog = PasswordDialog()
|
showPasswordDialog(isError = false)
|
||||||
if (isError) {
|
|
||||||
dialog.setError()
|
|
||||||
}
|
|
||||||
lifecycleScope.launch(dispatcherProvider.main()) {
|
lifecycleScope.launch(dispatcherProvider.main()) {
|
||||||
dialog.password.collectLatest { value ->
|
viewModel.password.collectLatest { value ->
|
||||||
if (value != null) {
|
when (val result = decryptWithPassphrase(value)) {
|
||||||
when (val result = decryptWithPassphrase(value)) {
|
is Ok -> {
|
||||||
is Ok -> {
|
val entry = passwordEntryFactory.create(result.value.toByteArray())
|
||||||
val entry = passwordEntryFactory.create(result.value.toByteArray())
|
passwordEntry = entry
|
||||||
passwordEntry = entry
|
createPasswordUI(entry)
|
||||||
createPasswordUI(entry)
|
startAutoDismissTimer()
|
||||||
startAutoDismissTimer()
|
}
|
||||||
}
|
is Err -> {
|
||||||
is Err -> {
|
logcat(ERROR) { result.error.stackTraceToString() }
|
||||||
logcat(ERROR) { result.error.stackTraceToString() }
|
showPasswordDialog(isError = true)
|
||||||
askPassphrase(isError = true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPasswordDialog(isError: Boolean) {
|
||||||
|
val dialog = PasswordDialog()
|
||||||
|
if (isError) {
|
||||||
|
dialog.setError()
|
||||||
|
}
|
||||||
dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
|
dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,22 +12,20 @@ import android.view.KeyEvent
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import app.passwordstore.R
|
import app.passwordstore.R
|
||||||
import app.passwordstore.databinding.DialogPasswordEntryBinding
|
import app.passwordstore.databinding.DialogPasswordEntryBinding
|
||||||
import app.passwordstore.util.extensions.finish
|
import app.passwordstore.util.extensions.finish
|
||||||
import app.passwordstore.util.extensions.unsafeLazy
|
import app.passwordstore.util.extensions.unsafeLazy
|
||||||
|
import app.passwordstore.util.viewmodel.PasswordViewModel
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
|
|
||||||
/** [DialogFragment] to request a password from the user and forward it along. */
|
/** [DialogFragment] to request a password from the user and forward it along. */
|
||||||
class PasswordDialog : DialogFragment() {
|
class PasswordDialog : DialogFragment() {
|
||||||
|
|
||||||
|
private val viewModel: PasswordViewModel by activityViewModels()
|
||||||
private val binding by unsafeLazy { DialogPasswordEntryBinding.inflate(layoutInflater) }
|
private val binding by unsafeLazy { DialogPasswordEntryBinding.inflate(layoutInflater) }
|
||||||
private var isError: Boolean = false
|
private var isError: Boolean = false
|
||||||
private val _password = MutableStateFlow<String?>(null)
|
|
||||||
val password = _password.asStateFlow()
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||||
@@ -66,7 +64,7 @@ class PasswordDialog : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setPasswordAndDismiss() {
|
private fun setPasswordAndDismiss() {
|
||||||
_password.update { binding.passwordEditText.text.toString() }
|
viewModel.setPassword(binding.passwordEditText.text.toString())
|
||||||
dismissAllowingStateLoss()
|
dismissAllowingStateLoss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2022 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package app.passwordstore.util.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import app.passwordstore.ui.crypto.PasswordDialog
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.dropWhile
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel for classes that use [PasswordDialog] for getting user input. [PasswordDialog] will
|
||||||
|
* ensure that it retrieves an instance of this [ViewModel] from the Activity scope and will post
|
||||||
|
* values into [PasswordViewModel.password].
|
||||||
|
*/
|
||||||
|
class PasswordViewModel : ViewModel() {
|
||||||
|
private val _password = MutableStateFlow("")
|
||||||
|
val password = _password.asStateFlow().dropWhile(String::isBlank)
|
||||||
|
|
||||||
|
fun setPassword(pass: String) {
|
||||||
|
_password.update { pass }
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user