aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/dev/msfjarvis/aps/ui/dialogs
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/dev/msfjarvis/aps/ui/dialogs')
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/BasicBottomSheet.kt166
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt90
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/FolderCreationDialogFragment.kt110
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/ItemCreationBottomSheet.kt77
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/OtpImportDialogFragment.kt47
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt149
6 files changed, 0 insertions, 639 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/BasicBottomSheet.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/BasicBottomSheet.kt
deleted file mode 100644
index 53d9a201..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/BasicBottomSheet.kt
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package dev.msfjarvis.aps.ui.dialogs
-
-import android.content.Context
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewTreeObserver
-import android.widget.FrameLayout
-import androidx.annotation.StringRes
-import androidx.core.view.isVisible
-import com.google.android.material.bottomsheet.BottomSheetBehavior
-import com.google.android.material.bottomsheet.BottomSheetDialog
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.databinding.BasicBottomSheetBinding
-import dev.msfjarvis.aps.util.extensions.viewBinding
-
-/**
- * [BottomSheetDialogFragment] that exposes a simple [androidx.appcompat.app.AlertDialog] like API
- * through [BasicBottomSheet.Builder] to create a similar UI, just at the bottom of the screen.
- */
-class BasicBottomSheet
-private constructor(
- val title: String?,
- val message: String,
- val positiveButtonLabel: String?,
- val negativeButtonLabel: String?,
- val positiveButtonClickListener: View.OnClickListener?,
- val negativeButtonClickListener: View.OnClickListener?,
-) : BottomSheetDialogFragment() {
-
- private val binding by viewBinding(BasicBottomSheetBinding::bind)
-
- private var behavior: BottomSheetBehavior<FrameLayout>? = null
- private val bottomSheetCallback =
- object : BottomSheetBehavior.BottomSheetCallback() {
- override fun onSlide(bottomSheet: View, slideOffset: Float) {}
-
- override fun onStateChanged(bottomSheet: View, newState: Int) {
- if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
- dismiss()
- }
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- if (savedInstanceState != null) dismiss()
- return layoutInflater.inflate(R.layout.basic_bottom_sheet, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- view.viewTreeObserver.addOnGlobalLayoutListener(
- object : ViewTreeObserver.OnGlobalLayoutListener {
- override fun onGlobalLayout() {
- view.viewTreeObserver.removeOnGlobalLayoutListener(this)
- val dialog = dialog as BottomSheetDialog? ?: return
- behavior = dialog.behavior
- behavior?.apply {
- state = BottomSheetBehavior.STATE_EXPANDED
- peekHeight = 0
- addBottomSheetCallback(bottomSheetCallback)
- }
- if (!title.isNullOrEmpty()) {
- binding.bottomSheetTitle.isVisible = true
- binding.bottomSheetTitle.text = title
- }
- binding.bottomSheetMessage.text = message
- if (positiveButtonClickListener != null) {
- positiveButtonLabel?.let { buttonLbl -> binding.bottomSheetOkButton.text = buttonLbl }
- binding.bottomSheetOkButton.isVisible = true
- binding.bottomSheetOkButton.setOnClickListener {
- positiveButtonClickListener.onClick(it)
- dismiss()
- }
- }
- if (negativeButtonClickListener != null) {
- binding.bottomSheetCancelButton.isVisible = true
- negativeButtonLabel?.let { buttonLbl ->
- binding.bottomSheetCancelButton.text = buttonLbl
- }
- binding.bottomSheetCancelButton.setOnClickListener {
- negativeButtonClickListener.onClick(it)
- dismiss()
- }
- }
- }
- }
- )
- }
-
- override fun dismiss() {
- super.dismiss()
- behavior?.removeBottomSheetCallback(bottomSheetCallback)
- }
-
- class Builder(val context: Context) {
-
- private var title: String? = null
- private var message: String? = null
- private var positiveButtonLabel: String? = null
- private var negativeButtonLabel: String? = null
- private var positiveButtonClickListener: View.OnClickListener? = null
- private var negativeButtonClickListener: View.OnClickListener? = null
-
- fun setTitleRes(@StringRes titleRes: Int): Builder {
- this.title = context.resources.getString(titleRes)
- return this
- }
-
- fun setTitle(title: String): Builder {
- this.title = title
- return this
- }
-
- fun setMessageRes(@StringRes messageRes: Int): Builder {
- this.message = context.resources.getString(messageRes)
- return this
- }
-
- fun setMessage(message: String): Builder {
- this.message = message
- return this
- }
-
- fun setPositiveButtonClickListener(
- buttonLabel: String? = null,
- listener: View.OnClickListener
- ): Builder {
- this.positiveButtonClickListener = listener
- this.positiveButtonLabel = buttonLabel
- return this
- }
-
- fun setNegativeButtonClickListener(
- buttonLabel: String? = null,
- listener: View.OnClickListener
- ): Builder {
- this.negativeButtonClickListener = listener
- this.negativeButtonLabel = buttonLabel
- return this
- }
-
- fun build(): BasicBottomSheet {
- require(message != null) { "Message needs to be set" }
- return BasicBottomSheet(
- title,
- message!!,
- positiveButtonLabel,
- negativeButtonLabel,
- positiveButtonClickListener,
- negativeButtonClickListener
- )
- }
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt
deleted file mode 100644
index 22f991c5..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package dev.msfjarvis.aps.ui.dialogs
-
-import android.app.AlertDialog
-import android.app.Dialog
-import android.content.SharedPreferences
-import android.graphics.Typeface
-import android.os.Bundle
-import androidx.core.content.edit
-import androidx.core.os.bundleOf
-import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.setFragmentResult
-import androidx.lifecycle.lifecycleScope
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import dagger.hilt.android.AndroidEntryPoint
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.databinding.FragmentPwgenDicewareBinding
-import dev.msfjarvis.aps.injection.prefs.PasswordGeneratorPreferences
-import dev.msfjarvis.aps.passgen.diceware.DicewarePassphraseGenerator
-import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
-import dev.msfjarvis.aps.util.extensions.getString
-import dev.msfjarvis.aps.util.settings.PreferenceKeys.DICEWARE_LENGTH
-import dev.msfjarvis.aps.util.settings.PreferenceKeys.DICEWARE_SEPARATOR
-import javax.inject.Inject
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import reactivecircus.flowbinding.android.widget.afterTextChanges
-
-@AndroidEntryPoint
-class DicewarePasswordGeneratorDialogFragment : DialogFragment() {
-
- @Inject lateinit var dicewareGenerator: DicewarePassphraseGenerator
- @Inject @PasswordGeneratorPreferences lateinit var prefs: SharedPreferences
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val builder = MaterialAlertDialogBuilder(requireContext())
-
- val binding = FragmentPwgenDicewareBinding.inflate(layoutInflater)
- builder.setView(binding.root)
-
- binding.passwordSeparatorText.setText(prefs.getString(DICEWARE_SEPARATOR) ?: "-")
- binding.passwordLengthText.setText(prefs.getInt(DICEWARE_LENGTH, 5).toString())
- binding.passwordText.typeface = Typeface.MONOSPACE
-
- merge(
- binding.passwordLengthText.afterTextChanges(),
- binding.passwordSeparatorText.afterTextChanges(),
- )
- .onEach { generatePassword(binding) }
- .launchIn(lifecycleScope)
- return builder
- .run {
- setTitle(R.string.pwgen_title)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
- setFragmentResult(
- PasswordCreationActivity.PASSWORD_RESULT_REQUEST_KEY,
- bundleOf(PasswordCreationActivity.RESULT to "${binding.passwordText.text}")
- )
- }
- setNeutralButton(R.string.dialog_cancel) { _, _ -> }
- setNegativeButton(R.string.pwgen_generate, null)
- create()
- }
- .apply {
- setOnShowListener {
- generatePassword(binding)
- getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener { generatePassword(binding) }
- }
- }
- }
-
- private fun generatePassword(binding: FragmentPwgenDicewareBinding) {
- val length = binding.passwordLengthText.text?.toString()?.toIntOrNull() ?: 5
- val separator = binding.passwordSeparatorText.text?.toString()?.getOrNull(0) ?: '-'
- setPreferences(length, separator)
- binding.passwordText.text = dicewareGenerator.generatePassphrase(length, separator)
- }
-
- private fun setPreferences(length: Int, separator: Char) {
- prefs.edit {
- putInt(DICEWARE_LENGTH, length)
- putString(DICEWARE_SEPARATOR, separator.toString())
- }
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/FolderCreationDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/FolderCreationDialogFragment.kt
deleted file mode 100644
index 9b7b3e21..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/FolderCreationDialogFragment.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.dialogs
-
-import android.app.Dialog
-import android.content.Intent
-import android.os.Bundle
-import android.view.WindowManager
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.os.bundleOf
-import androidx.fragment.app.DialogFragment
-import androidx.lifecycle.lifecycleScope
-import com.google.android.material.checkbox.MaterialCheckBox
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.textfield.TextInputEditText
-import com.google.android.material.textfield.TextInputLayout
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.data.repo.PasswordRepository
-import dev.msfjarvis.aps.ui.crypto.BasePgpActivity
-import dev.msfjarvis.aps.ui.crypto.GetKeyIdsActivity
-import dev.msfjarvis.aps.ui.passwords.PasswordStore
-import dev.msfjarvis.aps.util.extensions.commitChange
-import java.io.File
-import kotlinx.coroutines.launch
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
-
-class FolderCreationDialogFragment : DialogFragment() {
-
- private lateinit var newFolder: File
-
- private val keySelectAction =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- if (result.resultCode == AppCompatActivity.RESULT_OK) {
- result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds ->
- val gpgIdentifierFile = File(newFolder, ".gpg-id")
- gpgIdentifierFile.writeText(keyIds.joinToString("\n"))
- if (PasswordRepository.repository != null) {
- lifecycleScope.launch {
- val repoPath = PasswordRepository.getRepositoryDirectory().absolutePath
- requireActivity()
- .commitChange(
- getString(
- R.string.git_commit_gpg_id,
- BasePgpActivity.getLongName(
- gpgIdentifierFile.parentFile!!.absolutePath,
- repoPath,
- gpgIdentifierFile.name
- )
- ),
- )
- dismiss()
- }
- }
- }
- }
- }
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val alertDialogBuilder = MaterialAlertDialogBuilder(requireContext())
- alertDialogBuilder.setTitle(R.string.title_create_folder)
- alertDialogBuilder.setView(R.layout.folder_dialog_fragment)
- alertDialogBuilder.setPositiveButton(getString(R.string.button_create), null)
- alertDialogBuilder.setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dismiss() }
- val dialog = alertDialogBuilder.create()
- dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
- dialog.setOnShowListener {
- dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
- createDirectory(requireArguments().getString(CURRENT_DIR_EXTRA)!!)
- }
- }
- return dialog
- }
-
- private fun createDirectory(currentDir: String) {
- val dialog = requireDialog()
- val folderNameView = dialog.findViewById<TextInputEditText>(R.id.folder_name_text)
- val folderNameViewContainer = dialog.findViewById<TextInputLayout>(R.id.folder_name_container)
- newFolder = File("$currentDir/${folderNameView.text}")
- folderNameViewContainer.error =
- when {
- newFolder.isFile -> getString(R.string.folder_creation_err_file_exists)
- newFolder.isDirectory -> getString(R.string.folder_creation_err_folder_exists)
- else -> null
- }
- if (folderNameViewContainer.error != null) return
- newFolder.mkdirs()
- (requireActivity() as PasswordStore).refreshPasswordList(newFolder)
- if (dialog.findViewById<MaterialCheckBox>(R.id.set_gpg_key).isChecked) {
- keySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java))
- return
- } else {
- dismiss()
- }
- }
-
- companion object {
-
- private const val CURRENT_DIR_EXTRA = "CURRENT_DIRECTORY"
- fun newInstance(startingDirectory: String): FolderCreationDialogFragment {
- val extras = bundleOf(CURRENT_DIR_EXTRA to startingDirectory)
- val fragment = FolderCreationDialogFragment()
- fragment.arguments = extras
- return fragment
- }
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/ItemCreationBottomSheet.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/ItemCreationBottomSheet.kt
deleted file mode 100644
index af2a5f19..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/ItemCreationBottomSheet.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.dialogs
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewTreeObserver
-import android.widget.FrameLayout
-import androidx.core.os.bundleOf
-import androidx.fragment.app.setFragmentResult
-import com.google.android.material.bottomsheet.BottomSheetBehavior
-import com.google.android.material.bottomsheet.BottomSheetDialog
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.ui.passwords.PasswordFragment.Companion.ACTION_FOLDER
-import dev.msfjarvis.aps.ui.passwords.PasswordFragment.Companion.ACTION_KEY
-import dev.msfjarvis.aps.ui.passwords.PasswordFragment.Companion.ACTION_PASSWORD
-import dev.msfjarvis.aps.ui.passwords.PasswordFragment.Companion.ITEM_CREATION_REQUEST_KEY
-
-class ItemCreationBottomSheet : BottomSheetDialogFragment() {
-
- private var behavior: BottomSheetBehavior<FrameLayout>? = null
- private val bottomSheetCallback =
- object : BottomSheetBehavior.BottomSheetCallback() {
- override fun onSlide(bottomSheet: View, slideOffset: Float) {}
-
- override fun onStateChanged(bottomSheet: View, newState: Int) {
- if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
- dismiss()
- }
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- if (savedInstanceState != null) dismiss()
- return inflater.inflate(R.layout.item_create_sheet, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- view.viewTreeObserver.addOnGlobalLayoutListener(
- object : ViewTreeObserver.OnGlobalLayoutListener {
- override fun onGlobalLayout() {
- view.viewTreeObserver.removeOnGlobalLayoutListener(this)
- val dialog = dialog as BottomSheetDialog? ?: return
- behavior = dialog.behavior
- behavior?.apply {
- state = BottomSheetBehavior.STATE_EXPANDED
- peekHeight = 0
- addBottomSheetCallback(bottomSheetCallback)
- }
- dialog.findViewById<View>(R.id.create_folder)?.setOnClickListener {
- setFragmentResult(ITEM_CREATION_REQUEST_KEY, bundleOf(ACTION_KEY to ACTION_FOLDER))
- dismiss()
- }
- dialog.findViewById<View>(R.id.create_password)?.setOnClickListener {
- setFragmentResult(ITEM_CREATION_REQUEST_KEY, bundleOf(ACTION_KEY to ACTION_PASSWORD))
- dismiss()
- }
- }
- }
- )
- }
-
- override fun dismiss() {
- super.dismiss()
- behavior?.removeBottomSheetCallback(bottomSheetCallback)
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/OtpImportDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/OtpImportDialogFragment.kt
deleted file mode 100644
index 1670d263..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/OtpImportDialogFragment.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package dev.msfjarvis.aps.ui.dialogs
-
-import android.app.Dialog
-import android.net.Uri
-import android.os.Bundle
-import android.view.WindowManager
-import androidx.core.os.bundleOf
-import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.setFragmentResult
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import dev.msfjarvis.aps.databinding.FragmentManualOtpEntryBinding
-import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
-
-class OtpImportDialogFragment : DialogFragment() {
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val builder = MaterialAlertDialogBuilder(requireContext())
- val binding = FragmentManualOtpEntryBinding.inflate(layoutInflater)
- builder.setView(binding.root)
- builder.setPositiveButton(android.R.string.ok) { _, _ ->
- setFragmentResult(
- PasswordCreationActivity.OTP_RESULT_REQUEST_KEY,
- bundleOf(PasswordCreationActivity.RESULT to getTOTPUri(binding))
- )
- }
- val dialog = builder.create()
- dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
- return dialog
- }
-
- private fun getTOTPUri(binding: FragmentManualOtpEntryBinding): String {
- val secret = binding.secret.text.toString()
- val account = binding.account.text.toString()
- if (secret.isBlank()) return ""
- val builder = Uri.Builder()
- builder.scheme("otpauth")
- builder.authority("totp")
- builder.appendQueryParameter("secret", secret)
- if (account.isNotBlank()) builder.appendQueryParameter("issuer", account)
- return builder.build().toString()
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt
deleted file mode 100644
index e14076b1..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.dialogs
-
-import android.app.AlertDialog
-import android.app.Dialog
-import android.content.Context
-import android.graphics.Typeface
-import android.os.Bundle
-import android.widget.CheckBox
-import android.widget.EditText
-import android.widget.Toast
-import androidx.annotation.IdRes
-import androidx.appcompat.widget.AppCompatTextView
-import androidx.core.content.edit
-import androidx.core.os.bundleOf
-import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.setFragmentResult
-import androidx.lifecycle.lifecycleScope
-import com.github.michaelbull.result.getOrElse
-import com.github.michaelbull.result.runCatching
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.databinding.FragmentPwgenBinding
-import dev.msfjarvis.aps.passgen.random.MaxIterationsExceededException
-import dev.msfjarvis.aps.passgen.random.NoCharactersIncludedException
-import dev.msfjarvis.aps.passgen.random.PasswordGenerator
-import dev.msfjarvis.aps.passgen.random.PasswordLengthTooShortException
-import dev.msfjarvis.aps.passgen.random.PasswordOption
-import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
-import dev.msfjarvis.aps.util.settings.PreferenceKeys
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import reactivecircus.flowbinding.android.widget.afterTextChanges
-import reactivecircus.flowbinding.android.widget.checkedChanges
-
-class PasswordGeneratorDialogFragment : DialogFragment() {
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val prefs = requireContext().getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
- val builder = MaterialAlertDialogBuilder(requireContext())
-
- val binding = FragmentPwgenBinding.inflate(layoutInflater)
- builder.setView(binding.root)
-
- binding.numerals.isChecked = !prefs.getBoolean(PasswordOption.NoDigits.key, false)
- binding.symbols.isChecked = prefs.getBoolean(PasswordOption.AtLeastOneSymbol.key, false)
- binding.uppercase.isChecked = !prefs.getBoolean(PasswordOption.NoUppercaseLetters.key, false)
- binding.lowercase.isChecked = !prefs.getBoolean(PasswordOption.NoLowercaseLetters.key, false)
- binding.ambiguous.isChecked = !prefs.getBoolean(PasswordOption.NoAmbiguousCharacters.key, false)
- binding.pronounceable.isChecked = !prefs.getBoolean(PasswordOption.FullyRandom.key, true)
- binding.lengthNumber.setText(prefs.getInt(PreferenceKeys.LENGTH, 20).toString())
- binding.passwordText.typeface = Typeface.MONOSPACE
-
- merge(
- binding.numerals.checkedChanges().skipInitialValue(),
- binding.symbols.checkedChanges().skipInitialValue(),
- binding.uppercase.checkedChanges().skipInitialValue(),
- binding.lowercase.checkedChanges().skipInitialValue(),
- binding.ambiguous.checkedChanges().skipInitialValue(),
- binding.pronounceable.checkedChanges().skipInitialValue(),
- binding.lengthNumber.afterTextChanges().skipInitialValue(),
- )
- .onEach { generate(binding.passwordText) }
- .launchIn(lifecycleScope)
-
- return builder
- .run {
- setTitle(R.string.pwgen_title)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
- setFragmentResult(
- PasswordCreationActivity.PASSWORD_RESULT_REQUEST_KEY,
- bundleOf(PasswordCreationActivity.RESULT to "${binding.passwordText.text}")
- )
- }
- setNeutralButton(R.string.dialog_cancel) { _, _ -> }
- setNegativeButton(R.string.pwgen_generate, null)
- create()
- }
- .apply {
- setOnShowListener {
- generate(binding.passwordText)
- getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
- generate(binding.passwordText)
- }
- }
- }
- }
-
- private fun generate(passwordField: AppCompatTextView) {
- val passwordOptions = getSelectedOptions()
- val passwordLength = getLength()
- setPrefs(requireContext(), passwordOptions, passwordLength)
- passwordField.text =
- runCatching { PasswordGenerator.generate(passwordOptions, passwordLength) }
- .getOrElse { exception ->
- val errorText =
- when (exception) {
- is MaxIterationsExceededException ->
- requireContext().getString(R.string.pwgen_max_iterations_exceeded)
- is NoCharactersIncludedException ->
- requireContext().getString(R.string.pwgen_no_chars_error)
- is PasswordLengthTooShortException ->
- requireContext().getString(R.string.pwgen_length_too_short_error)
- else -> requireContext().getString(R.string.pwgen_some_error_occurred)
- }
- Toast.makeText(requireActivity(), errorText, Toast.LENGTH_SHORT).show()
- ""
- }
- }
-
- private fun isChecked(@IdRes id: Int): Boolean {
- return requireDialog().findViewById<CheckBox>(id).isChecked
- }
-
- private fun getSelectedOptions(): List<PasswordOption> {
- return listOfNotNull(
- PasswordOption.NoDigits.takeIf { !isChecked(R.id.numerals) },
- PasswordOption.AtLeastOneSymbol.takeIf { isChecked(R.id.symbols) },
- PasswordOption.NoUppercaseLetters.takeIf { !isChecked(R.id.uppercase) },
- PasswordOption.NoAmbiguousCharacters.takeIf { !isChecked(R.id.ambiguous) },
- PasswordOption.FullyRandom.takeIf { !isChecked(R.id.pronounceable) },
- PasswordOption.NoLowercaseLetters.takeIf { !isChecked(R.id.lowercase) }
- )
- }
-
- private fun getLength(): Int {
- val lengthText = requireDialog().findViewById<EditText>(R.id.lengthNumber).text.toString()
- return lengthText.toIntOrNull()?.takeIf { it >= 0 } ?: PasswordGenerator.DEFAULT_LENGTH
- }
-
- /**
- * Enables the [PasswordOption]s in [options] and sets [targetLength] as the length for generated
- * passwords.
- */
- private fun setPrefs(ctx: Context, options: List<PasswordOption>, targetLength: Int): Boolean {
- ctx.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE).edit {
- for (possibleOption in PasswordOption.values()) putBoolean(
- possibleOption.key,
- possibleOption in options
- )
- putInt("length", targetLength)
- }
- return true
- }
-}