mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'feature/Mnemonics' into develop
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
KeePassDX(3.4.0)
|
||||
* Show visual password strength indicator with entropy #631 #869
|
||||
* Dynamically save password generator configuration #618 #696
|
||||
* Add advanced password filters #1052
|
||||
* Add editable chars fields #539
|
||||
* Add color for special password chars #454
|
||||
* Passphrase implementation #218
|
||||
|
||||
KeePassDX(3.3.3)
|
||||
* Fix shared otpauth link if database not open #1274
|
||||
* Ellipsize attachment name #1253
|
||||
|
||||
@@ -127,6 +127,8 @@ dependencies {
|
||||
// Apache Commons
|
||||
implementation 'commons-io:commons-io:2.8.0'
|
||||
implementation 'commons-codec:commons-codec:1.15'
|
||||
// Password generator
|
||||
implementation 'me.gosimple:nbvcxz:1.5.0'
|
||||
// Encrypt lib
|
||||
implementation project(path: ':crypto')
|
||||
// Icon pack
|
||||
|
||||
@@ -131,6 +131,9 @@
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.IconPickerActivity"
|
||||
android:configChanges="keyboardHidden" />
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.KeyGeneratorActivity"
|
||||
android:configChanges="keyboardHidden" />
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.ImageViewerActivity"
|
||||
android:configChanges="keyboardHidden" />
|
||||
|
||||
@@ -58,9 +58,12 @@ import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.template.*
|
||||
import com.kunzisoft.keepass.database.element.template.Template
|
||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
||||
import com.kunzisoft.keepass.model.*
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.services.AttachmentFileNotificationService
|
||||
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
|
||||
@@ -78,11 +81,9 @@ import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
|
||||
import org.joda.time.DateTime
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class EntryEditActivity : DatabaseLockActivity(),
|
||||
EntryCustomFieldDialogFragment.EntryCustomFieldListener,
|
||||
GeneratePasswordDialogFragment.GeneratePasswordListener,
|
||||
SetOTPDialogFragment.CreateOtpListener,
|
||||
DatePickerDialog.OnDateSetListener,
|
||||
TimePickerDialog.OnTimeSetListener,
|
||||
@@ -119,6 +120,20 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
mEntryEditViewModel.selectIcon(icon)
|
||||
}
|
||||
|
||||
private var mPasswordField: Field? = null
|
||||
private var mKeyGeneratorResultLauncher = KeyGeneratorActivity.registerForGeneratedKeyResult(this) { keyGenerated ->
|
||||
keyGenerated?.let {
|
||||
mPasswordField?.let {
|
||||
it.protectedValue.stringValue = keyGenerated
|
||||
mEntryEditViewModel.selectPassword(it)
|
||||
}
|
||||
}
|
||||
mPasswordField = null
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
performedNextEducation()
|
||||
}
|
||||
}
|
||||
|
||||
// To ask data lost only one time
|
||||
private var backPressedAlreadyApproved = false
|
||||
|
||||
@@ -268,9 +283,8 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
}
|
||||
|
||||
mEntryEditViewModel.requestPasswordSelection.observe(this) { passwordField ->
|
||||
GeneratePasswordDialogFragment
|
||||
.getInstance(passwordField)
|
||||
.show(supportFragmentManager, "PasswordGeneratorFragment")
|
||||
mPasswordField = passwordField
|
||||
KeyGeneratorActivity.launch(this, mKeyGeneratorResultLauncher)
|
||||
}
|
||||
|
||||
mEntryEditViewModel.requestCustomFieldEdition.observe(this) { field ->
|
||||
@@ -656,17 +670,6 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
mEntryEditViewModel.selectTime(hours, minutes)
|
||||
}
|
||||
|
||||
override fun acceptPassword(passwordField: Field) {
|
||||
mEntryEditViewModel.selectPassword(passwordField)
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
performedNextEducation()
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelPassword(passwordField: Field) {
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
onApprovedBackPressed {
|
||||
super@EntryEditActivity.onBackPressed()
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.commit
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.fragments.KeyGeneratorFragment
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.updateLockPaddingLeft
|
||||
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
|
||||
|
||||
class KeyGeneratorActivity : DatabaseLockActivity() {
|
||||
|
||||
private lateinit var toolbar: Toolbar
|
||||
private lateinit var coordinatorLayout: CoordinatorLayout
|
||||
private lateinit var validationButton: View
|
||||
private var lockView: View? = null
|
||||
|
||||
private val keyGeneratorViewModel: KeyGeneratorViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_key_generator)
|
||||
|
||||
toolbar = findViewById(R.id.toolbar)
|
||||
toolbar.title = " "
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
coordinatorLayout = findViewById(R.id.key_generator_coordinator)
|
||||
|
||||
lockView = findViewById(R.id.lock_button)
|
||||
lockView?.setOnClickListener {
|
||||
lockAndExit()
|
||||
}
|
||||
|
||||
validationButton = findViewById(R.id.key_generator_validation)
|
||||
validationButton.setOnClickListener {
|
||||
keyGeneratorViewModel.validateKeyGenerated()
|
||||
}
|
||||
|
||||
supportFragmentManager.commit {
|
||||
replace(R.id.key_generator_fragment, KeyGeneratorFragment.getInstance(
|
||||
// Default selection tab
|
||||
KeyGeneratorFragment.KeyGeneratorTab.PASSWORD
|
||||
), KEY_GENERATED_FRAGMENT_TAG
|
||||
)
|
||||
}
|
||||
|
||||
keyGeneratorViewModel.keyGenerated.observe(this) { keyGenerated ->
|
||||
setResult(Activity.RESULT_OK, Intent().apply {
|
||||
putExtra(KEY_GENERATED, keyGenerated)
|
||||
})
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun viewToInvalidateTimeout(): View? {
|
||||
return findViewById<ViewGroup>(R.id.key_generator_container)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// Show the lock button
|
||||
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
// Padding if lock button visible
|
||||
toolbar.updateLockPaddingLeft()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
menuInflater.inflate(R.menu.key_generator, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
}
|
||||
R.id.menu_generate -> {
|
||||
keyGeneratorViewModel.requireKeyGeneration()
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
setResult(Activity.RESULT_CANCELED, Intent())
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_GENERATED = "KEY_GENERATED"
|
||||
private const val KEY_GENERATED_FRAGMENT_TAG = "KEY_GENERATED_FRAGMENT_TAG"
|
||||
|
||||
fun registerForGeneratedKeyResult(activity: FragmentActivity,
|
||||
keyGeneratedListener: (String?) -> Unit): ActivityResultLauncher<Intent> {
|
||||
return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
keyGeneratedListener.invoke(
|
||||
result.data?.getStringExtra(KEY_GENERATED)
|
||||
)
|
||||
} else {
|
||||
keyGeneratedListener.invoke(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun launch(context: FragmentActivity,
|
||||
resultLauncher: ActivityResultLauncher<Intent>) {
|
||||
// Create an instance to return the picker icon
|
||||
resultLauncher.launch(
|
||||
Intent(context, KeyGeneratorActivity::class.java)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Field
|
||||
import com.kunzisoft.keepass.password.PasswordGenerator
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||
import com.kunzisoft.keepass.view.applyFontVisibility
|
||||
|
||||
class GeneratePasswordDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var mListener: GeneratePasswordListener? = null
|
||||
|
||||
private var root: View? = null
|
||||
private var lengthTextView: EditText? = null
|
||||
private var passwordInputLayoutView: TextInputLayout? = null
|
||||
private var passwordView: EditText? = null
|
||||
|
||||
private var mPasswordField: Field? = null
|
||||
|
||||
private var uppercaseBox: CompoundButton? = null
|
||||
private var lowercaseBox: CompoundButton? = null
|
||||
private var digitsBox: CompoundButton? = null
|
||||
private var minusBox: CompoundButton? = null
|
||||
private var underlineBox: CompoundButton? = null
|
||||
private var spaceBox: CompoundButton? = null
|
||||
private var specialsBox: CompoundButton? = null
|
||||
private var bracketsBox: CompoundButton? = null
|
||||
private var extendedBox: CompoundButton? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
mListener = context as GeneratePasswordListener
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + GeneratePasswordListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
val inflater = activity.layoutInflater
|
||||
root = inflater.inflate(R.layout.fragment_generate_password, null)
|
||||
|
||||
passwordInputLayoutView = root?.findViewById(R.id.password_input_layout)
|
||||
passwordView = root?.findViewById(R.id.password)
|
||||
passwordView?.applyFontVisibility()
|
||||
val passwordCopyView: ImageView? = root?.findViewById(R.id.password_copy_button)
|
||||
passwordCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(activity))
|
||||
View.VISIBLE else View.GONE
|
||||
val clipboardHelper = ClipboardHelper(activity)
|
||||
passwordCopyView?.setOnClickListener {
|
||||
clipboardHelper.timeoutCopyToClipboard(passwordView!!.text.toString(),
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_password)))
|
||||
}
|
||||
|
||||
lengthTextView = root?.findViewById(R.id.length)
|
||||
|
||||
uppercaseBox = root?.findViewById(R.id.cb_uppercase)
|
||||
lowercaseBox = root?.findViewById(R.id.cb_lowercase)
|
||||
digitsBox = root?.findViewById(R.id.cb_digits)
|
||||
minusBox = root?.findViewById(R.id.cb_minus)
|
||||
underlineBox = root?.findViewById(R.id.cb_underline)
|
||||
spaceBox = root?.findViewById(R.id.cb_space)
|
||||
specialsBox = root?.findViewById(R.id.cb_specials)
|
||||
bracketsBox = root?.findViewById(R.id.cb_brackets)
|
||||
extendedBox = root?.findViewById(R.id.cb_extended)
|
||||
|
||||
mPasswordField = arguments?.getParcelable(KEY_PASSWORD_FIELD)
|
||||
|
||||
assignDefaultCharacters()
|
||||
|
||||
val seekBar = root?.findViewById<SeekBar>(R.id.seekbar_length)
|
||||
seekBar?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
lengthTextView?.setText(progress.toString())
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
|
||||
context?.let { context ->
|
||||
seekBar?.progress = PreferencesUtil.getDefaultPasswordLength(context)
|
||||
}
|
||||
|
||||
root?.findViewById<Button>(R.id.generate_password_button)
|
||||
?.setOnClickListener { fillPassword() }
|
||||
|
||||
builder.setView(root)
|
||||
.setPositiveButton(R.string.accept) { _, _ ->
|
||||
mPasswordField?.let { passwordField ->
|
||||
passwordView?.text?.toString()?.let { passwordValue ->
|
||||
passwordField.protectedValue.stringValue = passwordValue
|
||||
}
|
||||
mListener?.acceptPassword(passwordField)
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
mPasswordField?.let { passwordField ->
|
||||
mListener?.cancelPassword(passwordField)
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
// Pre-populate a password to possibly save the user a few clicks
|
||||
fillPassword()
|
||||
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun assignDefaultCharacters() {
|
||||
uppercaseBox?.isChecked = false
|
||||
lowercaseBox?.isChecked = false
|
||||
digitsBox?.isChecked = false
|
||||
minusBox?.isChecked = false
|
||||
underlineBox?.isChecked = false
|
||||
spaceBox?.isChecked = false
|
||||
specialsBox?.isChecked = false
|
||||
bracketsBox?.isChecked = false
|
||||
extendedBox?.isChecked = false
|
||||
|
||||
context?.let { context ->
|
||||
PreferencesUtil.getDefaultPasswordCharacters(context)?.let { charSet ->
|
||||
for (passwordChar in charSet) {
|
||||
when (passwordChar) {
|
||||
getString(R.string.value_password_uppercase) -> uppercaseBox?.isChecked = true
|
||||
getString(R.string.value_password_lowercase) -> lowercaseBox?.isChecked = true
|
||||
getString(R.string.value_password_digits) -> digitsBox?.isChecked = true
|
||||
getString(R.string.value_password_minus) -> minusBox?.isChecked = true
|
||||
getString(R.string.value_password_underline) -> underlineBox?.isChecked = true
|
||||
getString(R.string.value_password_space) -> spaceBox?.isChecked = true
|
||||
getString(R.string.value_password_special) -> specialsBox?.isChecked = true
|
||||
getString(R.string.value_password_brackets) -> bracketsBox?.isChecked = true
|
||||
getString(R.string.value_password_extended) -> extendedBox?.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fillPassword() {
|
||||
root?.findViewById<EditText>(R.id.password)?.setText(generatePassword())
|
||||
}
|
||||
|
||||
fun generatePassword(): String {
|
||||
var password = ""
|
||||
try {
|
||||
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
|
||||
password = PasswordGenerator(resources).generatePassword(length,
|
||||
uppercaseBox?.isChecked == true,
|
||||
lowercaseBox?.isChecked == true,
|
||||
digitsBox?.isChecked == true,
|
||||
minusBox?.isChecked == true,
|
||||
underlineBox?.isChecked == true,
|
||||
spaceBox?.isChecked == true,
|
||||
specialsBox?.isChecked == true,
|
||||
bracketsBox?.isChecked == true,
|
||||
extendedBox?.isChecked == true)
|
||||
passwordInputLayoutView?.error = null
|
||||
} catch (e: NumberFormatException) {
|
||||
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
passwordInputLayoutView?.error = e.message
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
interface GeneratePasswordListener {
|
||||
fun acceptPassword(passwordField: Field)
|
||||
fun cancelPassword(passwordField: Field)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_PASSWORD_FIELD = "KEY_PASSWORD_FIELD"
|
||||
|
||||
fun getInstance(field: Field): GeneratePasswordDialogFragment {
|
||||
return GeneratePasswordDialogFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(KEY_PASSWORD_FIELD, field)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,11 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.password.PasswordEntropy
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||
import com.kunzisoft.keepass.view.PassKeyView
|
||||
import com.kunzisoft.keepass.view.applyFontVisibility
|
||||
|
||||
class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
@@ -48,8 +51,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var passwordCheckBox: CompoundButton? = null
|
||||
|
||||
private var passwordTextInputLayout: TextInputLayout? = null
|
||||
private var passwordView: TextView? = null
|
||||
private var passKeyView: PassKeyView? = null
|
||||
private var passwordRepeatTextInputLayout: TextInputLayout? = null
|
||||
private var passwordRepeatView: TextView? = null
|
||||
|
||||
@@ -59,6 +61,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
private var mListener: AssignMainCredentialDialogListener? = null
|
||||
|
||||
private var mExternalFileHelper: ExternalFileHelper? = null
|
||||
private var mPasswordEntropyCalculator: PasswordEntropy? = null
|
||||
|
||||
private var mEmptyPasswordConfirmationDialog: AlertDialog? = null
|
||||
private var mNoKeyConfirmationDialog: AlertDialog? = null
|
||||
@@ -100,6 +103,13 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Create the password entropy object
|
||||
mPasswordEntropyCalculator = PasswordEntropy()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
activity?.let { activity ->
|
||||
|
||||
@@ -123,10 +133,10 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
}
|
||||
|
||||
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
|
||||
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
|
||||
passwordView = rootView?.findViewById(R.id.pass_password)
|
||||
passKeyView = rootView?.findViewById(R.id.password_view)
|
||||
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
|
||||
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
|
||||
passwordRepeatView = rootView?.findViewById(R.id.password_confirmation)
|
||||
passwordRepeatView?.applyFontVisibility()
|
||||
|
||||
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
|
||||
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
|
||||
@@ -162,7 +172,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
if (allowNoMasterKey)
|
||||
showNoKeyConfirmationDialog()
|
||||
else {
|
||||
passwordTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
|
||||
passwordRepeatTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
|
||||
}
|
||||
}
|
||||
if (!error) {
|
||||
@@ -194,22 +204,22 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
super.onResume()
|
||||
|
||||
// To check checkboxes if a text is present
|
||||
passwordView?.addTextChangedListener(passwordTextWatcher)
|
||||
passKeyView?.addTextChangedListener(passwordTextWatcher)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
passwordView?.removeTextChangedListener(passwordTextWatcher)
|
||||
passKeyView?.removeTextChangedListener(passwordTextWatcher)
|
||||
}
|
||||
|
||||
private fun verifyPassword(): Boolean {
|
||||
var error = false
|
||||
if (passwordCheckBox != null
|
||||
&& passwordCheckBox!!.isChecked
|
||||
&& passwordView != null
|
||||
&& passKeyView != null
|
||||
&& passwordRepeatView != null) {
|
||||
mMasterPassword = passwordView!!.text.toString()
|
||||
mMasterPassword = passKeyView!!.passwordString
|
||||
val confPassword = passwordRepeatView!!.text.toString()
|
||||
|
||||
// Verify that passwords match
|
||||
|
||||
@@ -26,14 +26,14 @@ class IconPickerFragment : DatabaseFragment() {
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_icon_picker, container, false)
|
||||
return inflater.inflate(R.layout.fragment_tabs_pagination, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewPager = view.findViewById(R.id.icon_picker_pager)
|
||||
tabLayout = view.findViewById(R.id.icon_picker_tabs)
|
||||
viewPager = view.findViewById(R.id.tabs_view_pager)
|
||||
tabLayout = view.findViewById(R.id.tabs_layout)
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(view)
|
||||
|
||||
arguments?.apply {
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.adapters.KeyGeneratorPagerAdapter
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
|
||||
|
||||
class KeyGeneratorFragment : DatabaseFragment() {
|
||||
|
||||
private var keyGeneratorPagerAdapter: KeyGeneratorPagerAdapter? = null
|
||||
private lateinit var viewPager: ViewPager2
|
||||
private lateinit var tabLayout: TabLayout
|
||||
|
||||
private val mKeyGeneratorViewModel: KeyGeneratorViewModel by activityViewModels()
|
||||
|
||||
private var mSelectedTab = KeyGeneratorTab.PASSWORD
|
||||
private var mOnPageChangeCallback: ViewPager2.OnPageChangeCallback = object:
|
||||
ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
mSelectedTab = KeyGeneratorTab.getKeyGeneratorTabByPosition(position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_tabs_pagination, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
keyGeneratorPagerAdapter = KeyGeneratorPagerAdapter(this, )
|
||||
viewPager = view.findViewById(R.id.tabs_view_pager)
|
||||
tabLayout = view.findViewById(R.id.tabs_layout)
|
||||
viewPager.adapter = keyGeneratorPagerAdapter
|
||||
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||
tab.text = getString(KeyGeneratorTab.getKeyGeneratorTabByPosition(position).stringId)
|
||||
}.attach()
|
||||
viewPager.registerOnPageChangeCallback(mOnPageChangeCallback)
|
||||
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(view)
|
||||
|
||||
arguments?.apply {
|
||||
if (containsKey(PASSWORD_TAB_ARG)) {
|
||||
viewPager.currentItem = getInt(PASSWORD_TAB_ARG)
|
||||
}
|
||||
remove(PASSWORD_TAB_ARG)
|
||||
}
|
||||
|
||||
mKeyGeneratorViewModel.requireKeyGeneration.observe(viewLifecycleOwner) {
|
||||
when (mSelectedTab) {
|
||||
KeyGeneratorTab.PASSWORD -> {
|
||||
mKeyGeneratorViewModel.requirePasswordGeneration()
|
||||
}
|
||||
KeyGeneratorTab.PASSPHRASE -> {
|
||||
mKeyGeneratorViewModel.requirePassphraseGeneration()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mKeyGeneratorViewModel.keyGeneratedValidated.observe(viewLifecycleOwner) {
|
||||
when (mSelectedTab) {
|
||||
KeyGeneratorTab.PASSWORD -> {
|
||||
mKeyGeneratorViewModel.validatePasswordGenerated()
|
||||
}
|
||||
KeyGeneratorTab.PASSPHRASE -> {
|
||||
mKeyGeneratorViewModel.validatePassphraseGenerated()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
viewPager.unregisterOnPageChangeCallback(mOnPageChangeCallback)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
enum class KeyGeneratorTab(@StringRes val stringId: Int) {
|
||||
PASSWORD(R.string.password), PASSPHRASE(R.string.passphrase);
|
||||
|
||||
companion object {
|
||||
fun getKeyGeneratorTabByPosition(position: Int): KeyGeneratorTab {
|
||||
return when (position) {
|
||||
0 -> PASSWORD
|
||||
else -> PASSPHRASE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PASSWORD_TAB_ARG = "PASSWORD_TAB_ARG"
|
||||
|
||||
fun getInstance(keyGeneratorTab: KeyGeneratorTab): KeyGeneratorFragment {
|
||||
val fragment = KeyGeneratorFragment()
|
||||
fragment.arguments = Bundle().apply {
|
||||
putInt(PASSWORD_TAB_ARG, keyGeneratorTab.ordinal)
|
||||
}
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.password.PassphraseGenerator
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||
import com.kunzisoft.keepass.view.PassKeyView
|
||||
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
|
||||
|
||||
class PassphraseGeneratorFragment : DatabaseFragment() {
|
||||
|
||||
private lateinit var passKeyView: PassKeyView
|
||||
|
||||
private lateinit var sliderWordCount: Slider
|
||||
private lateinit var wordCountText: EditText
|
||||
private lateinit var charactersCountText: TextView
|
||||
private lateinit var wordSeparator: EditText
|
||||
private lateinit var wordCaseSpinner: Spinner
|
||||
|
||||
private var minSliderWordCount: Int = 0
|
||||
private var maxSliderWordCount: Int = 0
|
||||
private var wordCaseAdapter: ArrayAdapter<String>? = null
|
||||
|
||||
private val mKeyGeneratorViewModel: KeyGeneratorViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View {
|
||||
return inflater.inflate(R.layout.fragment_generate_passphrase, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
passKeyView = view.findViewById(R.id.passphrase_view)
|
||||
val passphraseCopyView: ImageView? = view.findViewById(R.id.passphrase_copy_button)
|
||||
sliderWordCount = view.findViewById(R.id.slider_word_count)
|
||||
wordCountText = view.findViewById(R.id.word_count)
|
||||
charactersCountText = view.findViewById(R.id.character_count)
|
||||
wordSeparator = view.findViewById(R.id.word_separator)
|
||||
wordCaseSpinner = view.findViewById(R.id.word_case)
|
||||
|
||||
minSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_min)
|
||||
maxSliderWordCount = resources.getInteger(R.integer.passphrase_generator_word_count_max)
|
||||
|
||||
contextThemed?.let { context ->
|
||||
passphraseCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context))
|
||||
View.VISIBLE else View.GONE
|
||||
val clipboardHelper = ClipboardHelper(context)
|
||||
passphraseCopyView?.setOnClickListener {
|
||||
clipboardHelper.timeoutCopyToClipboard(passKeyView.passwordString,
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_password)))
|
||||
}
|
||||
|
||||
wordCaseAdapter = ArrayAdapter(context,
|
||||
android.R.layout.simple_spinner_item, resources.getStringArray(R.array.word_case_array)).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
wordCaseSpinner.adapter = wordCaseAdapter
|
||||
}
|
||||
|
||||
loadSettings()
|
||||
|
||||
var listenSlider = true
|
||||
var listenEditText = true
|
||||
sliderWordCount.addOnChangeListener { _, value, _ ->
|
||||
try {
|
||||
listenEditText = false
|
||||
if (listenSlider) {
|
||||
wordCountText.setText(value.toInt().toString())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to set the word count value", e)
|
||||
} finally {
|
||||
listenEditText = true
|
||||
}
|
||||
}
|
||||
sliderWordCount.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
|
||||
// TODO upgrade material-components lib
|
||||
// https://stackoverflow.com/questions/70873160/material-slider-onslidertouchlisteners-methods-can-only-be-called-from-within-t
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onStartTrackingTouch(slider: Slider) {}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onStopTrackingTouch(slider: Slider) {
|
||||
generatePassphrase()
|
||||
}
|
||||
})
|
||||
wordCountText.doOnTextChanged { _, _, _, _ ->
|
||||
if (listenEditText) {
|
||||
try {
|
||||
listenSlider = false
|
||||
setSliderValue(getWordCount())
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to get the word count value", e)
|
||||
} finally {
|
||||
listenSlider = true
|
||||
generatePassphrase()
|
||||
}
|
||||
}
|
||||
}
|
||||
wordSeparator.doOnTextChanged { _, _, _, _ ->
|
||||
generatePassphrase()
|
||||
}
|
||||
wordCaseSpinner.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
generatePassphrase()
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
}
|
||||
|
||||
generatePassphrase()
|
||||
|
||||
mKeyGeneratorViewModel.passphraseGeneratedValidated.observe(viewLifecycleOwner) {
|
||||
mKeyGeneratorViewModel.setKeyGenerated(passKeyView.passwordString)
|
||||
}
|
||||
|
||||
mKeyGeneratorViewModel.requirePassphraseGeneration.observe(viewLifecycleOwner) {
|
||||
generatePassphrase()
|
||||
}
|
||||
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(view)
|
||||
}
|
||||
|
||||
private fun getWordCount(): Int {
|
||||
return try {
|
||||
Integer.valueOf(wordCountText.text.toString())
|
||||
} catch (numberException: NumberFormatException) {
|
||||
minSliderWordCount
|
||||
}
|
||||
}
|
||||
|
||||
private fun setWordCount(wordCount: Int) {
|
||||
setSliderValue(wordCount)
|
||||
wordCountText.setText(wordCount.toString())
|
||||
}
|
||||
|
||||
private fun setSliderValue(value: Int) {
|
||||
when {
|
||||
value < minSliderWordCount -> {
|
||||
sliderWordCount.value = minSliderWordCount.toFloat()
|
||||
}
|
||||
value > maxSliderWordCount -> {
|
||||
sliderWordCount.value = maxSliderWordCount.toFloat()
|
||||
}
|
||||
else -> {
|
||||
sliderWordCount.value = value.toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWordSeparator(): String {
|
||||
return wordSeparator.text.toString().ifEmpty { " " }
|
||||
}
|
||||
|
||||
private fun getWordCase(): PassphraseGenerator.WordCase {
|
||||
var wordCase = PassphraseGenerator.WordCase.LOWER_CASE
|
||||
try {
|
||||
wordCase = PassphraseGenerator.WordCase.getByOrdinal(wordCaseSpinner.selectedItemPosition)
|
||||
} catch (caseException: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve the word case", caseException)
|
||||
}
|
||||
return wordCase
|
||||
}
|
||||
|
||||
private fun setWordCase(wordCase: PassphraseGenerator.WordCase) {
|
||||
wordCaseSpinner.setSelection(wordCase.ordinal)
|
||||
}
|
||||
|
||||
private fun getSeparator(): String {
|
||||
return wordSeparator.text?.toString() ?: ""
|
||||
}
|
||||
|
||||
private fun setSeparator(separator: String) {
|
||||
wordSeparator.setText(separator)
|
||||
}
|
||||
|
||||
private fun generatePassphrase() {
|
||||
var passphrase = ""
|
||||
try {
|
||||
passphrase = PassphraseGenerator().generatePassphrase(
|
||||
getWordCount(),
|
||||
getWordSeparator(),
|
||||
getWordCase())
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to generate a passphrase", e)
|
||||
}
|
||||
passKeyView.passwordString = passphrase
|
||||
charactersCountText.text = getString(R.string.character_count, passphrase.length)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
saveSettings()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun saveSettings() {
|
||||
context?.let { context ->
|
||||
PreferencesUtil.setDefaultPassphraseWordCount(context, getWordCount())
|
||||
PreferencesUtil.setDefaultPassphraseWordCase(context, getWordCase())
|
||||
PreferencesUtil.setDefaultPassphraseSeparator(context, getSeparator())
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadSettings() {
|
||||
context?.let { context ->
|
||||
setWordCount(PreferencesUtil.getDefaultPassphraseWordCount(context))
|
||||
setWordCase(PreferencesUtil.getDefaultPassphraseWordCase(context))
|
||||
setSeparator(PreferencesUtil.getDefaultPassphraseSeparator(context))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PassphraseGnrtrFrgmt"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.password.PasswordGenerator
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||
import com.kunzisoft.keepass.view.PassKeyView
|
||||
import com.kunzisoft.keepass.viewmodels.KeyGeneratorViewModel
|
||||
|
||||
class PasswordGeneratorFragment : DatabaseFragment() {
|
||||
|
||||
private lateinit var passKeyView: PassKeyView
|
||||
|
||||
private lateinit var sliderLength: Slider
|
||||
private lateinit var lengthEditView: EditText
|
||||
|
||||
private lateinit var uppercaseCompound: CompoundButton
|
||||
private lateinit var lowercaseCompound: CompoundButton
|
||||
private lateinit var digitsCompound: CompoundButton
|
||||
private lateinit var minusCompound: CompoundButton
|
||||
private lateinit var underlineCompound: CompoundButton
|
||||
private lateinit var spaceCompound: CompoundButton
|
||||
private lateinit var specialsCompound: CompoundButton
|
||||
private lateinit var bracketsCompound: CompoundButton
|
||||
private lateinit var extendedCompound: CompoundButton
|
||||
private lateinit var considerCharsEditText: EditText
|
||||
private lateinit var ignoreCharsEditText: EditText
|
||||
private lateinit var atLeastOneCompound: CompoundButton
|
||||
private lateinit var excludeAmbiguousCompound: CompoundButton
|
||||
|
||||
private var minLengthSlider: Int = 0
|
||||
private var maxLengthSlider: Int = 0
|
||||
|
||||
private val mKeyGeneratorViewModel: KeyGeneratorViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View {
|
||||
return inflater.inflate(R.layout.fragment_generate_password, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
passKeyView = view.findViewById(R.id.password_view)
|
||||
val passwordCopyView: ImageView? = view.findViewById(R.id.password_copy_button)
|
||||
|
||||
sliderLength = view.findViewById(R.id.slider_length)
|
||||
lengthEditView = view.findViewById(R.id.length)
|
||||
|
||||
uppercaseCompound = view.findViewById(R.id.upperCase_filter)
|
||||
lowercaseCompound = view.findViewById(R.id.lowerCase_filter)
|
||||
digitsCompound = view.findViewById(R.id.digits_filter)
|
||||
minusCompound = view.findViewById(R.id.minus_filter)
|
||||
underlineCompound = view.findViewById(R.id.underline_filter)
|
||||
spaceCompound = view.findViewById(R.id.space_filter)
|
||||
specialsCompound = view.findViewById(R.id.special_filter)
|
||||
bracketsCompound = view.findViewById(R.id.brackets_filter)
|
||||
extendedCompound = view.findViewById(R.id.extendedASCII_filter)
|
||||
considerCharsEditText = view.findViewById(R.id.consider_chars_filter)
|
||||
ignoreCharsEditText = view.findViewById(R.id.ignore_chars_filter)
|
||||
atLeastOneCompound = view.findViewById(R.id.atLeastOne_filter)
|
||||
excludeAmbiguousCompound = view.findViewById(R.id.excludeAmbiguous_filter)
|
||||
|
||||
contextThemed?.let { context ->
|
||||
passwordCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(context))
|
||||
View.VISIBLE else View.GONE
|
||||
val clipboardHelper = ClipboardHelper(context)
|
||||
passwordCopyView?.setOnClickListener {
|
||||
clipboardHelper.timeoutCopyToClipboard(passKeyView.passwordString,
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_password)))
|
||||
}
|
||||
}
|
||||
|
||||
minLengthSlider = resources.getInteger(R.integer.password_generator_length_min)
|
||||
maxLengthSlider = resources.getInteger(R.integer.password_generator_length_max)
|
||||
|
||||
loadSettings()
|
||||
|
||||
uppercaseCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
lowercaseCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
digitsCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
minusCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
underlineCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
spaceCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
specialsCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
bracketsCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
extendedCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
considerCharsEditText.doOnTextChanged { _, _, _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
ignoreCharsEditText.doOnTextChanged { _, _, _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
atLeastOneCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
excludeAmbiguousCompound.setOnCheckedChangeListener { _, _ ->
|
||||
generatePassword()
|
||||
}
|
||||
|
||||
var listenSlider = true
|
||||
var listenEditText = true
|
||||
sliderLength.addOnChangeListener { _, value, _ ->
|
||||
try {
|
||||
listenEditText = false
|
||||
if (listenSlider) {
|
||||
lengthEditView.setText(value.toInt().toString())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to set the length value", e)
|
||||
} finally {
|
||||
listenEditText = true
|
||||
}
|
||||
}
|
||||
sliderLength.addOnSliderTouchListener(object : Slider.OnSliderTouchListener {
|
||||
// TODO upgrade material-components lib
|
||||
// https://stackoverflow.com/questions/70873160/material-slider-onslidertouchlisteners-methods-can-only-be-called-from-within-t
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onStartTrackingTouch(slider: Slider) {}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onStopTrackingTouch(slider: Slider) {
|
||||
generatePassword()
|
||||
}
|
||||
})
|
||||
lengthEditView.doOnTextChanged { _, _, _, _ ->
|
||||
if (listenEditText) {
|
||||
try {
|
||||
listenSlider = false
|
||||
setSliderValue(getPasswordLength())
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to get the length value", e)
|
||||
} finally {
|
||||
listenSlider = true
|
||||
generatePassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-populate a password to possibly save the user a few clicks
|
||||
generatePassword()
|
||||
|
||||
mKeyGeneratorViewModel.passwordGeneratedValidated.observe(viewLifecycleOwner) {
|
||||
mKeyGeneratorViewModel.setKeyGenerated(passKeyView.passwordString)
|
||||
}
|
||||
|
||||
mKeyGeneratorViewModel.requirePasswordGeneration.observe(viewLifecycleOwner) {
|
||||
generatePassword()
|
||||
}
|
||||
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(view)
|
||||
}
|
||||
|
||||
private fun getPasswordLength(): Int {
|
||||
return try {
|
||||
Integer.valueOf(lengthEditView.text.toString())
|
||||
} catch (numberException: NumberFormatException) {
|
||||
minLengthSlider
|
||||
}
|
||||
}
|
||||
|
||||
private fun setPasswordLength(passwordLength: Int) {
|
||||
setSliderValue(passwordLength)
|
||||
lengthEditView.setText(passwordLength.toString())
|
||||
}
|
||||
|
||||
private fun getOptions(): Set<String> {
|
||||
val optionsSet = mutableSetOf<String>()
|
||||
if (uppercaseCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_uppercase))
|
||||
if (lowercaseCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_lowercase))
|
||||
if (digitsCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_digits))
|
||||
if (minusCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_minus))
|
||||
if (underlineCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_underline))
|
||||
if (spaceCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_space))
|
||||
if (specialsCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_special))
|
||||
if (bracketsCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_brackets))
|
||||
if (extendedCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_extended))
|
||||
if (atLeastOneCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_atLeastOne))
|
||||
if (excludeAmbiguousCompound.isChecked)
|
||||
optionsSet.add(getString(R.string.value_password_excludeAmbiguous))
|
||||
return optionsSet
|
||||
}
|
||||
|
||||
private fun setOptions(options: Set<String>) {
|
||||
uppercaseCompound.isChecked = false
|
||||
lowercaseCompound.isChecked = false
|
||||
digitsCompound.isChecked = false
|
||||
minusCompound.isChecked = false
|
||||
underlineCompound.isChecked = false
|
||||
spaceCompound.isChecked = false
|
||||
specialsCompound.isChecked = false
|
||||
bracketsCompound.isChecked = false
|
||||
extendedCompound.isChecked = false
|
||||
atLeastOneCompound.isChecked = false
|
||||
excludeAmbiguousCompound.isChecked = false
|
||||
for (option in options) {
|
||||
when (option) {
|
||||
getString(R.string.value_password_uppercase) -> uppercaseCompound.isChecked = true
|
||||
getString(R.string.value_password_lowercase) -> lowercaseCompound.isChecked = true
|
||||
getString(R.string.value_password_digits) -> digitsCompound.isChecked = true
|
||||
getString(R.string.value_password_minus) -> minusCompound.isChecked = true
|
||||
getString(R.string.value_password_underline) -> underlineCompound.isChecked = true
|
||||
getString(R.string.value_password_space) -> spaceCompound.isChecked = true
|
||||
getString(R.string.value_password_special) -> specialsCompound.isChecked = true
|
||||
getString(R.string.value_password_brackets) -> bracketsCompound.isChecked = true
|
||||
getString(R.string.value_password_extended) -> extendedCompound.isChecked = true
|
||||
getString(R.string.value_password_atLeastOne) -> atLeastOneCompound.isChecked = true
|
||||
getString(R.string.value_password_excludeAmbiguous) -> excludeAmbiguousCompound.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConsiderChars(): String {
|
||||
return considerCharsEditText.text.toString()
|
||||
}
|
||||
|
||||
private fun setConsiderChars(chars: String) {
|
||||
considerCharsEditText.setText(chars)
|
||||
}
|
||||
|
||||
private fun getIgnoreChars(): String {
|
||||
return ignoreCharsEditText.text.toString()
|
||||
}
|
||||
|
||||
private fun setIgnoreChars(chars: String) {
|
||||
ignoreCharsEditText.setText(chars)
|
||||
}
|
||||
|
||||
private fun generatePassword() {
|
||||
var password = ""
|
||||
try {
|
||||
password = PasswordGenerator(resources).generatePassword(getPasswordLength(),
|
||||
uppercaseCompound.isChecked,
|
||||
lowercaseCompound.isChecked,
|
||||
digitsCompound.isChecked,
|
||||
minusCompound.isChecked,
|
||||
underlineCompound.isChecked,
|
||||
spaceCompound.isChecked,
|
||||
specialsCompound.isChecked,
|
||||
bracketsCompound.isChecked,
|
||||
extendedCompound.isChecked,
|
||||
getConsiderChars(),
|
||||
getIgnoreChars(),
|
||||
atLeastOneCompound.isChecked,
|
||||
excludeAmbiguousCompound.isChecked)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to generate a password", e)
|
||||
}
|
||||
passKeyView.passwordString = password
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
saveSettings()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
private fun saveSettings() {
|
||||
context?.let { context ->
|
||||
PreferencesUtil.setDefaultPasswordOptions(context, getOptions())
|
||||
PreferencesUtil.setDefaultPasswordLength(context, getPasswordLength())
|
||||
PreferencesUtil.setDefaultPasswordConsiderChars(context, getConsiderChars())
|
||||
PreferencesUtil.setDefaultPasswordIgnoreChars(context, getIgnoreChars())
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadSettings() {
|
||||
context?.let { context ->
|
||||
setOptions(PreferencesUtil.getDefaultPasswordOptions(context))
|
||||
setPasswordLength(PreferencesUtil.getDefaultPasswordLength(context))
|
||||
setConsiderChars(PreferencesUtil.getDefaultPasswordConsiderChars(context))
|
||||
setIgnoreChars(PreferencesUtil.getDefaultPasswordIgnoreChars(context))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSliderValue(value: Int) {
|
||||
when {
|
||||
value < minLengthSlider -> {
|
||||
sliderLength.value = minLengthSlider.toFloat()
|
||||
}
|
||||
value > maxLengthSlider -> {
|
||||
sliderLength.value = maxLengthSlider.toFloat()
|
||||
}
|
||||
else -> {
|
||||
sliderLength.value = value.toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PasswordGeneratorFrgmt"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kunzisoft.keepass.adapters
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.kunzisoft.keepass.activities.fragments.PassphraseGeneratorFragment
|
||||
import com.kunzisoft.keepass.activities.fragments.PasswordGeneratorFragment
|
||||
import com.kunzisoft.keepass.activities.fragments.KeyGeneratorFragment
|
||||
|
||||
class KeyGeneratorPagerAdapter(fragment: Fragment)
|
||||
: FragmentStateAdapter(fragment) {
|
||||
|
||||
private val passwordGeneratorFragment = PasswordGeneratorFragment()
|
||||
private val passphraseGeneratorFragment = PassphraseGeneratorFragment()
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return KeyGeneratorFragment.KeyGeneratorTab.values().size
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (KeyGeneratorFragment.KeyGeneratorTab.getKeyGeneratorTabByPosition(position)) {
|
||||
KeyGeneratorFragment.KeyGeneratorTab.PASSWORD -> passwordGeneratorFragment
|
||||
KeyGeneratorFragment.KeyGeneratorTab.PASSPHRASE -> passphraseGeneratorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import kotlinx.coroutines.*
|
||||
*/
|
||||
class IOActionTask<T>(
|
||||
private val action: () -> T ,
|
||||
private val afterActionDatabaseListener: ((T?) -> Unit)? = null) {
|
||||
private val afterActionListener: ((T?) -> Unit)? = null) {
|
||||
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
@@ -42,7 +42,7 @@ class IOActionTask<T>(
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
afterActionDatabaseListener?.invoke(asyncResult.await())
|
||||
afterActionListener?.invoke(asyncResult.await())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ object TemplateField {
|
||||
const val LABEL_SECURE_NOTE = "Secure Note"
|
||||
const val LABEL_MEMBERSHIP = "Membership"
|
||||
|
||||
fun isStandardPasswordName(context: Context, name: String): Boolean {
|
||||
return name.equals(LABEL_PASSWORD, true)
|
||||
|| name == getLocalizedName(context, LABEL_PASSWORD)
|
||||
}
|
||||
|
||||
fun isStandardFieldName(name: String): Boolean {
|
||||
return arrayOf(
|
||||
LABEL_TITLE,
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.password
|
||||
|
||||
import me.gosimple.nbvcxz.resources.Generator
|
||||
|
||||
class PassphraseGenerator {
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun generatePassphrase(wordCount: Int,
|
||||
wordSeparator: String,
|
||||
wordCase: WordCase): String {
|
||||
// From eff_large dictionary
|
||||
return when (wordCase) {
|
||||
WordCase.LOWER_CASE -> {
|
||||
Generator.generatePassphrase(wordSeparator, wordCount)
|
||||
}
|
||||
WordCase.UPPER_CASE -> {
|
||||
applyWordCase(wordCount, wordSeparator) { word ->
|
||||
word.uppercase()
|
||||
}
|
||||
}
|
||||
WordCase.TITLE_CASE -> {
|
||||
applyWordCase(wordCount, wordSeparator) { word ->
|
||||
word.replaceFirstChar { char -> char.uppercaseChar() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyWordCase(wordCount: Int,
|
||||
wordSeparator: String,
|
||||
wordAction: (word: String) -> String): String {
|
||||
val splitWords = Generator.generatePassphrase(TEMP_SPLIT, wordCount).split(TEMP_SPLIT)
|
||||
val stringBuilder = StringBuilder()
|
||||
splitWords.forEach {
|
||||
stringBuilder
|
||||
.append(wordAction(it))
|
||||
.append(wordSeparator)
|
||||
}
|
||||
return stringBuilder.toString().removeSuffix(wordSeparator)
|
||||
}
|
||||
|
||||
enum class WordCase {
|
||||
LOWER_CASE,
|
||||
UPPER_CASE,
|
||||
TITLE_CASE;
|
||||
|
||||
companion object {
|
||||
fun getByOrdinal(position: Int): WordCase {
|
||||
return when (position) {
|
||||
0 -> LOWER_CASE
|
||||
1 -> UPPER_CASE
|
||||
else -> TITLE_CASE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TEMP_SPLIT = "-"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.password
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.app.database.IOActionTask
|
||||
import kotlinx.coroutines.*
|
||||
import me.gosimple.nbvcxz.Nbvcxz
|
||||
import me.gosimple.nbvcxz.resources.Configuration
|
||||
import me.gosimple.nbvcxz.resources.ConfigurationBuilder
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
|
||||
class PasswordEntropy(actionOnInitFinished: (() -> Unit)? = null) {
|
||||
|
||||
private var mPasswordEntropyCalculator: Nbvcxz? = null
|
||||
private var entropyJob: Job? = null
|
||||
|
||||
init {
|
||||
IOActionTask({
|
||||
// Create the password generator object
|
||||
val configuration: Configuration = ConfigurationBuilder()
|
||||
.setLocale(Locale.getDefault())
|
||||
.setMinimumEntropy(80.0)
|
||||
.createConfiguration()
|
||||
mPasswordEntropyCalculator = Nbvcxz(configuration)
|
||||
}, {
|
||||
actionOnInitFinished?.invoke()
|
||||
}).execute()
|
||||
}
|
||||
|
||||
enum class Strength(val color: Int) {
|
||||
RISKY(Color.rgb(224, 56, 56)),
|
||||
VERY_GUESSABLE(Color.rgb(196, 63, 49)),
|
||||
SOMEWHAT_GUESSABLE(Color.rgb(219, 152, 55)),
|
||||
SAFELY_UNGUESSABLE(Color.rgb(118, 168, 24)),
|
||||
VERY_UNGUESSABLE(Color.rgb(37, 152, 41))
|
||||
}
|
||||
|
||||
data class EntropyStrength(val strength: Strength,
|
||||
val entropy: Double,
|
||||
val estimationPercent: Int) {
|
||||
override fun toString(): String {
|
||||
return "EntropyStrength(strength=$strength, entropy=$entropy, estimationPercent=$estimationPercent)"
|
||||
}
|
||||
}
|
||||
|
||||
fun getEntropyStrength(passwordString: String,
|
||||
entropyStrengthResult: (EntropyStrength) -> Unit) {
|
||||
|
||||
entropyStrengthResult.invoke(EntropyStrength(Strength.RISKY, CALCULATE_ENTROPY, 0))
|
||||
entropyJob?.cancel()
|
||||
entropyJob = CoroutineScope(Dispatchers.Main).launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val asyncResult: Deferred<EntropyStrength?> = async {
|
||||
try {
|
||||
if (passwordString.length <= MAX_PASSWORD_LENGTH) {
|
||||
val estimate = mPasswordEntropyCalculator?.estimate(passwordString)
|
||||
val basicScore = estimate?.basicScore ?: 0
|
||||
val entropy = estimate?.entropy ?: 0.0
|
||||
val percentScore = min(entropy * 100 / 200, 100.0).toInt()
|
||||
val strength =
|
||||
if (basicScore == 0 || percentScore < 10) {
|
||||
Strength.RISKY
|
||||
} else if (basicScore == 1 || percentScore < 20) {
|
||||
Strength.VERY_GUESSABLE
|
||||
} else if (basicScore == 2 || percentScore < 33) {
|
||||
Strength.SOMEWHAT_GUESSABLE
|
||||
} else if (basicScore == 3 || percentScore < 50) {
|
||||
Strength.SAFELY_UNGUESSABLE
|
||||
} else if (basicScore == 4) {
|
||||
Strength.VERY_UNGUESSABLE
|
||||
} else {
|
||||
Strength.RISKY
|
||||
}
|
||||
EntropyStrength(strength, entropy, percentScore)
|
||||
} else {
|
||||
EntropyStrength(Strength.VERY_UNGUESSABLE, HIGH_ENTROPY, 100)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
asyncResult.await()?.let { entropyStrength ->
|
||||
entropyStrengthResult.invoke(entropyStrength)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_PASSWORD_LENGTH = 128
|
||||
private const val CALCULATE_ENTROPY = -1.0
|
||||
private const val HIGH_ENTROPY = 1000.0
|
||||
|
||||
fun getStringEntropy(resources: Resources, entropy: Double): String {
|
||||
return when (entropy) {
|
||||
CALCULATE_ENTROPY -> {
|
||||
resources.getString(R.string.entropy_calculate)
|
||||
}
|
||||
HIGH_ENTROPY -> {
|
||||
resources.getString(R.string.entropy_high)
|
||||
}
|
||||
else -> {
|
||||
resources.getString(
|
||||
R.string.entropy,
|
||||
"%.${2}f".format(entropy)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,33 +20,17 @@
|
||||
package com.kunzisoft.keepass.password
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import com.kunzisoft.keepass.R
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
class PasswordGenerator(private val resources: Resources) {
|
||||
|
||||
// From KeePassXC code https://github.com/keepassxreboot/keepassxc/pull/538
|
||||
private fun extendedChars(): String {
|
||||
val charSet = StringBuilder()
|
||||
// [U+0080, U+009F] are C1 control characters,
|
||||
// U+00A0 is non-breaking space
|
||||
run {
|
||||
var ch = '\u00A1'
|
||||
while (ch <= '\u00AC') {
|
||||
charSet.append(ch)
|
||||
++ch
|
||||
}
|
||||
}
|
||||
// U+00AD is soft hyphen (format character)
|
||||
var ch = '\u00AE'
|
||||
while (ch < '\u00FF') {
|
||||
charSet.append(ch)
|
||||
++ch
|
||||
}
|
||||
charSet.append('\u00FF')
|
||||
return charSet.toString()
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun generatePassword(length: Int,
|
||||
upperCase: Boolean,
|
||||
@@ -57,7 +41,11 @@ class PasswordGenerator(private val resources: Resources) {
|
||||
space: Boolean,
|
||||
specials: Boolean,
|
||||
brackets: Boolean,
|
||||
extended: Boolean): String {
|
||||
extended: Boolean,
|
||||
considerChars: String,
|
||||
ignoreChars: String,
|
||||
atLeastOneFromEach: Boolean,
|
||||
excludeAmbiguousChar: Boolean): String {
|
||||
// Desired password length is 0 or less
|
||||
if (length <= 0) {
|
||||
throw IllegalArgumentException(resources.getString(R.string.error_wrong_length))
|
||||
@@ -72,74 +60,164 @@ class PasswordGenerator(private val resources: Resources) {
|
||||
&& !space
|
||||
&& !specials
|
||||
&& !brackets
|
||||
&& !extended) {
|
||||
&& !extended
|
||||
&& considerChars.isEmpty()) {
|
||||
throw IllegalArgumentException(resources.getString(R.string.error_pass_gen_type))
|
||||
}
|
||||
|
||||
val characterSet = getCharacterSet(
|
||||
upperCase,
|
||||
lowerCase,
|
||||
digits,
|
||||
minus,
|
||||
underline,
|
||||
space,
|
||||
specials,
|
||||
brackets,
|
||||
extended)
|
||||
|
||||
val size = characterSet.length
|
||||
|
||||
val buffer = StringBuilder()
|
||||
|
||||
val random = SecureRandom() // use more secure variant of Random!
|
||||
if (size > 0) {
|
||||
for (i in 0 until length) {
|
||||
buffer.append(characterSet[random.nextInt(size)])
|
||||
// Filter builder
|
||||
val passwordFilters = PasswordFilters().apply {
|
||||
this.length = length
|
||||
this.ignoreChars = ignoreChars
|
||||
if (excludeAmbiguousChar)
|
||||
this.ignoreChars += AMBIGUOUS_CHARS
|
||||
if (upperCase) {
|
||||
addFilter(
|
||||
UPPERCASE_CHARS,
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
if (lowerCase) {
|
||||
addFilter(
|
||||
LOWERCASE_CHARS,
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
if (digits) {
|
||||
addFilter(
|
||||
DIGIT_CHARS,
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
if (minus) {
|
||||
addFilter(
|
||||
MINUS_CHAR,
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
if (underline) {
|
||||
addFilter(
|
||||
UNDERLINE_CHAR,
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
if (space) {
|
||||
addFilter(
|
||||
SPACE_CHAR,
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
if (specials) {
|
||||
addFilter(
|
||||
SPECIAL_CHARS,
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
if (brackets) {
|
||||
addFilter(
|
||||
BRACKET_CHARS,
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
if (extended) {
|
||||
addFilter(
|
||||
extendedChars(),
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
if (considerChars.isNotEmpty()) {
|
||||
addFilter(
|
||||
considerChars,
|
||||
if (atLeastOneFromEach) 1 else 0
|
||||
)
|
||||
}
|
||||
}
|
||||
return buffer.toString()
|
||||
|
||||
return generateRandomString(SecureRandom(), passwordFilters)
|
||||
}
|
||||
|
||||
private fun getCharacterSet(upperCase: Boolean,
|
||||
lowerCase: Boolean,
|
||||
digits: Boolean,
|
||||
minus: Boolean,
|
||||
underline: Boolean,
|
||||
space: Boolean,
|
||||
specials: Boolean,
|
||||
brackets: Boolean,
|
||||
extended: Boolean): String {
|
||||
val charSet = StringBuilder()
|
||||
private fun generateRandomString(random: Random, passwordFilters: PasswordFilters): String {
|
||||
val randomString = StringBuilder()
|
||||
|
||||
if (upperCase) {
|
||||
charSet.append(UPPERCASE_CHARS)
|
||||
// Allocate appropriate memory for the password.
|
||||
var requiredCharactersLeft = passwordFilters.getRequiredCharactersLeft()
|
||||
|
||||
// Build the password.
|
||||
for (i in 0 until passwordFilters.length) {
|
||||
var selectableChars: String = if (requiredCharactersLeft < passwordFilters.length - i) {
|
||||
// choose from any group at random
|
||||
passwordFilters.getSelectableChars()
|
||||
} else {
|
||||
// choose only from a group that we need to satisfy a minimum for.
|
||||
passwordFilters.getSelectableCharsForNeed()
|
||||
}
|
||||
passwordFilters.ignoreChars.forEach {
|
||||
selectableChars = selectableChars.replace(it.toString(), "")
|
||||
}
|
||||
|
||||
// Now that the string is built, get the next random character.
|
||||
val selectableCharsMaxIndex = selectableChars.length
|
||||
val randomSelectableCharsIndex = if (selectableCharsMaxIndex > 0) random.nextInt(selectableCharsMaxIndex) else 0
|
||||
val nextChar = selectableChars[randomSelectableCharsIndex]
|
||||
|
||||
// Put at random position
|
||||
val randomStringMaxIndex = randomString.length
|
||||
val randomStringIndex = if (randomStringMaxIndex > 0) random.nextInt(randomStringMaxIndex) else 0
|
||||
randomString.insert(randomStringIndex, nextChar)
|
||||
|
||||
// Now figure out where it came from, and decrement the appropriate minimum value
|
||||
passwordFilters.getFilterThatContainsChar(nextChar)?.let {
|
||||
if (it.minCharsNeeded > 0) {
|
||||
it.minCharsNeeded--
|
||||
requiredCharactersLeft--
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lowerCase) {
|
||||
charSet.append(LOWERCASE_CHARS)
|
||||
}
|
||||
if (digits) {
|
||||
charSet.append(DIGIT_CHARS)
|
||||
}
|
||||
if (minus) {
|
||||
charSet.append(MINUS_CHAR)
|
||||
}
|
||||
if (underline) {
|
||||
charSet.append(UNDERLINE_CHAR)
|
||||
}
|
||||
if (space) {
|
||||
charSet.append(SPACE_CHAR)
|
||||
}
|
||||
if (specials) {
|
||||
charSet.append(SPECIAL_CHARS)
|
||||
}
|
||||
if (brackets) {
|
||||
charSet.append(BRACKET_CHARS)
|
||||
}
|
||||
if (extended) {
|
||||
charSet.append(extendedChars())
|
||||
return randomString.toString()
|
||||
}
|
||||
|
||||
private data class Filter(var chars: String,
|
||||
var minCharsNeeded: Int)
|
||||
|
||||
private class PasswordFilters {
|
||||
var length: Int = 0
|
||||
var ignoreChars = ""
|
||||
val filters = mutableListOf<Filter>()
|
||||
|
||||
fun addFilter(chars: String, minCharsNeeded: Int) {
|
||||
filters.add(Filter(chars, minCharsNeeded))
|
||||
}
|
||||
|
||||
return charSet.toString()
|
||||
fun getRequiredCharactersLeft(): Int {
|
||||
var charsRequired = 0
|
||||
filters.forEach {
|
||||
charsRequired += it.minCharsNeeded
|
||||
}
|
||||
return charsRequired
|
||||
}
|
||||
|
||||
fun getSelectableChars(): String {
|
||||
val stringBuilder = StringBuilder()
|
||||
filters.forEach {
|
||||
stringBuilder.append(it.chars)
|
||||
}
|
||||
return stringBuilder.toString()
|
||||
}
|
||||
|
||||
fun getFilterThatContainsChar(char: Char): Filter? {
|
||||
return filters.find { it.chars.contains(char) }
|
||||
}
|
||||
|
||||
fun getSelectableCharsForNeed(): String {
|
||||
val selectableChars = StringBuilder()
|
||||
// choose only from a group that we need to satisfy a minimum for.
|
||||
filters.forEach {
|
||||
if (it.minCharsNeeded > 0) {
|
||||
selectableChars.append(it.chars)
|
||||
}
|
||||
}
|
||||
return selectableChars.toString()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -151,5 +229,75 @@ class PasswordGenerator(private val resources: Resources) {
|
||||
private const val SPACE_CHAR = " "
|
||||
private const val SPECIAL_CHARS = "!\"#$%&'*+,./:;=?@\\^`"
|
||||
private const val BRACKET_CHARS = "[]{}()<>"
|
||||
private const val AMBIGUOUS_CHARS = "iI|lLoO01"
|
||||
|
||||
// From KeePassXC code https://github.com/keepassxreboot/keepassxc/pull/538
|
||||
private fun extendedChars(): String {
|
||||
val charSet = StringBuilder()
|
||||
// [U+0080, U+009F] are C1 control characters,
|
||||
// U+00A0 is non-breaking space
|
||||
run {
|
||||
var ch = '\u00A1'
|
||||
while (ch <= '\u00AC') {
|
||||
charSet.append(ch)
|
||||
++ch
|
||||
}
|
||||
}
|
||||
// U+00AD is soft hyphen (format character)
|
||||
var ch = '\u00AE'
|
||||
while (ch < '\u00FF') {
|
||||
charSet.append(ch)
|
||||
++ch
|
||||
}
|
||||
charSet.append('\u00FF')
|
||||
return charSet.toString()
|
||||
}
|
||||
|
||||
fun getColorizedPassword(password: String): Spannable {
|
||||
val spannableString = SpannableStringBuilder()
|
||||
if (password.isNotEmpty()) {
|
||||
password.forEach {
|
||||
when {
|
||||
UPPERCASE_CHARS.contains(it)||
|
||||
LOWERCASE_CHARS.contains(it) -> {
|
||||
spannableString.append(it)
|
||||
}
|
||||
DIGIT_CHARS.contains(it) -> {
|
||||
// RED
|
||||
spannableString.append(colorizeChar(it, Color.rgb(246, 79, 62)))
|
||||
}
|
||||
SPECIAL_CHARS.contains(it) -> {
|
||||
// Blue
|
||||
spannableString.append(colorizeChar(it, Color.rgb(39, 166, 228)))
|
||||
}
|
||||
MINUS_CHAR.contains(it)||
|
||||
UNDERLINE_CHAR.contains(it)||
|
||||
BRACKET_CHARS.contains(it) -> {
|
||||
// Purple
|
||||
spannableString.append(colorizeChar(it, Color.rgb(185, 38, 209)))
|
||||
}
|
||||
extendedChars().contains(it) -> {
|
||||
// Green
|
||||
spannableString.append(colorizeChar(it, Color.rgb(44, 181, 50)))
|
||||
}
|
||||
else -> {
|
||||
spannableString.append(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return spannableString
|
||||
}
|
||||
|
||||
private fun colorizeChar(char: Char, color: Int): Spannable {
|
||||
val spannableColorChar = SpannableString(char.toString())
|
||||
spannableColorChar.setSpan(
|
||||
ForegroundColorSpan(color),
|
||||
0,
|
||||
1,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
return spannableColorChar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.password.PassphraseGenerator
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import java.util.*
|
||||
@@ -135,6 +136,18 @@ object PreferencesUtil {
|
||||
context.resources.getBoolean(R.bool.show_uuid_default))
|
||||
}
|
||||
|
||||
fun hideProtectedValue(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.hide_password_key),
|
||||
context.resources.getBoolean(R.bool.hide_password_default))
|
||||
}
|
||||
|
||||
fun colorizePassword(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.colorize_password_key),
|
||||
context.resources.getBoolean(R.bool.colorize_password_default))
|
||||
}
|
||||
|
||||
fun showExpiredEntries(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return ! prefs.getBoolean(context.getString(R.string.hide_expired_entries_key),
|
||||
@@ -192,15 +205,118 @@ object PreferencesUtil {
|
||||
|
||||
fun getDefaultPasswordLength(context: Context): Int {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getInt(context.getString(R.string.password_length_key),
|
||||
Integer.parseInt(context.getString(R.string.default_password_length)))
|
||||
return prefs.getInt(context.getString(R.string.password_generator_length_key),
|
||||
context.resources.getInteger(R.integer.password_generator_length_default))
|
||||
}
|
||||
|
||||
fun getDefaultPasswordCharacters(context: Context): Set<String>? {
|
||||
fun setDefaultPasswordLength(context: Context, passwordLength: Int) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
|
||||
putInt(
|
||||
context.getString(R.string.password_generator_length_key),
|
||||
passwordLength
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun getDefaultPasswordOptions(context: Context): Set<String> {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getStringSet(context.getString(R.string.list_password_generator_options_key),
|
||||
HashSet(listOf(*context.resources
|
||||
.getStringArray(R.array.list_password_generator_options_default_values))))
|
||||
return prefs.getStringSet(context.getString(R.string.password_generator_options_key),
|
||||
HashSet(listOf(*context.resources
|
||||
.getStringArray(R.array.list_password_generator_options_default_values)))) ?: setOf()
|
||||
}
|
||||
|
||||
fun setDefaultPasswordOptions(context: Context, passwordOptionsSet: Set<String>) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
|
||||
putStringSet(
|
||||
context.getString(R.string.password_generator_options_key),
|
||||
passwordOptionsSet
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun getDefaultPasswordConsiderChars(context: Context): String {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getString(context.getString(R.string.password_generator_consider_chars_key),
|
||||
context.getString(R.string.password_generator_consider_chars_default)) ?: ""
|
||||
}
|
||||
|
||||
fun setDefaultPasswordConsiderChars(context: Context, considerChars: String) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
|
||||
putString(
|
||||
context.getString(R.string.password_generator_consider_chars_key),
|
||||
considerChars
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun getDefaultPasswordIgnoreChars(context: Context): String {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getString(context.getString(R.string.password_generator_ignore_chars_key),
|
||||
context.getString(R.string.password_generator_ignore_chars_default)) ?: ""
|
||||
}
|
||||
|
||||
fun setDefaultPasswordIgnoreChars(context: Context, ignoreChars: String) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
|
||||
putString(
|
||||
context.getString(R.string.password_generator_ignore_chars_key),
|
||||
ignoreChars
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun getDefaultPassphraseWordCount(context: Context): Int {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getInt(context.getString(R.string.passphrase_generator_word_count_key),
|
||||
context.resources.getInteger(R.integer.passphrase_generator_word_count_default))
|
||||
}
|
||||
|
||||
fun setDefaultPassphraseWordCount(context: Context, passphraseWordCount: Int) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
|
||||
putInt(
|
||||
context.getString(R.string.passphrase_generator_word_count_key),
|
||||
passphraseWordCount
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun getDefaultPassphraseWordCase(context: Context): PassphraseGenerator.WordCase {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return PassphraseGenerator.WordCase
|
||||
.getByOrdinal(prefs.getInt(context
|
||||
.getString(R.string.passphrase_generator_word_case_key),
|
||||
0)
|
||||
)
|
||||
}
|
||||
|
||||
fun setDefaultPassphraseWordCase(context: Context, wordCase: PassphraseGenerator.WordCase) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
|
||||
putInt(
|
||||
context.getString(R.string.passphrase_generator_word_case_key),
|
||||
wordCase.ordinal
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun getDefaultPassphraseSeparator(context: Context): String {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getString(context.getString(R.string.passphrase_generator_separator_key),
|
||||
context.getString(R.string.passphrase_generator_separator_default)) ?: ""
|
||||
}
|
||||
|
||||
fun setDefaultPassphraseSeparator(context: Context, separator: String) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
|
||||
putString(
|
||||
context.getString(R.string.passphrase_generator_separator_key),
|
||||
separator
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun isClipboardNotificationsEnable(context: Context): Boolean {
|
||||
@@ -351,12 +467,6 @@ object PreferencesUtil {
|
||||
context.resources.getBoolean(R.bool.sort_recycle_bin_bottom_default))
|
||||
}
|
||||
|
||||
fun hideProtectedValue(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.hide_password_key),
|
||||
context.resources.getBoolean(R.bool.hide_password_default))
|
||||
}
|
||||
|
||||
fun fieldFontIsInVisibility(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.monospace_font_fields_enable_key),
|
||||
@@ -609,9 +719,8 @@ object PreferencesUtil {
|
||||
context.getString(R.string.lock_database_screen_off_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.lock_database_back_root_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.lock_database_show_button_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.password_length_key) -> editor.putInt(name, value.toInt())
|
||||
context.getString(R.string.list_password_generator_options_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
|
||||
context.getString(R.string.hide_password_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.password_generator_length_key) -> editor.putInt(name, value.toInt())
|
||||
context.getString(R.string.password_generator_options_key) -> editor.putStringSet(name, getStringSetFromProperties(value))
|
||||
context.getString(R.string.allow_copy_password_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.remember_database_locations_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.show_recent_files_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
@@ -659,6 +768,8 @@ object PreferencesUtil {
|
||||
context.getString(R.string.show_uuid_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.list_size_key) -> editor.putString(name, value)
|
||||
context.getString(R.string.monospace_font_fields_enable_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.hide_password_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.colorize_password_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.hide_expired_entries_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
context.getString(R.string.enable_education_screens_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
defStyle: Int = 0)
|
||||
: FrameLayout(context, attrs, defStyle) {
|
||||
|
||||
private var passwordView: EditText
|
||||
private var passwordTextView: EditText
|
||||
private var keyFileSelectionView: KeyFileSelectionView
|
||||
private var checkboxPasswordView: CompoundButton
|
||||
private var checkboxKeyFileView: CompoundButton
|
||||
@@ -60,7 +60,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_main_credentials, this)
|
||||
|
||||
passwordView = findViewById(R.id.password)
|
||||
passwordTextView = findViewById(R.id.password_text_view)
|
||||
keyFileSelectionView = findViewById(R.id.keyfile_selection)
|
||||
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
||||
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
||||
@@ -75,8 +75,8 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
|
||||
passwordView.setOnEditorActionListener(onEditorActionListener)
|
||||
passwordView.addTextChangedListener(object : TextWatcher {
|
||||
passwordTextView.setOnEditorActionListener(onEditorActionListener)
|
||||
passwordTextView.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||
@@ -86,7 +86,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
checkboxPasswordView.isChecked = true
|
||||
}
|
||||
})
|
||||
passwordView.setOnKeyListener { _, _, keyEvent ->
|
||||
passwordTextView.setOnKeyListener { _, _, keyEvent ->
|
||||
var handled = false
|
||||
if (keyEvent.action == KeyEvent.ACTION_DOWN
|
||||
&& keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER
|
||||
@@ -108,11 +108,11 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
|
||||
fun populatePasswordTextView(text: String?) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
passwordView.setText("")
|
||||
passwordTextView.setText("")
|
||||
if (checkboxPasswordView.isChecked)
|
||||
checkboxPasswordView.isChecked = false
|
||||
} else {
|
||||
passwordView.setText(text)
|
||||
passwordTextView.setText(text)
|
||||
if (checkboxPasswordView.isChecked)
|
||||
checkboxPasswordView.isChecked = true
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
fun getMainCredential(): MainCredential {
|
||||
return MainCredential().apply {
|
||||
this.masterPassword = if (checkboxPasswordView.isChecked)
|
||||
passwordView.text?.toString() else null
|
||||
passwordTextView.text?.toString() else null
|
||||
this.keyFileUri = if (checkboxKeyFileView.isChecked)
|
||||
keyFileSelectionView.uri else null
|
||||
}
|
||||
@@ -163,7 +163,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
*/
|
||||
fun retrieveCredentialForStorage(listener: CredentialStorageListener): ByteArray? {
|
||||
return when (mCredentialStorage) {
|
||||
CredentialStorage.PASSWORD -> listener.passwordToStore(passwordView.text?.toString())
|
||||
CredentialStorage.PASSWORD -> listener.passwordToStore(passwordTextView.text?.toString())
|
||||
CredentialStorage.KEY_FILE -> listener.keyfileToStore(keyFileSelectionView.uri)
|
||||
CredentialStorage.HARDWARE_KEY -> listener.hardwareKeyToStore()
|
||||
}
|
||||
@@ -176,15 +176,15 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
|
||||
fun requestPasswordFocus() {
|
||||
passwordView.requestFocusFromTouch()
|
||||
passwordTextView.requestFocusFromTouch()
|
||||
}
|
||||
|
||||
// Auto select the password field and open keyboard
|
||||
fun focusPasswordFieldAndOpenKeyboard() {
|
||||
passwordView.postDelayed({
|
||||
passwordView.requestFocusFromTouch()
|
||||
passwordTextView.postDelayed({
|
||||
passwordTextView.requestFocusFromTouch()
|
||||
val inputMethodManager = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as? InputMethodManager?
|
||||
inputMethodManager?.showSoftInput(passwordView, InputMethodManager.SHOW_IMPLICIT)
|
||||
inputMethodManager?.showSoftInput(passwordTextView, InputMethodManager.SHOW_IMPLICIT)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
|
||||
157
app/src/main/java/com/kunzisoft/keepass/view/PassKeyView.kt
Normal file
157
app/src/main/java/com/kunzisoft/keepass/view/PassKeyView.kt
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Editable
|
||||
import android.text.InputType
|
||||
import android.text.SpannableString
|
||||
import android.text.TextWatcher
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.password.PasswordGenerator
|
||||
import com.kunzisoft.keepass.password.PasswordEntropy
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
|
||||
class PassKeyView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: FrameLayout(context, attrs, defStyle) {
|
||||
|
||||
private var mPasswordEntropyCalculator: PasswordEntropy? = null
|
||||
|
||||
private val passwordInputLayout: TextInputLayout
|
||||
private val passwordText: TextView
|
||||
private val passwordStrengthProgress: LinearProgressIndicator
|
||||
private val passwordEntropy: TextView
|
||||
|
||||
private var mViewHint: String = ""
|
||||
private var mMaxLines: Int = 3
|
||||
private var mShowPassword: Boolean = false
|
||||
|
||||
private var mPasswordTextWatcher: MutableList<TextWatcher> = mutableListOf()
|
||||
private val passwordTextWatcher = object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
||||
mPasswordTextWatcher.forEach {
|
||||
it.beforeTextChanged(charSequence, i, i1, i2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
||||
mPasswordTextWatcher.forEach {
|
||||
it.onTextChanged(charSequence, i, i1, i2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
mPasswordTextWatcher.forEach {
|
||||
it.afterTextChanged(editable)
|
||||
}
|
||||
getEntropyStrength(editable.toString())
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
context.theme.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.PassKeyView,
|
||||
0, 0).apply {
|
||||
try {
|
||||
mViewHint = getString(R.styleable.PassKeyView_passKeyHint)
|
||||
?: context.getString(R.string.password)
|
||||
mMaxLines = getInteger(R.styleable.PassKeyView_passKeyMaxLines, mMaxLines)
|
||||
mShowPassword = getBoolean(R.styleable.PassKeyView_passKeyVisible,
|
||||
!PreferencesUtil.hideProtectedValue(context))
|
||||
} finally {
|
||||
recycle()
|
||||
}
|
||||
}
|
||||
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_passkey, this)
|
||||
|
||||
passwordInputLayout = findViewById(R.id.password_input_layout)
|
||||
passwordInputLayout?.hint = mViewHint
|
||||
passwordText = findViewById(R.id.password_text)
|
||||
if (mShowPassword) {
|
||||
passwordText?.inputType = passwordText.inputType and
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD or
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
}
|
||||
passwordText?.maxLines = mMaxLines
|
||||
passwordText?.applyFontVisibility()
|
||||
passwordText.addTextChangedListener(passwordTextWatcher)
|
||||
passwordStrengthProgress = findViewById(R.id.password_strength_progress)
|
||||
passwordStrengthProgress?.apply {
|
||||
setIndicatorColor(PasswordEntropy.Strength.RISKY.color)
|
||||
progress = 0
|
||||
max = 100
|
||||
}
|
||||
passwordEntropy = findViewById(R.id.password_entropy)
|
||||
|
||||
mPasswordEntropyCalculator = PasswordEntropy {
|
||||
passwordText?.text?.toString()?.let { firstPassword ->
|
||||
getEntropyStrength(firstPassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEntropyStrength(passwordText: String) {
|
||||
mPasswordEntropyCalculator?.getEntropyStrength(passwordText) { entropyStrength ->
|
||||
passwordStrengthProgress.apply {
|
||||
post {
|
||||
setIndicatorColor(entropyStrength.strength.color)
|
||||
setProgressCompat(entropyStrength.estimationPercent, true)
|
||||
}
|
||||
}
|
||||
passwordEntropy.apply {
|
||||
post {
|
||||
text = PasswordEntropy.getStringEntropy(resources, entropyStrength.entropy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addTextChangedListener(textWatcher: TextWatcher) {
|
||||
mPasswordTextWatcher.add(textWatcher)
|
||||
}
|
||||
|
||||
fun removeTextChangedListener(textWatcher: TextWatcher) {
|
||||
mPasswordTextWatcher.remove(textWatcher)
|
||||
}
|
||||
|
||||
var passwordString: String
|
||||
get() {
|
||||
return passwordText.text.toString()
|
||||
}
|
||||
set(value) {
|
||||
val spannableString =
|
||||
if (PreferencesUtil.colorizePassword(context))
|
||||
PasswordGenerator.getColorizedPassword(value)
|
||||
else
|
||||
SpannableString(value)
|
||||
passwordText.text = spannableString
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
if (templateAttribute.options.isAssociatedWithPasswordGenerator()) {
|
||||
setOnActionClickListener({
|
||||
mOnPasswordGenerationActionClickListener?.invoke(field)
|
||||
}, R.drawable.ic_generate_password_white_24dp)
|
||||
}, R.drawable.ic_random_white_24dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.os.Build
|
||||
import android.text.InputFilter
|
||||
import android.text.InputType
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.StyleSpan
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.ContextThemeWrapper
|
||||
@@ -19,6 +23,9 @@ import androidx.core.view.isVisible
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateField
|
||||
import com.kunzisoft.keepass.password.PasswordGenerator
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
|
||||
class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@@ -123,7 +130,13 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
return valueView.text?.toString() ?: ""
|
||||
}
|
||||
set(value) {
|
||||
valueView.setText(value)
|
||||
val spannableString =
|
||||
if (PreferencesUtil.colorizePassword(context)
|
||||
&& TemplateField.isStandardPasswordName(context, label))
|
||||
PasswordGenerator.getColorizedPassword(value)
|
||||
else
|
||||
SpannableString(value)
|
||||
valueView.setText(spannableString)
|
||||
}
|
||||
|
||||
override var default: String = ""
|
||||
@@ -164,7 +177,11 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
fun setProtection(protection: Boolean) {
|
||||
if (protection) {
|
||||
labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
val visibilityTag = if (PreferencesUtil.hideProtectedValue(context))
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
else
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
valueView.inputType = valueView.inputType or visibilityTag
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,18 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.os.Build
|
||||
import android.text.InputFilter
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.StyleSpan
|
||||
import android.text.util.Linkify
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.View
|
||||
import android.view.View.OnClickListener
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.AppCompatImageButton
|
||||
@@ -36,7 +41,10 @@ import androidx.core.text.util.LinkifyCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateField
|
||||
import com.kunzisoft.keepass.model.EntryInfo.Companion.APPLICATION_ID_FIELD_NAME
|
||||
import com.kunzisoft.keepass.password.PasswordGenerator
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
|
||||
@@ -191,7 +199,13 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
return valueView.text.toString()
|
||||
}
|
||||
set(value) {
|
||||
valueView.text = value
|
||||
val spannableString =
|
||||
if (PreferencesUtil.colorizePassword(context)
|
||||
&& TemplateField.isStandardPasswordName(context, label))
|
||||
PasswordGenerator.getColorizedPassword(value)
|
||||
else
|
||||
SpannableString(value)
|
||||
valueView.text = spannableString
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class KeyGeneratorViewModel: ViewModel() {
|
||||
|
||||
val keyGenerated : LiveData<String> get() = _keyGenerated
|
||||
private val _keyGenerated = MutableLiveData<String>()
|
||||
|
||||
val keyGeneratedValidated : LiveData<Void?> get() = _keyGeneratedValidated
|
||||
private val _keyGeneratedValidated = SingleLiveEvent<Void?>()
|
||||
val requireKeyGeneration : LiveData<Void?> get() = _requireKeyGeneration
|
||||
private val _requireKeyGeneration = SingleLiveEvent<Void?>()
|
||||
|
||||
val passwordGeneratedValidated : LiveData<Void?> get() = _passwordGeneratedValidated
|
||||
private val _passwordGeneratedValidated = SingleLiveEvent<Void?>()
|
||||
val requirePasswordGeneration : LiveData<Void?> get() = _requirePasswordGeneration
|
||||
private val _requirePasswordGeneration = SingleLiveEvent<Void?>()
|
||||
|
||||
val passphraseGeneratedValidated : LiveData<Void?> get() = _passphraseGeneratedValidated
|
||||
private val _passphraseGeneratedValidated = SingleLiveEvent<Void?>()
|
||||
val requirePassphraseGeneration : LiveData<Void?> get() = _requirePassphraseGeneration
|
||||
private val _requirePassphraseGeneration = SingleLiveEvent<Void?>()
|
||||
|
||||
fun setKeyGenerated(passKey: String) {
|
||||
_keyGenerated.value = passKey
|
||||
}
|
||||
|
||||
fun validateKeyGenerated() {
|
||||
_keyGeneratedValidated.call()
|
||||
}
|
||||
|
||||
fun validatePasswordGenerated() {
|
||||
_passwordGeneratedValidated.call()
|
||||
}
|
||||
|
||||
fun validatePassphraseGenerated() {
|
||||
_passphraseGeneratedValidated.call()
|
||||
}
|
||||
|
||||
fun requireKeyGeneration() {
|
||||
_requireKeyGeneration.call()
|
||||
}
|
||||
|
||||
fun requirePasswordGeneration() {
|
||||
_requirePasswordGeneration.call()
|
||||
}
|
||||
|
||||
fun requirePassphraseGeneration() {
|
||||
_requirePassphraseGeneration.call()
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,11 @@ class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
}
|
||||
|
||||
// Observe the internal MutableLiveData
|
||||
super.observe(owner, { t ->
|
||||
super.observe(owner) { t ->
|
||||
if (mPending.compareAndSet(true, false)) {
|
||||
observer.onChanged(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
|
||||
66
app/src/main/res/layout/activity_key_generator.xml
Normal file
66
app/src/main/res/layout/activity_key_generator.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2022 Jeremy Jamet / Kunzisoft.
|
||||
|
||||
This file is part of KeePassDX.
|
||||
|
||||
KeePassDX is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
KeePassDX is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/key_generator_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/key_generator_coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/toolbar">
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/key_generator_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<com.kunzisoft.keepass.view.ToolbarAction
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_gravity="bottom"
|
||||
android:theme="?attr/toolbarActionAppearance"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/key_generator_validation"
|
||||
style="@style/KeepassDXStyle.Fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/validate"
|
||||
android:src="@drawable/ic_check_white_24dp"
|
||||
app:fabSize="mini"
|
||||
app:layout_constraintTop_toTopOf="@+id/toolbar"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<include
|
||||
layout="@layout/view_button_lock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
168
app/src/main/res/layout/fragment_generate_passphrase.xml
Normal file
168
app/src/main/res/layout/fragment_generate_passphrase.xml
Normal file
@@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
|
||||
This file is part of KeePassDX.
|
||||
|
||||
KeePassDX is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
KeePassDX is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAutofill="noExcludeDescendants"
|
||||
android:paddingBottom="@dimen/card_view_margin_vertical"
|
||||
tools:targetApi="o">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="240dp">
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="180dp"
|
||||
android:background="?attr/colorPrimary" />
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginRight="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginTop="@dimen/card_view_margin_vertical"
|
||||
android:layout_marginBottom="@dimen/card_view_margin_vertical"
|
||||
android:layout_gravity="bottom"
|
||||
app:cardCornerRadius="4dp">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_view_padding">
|
||||
|
||||
<com.kunzisoft.keepass.view.PassKeyView
|
||||
android:id="@+id/passphrase_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@+id/passphrase_copy_button"
|
||||
android:layout_toLeftOf="@+id/passphrase_copy_button"
|
||||
app:passKeyHint="@string/passphrase"
|
||||
app:passKeyMaxLines="7"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/passphrase_copy_button"
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/passphrase_view"
|
||||
android:layout_alignBottom="@+id/passphrase_view"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:contentDescription="@string/menu_copy"
|
||||
android:src="@drawable/ic_content_copy_white_24dp" />
|
||||
</RelativeLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</FrameLayout>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/ScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginRight="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginTop="@dimen/card_view_margin_vertical"
|
||||
android:layout_marginBottom="@dimen/card_view_margin_vertical"
|
||||
app:cardCornerRadius="4dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_view_padding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/slider_word_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/word_count"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_toStartOf="@+id/word_count"
|
||||
android:layout_toLeftOf="@+id/word_count"
|
||||
android:contentDescription="@string/content_description_password_length"
|
||||
android:stepSize="1"
|
||||
android:value="@integer/passphrase_generator_word_count_default"
|
||||
android:valueFrom="@integer/passphrase_generator_word_count_min"
|
||||
android:valueTo="@integer/passphrase_generator_word_count_max"
|
||||
app:thumbColor="?attr/chipFilterBackgroundColor"
|
||||
app:trackColorActive="?attr/chipFilterBackgroundColor"
|
||||
app:trackColorInactive="?attr/chipFilterBackgroundColorDisabled" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/word_count"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:inputType="number"
|
||||
android:maxLength="2"
|
||||
android:maxLines="1" />
|
||||
|
||||
</RelativeLayout>
|
||||
<TextView
|
||||
android:id="@+id/character_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Character count: 51" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/word_case"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/word_separator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/word_case"
|
||||
android:layout_toRightOf="@+id/word_case">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/word_separator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/word_separator" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@@ -23,319 +23,242 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/default_margin"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAutofill="noExcludeDescendants"
|
||||
android:paddingBottom="@dimen/card_view_margin_vertical"
|
||||
tools:targetApi="o">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="180dp">
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/password_input_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/password_copy_button"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
app:endIconMode="password_toggle"
|
||||
app:endIconTint="?attr/colorAccent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:hint="@string/hint_generated_password"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword|textMultiLine"
|
||||
android:maxLines="3"
|
||||
tools:ignore="TextFields" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/password_copy_button"
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:contentDescription="@string/menu_copy"
|
||||
android:src="@drawable/ic_content_copy_white_24dp" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/generate_password_button"
|
||||
android:layout_margin="@dimen/button_margin"
|
||||
android:layout_height="120dp"
|
||||
android:background="?attr/colorPrimary" />
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp"
|
||||
android:drawableEnd="@drawable/ic_generate_password_white_24dp"
|
||||
android:drawableRight="@drawable/ic_generate_password_white_24dp"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:text="@string/generate_password" />
|
||||
</LinearLayout>
|
||||
android:layout_marginStart="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginRight="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginTop="@dimen/card_view_margin_vertical"
|
||||
android:layout_marginBottom="@dimen/card_view_margin_vertical"
|
||||
android:layout_gravity="bottom"
|
||||
app:cardCornerRadius="4dp">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_view_padding">
|
||||
|
||||
<com.kunzisoft.keepass.view.PassKeyView
|
||||
android:id="@+id/password_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@+id/password_copy_button"
|
||||
android:layout_toLeftOf="@+id/password_copy_button" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/password_copy_button"
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/password_view"
|
||||
android:layout_alignBottom="@+id/password_view"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:contentDescription="@string/menu_copy"
|
||||
android:src="@drawable/ic_content_copy_white_24dp" />
|
||||
</RelativeLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</FrameLayout>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/ScrollView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/length_label"
|
||||
android:text="@string/length"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/length"
|
||||
android:layout_width="50dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/length_label"
|
||||
android:maxLines="1"
|
||||
android:maxLength="3"
|
||||
android:inputType="number"
|
||||
android:text="@string/default_password_length"
|
||||
android:hint="@string/hint_length"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
android:id="@+id/seekbar_length"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/length"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignTop="@+id/length"
|
||||
android:layout_toEndOf="@+id/length"
|
||||
android:layout_toRightOf="@+id/length"
|
||||
android:contentDescription="@string/content_description_password_length"
|
||||
app:min="@string/min_password_length"
|
||||
android:progress="@string/default_password_length"
|
||||
android:max="@string/max_password_length"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
android:layout_marginStart="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginRight="@dimen/card_view_margin_horizontal"
|
||||
android:layout_marginTop="@dimen/card_view_margin_vertical"
|
||||
android:layout_marginBottom="@dimen/card_view_margin_vertical"
|
||||
app:cardCornerRadius="4dp">
|
||||
<LinearLayout
|
||||
android:id="@+id/RelativeLayout"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginEnd="20dp">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_view_padding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<CheckBox
|
||||
android:id="@+id/cb_uppercase"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/uppercase"
|
||||
android:checked="true" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="end"
|
||||
android:layout_toEndOf="@+id/cb_uppercase"
|
||||
android:layout_toRightOf="@+id/cb_uppercase"
|
||||
android:text="@string/visual_uppercase" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<CheckBox
|
||||
android:id="@+id/cb_lowercase"
|
||||
android:layout_width="wrap_content"
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/slider_length"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/lowercase"
|
||||
android:checked="true" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="end"
|
||||
android:layout_toEndOf="@+id/cb_lowercase"
|
||||
android:layout_toRightOf="@+id/cb_lowercase"
|
||||
android:text="@string/visual_lowercase" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<CheckBox
|
||||
android:id="@+id/cb_digits"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/digits"
|
||||
android:checked="true" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="end"
|
||||
android:layout_toEndOf="@+id/cb_digits"
|
||||
android:layout_toRightOf="@+id/cb_digits"
|
||||
android:text="@string/visual_digits" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<CheckBox
|
||||
android:id="@+id/cb_minus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/minus" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="end"
|
||||
android:layout_toEndOf="@+id/cb_minus"
|
||||
android:layout_toRightOf="@+id/cb_minus"
|
||||
android:text="@string/visual_minus" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<CheckBox
|
||||
android:id="@+id/cb_underline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/underline" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="end"
|
||||
android:layout_toEndOf="@+id/cb_underline"
|
||||
android:layout_toRightOf="@+id/cb_underline"
|
||||
android:text="@string/visual_underline" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<CheckBox
|
||||
android:id="@+id/cb_space"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/space" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="end"
|
||||
android:layout_toEndOf="@+id/cb_space"
|
||||
android:layout_toRightOf="@+id/cb_space"
|
||||
android:text="@string/visual_space" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<CheckBox
|
||||
android:id="@+id/cb_specials"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/special" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="end"
|
||||
android:layout_toEndOf="@+id/cb_specials"
|
||||
android:layout_toRightOf="@+id/cb_specials"
|
||||
android:text="@string/visual_special" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<CheckBox
|
||||
android:id="@+id/cb_brackets"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/brackets"
|
||||
android:layout_alignBottom="@+id/length"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_toStartOf="@+id/length"
|
||||
android:layout_toLeftOf="@+id/length"
|
||||
android:contentDescription="@string/content_description_password_length"
|
||||
android:stepSize="1"
|
||||
android:value="@integer/password_generator_length_default"
|
||||
android:valueFrom="@integer/password_generator_length_min"
|
||||
android:valueTo="@integer/password_generator_length_max"
|
||||
app:thumbColor="?attr/chipFilterBackgroundColor"
|
||||
app:trackColorActive="?attr/chipFilterBackgroundColor"
|
||||
app:trackColorInactive="?attr/chipFilterBackgroundColorDisabled" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/length"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="end"
|
||||
android:layout_toEndOf="@+id/cb_brackets"
|
||||
android:layout_toRightOf="@+id/cb_brackets"
|
||||
android:text="@string/visual_brackets" />
|
||||
android:inputType="number"
|
||||
android:maxLength="3"
|
||||
android:maxLines="1" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/password_filters"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:chipSpacingVertical="16dp"
|
||||
android:paddingTop="@dimen/default_margin"
|
||||
android:paddingBottom="@dimen/default_margin">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/upperCase_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/visual_uppercase"/>
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/lowerCase_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/visual_lowercase"/>
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/digits_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/visual_digits"/>
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/minus_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/visual_minus"/>
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/underline_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/visual_underline"/>
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/space_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/space"/>
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/special_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/visual_special"/>
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/brackets_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/visual_brackets"/>
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/extendedASCII_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/visual_extended"/>
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<CheckBox
|
||||
android:id="@+id/cb_extended"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/consider_chars_filter_layout"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/ignore_chars_filter_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/consider_chars_filter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/consider_chars_filter"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/ignore_chars_filter_layout"
|
||||
app:layout_constraintStart_toEndOf="@+id/consider_chars_filter_layout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/ignore_chars_filter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/ignore_chars_filter"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/password_advanced"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:chipSpacingVertical="16dp"
|
||||
android:paddingTop="@dimen/default_margin"
|
||||
android:paddingBottom="@dimen/default_margin">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/atLeastOne_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/extended_ASCII"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true" />
|
||||
<TextView
|
||||
android:checked="true"
|
||||
android:text="@string/at_least_one_char"/>
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/excludeAmbiguous_filter"
|
||||
style="@style/KeepassDXStyle.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="end"
|
||||
android:layout_toEndOf="@+id/cb_extended"
|
||||
android:layout_toRightOf="@+id/cb_extended"
|
||||
android:text="@string/visual_extended" />
|
||||
</RelativeLayout>
|
||||
android:checked="true"
|
||||
android:text="@string/exclude_ambiguous_chars"/>
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@@ -71,25 +71,11 @@
|
||||
android:text="@string/password"/>
|
||||
|
||||
<!-- Password Input -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/password_input_layout"
|
||||
<com.kunzisoft.keepass.view.PassKeyView
|
||||
android:id="@+id/password_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
app:endIconMode="password_toggle"
|
||||
app:endIconTint="?attr/colorAccent">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/pass_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="yes"
|
||||
android:autofillHints="newPassword"
|
||||
android:maxLines="1"
|
||||
android:hint="@string/hint_pass"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
app:passKeyVisible="false"/>
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/password_repeat_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
@@ -100,14 +86,14 @@
|
||||
app:endIconMode="password_toggle"
|
||||
app:endIconTint="?attr/colorAccent">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/pass_conf_password"
|
||||
android:id="@+id/password_confirmation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:ems="10"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="yes"
|
||||
android:autofillHints="newPassword"
|
||||
android:maxLines="1"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword|textMultiLine"
|
||||
android:maxLines="3"
|
||||
android:hint="@string/hint_conf_pass"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
@@ -23,15 +23,15 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/icon_picker_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/icon_picker_pager"
|
||||
android:id="@+id/tabs_view_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -36,19 +36,16 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@+id/password_checkbox"
|
||||
android:layout_toEndOf="@+id/password_checkbox"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
app:endIconMode="password_toggle"
|
||||
app:endIconTint="?attr/colorAccent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/password"
|
||||
android:id="@+id/password_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp"
|
||||
android:hint="@string/password"
|
||||
android:inputType="textPassword"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="yes"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
|
||||
52
app/src/main/res/layout/view_passkey.xml
Normal file
52
app/src/main/res/layout/view_passkey.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/password_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
app:endIconMode="password_toggle"
|
||||
app:endIconTint="?attr/colorAccent"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/password_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:hint="@string/hint_pass"
|
||||
android:importantForAccessibility="no"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword|textMultiLine"
|
||||
android:maxLines="3"
|
||||
tools:ignore="TextFields" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/password_strength_progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
app:trackCornerRadius="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/password_input_layout"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/password_entropy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Entropy: 72.50 bit"
|
||||
android:textSize="11sp"
|
||||
android:layout_margin="4dp"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/password_input_layout"
|
||||
app:layout_constraintEnd_toEndOf="@+id/password_input_layout" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
28
app/src/main/res/menu/key_generator.xml
Normal file
28
app/src/main/res/menu/key_generator.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2021 Jeremy Jamet / Kunzisoft.
|
||||
|
||||
This file is part of KeePassDX.
|
||||
|
||||
KeePassDX is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
KeePassDX is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@+id/menu_generate"
|
||||
android:icon="@drawable/ic_random_white_24dp"
|
||||
android:title="@string/generate_password"
|
||||
android:orderInCategory="8"
|
||||
app:iconTint="?attr/colorControlNormal"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@@ -41,6 +41,12 @@
|
||||
<attr name="explanations" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="PassKeyView">
|
||||
<attr name="passKeyHint" format="string" />
|
||||
<attr name="passKeyMaxLines" format="integer" />
|
||||
<attr name="passKeyVisible" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<!-- Specific keyboard attributes -->
|
||||
<declare-styleable name="KeyboardView">
|
||||
<!-- Default KeyboardView style. -->
|
||||
|
||||
@@ -81,10 +81,6 @@
|
||||
<bool name="lock_database_back_root_default" translatable="false">false</bool>
|
||||
<string name="lock_database_show_button_key" translatable="false">lock_database_show_button_key</string>
|
||||
<bool name="lock_database_show_button_default" translatable="false">true</bool>
|
||||
<string name="password_length_key" translatable="false">password_length_key</string>
|
||||
<string name="list_password_generator_options_key" translatable="false">list_password_generator_options_key</string>
|
||||
<string name="hide_password_key" translatable="false">hide_password_key</string>
|
||||
<bool name="hide_password_default" translatable="false">true</bool>
|
||||
<string name="allow_copy_password_key" translatable="false">allow_copy_password_key</string>
|
||||
<bool name="allow_copy_password_default" translatable="false">false</bool>
|
||||
<string name="remember_database_locations_key" translatable="false">remember_database_locations_key</string>
|
||||
@@ -195,12 +191,34 @@
|
||||
<string name="list_size_key" translatable="false">list_size_key</string>
|
||||
<string name="monospace_font_fields_enable_key" translatable="false">monospace_font_extra_fields_enable_key</string>
|
||||
<bool name="monospace_font_fields_enable_default" translatable="false">true</bool>
|
||||
<string name="hide_password_key" translatable="false">hide_password_key</string>
|
||||
<bool name="hide_password_default" translatable="false">true</bool>
|
||||
<string name="colorize_password_key" translatable="false">colorize_password_key</string>
|
||||
<bool name="colorize_password_default" translatable="false">true</bool>
|
||||
<string name="hide_expired_entries_key" translatable="false">hide_expired_entries_key</string>
|
||||
<bool name="hide_expired_entries_default" translatable="false">false</bool>
|
||||
<string name="enable_education_screens_key" translatable="false">enable_education_screens_key</string>
|
||||
<bool name="enable_education_screens_default" translatable="false">true</bool>
|
||||
<string name="reset_education_screens_key" translatable="false">relaunch_education_screens_key</string>
|
||||
|
||||
<!-- Password Generator Settings -->
|
||||
<string name="password_generator_length_key" translatable="false">password_generator_length_key</string>
|
||||
<integer name="password_generator_length_min" translatable="false">1</integer>
|
||||
<integer name="password_generator_length_default" translatable="false">14</integer>
|
||||
<integer name="password_generator_length_max" translatable="false">64</integer>
|
||||
<string name="password_generator_options_key" translatable="false">password_generator_options_key</string>
|
||||
<string name="password_generator_consider_chars_key" translatable="false">password_generator_consider_chars_key</string>
|
||||
<string name="password_generator_consider_chars_default" translatable="false" />
|
||||
<string name="password_generator_ignore_chars_key" translatable="false">password_generator_ignore_chars_key</string>
|
||||
<string name="password_generator_ignore_chars_default" translatable="false" />
|
||||
<string name="passphrase_generator_word_count_key" translatable="false">passphrase_generator_word_count_key</string>
|
||||
<integer name="passphrase_generator_word_count_min" translatable="false">1</integer>
|
||||
<integer name="passphrase_generator_word_count_default" translatable="false">8</integer>
|
||||
<integer name="passphrase_generator_word_count_max" translatable="false">20</integer>
|
||||
<string name="passphrase_generator_word_case_key" translatable="false">passphrase_generator_word_case_key</string>
|
||||
<string name="passphrase_generator_separator_key" translatable="false">passphrase_generator_separator_key</string>
|
||||
<string name="passphrase_generator_separator_default" translatable="false" />
|
||||
|
||||
<!-- Database Settings -->
|
||||
<string name="settings_database_key" translatable="false">settings_database_key</string>
|
||||
<string name="settings_database_security_key" translatable="false">settings_database_security_key</string>
|
||||
@@ -365,11 +383,6 @@
|
||||
<!-- WARNING ! module icon-pack-material must be import in gradle -->
|
||||
<string name="setting_icon_pack_choose_default" translatable="false">@string/material_resource_id</string>
|
||||
|
||||
<!-- Password generator -->
|
||||
<string name="min_password_length" translatable="false">1</string>
|
||||
<string name="default_password_length" translatable="false">14</string>
|
||||
<string name="max_password_length" translatable="false">128</string>
|
||||
|
||||
<string name="value_password_uppercase" translatable="false">value_password_uppercase</string>
|
||||
<string name="value_password_lowercase" translatable="false">value_password_lowercase</string>
|
||||
<string name="value_password_digits" translatable="false">value_password_digits</string>
|
||||
@@ -379,6 +392,8 @@
|
||||
<string name="value_password_special" translatable="false">value_password_special</string>
|
||||
<string name="value_password_brackets" translatable="false">value_password_brackets</string>
|
||||
<string name="value_password_extended" translatable="false">value_password_extended</string>
|
||||
<string name="value_password_atLeastOne" translatable="false">value_password_atLeastOne</string>
|
||||
<string name="value_password_excludeAmbiguous" translatable="false">value_password_excludeAmbiguous</string>
|
||||
<string name="visual_uppercase" translatable="false">A-Z</string>
|
||||
<string name="visual_lowercase" translatable="false">a-z</string>
|
||||
<string name="visual_digits" translatable="false">0-9</string>
|
||||
@@ -392,6 +407,7 @@
|
||||
<item translatable="false">@string/value_password_uppercase</item>
|
||||
<item translatable="false">@string/value_password_lowercase</item>
|
||||
<item translatable="false">@string/value_password_digits</item>
|
||||
<item translatable="false">@string/value_password_special</item>
|
||||
</string-array>
|
||||
<string-array name="list_password_generator_options_values">
|
||||
<item translatable="false">@string/value_password_uppercase</item>
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<string name="discard">Discard</string>
|
||||
<string name="entry_password_generator">Password generator</string>
|
||||
<string name="content_description_password_length">Password length</string>
|
||||
<string name="content_description_passphrase_word_count">Passphrase word count</string>
|
||||
<string name="entry_add_field">Add field</string>
|
||||
<string name="entry_add_attachment">Add attachment</string>
|
||||
<string name="content_description_remove_field">Remove field</string>
|
||||
@@ -204,6 +205,7 @@
|
||||
<string name="hint_length">Length</string>
|
||||
<string name="hint_pass">Password</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="passphrase">Passphrase</string>
|
||||
<string name="invalid_credentials">Could not read credentials.</string>
|
||||
<string name="invalid_algorithm">Wrong algorithm.</string>
|
||||
<string name="invalid_db_same_uuid">%1$s with the same UUID %2$s already exists.</string>
|
||||
@@ -225,6 +227,8 @@
|
||||
<string name="lowercase">Lower-case</string>
|
||||
<string name="hide_password_title">Hide passwords</string>
|
||||
<string name="hide_password_summary">Mask passwords (***) by default</string>
|
||||
<string name="colorize_password_title">Colorize passwords</string>
|
||||
<string name="colorize_password_summary">Colorize password characters by type</string>
|
||||
<string name="about">About</string>
|
||||
<string name="menu_change_key_settings">Change master key</string>
|
||||
<string name="copy_field">Copy of %1$s</string>
|
||||
@@ -603,6 +607,23 @@
|
||||
<string name="unit_kibibyte">KiB</string>
|
||||
<string name="unit_mebibyte">MiB</string>
|
||||
<string name="unit_gibibyte">GiB</string>
|
||||
<string name="entropy">Entropy: %1$s bit</string>
|
||||
<string name="entropy_high">Entropy: High</string>
|
||||
<string name="entropy_calculate">Entropy: Calculate…</string>
|
||||
<string name="at_least_one_char">At least one character from each</string>
|
||||
<string name="exclude_ambiguous_chars">Exclude ambiguous characters</string>
|
||||
<string name="consider_chars_filter">Consider characters</string>
|
||||
<string name="word_separator">Separator</string>
|
||||
<string name="ignore_chars_filter">Ignore characters</string>
|
||||
<string name="lower_case">lower case</string>
|
||||
<string name="upper_case">UPPER CASE</string>
|
||||
<string name="title_case">Title Case</string>
|
||||
<string name="character_count">Character count: %1$d</string>
|
||||
<string-array name="word_case_array">
|
||||
<item>@string/lower_case</item>
|
||||
<item>@string/upper_case</item>
|
||||
<item>@string/title_case</item>
|
||||
</string-array>
|
||||
<string-array name="timeout_options">
|
||||
<item>5 seconds</item>
|
||||
<item>10 seconds</item>
|
||||
|
||||
@@ -87,6 +87,22 @@
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/password">
|
||||
|
||||
<SwitchPreference
|
||||
android:key="@string/hide_password_key"
|
||||
android:title="@string/hide_password_title"
|
||||
android:summary="@string/hide_password_summary"
|
||||
android:defaultValue="@bool/hide_password_default"/>
|
||||
<SwitchPreference
|
||||
android:key="@string/colorize_password_key"
|
||||
android:title="@string/colorize_password_title"
|
||||
android:summary="@string/colorize_password_summary"
|
||||
android:defaultValue="@bool/colorize_password_default"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/other">
|
||||
|
||||
|
||||
@@ -93,32 +93,6 @@
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/password">
|
||||
|
||||
<SeekBarPreference
|
||||
android:key="@string/password_length_key"
|
||||
android:title="@string/password_size_title"
|
||||
android:summary="@string/password_size_summary"
|
||||
android:defaultValue="@string/default_password_length"
|
||||
app:min="@string/min_password_length"
|
||||
app:showSeekBarValue="true"
|
||||
android:max="@string/max_password_length" />
|
||||
<MultiSelectListPreference
|
||||
android:key="@string/list_password_generator_options_key"
|
||||
android:title="@string/list_password_generator_options_title"
|
||||
android:summary="@string/list_password_generator_options_summary"
|
||||
android:entries="@array/list_password_generator_options_entries"
|
||||
android:entryValues="@array/list_password_generator_options_values"
|
||||
android:defaultValue="@array/list_password_generator_options_default_values"/>
|
||||
<SwitchPreference
|
||||
android:key="@string/hide_password_key"
|
||||
android:title="@string/hide_password_title"
|
||||
android:summary="@string/hide_password_summary"
|
||||
android:defaultValue="@bool/hide_password_default"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/database_history">
|
||||
|
||||
|
||||
6
fastlane/metadata/android/en-US/changelogs/106.txt
Normal file
6
fastlane/metadata/android/en-US/changelogs/106.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
* Show visual password strength indicator with entropy #631 #869
|
||||
* Dynamically save password generator configuration #618 #696
|
||||
* Add advanced password filters #1052
|
||||
* Add editable chars fields #539
|
||||
* Add color for special password chars #454
|
||||
* Passphrase implementation #218
|
||||
6
fastlane/metadata/android/fr-FR/changelogs/106.txt
Normal file
6
fastlane/metadata/android/fr-FR/changelogs/106.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
* Affichage d'un indicateur visuel de la force du mot de passe avec entropie #631 #869
|
||||
* Sauvegarde dynamique de la configuration du générateur de mots de passe #618 #696
|
||||
* Ajout des filtres de mots de passe avancés #1052
|
||||
* Ajout de champs éditable de génération #539
|
||||
* Ajout des couleurs pour chaque caractère spécial de mots de passe #454
|
||||
* Phrases secrètes #218
|
||||
Reference in New Issue
Block a user