aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/dev/msfjarvis/aps/ui/dialogs
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-12-05 06:07:18 +0530
committerGitHub <noreply@github.com>2020-12-05 06:07:18 +0530
commit5e66d99c852ea67a88b650c03b0e8d55e83eccde (patch)
treeaee323a08253fe7b863976eff92a1bd412bd1430 /app/src/main/java/dev/msfjarvis/aps/ui/dialogs
parent8eb55f18a11d6b2f155c7c9d48ca833313ee13db (diff)
downloadHardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.tar.gz
HardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.zip
Refactor package structure (#1233)
* idea: default test runner to Gradle * Kick off package structure revamp * Reparent all classes under dev.msfjarvis.aps Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
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.kt161
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/FolderCreationDialogFragment.kt106
-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/PasswordGeneratorDialogFragment.kt101
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/dialogs/XkPasswordGeneratorDialogFragment.kt131
5 files changed, 576 insertions, 0 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
new file mode 100644
index 00000000..c2577443
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/BasicBottomSheet.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright © 2014-2020 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.graphics.drawable.GradientDrawable
+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.resolveAttribute
+import dev.msfjarvis.aps.util.extensions.viewBinding
+
+/**
+ * [BottomSheetDialogFragment] that exposes a simple [androidx.appcompat.app.AlertDialog] like
+ * API through [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()
+ }
+ }
+ }
+ })
+ val gradientDrawable = GradientDrawable().apply {
+ setColor(requireContext().resolveAttribute(android.R.attr.windowBackground))
+ }
+ view.background = gradientDrawable
+ }
+
+ 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/FolderCreationDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/FolderCreationDialogFragment.kt
new file mode 100644
index 00000000..7f50a619
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/FolderCreationDialogFragment.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright © 2014-2020 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 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.ui.passwords.PasswordStore
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.ui.crypto.BasePgpActivity
+import dev.msfjarvis.aps.ui.crypto.GetKeyIdsActivity
+import dev.msfjarvis.aps.data.repo.PasswordRepository
+import dev.msfjarvis.aps.util.extensions.commitChange
+import dev.msfjarvis.aps.util.extensions.requestInputFocusOnView
+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"))
+ val repo = PasswordRepository.getRepository(null)
+ if (repo != 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.requestInputFocusOnView<TextInputEditText>(R.id.folder_name_text)
+ 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
new file mode 100644
index 00000000..4889f1e5
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/ItemCreationBottomSheet.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package dev.msfjarvis.aps.ui.dialogs
+
+import android.graphics.drawable.GradientDrawable
+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.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
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.util.extensions.resolveAttribute
+
+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()
+ }
+ }
+ })
+ val gradientDrawable = GradientDrawable().apply {
+ setColor(requireContext().resolveAttribute(android.R.attr.windowBackground))
+ }
+ view.background = gradientDrawable
+ }
+
+ override fun dismiss() {
+ super.dismiss()
+ behavior?.removeBottomSheetCallback(bottomSheetCallback)
+ }
+}
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
new file mode 100644
index 00000000..0676ef1c
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package dev.msfjarvis.aps.ui.dialogs
+
+import android.annotation.SuppressLint
+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.AppCompatEditText
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.fragment.app.DialogFragment
+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.util.pwgen.PasswordGenerator
+import dev.msfjarvis.aps.util.pwgen.PasswordGenerator.generate
+import dev.msfjarvis.aps.util.pwgen.PasswordGenerator.setPrefs
+import dev.msfjarvis.aps.util.pwgen.PasswordOption
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+
+class PasswordGeneratorDialogFragment : DialogFragment() {
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val callingActivity = requireActivity()
+ val inflater = callingActivity.layoutInflater
+
+ @SuppressLint("InflateParams")
+ val view = inflater.inflate(R.layout.fragment_pwgen, null)
+ val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf")
+ val prefs = requireActivity().applicationContext
+ .getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
+
+ view.findViewById<CheckBox>(R.id.numerals)?.isChecked = !prefs.getBoolean(PasswordOption.NoDigits.key, false)
+ view.findViewById<CheckBox>(R.id.symbols)?.isChecked = prefs.getBoolean(PasswordOption.AtLeastOneSymbol.key, false)
+ view.findViewById<CheckBox>(R.id.uppercase)?.isChecked = !prefs.getBoolean(PasswordOption.NoUppercaseLetters.key, false)
+ view.findViewById<CheckBox>(R.id.lowercase)?.isChecked = !prefs.getBoolean(PasswordOption.NoLowercaseLetters.key, false)
+ view.findViewById<CheckBox>(R.id.ambiguous)?.isChecked = !prefs.getBoolean(PasswordOption.NoAmbiguousCharacters.key, false)
+ view.findViewById<CheckBox>(R.id.pronounceable)?.isChecked = !prefs.getBoolean(PasswordOption.FullyRandom.key, true)
+
+ val textView: AppCompatEditText = view.findViewById(R.id.lengthNumber)
+ textView.setText(prefs.getInt(PreferenceKeys.LENGTH, 20).toString())
+ val passwordText: AppCompatTextView = view.findViewById(R.id.passwordText)
+ passwordText.typeface = monoTypeface
+ return MaterialAlertDialogBuilder(requireContext()).run {
+ setTitle(R.string.pwgen_title)
+ setView(view)
+ setPositiveButton(R.string.dialog_ok) { _, _ ->
+ val edit = callingActivity.findViewById<EditText>(R.id.password)
+ edit.setText(passwordText.text)
+ }
+ setNeutralButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(R.string.pwgen_generate, null)
+ create()
+ }.apply {
+ setOnShowListener {
+ generate(passwordText)
+ getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
+ generate(passwordText)
+ }
+ }
+ }
+ }
+
+ private fun generate(passwordField: AppCompatTextView) {
+ setPreferences()
+ passwordField.text = runCatching {
+ generate(requireContext().applicationContext)
+ }.getOrElse { e ->
+ Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
+ ""
+ }
+ }
+
+ private fun isChecked(@IdRes id: Int): Boolean {
+ return requireDialog().findViewById<CheckBox>(id).isChecked
+ }
+
+ private fun setPreferences() {
+ val preferences = 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) }
+ )
+ val lengthText = requireDialog().findViewById<EditText>(R.id.lengthNumber).text.toString()
+ val length = lengthText.toIntOrNull()?.takeIf { it >= 0 }
+ ?: PasswordGenerator.DEFAULT_LENGTH
+ setPrefs(requireActivity().applicationContext, preferences, length)
+ }
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/XkPasswordGeneratorDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/XkPasswordGeneratorDialogFragment.kt
new file mode 100644
index 00000000..995c7e2f
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/XkPasswordGeneratorDialogFragment.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright © 2014-2020 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.Context
+import android.content.SharedPreferences
+import android.graphics.Typeface
+import android.os.Bundle
+import android.widget.EditText
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.core.content.edit
+import androidx.fragment.app.DialogFragment
+import com.github.ajalt.timberkt.Timber.tag
+import com.github.michaelbull.result.fold
+import com.github.michaelbull.result.getOr
+import com.github.michaelbull.result.runCatching
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.databinding.FragmentXkpwgenBinding
+import dev.msfjarvis.aps.util.pwgenxkpwd.CapsType
+import dev.msfjarvis.aps.util.pwgenxkpwd.PasswordBuilder
+import dev.msfjarvis.aps.util.extensions.getString
+
+/** A placeholder fragment containing a simple view. */
+class XkPasswordGeneratorDialogFragment : DialogFragment() {
+
+ private lateinit var prefs: SharedPreferences
+ private lateinit var binding: FragmentXkpwgenBinding
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val builder = MaterialAlertDialogBuilder(requireContext())
+ val callingActivity = requireActivity()
+ val inflater = callingActivity.layoutInflater
+ binding = FragmentXkpwgenBinding.inflate(inflater)
+
+ val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf")
+
+ builder.setView(binding.root)
+
+ prefs = callingActivity.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
+
+ val previousStoredCapStyle: String = runCatching {
+ prefs.getString(PREF_KEY_CAPITALS_STYLE)!!
+ }.getOr(DEFAULT_CAPS_STYLE)
+
+ val lastCapitalsStyleIndex: Int = runCatching {
+ CapsType.valueOf(previousStoredCapStyle).ordinal
+ }.getOr(DEFAULT_CAPS_INDEX)
+ binding.xkCapType.setSelection(lastCapitalsStyleIndex)
+ binding.xkNumWords.setText(prefs.getString(PREF_KEY_NUM_WORDS, DEFAULT_NUMBER_OF_WORDS))
+
+ binding.xkSeparator.setText(prefs.getString(PREF_KEY_SEPARATOR, DEFAULT_WORD_SEPARATOR))
+ binding.xkNumberSymbolMask.setText(prefs.getString(PREF_KEY_EXTRA_SYMBOLS_MASK, DEFAULT_EXTRA_SYMBOLS_MASK))
+
+ binding.xkPasswordText.typeface = monoTypeface
+
+ builder.setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ ->
+ setPreferences()
+ val edit = callingActivity.findViewById<EditText>(R.id.password)
+ edit.setText(binding.xkPasswordText.text)
+ }
+
+ // flip neutral and negative buttons
+ builder.setNeutralButton(resources.getString(R.string.dialog_cancel)) { _, _ -> }
+ builder.setNegativeButton(resources.getString(R.string.pwgen_generate), null)
+
+ val dialog = builder.setTitle(this.resources.getString(R.string.xkpwgen_title)).create()
+
+ dialog.setOnShowListener {
+ setPreferences()
+ makeAndSetPassword(binding.xkPasswordText)
+
+ dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener {
+ setPreferences()
+ makeAndSetPassword(binding.xkPasswordText)
+ }
+ }
+ return dialog
+ }
+
+ private fun makeAndSetPassword(passwordText: AppCompatTextView) {
+ PasswordBuilder(requireContext())
+ .setNumberOfWords(Integer.valueOf(binding.xkNumWords.text.toString()))
+ .setMinimumWordLength(DEFAULT_MIN_WORD_LENGTH)
+ .setMaximumWordLength(DEFAULT_MAX_WORD_LENGTH)
+ .setSeparator(binding.xkSeparator.text.toString())
+ .appendNumbers(binding.xkNumberSymbolMask.text!!.count { c -> c == EXTRA_CHAR_PLACEHOLDER_DIGIT })
+ .appendSymbols(binding.xkNumberSymbolMask.text!!.count { c -> c == EXTRA_CHAR_PLACEHOLDER_SYMBOL })
+ .setCapitalization(CapsType.valueOf(binding.xkCapType.selectedItem.toString())).create()
+ .fold(
+ success = { passwordText.text = it },
+ failure = { e ->
+ Toast.makeText(requireActivity(), e.message, Toast.LENGTH_SHORT).show()
+ tag("xkpw").e(e, "failure generating xkpasswd")
+ passwordText.text = FALLBACK_ERROR_PASS
+ },
+ )
+ }
+
+ private fun setPreferences() {
+ prefs.edit {
+ putString(PREF_KEY_CAPITALS_STYLE, binding.xkCapType.selectedItem.toString())
+ putString(PREF_KEY_NUM_WORDS, binding.xkNumWords.text.toString())
+ putString(PREF_KEY_SEPARATOR, binding.xkSeparator.text.toString())
+ putString(PREF_KEY_EXTRA_SYMBOLS_MASK, binding.xkNumberSymbolMask.text.toString())
+ }
+ }
+
+ companion object {
+
+ const val PREF_KEY_CAPITALS_STYLE = "pref_key_capitals_style"
+ const val PREF_KEY_NUM_WORDS = "pref_key_num_words"
+ const val PREF_KEY_SEPARATOR = "pref_key_separator"
+ const val PREF_KEY_EXTRA_SYMBOLS_MASK = "pref_key_xkpwgen_extra_symbols_mask"
+ val DEFAULT_CAPS_STYLE = CapsType.Sentence.name
+ val DEFAULT_CAPS_INDEX = CapsType.Sentence.ordinal
+ const val DEFAULT_NUMBER_OF_WORDS = "3"
+ const val DEFAULT_WORD_SEPARATOR = "."
+ const val DEFAULT_EXTRA_SYMBOLS_MASK = "ds"
+ const val DEFAULT_MIN_WORD_LENGTH = 3
+ const val DEFAULT_MAX_WORD_LENGTH = 9
+ const val FALLBACK_ERROR_PASS = "42"
+ const val EXTRA_CHAR_PLACEHOLDER_DIGIT = 'd'
+ const val EXTRA_CHAR_PLACEHOLDER_SYMBOL = 's'
+ }
+}