Compare commits

..

91 Commits
3.3.3 ... 3.4.0

Author SHA1 Message Date
J-Jamet
6b54dd9e0d Merge branch 'release/3.4.0' 2022-04-12 19:45:10 +02:00
J-Jamet
1f54e7752d Disable keyboard timeout by default 2022-04-12 15:04:12 +02:00
J-Jamet
6ac941f276 Upgrade to 3.4.0 2022-04-12 14:58:13 +02:00
J-Jamet
6c308483f7 Upgrade gradle and kotlin 2022-04-12 12:31:18 +02:00
J-Jamet
9d25fb74ec Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2022-04-12 12:14:23 +02:00
J-Jamet
d217b52744 Upgrade to 3.4.0_beta02
Fix #1282 with workaround
2022-04-12 12:12:05 +02:00
J-Jamet
319da4b174 Rollback openOutputStream in "rwt" 2022-04-12 11:32:49 +02:00
nautilusx
9bee467942 Translated using Weblate (German)
Currently translated at 99.6% (611 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-12 07:46:27 +02:00
VfBFan
44ac70fc97 Translated using Weblate (German)
Currently translated at 99.6% (611 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-12 07:46:26 +02:00
J-Jamet
d897611d62 Change main screenshot 2022-04-10 17:21:35 +02:00
J-Jamet
c2ae251e73 Change screenshots 2022-04-10 17:10:01 +02:00
J-Jamet
35ad285864 Fix education screen 2022-04-09 16:52:25 +02:00
J-Jamet
97bdae21eb Small change 2022-04-09 16:37:11 +02:00
J-Jamet
d6dc6e43c7 Update strings 2022-04-09 16:35:20 +02:00
Oğuz Ersen
01e6e530d5 Translated using Weblate (Turkish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-04-09 16:28:35 +02:00
Kunzisoft
9ec0178beb Translated using Weblate (French)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2022-04-09 16:28:35 +02:00
Hosted Weblate
d66f2f6d24 Merge branch 'origin/develop' into Weblate. 2022-04-09 16:15:08 +02:00
Milo Ivir
79cd4004cc Translated using Weblate (Croatian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-04-09 16:15:08 +02:00
VfBFan
991243e2df Translated using Weblate (German)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-09 16:15:07 +02:00
J-Jamet
b91cf11d86 Upgrade to 3.4 beta01 2022-04-09 16:14:15 +02:00
J-Jamet
d182ec09fa Upgrade CHANGELOG 2022-04-09 16:12:57 +02:00
J-Jamet
8641822358 Fix small bugs 2022-04-09 16:03:12 +02:00
J-Jamet
9665cbb428 Fix small visual bug 2022-04-08 18:37:06 +02:00
J-Jamet
a280dfaf3b Better magikeyboard views 2022-04-08 18:23:02 +02:00
J-Jamet
3e56521ea8 Empty Magikeyboard memory when the main service is killed #1261 2022-04-08 16:53:03 +02:00
SC
b205230ea9 Translated using Weblate (Portuguese)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2022-04-08 09:31:01 +02:00
Oğuz Ersen
51645ab126 Translated using Weblate (Turkish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-04-08 09:31:00 +02:00
Eric
5d04897e75 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-04-08 09:30:59 +02:00
Ihor Hordiichuk
1ac0ea5cc6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-04-08 09:30:59 +02:00
solokot
a07e8b51e5 Translated using Weblate (Russian)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-04-08 09:30:58 +02:00
Vitor Henrique
a81f0238f4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2022-04-08 09:30:57 +02:00
Matthaiks
2b81eb8ec7 Translated using Weblate (Polish)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-04-08 09:30:56 +02:00
Retrial
e5eb642781 Translated using Weblate (Greek)
Currently translated at 100.0% (613 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-04-08 09:30:56 +02:00
VfBFan
a4cbe25733 Translated using Weblate (German)
Currently translated at 99.8% (612 of 613 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-04-08 09:30:55 +02:00
J-Jamet
2042c85b22 Ask confirmation to lock if changes without save #970 2022-04-07 18:06:47 +02:00
J-Jamet
3149f8745c Fix passkey view 2022-04-07 17:39:46 +02:00
J-Jamet
15b9f1616f Fix keyboard search and selection 2022-04-07 14:28:45 +02:00
J-Jamet
94c02b7288 Fix selection mode instance issue 2022-04-07 13:26:07 +02:00
J-Jamet
7e70b59a59 Fix selection mode instance issue 2022-04-07 13:22:19 +02:00
Hosted Weblate
2c7f5e41ed Merge branch 'origin/develop' into Weblate. 2022-04-05 18:00:37 +02:00
J-Jamet
108b8df280 Fix CHANGELOG issue number 2022-04-05 15:32:42 +02:00
J-Jamet
553098f9be Manage package name from Magikeyboard #1010 2022-04-05 15:28:15 +02:00
J-Jamet
131eb78407 Setting to change the keyboard during a search #1254 2022-04-05 13:38:36 +02:00
J-Jamet
f956a279a5 Save search parameters #1254 2022-04-05 13:07:36 +02:00
J-Jamet
7150686b92 Fix search parameter parcelable 2022-04-05 11:59:02 +02:00
J-Jamet
4b1fb2c173 Upgrade CHANGELOG 2022-04-05 11:28:58 +02:00
J-Jamet
94464bf608 Save pass generator options in app.properties 2022-04-04 15:30:57 +02:00
J-Jamet
2faa88784a Update CHANGELOG 2022-04-04 15:09:32 +02:00
J-Jamet
e6607b53d8 Better search implementation #175 2022-04-04 15:08:11 +02:00
J-Jamet
3f6a6c864a Show TOTP in 3-digit grouping #1270 2022-04-04 13:25:01 +02:00
J-Jamet
30a578257d Update CHANGELOG 2022-04-04 13:07:38 +02:00
J-Jamet
8411134adf Save files with "wt" #1282 2022-04-04 12:21:35 +02:00
J-Jamet
f86a5d1a19 Fix small bug 2022-04-01 19:19:47 +02:00
J-Jamet
be72492537 Merge branch 'feature/Mnemonics' into develop 2022-04-01 19:14:07 +02:00
J-Jamet
76f9e8ec6e Generate passphrase #218 2022-04-01 19:08:50 +02:00
Milo Ivir
8fb1c44e58 Translated using Weblate (Croatian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-04-01 13:08:19 +02:00
J-Jamet
f607b35cf3 Change max slider to 64 2022-04-01 11:01:00 +02:00
J-Jamet
0e56bec35a Key generator as tabs 2022-04-01 10:51:38 +02:00
Hosted Weblate
c890d10114 Merge branch 'origin/develop' into Weblate. 2022-03-31 12:09:22 +02:00
Milo Ivir
dee2fe5ce7 Translated using Weblate (Croatian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2022-03-31 12:09:21 +02:00
J-Jamet
4a4d767bce Manage setting to hide password #696 2022-03-29 17:18:49 +02:00
J-Jamet
d57e0cf601 Add setting to colorize password 2022-03-29 16:41:23 +02:00
J-Jamet
7fa141dd1b Merge branch 'develop' into feature/Mnemonics 2022-03-29 16:01:50 +02:00
J-Jamet
c261a0cbca Smaller tab buttons 2022-03-29 16:01:20 +02:00
J-Jamet
66661cbd49 Add bold and change password colors 2022-03-29 15:54:37 +02:00
J-Jamet
100c126c3d Update CHANGELOG 2022-03-29 15:32:51 +02:00
J-Jamet
d466e3077d Add color for special password chars #454 2022-03-29 15:32:29 +02:00
J-Jamet
24587dc34e Small refactorization 2022-03-29 12:42:53 +02:00
J-Jamet
32cc57dd03 Add editable chars fields #539 2022-03-28 22:14:24 +02:00
J-Jamet
a55488846b Add advanced password filters #1052 2022-03-28 21:26:26 +02:00
J-Jamet
dcf61fd4e2 Update CHANGELOG 2022-03-28 16:34:37 +02:00
J-Jamet
5bf998468a Save password preferences dynamically 2022-03-28 16:24:25 +02:00
J-Jamet
01c9625c59 Dynamic filter change and fix bugs 2022-03-28 15:08:56 +02:00
Dixon Huang
772c378922 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2022-03-28 13:09:14 +02:00
J-Jamet
ee50a91379 Move entropy to the top 2022-03-27 21:40:13 +02:00
J-Jamet
9cfda3bad8 Fix small bugs 2022-03-27 21:33:44 +02:00
J-Jamet
aa19b08bd9 Fix password entropy and add chips 2022-03-27 20:41:51 +02:00
J-Jamet
87f69bb7e2 Entropy #869 2022-03-27 18:56:12 +02:00
J-Jamet
41c0aeedbe Show visual password strength indicator #631 2022-03-27 17:22:52 +02:00
Eric
3cbe53d76f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2022-03-27 00:27:21 +01:00
Ihor Hordiichuk
aed60d6c1e Translated using Weblate (Ukrainian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/uk/
2022-03-27 00:27:21 +01:00
Stephan Paternotte
be7d35490d Translated using Weblate (Dutch)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nl/
2022-03-27 00:27:20 +01:00
VfBFan
d0ea997c63 Translated using Weblate (German)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2022-03-27 00:27:20 +01:00
J-Jamet
1fecffeba2 Merge branch 'master' of github.com:Kunzisoft/KeePassDX 2022-03-26 16:28:39 +01:00
J-Jamet
76319a56a2 Merge tag '3.3.3' into develop
3.3.3
2022-03-26 16:27:23 +01:00
Oğuz Ersen
e9a1cfea11 Translated using Weblate (Turkish)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2022-03-26 04:15:30 +01:00
solokot
9115856d19 Translated using Weblate (Russian)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2022-03-26 04:15:30 +01:00
Matthaiks
8c45266c18 Translated using Weblate (Polish)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2022-03-26 04:15:29 +01:00
Retrial
6b4130df89 Translated using Weblate (Greek)
Currently translated at 100.0% (595 of 595 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2022-03-26 04:15:29 +01:00
Jérémy JAMET
7948358d85 Add spaces in issue template 2022-03-18 19:46:32 +01:00
Jérémy JAMET
96b82bb9b2 Remove "please report..." in issue template 2022-03-18 19:45:39 +01:00
121 changed files with 3504 additions and 1128 deletions

View File

@@ -8,9 +8,11 @@ assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
@@ -18,9 +20,11 @@ Steps to reproduce the behavior:
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**KeePass Database**
- Created with: [e.g Windows KeePass 2.42]
- Version: [e.g. 2]
- Location: [e.g. Remote file retrieved with GDrive app]
@@ -28,15 +32,18 @@ A clear and concise description of what you expected to happen.
- Size: [e.g. 150Mo]
- Contains attachment: [e.g. Yes]
**KeePassDX (please complete the following information):**
**KeePassDX:**
- Version: [e.g. 2.5.0.0beta23]
- Build: [e.g. Free]
- Language: [e.g. French]
**Android (please complete the following information):**
**Android:**
- Device: [e.g. GalaxyS8]
- Version: [e.g. 8.1]
**Additional context**
Add any other context about the problem here.
- Browser for Autofill: [e.g. Chrome version X]

View File

@@ -1,3 +1,13 @@
KeePassDX(3.4.0)
* Passphrase implementation #218
* Show visual password strength indicator with entropy #631 #869 #454 #1270
* Dynamically save password generator configuration #618 #696
* Add advanced password filters #1052 #448 #983 #271 #539
* Better search implementation #175 #1254 #1267
* Manage package name from Magikeyboard #1010 #1261
* Ask confirmation to lock if changes without save #970
* Fix small bugs #1282
KeePassDX(3.3.3)
* Fix shared otpauth link if database not open #1274
* Ellipsize attachment name #1253

View File

@@ -12,8 +12,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 15
targetSdkVersion 31
versionCode = 105
versionName = "3.3.3"
versionCode = 108
versionName = "3.4.0"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -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

View File

@@ -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" />
@@ -155,6 +158,7 @@
<activity
android:name="com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity"
android:theme="@style/Theme.Transparent"
android:excludeFromRecents="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
@@ -169,9 +173,6 @@
<data android:scheme="otpauth" android:host="hotp" />
</intent-filter>
</activity>
<activity
android:name="com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity"
android:theme="@style/Theme.Transparent" />
<activity
android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
android:label="@string/keyboard_setting_label"

View File

@@ -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()

View File

@@ -20,8 +20,11 @@
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
@@ -50,6 +53,17 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
val keySelectionBundle = intent.getBundleExtra(KEY_SELECTION_BUNDLE)
if (keySelectionBundle != null) {
// To manage package name
var searchInfo = SearchInfo()
keySelectionBundle.getParcelable<SearchInfo>(KEY_SEARCH_INFO)?.let { mSearchInfo ->
searchInfo = mSearchInfo
}
launch(database, searchInfo, true)
} else {
// To manage share
var sharedWebDomain: String? = null
var otpString: String? = null
@@ -86,11 +100,12 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
launch(database, searchInfo)
}
}
}
private fun launch(database: Database?,
searchInfo: SearchInfo) {
searchInfo: SearchInfo,
forceSelection: Boolean = false) {
if (!searchInfo.containsOnlyNullValues()) {
// Setting to integrate Magikeyboard
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
@@ -115,13 +130,14 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
.show()
}
} else if (searchShareForMagikeyboard) {
if (items.size == 1) {
if (items.size == 1 && !forceSelection) {
// Automatically populate keyboard
val entryPopulate = items[0]
populateKeyboardAndMoveAppToBackground(
this,
entryPopulate,
intent)
Log.e("TEST", "One item activity")
} else {
// Select the one we want
GroupActivity.launchForKeyboardSelectionResult(this,
@@ -176,9 +192,27 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
}
}
)
}
finish()
}
companion object {
private const val KEY_SELECTION_BUNDLE = "KEY_SELECTION_BUNDLE"
private const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
fun launch(context: Context,
searchInfo: SearchInfo? = null) {
val intent = Intent(context, EntrySelectionLauncherActivity::class.java).apply {
putExtra(KEY_SELECTION_BUNDLE, Bundle().apply {
putParcelable(KEY_SEARCH_INFO, searchInfo)
})
}
// New task needed because don't launch from an Activity context
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TASK
context.startActivity(intent)
}
}
}
fun populateKeyboardAndMoveAppToBackground(activity: Activity,

View File

@@ -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

View File

@@ -79,6 +79,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsActivity
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.*
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
@@ -182,6 +183,11 @@ class GroupActivity : DatabaseLockActivity(),
addSearch()
//loadGroup()
// Back to previous keyboard
if (PreferencesUtil.isKeyboardPreviousSearchEnable(this@GroupActivity)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
return true
}
@@ -200,7 +206,7 @@ class GroupActivity : DatabaseLockActivity(),
finishNodeAction()
if (mSearchState == null) {
mSearchState = SearchState(searchFiltersView?.searchParameters
?: SearchParameters(), 0)
?: PreferencesUtil.getDefaultSearchParameters(this), 0)
}
}
@@ -719,7 +725,7 @@ class GroupActivity : DatabaseLockActivity(),
val stringQuery = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
intent.action = Intent.ACTION_DEFAULT
intent.removeExtra(SearchManager.QUERY)
mSearchState = SearchState(SearchParameters().apply {
mSearchState = SearchState(PreferencesUtil.getDefaultSearchParameters(this).apply {
searchQuery = stringQuery
}, mSearchState?.firstVisibleItem ?: 0)
}
@@ -1126,6 +1132,7 @@ class GroupActivity : DatabaseLockActivity(),
finishNodeAction()
searchView?.setOnQueryTextListener(null)
searchFiltersView?.saveSearchParameters()
}
private fun addSearchQueryInSearchView(searchQuery: String) {

View File

@@ -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)
)
}
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2020 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
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
/**
* Activity to select entry in database and populate it in Magikeyboard
*/
class MagikeyboardLauncherActivity : DatabaseModeActivity() {
override fun applyCustomStyle(): Boolean {
return false
}
override fun finishActivityIfReloadRequested(): Boolean {
return true
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
SearchHelper.checkAutoSearchInfo(this,
database,
null,
{ _, _ ->
// Not called
// if items found directly returns before calling this activity
},
{ openedDatabase ->
// Select if not found
GroupActivity.launchForKeyboardSelectionResult(this, openedDatabase)
},
{
// Pass extra to get entry
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this)
}
)
finish()
}
}

View File

@@ -612,19 +612,24 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !readOnlyEducationPerformed) {
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(this)
val biometricPerformed =
(biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
if ((biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockButton != null
&& mPasswordActivityEducation.checkAndPerformedBiometricEducation(
&& advancedUnlockButton != null) {
mPasswordActivityEducation.checkAndPerformedBiometricEducation(
advancedUnlockButton!!,
{
startActivity(Intent(this, SettingsAdvancedUnlockActivity::class.java))
startActivity(
Intent(
this,
SettingsAdvancedUnlockActivity::class.java
)
)
},
{
})
}
}
} catch (ignored: Exception) {}
}
}

View File

@@ -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)
}
}
}
}
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
}
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -29,6 +29,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
@@ -431,7 +432,19 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
}
protected fun lockAndExit() {
// Ask confirmation if modification not saved
if (mDatabase?.isReadOnly == false
&& mDatabase?.dataModifiedSinceLastLoading == true
&& !PreferencesUtil.isAutoSaveDatabaseEnabled(this)) {
AlertDialog.Builder(this)
.setMessage(R.string.discard_changes)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.lock) { _, _ ->
sendBroadcast(Intent(LOCK_ACTION))
}.create().show()
} else {
sendBroadcast(Intent(LOCK_ACTION))
}
}
fun resetAppTimeout() {

View File

@@ -1,5 +1,6 @@
package com.kunzisoft.keepass.activities.legacy
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@@ -95,11 +96,9 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
private fun backToTheMainAppAndFinish() {
// To move the app in background and return to the main app
// Not visible as opened with FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
moveTaskToBack(true)
// To remove this instance in the OS app selector
Handler(Looper.getMainLooper()).postDelayed({
finish()
}, 500)
// Not finish() to prevent service kill
}
override fun onCreate(savedInstanceState: Bundle?) {

View File

@@ -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
}
}
}

View File

@@ -516,7 +516,7 @@ class NodesAdapter (private val context: Context,
null -> {}
}
holder?.otpToken?.apply {
text = otpElement?.token
text = otpElement?.tokenString
setTextSize(mTextSizeUnit, mOtpTokenTextDefaultDimension, mPrefSizeMultiplier)
}
holder?.otpContainer?.setOnClickListener {

View File

@@ -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())
}
}
}

View File

@@ -913,37 +913,9 @@ class Database {
try {
val saveUri = databaseCopyUri ?: this.fileUri
if (saveUri != null) {
if (saveUri.scheme == "file") {
saveUri.path?.let { filename ->
val tempFile = File("$filename.tmp")
var fileOutputStream: FileOutputStream? = null
try {
fileOutputStream = FileOutputStream(tempFile)
val pmo = mDatabaseKDB?.let { DatabaseOutputKDB(it, fileOutputStream) }
?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, fileOutputStream) }
pmo?.output()
} catch (e: Exception) {
throw IOException(e)
} finally {
fileOutputStream?.close()
}
// Force data to disk before continuing
try {
fileOutputStream?.fd?.sync()
} catch (e: SyncFailedException) {
// Ignore if fsync fails. We tried.
}
if (!tempFile.renameTo(File(filename))) {
throw IOException()
}
}
} else {
var outputStream: OutputStream? = null
try {
outputStream = contentResolver.openOutputStream(saveUri, "rwt")
outputStream = UriUtil.getUriOutputStream(contentResolver, saveUri)
outputStream?.let { definedOutputStream ->
val databaseOutput =
mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
@@ -960,7 +932,6 @@ class Database {
} finally {
outputStream?.close()
}
}
if (databaseCopyUri == null) {
this.dataModifiedSinceLastLoading = false
}

View File

@@ -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,

View File

@@ -181,7 +181,7 @@ class SearchHelper {
return false
// Exclude entry expired
if (searchParameters.excludeExpired) {
if (!searchParameters.searchInExpired) {
if (entry.isCurrentlyExpires)
return false
}
@@ -237,14 +237,20 @@ class SearchHelper {
return false
return if (searchParameters.isRegex) {
val regex = if (searchParameters.caseSensitive) {
searchParameters.searchQuery.toRegex(RegexOption.DOT_MATCHES_ALL)
searchParameters.searchQuery
.toRegex(RegexOption.DOT_MATCHES_ALL)
} else {
searchParameters.searchQuery
.toRegex(setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE))
}
regex.matches(stringToCheck)
} else {
stringToCheck.contains(searchParameters.searchQuery, !searchParameters.caseSensitive)
var searchFound = true
searchParameters.searchQuery.split(" ").forEach { word ->
searchFound = searchFound
&& stringToCheck.contains(word, !searchParameters.caseSensitive)
}
searchFound
}
}
}

View File

@@ -34,7 +34,7 @@ class SearchParameters() : Parcelable{
var searchInUsernames = true
var searchInPasswords = false
var searchInUrls = true
var excludeExpired = false
var searchInExpired = false
var searchInNotes = true
var searchInOTP = false
var searchInOther = true
@@ -49,11 +49,12 @@ class SearchParameters() : Parcelable{
constructor(parcel: Parcel) : this() {
searchQuery = parcel.readString() ?: searchQuery
caseSensitive = parcel.readByte() != 0.toByte()
isRegex = parcel.readByte() != 0.toByte()
searchInTitles = parcel.readByte() != 0.toByte()
searchInUsernames = parcel.readByte() != 0.toByte()
searchInPasswords = parcel.readByte() != 0.toByte()
searchInUrls = parcel.readByte() != 0.toByte()
excludeExpired = parcel.readByte() != 0.toByte()
searchInExpired = parcel.readByte() != 0.toByte()
searchInNotes = parcel.readByte() != 0.toByte()
searchInOTP = parcel.readByte() != 0.toByte()
searchInOther = parcel.readByte() != 0.toByte()
@@ -68,11 +69,12 @@ class SearchParameters() : Parcelable{
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(searchQuery)
parcel.writeByte(if (caseSensitive) 1 else 0)
parcel.writeByte(if (isRegex) 1 else 0)
parcel.writeByte(if (searchInTitles) 1 else 0)
parcel.writeByte(if (searchInUsernames) 1 else 0)
parcel.writeByte(if (searchInPasswords) 1 else 0)
parcel.writeByte(if (searchInUrls) 1 else 0)
parcel.writeByte(if (excludeExpired) 1 else 0)
parcel.writeByte(if (searchInExpired) 1 else 0)
parcel.writeByte(if (searchInNotes) 1 else 0)
parcel.writeByte(if (searchInOTP) 1 else 0)
parcel.writeByte(if (searchInOther) 1 else 0)

View File

@@ -18,6 +18,8 @@ package com.kunzisoft.keepass.magikeyboard;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_BACK_KEYBOARD;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_CHANGE_KEYBOARD;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_ENTRY;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_ENTRY_ALT;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_OTP;
import static com.kunzisoft.keepass.magikeyboard.MagikeyboardService.KEY_OTP_ALT;
@@ -1049,6 +1051,9 @@ public class KeyboardView extends View implements View.OnClickListener {
if (popupKey.codes[0] == KEY_BACK_KEYBOARD) {
mKeyboardActionListener.onKey(KEY_CHANGE_KEYBOARD, popupKey.codes);
return true;
} else if (popupKey.codes[0] == KEY_ENTRY) {
mKeyboardActionListener.onKey(KEY_ENTRY_ALT, popupKey.codes);
return true;
} else if (popupKey.codes[0] == KEY_OTP) {
mKeyboardActionListener.onKey(KEY_OTP_ALT, popupKey.codes);
return true;

View File

@@ -30,19 +30,27 @@ import android.view.*
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.PopupWindow
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity
import com.kunzisoft.keepass.activities.EntrySelectionLauncherActivity
import com.kunzisoft.keepass.adapters.FieldsAdapter
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.*
import java.util.*
class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionListener {
@@ -50,13 +58,19 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
private var mDatabase: Database? = null
private var keyboardView: KeyboardView? = null
private var entryContainer: View? = null
private var entryText: TextView? = null
private var databaseText: TextView? = null
private var databaseColorView: ImageView? = null
private var packageText: TextView? = null
private var keyboard: Keyboard? = null
private var keyboardEntry: Keyboard? = null
private var popupCustomKeys: PopupWindow? = null
private var fieldsAdapter: FieldsAdapter? = null
private var playSoundDuringCLick: Boolean = false
private var mFormPackageName: String? = null
private var lockReceiver: LockReceiver? = null
override fun onCreate() {
@@ -66,6 +80,7 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
mDatabaseTaskProvider?.registerProgressTask()
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
this.mDatabase = database
assignKeyboardView()
}
// Remove the entry and lock the keyboard when the lock signal is receive
lockReceiver = LockReceiver {
@@ -82,7 +97,11 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
override fun onCreateInputView(): View {
val rootKeyboardView = layoutInflater.inflate(R.layout.keyboard_container, null)
entryContainer = rootKeyboardView.findViewById(R.id.magikeyboard_entry_container)
entryText = rootKeyboardView.findViewById(R.id.magikeyboard_entry_text)
databaseText = rootKeyboardView.findViewById(R.id.magikeyboard_database_text)
databaseColorView = rootKeyboardView.findViewById(R.id.magikeyboard_database_color)
packageText = rootKeyboardView.findViewById(R.id.magikeyboard_package_text)
keyboardView = rootKeyboardView.findViewById(R.id.magikeyboard_view)
if (keyboardView != null) {
@@ -94,7 +113,7 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
.inflate(R.layout.keyboard_popup_fields, FrameLayout(context))
popupCustomKeys = PopupWindow(context).apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
width = WindowManager.LayoutParams.MATCH_PARENT
height = WindowManager.LayoutParams.WRAP_CONTENT
inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED
contentView = popupFieldsView
@@ -104,7 +123,7 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
fieldsAdapter = FieldsAdapter(this)
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
override fun onItemClick(item: Field) {
currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1)
currentInputConnection.commitText(getEntryInfo()?.getGeneratedFieldValue(item.name) , 1)
actionTabAutomatically()
}
}
@@ -114,17 +133,6 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close)
closeView.setOnClickListener { popupCustomKeys?.dismiss() }
// Remove entry info if the database is not loaded
// or if entry info timestamp is before database loaded timestamp
val databaseTime = mDatabase?.loadTimestamp
val entryTime = entryInfoTimestamp
if (mDatabase == null
|| mDatabase?.loaded != true
|| databaseTime == null
|| entryTime == null
|| entryTime < databaseTime) {
removeEntryInfo()
}
assignKeyboardView()
keyboardView?.onKeyboardActionListener = this
@@ -134,17 +142,27 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
return super.onCreateInputView()
}
private fun getEntryInfo(): EntryInfo? {
var entryInfoRetrieved: EntryInfo? = null
entryUUID?.let { entryId ->
entryInfoRetrieved = mDatabase
?.getEntryById(NodeIdUUID(entryId))
?.getEntryInfo(mDatabase)
}
return entryInfoRetrieved
}
private fun assignKeyboardView() {
dismissCustomKeys()
if (keyboardView != null) {
if (entryInfoKey != null) {
val entryInfo = getEntryInfo()
populateEntryInfoInView(entryInfo)
if (entryInfo != null) {
if (keyboardEntry != null) {
populateEntryInfoInView()
keyboardView?.keyboard = keyboardEntry
}
} else {
if (keyboard != null) {
hideEntryInfo()
keyboardView?.keyboard = keyboard
}
}
@@ -153,23 +171,45 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
keyboardView?.isHapticFeedbackEnabled = PreferencesUtil.isKeyboardVibrationEnable(this)
playSoundDuringCLick = PreferencesUtil.isKeyboardSoundEnable(this)
}
setDatabaseViews()
}
private fun populateEntryInfoInView() {
entryText?.visibility = View.VISIBLE
if (entryInfoKey?.title?.isNotEmpty() == true) {
entryText?.text = entryInfoKey?.title
private fun setDatabaseViews() {
if (mDatabase == null || mDatabase?.loaded != true) {
entryContainer?.visibility = View.GONE
} else {
hideEntryInfo()
entryContainer?.visibility = View.VISIBLE
}
databaseText?.text = mDatabase?.name ?: ""
val databaseColor = mDatabase?.customColor
if (databaseColor != null) {
databaseColorView?.drawable?.colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(databaseColor, BlendModeCompat.SRC_IN)
databaseColorView?.visibility = View.VISIBLE
} else {
databaseColorView?.visibility = View.GONE
}
}
private fun hideEntryInfo() {
private fun populateEntryInfoInView(entryInfo: EntryInfo?) {
if (entryInfo == null) {
entryText?.text = ""
entryText?.visibility = View.GONE
} else {
entryText?.text = entryInfo.getVisualTitle()
entryText?.visibility = View.VISIBLE
}
}
override fun onStartInputView(info: EditorInfo, restarting: Boolean) {
super.onStartInputView(info, restarting)
mFormPackageName = info.packageName
if (!mFormPackageName.isNullOrEmpty()) {
packageText?.text = mFormPackageName
packageText?.visibility = View.VISIBLE
} else {
packageText?.visibility = View.GONE
}
assignKeyboardView()
}
@@ -228,16 +268,17 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
(getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?)
?.showInputMethodPicker()
}
KEY_UNLOCK -> {
}
KEY_ENTRY -> {
// Stop current service and reinit entry
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
removeEntryInfo()
val intent = Intent(this, MagikeyboardLauncherActivity::class.java)
// New task needed because don't launch from an Activity context
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
var searchInfo: SearchInfo? = null
if (mFormPackageName != null) {
searchInfo = SearchInfo().apply {
applicationId = mFormPackageName
}
}
actionKeyEntry(searchInfo)
}
KEY_ENTRY_ALT -> {
actionKeyEntry()
}
KEY_LOCK -> {
removeEntryInfo()
@@ -245,12 +286,13 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
dismissCustomKeys()
}
KEY_USERNAME -> {
entryInfoKey?.username?.let { username ->
getEntryInfo()?.username?.let { username ->
currentInputConnection.commitText(username, 1)
}
actionTabAutomatically()
}
KEY_PASSWORD -> {
val entryInfoKey = getEntryInfo()
entryInfoKey?.password?.let { password ->
currentInputConnection.commitText(password, 1)
}
@@ -258,14 +300,14 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
actionGoAutomatically(!otpFieldExists)
}
KEY_OTP -> {
entryInfoKey?.let { entryInfo ->
getEntryInfo()?.let { entryInfo ->
currentInputConnection.commitText(
entryInfo.getGeneratedFieldValue(OTP_TOKEN_FIELD), 1)
}
actionGoAutomatically()
}
KEY_OTP_ALT -> {
entryInfoKey?.let { entryInfo ->
getEntryInfo()?.let { entryInfo ->
val otpToken = entryInfo.getGeneratedFieldValue(OTP_TOKEN_FIELD)
if (otpToken.isNotEmpty()) {
// Cut to fill each digit separatelyKeyEvent.KEYCODE_TAB
@@ -282,19 +324,20 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
actionGoAutomatically()
}
KEY_URL -> {
entryInfoKey?.url?.let { url ->
getEntryInfo()?.url?.let { url ->
currentInputConnection.commitText(url, 1)
}
actionGoAutomatically()
}
KEY_FIELDS -> {
entryInfoKey?.customFields?.let { customFields ->
getEntryInfo()?.customFields?.let { customFields ->
fieldsAdapter?.apply {
setFields(customFields.filter { it.name != OTP_TOKEN_FIELD})
notifyDataSetChanged()
}
}
popupCustomKeys?.showAtLocation(keyboardView, Gravity.END or Gravity.TOP, 0, 0)
popupCustomKeys?.showAtLocation(keyboardView,
Gravity.END or Gravity.TOP, 0, 180)
}
Keyboard.KEYCODE_DELETE -> {
inputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
@@ -303,6 +346,46 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
}
}
private fun actionKeyEntry(searchInfo: SearchInfo? = null) {
SearchHelper.checkAutoSearchInfo(this,
mDatabase,
searchInfo,
{ _, items ->
if (items.size == 1) {
if (entryUUID == null) {
// Automatically populate keyboard
removeEntryInfo()
addEntryAndLaunchNotificationIfAllowed(
this,
items[0],
true
)
assignKeyboardView()
} else {
// Choose another one
launchEntrySelection(null)
}
} else {
// Select if multiple
launchEntrySelection(searchInfo)
}
},
{ _ ->
// Select if not found
launchEntrySelection(searchInfo)
},
{
// Select if database not opened
launchEntrySelection(searchInfo)
}
)
}
private fun launchEntrySelection(searchInfo: SearchInfo?) {
removeEntryInfo()
EntrySelectionLauncherActivity.launch(this, searchInfo)
}
private fun actionTabAutomatically() {
if (PreferencesUtil.isAutoGoActionEnable(this))
currentInputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB))
@@ -359,23 +442,20 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
const val KEY_BACK_KEYBOARD = 600
const val KEY_CHANGE_KEYBOARD = 601
private const val KEY_UNLOCK = 610
private const val KEY_LOCK = 611
private const val KEY_ENTRY = 620
private const val KEY_USERNAME = 500
private const val KEY_PASSWORD = 510
const val KEY_LOCK = 611
const val KEY_ENTRY = 620
const val KEY_ENTRY_ALT = 621
const val KEY_USERNAME = 500
const val KEY_PASSWORD = 510
const val KEY_OTP = 515
const val KEY_OTP_ALT = 516
private const val KEY_URL = 520
private const val KEY_FIELDS = 530
const val KEY_URL = 520
const val KEY_FIELDS = 530
// TODO Retrieve entry info from id and service when database is open
private var entryInfoKey: EntryInfo? = null
private var entryInfoTimestamp: Long? = null
private var entryUUID: UUID? = null
private fun removeEntryInfo() {
entryInfoKey = null
entryInfoTimestamp = null
entryUUID = null
}
fun removeEntry(context: Context) {
@@ -384,10 +464,17 @@ class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionL
fun addEntryAndLaunchNotificationIfAllowed(context: Context, entry: EntryInfo, toast: Boolean = false) {
// Add a new entry
entryInfoKey = entry
entryInfoTimestamp = System.currentTimeMillis()
entryUUID = entry.id
// Launch notification if allowed
KeyboardEntryNotificationService.launchNotificationIfAllowed(context, entry, toast)
}
fun activatedInSettings(context: Context): Boolean {
return ContextCompat.getSystemService(context, InputMethodManager::class.java)
?.enabledInputMethodList
?.any {
it.packageName == context.packageName
} ?: false
}
}
}

View File

@@ -225,6 +225,14 @@ class EntryInfo : NodeInfo {
}
}
fun getVisualTitle(): String {
return title.ifEmpty {
url.ifEmpty {
username.ifEmpty { id.toString() }
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is EntryInfo) return false

View File

@@ -178,6 +178,14 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
}
}
/**
* Token with space each 3 digits
*/
val tokenString: String
get() {
return token.replace("...".toRegex(), "$0 ")
}
val secondsRemaining: Int
get() = otpModel.period - (System.currentTimeMillis() / 1000 % otpModel.period).toInt()

View File

@@ -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 = "-"
}
}

View File

@@ -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)
)
}
}
}
}
}

View File

@@ -20,11 +20,217 @@
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) {
@Throws(IllegalArgumentException::class)
fun generatePassword(length: Int,
upperCase: Boolean,
lowerCase: Boolean,
digits: Boolean,
minus: Boolean,
underline: Boolean,
space: Boolean,
specials: Boolean,
brackets: Boolean,
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))
}
// No option has been checked
if (!upperCase
&& !lowerCase
&& !digits
&& !minus
&& !underline
&& !space
&& !specials
&& !brackets
&& !extended
&& considerChars.isEmpty()) {
throw IllegalArgumentException(resources.getString(R.string.error_pass_gen_type))
}
// 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 generateRandomString(SecureRandom(), passwordFilters)
}
private fun generateRandomString(random: Random, passwordFilters: PasswordFilters): String {
val randomString = StringBuilder()
// 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--
}
}
}
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))
}
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 {
private const val UPPERCASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
private const val LOWERCASE_CHARS = "abcdefghijklmnopqrstuvwxyz"
private const val DIGIT_CHARS = "0123456789"
private const val MINUS_CHAR = "-"
private const val UNDERLINE_CHAR = "_"
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()
@@ -47,109 +253,51 @@ class PasswordGenerator(private val resources: Resources) {
return charSet.toString()
}
@Throws(IllegalArgumentException::class)
fun generatePassword(length: Int,
upperCase: Boolean,
lowerCase: Boolean,
digits: Boolean,
minus: Boolean,
underline: Boolean,
space: Boolean,
specials: Boolean,
brackets: Boolean,
extended: Boolean): String {
// Desired password length is 0 or less
if (length <= 0) {
throw IllegalArgumentException(resources.getString(R.string.error_wrong_length))
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
}
// No option has been checked
if (!upperCase
&& !lowerCase
&& !digits
&& !minus
&& !underline
&& !space
&& !specials
&& !brackets
&& !extended) {
throw IllegalArgumentException(resources.getString(R.string.error_pass_gen_type))
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
}
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)])
}
}
return buffer.toString()
}
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()
if (upperCase) {
charSet.append(UPPERCASE_CHARS)
}
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 charSet.toString()
}
companion object {
private const val UPPERCASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
private const val LOWERCASE_CHARS = "abcdefghijklmnopqrstuvwxyz"
private const val DIGIT_CHARS = "0123456789"
private const val MINUS_CHAR = "-"
private const val UNDERLINE_CHAR = "_"
private const val SPACE_CHAR = " "
private const val SPECIAL_CHARS = "!\"#$%&'*+,./:;=?@\\^`"
private const val BRACKET_CHARS = "[]{}()<>"
}
}

View File

@@ -23,7 +23,6 @@ import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat

View File

@@ -31,7 +31,10 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.password.PassphraseGenerator
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil
import java.util.*
@@ -111,6 +114,18 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.show_entry_colors_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 showUsernamesListEntries(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.list_entries_show_username_key),
@@ -192,15 +207,194 @@ 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),
return prefs.getStringSet(context.getString(R.string.password_generator_options_key),
HashSet(listOf(*context.resources
.getStringArray(R.array.list_password_generator_options_default_values))))
.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 getDefaultSearchParameters(context: Context): SearchParameters {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return SearchParameters().apply {
caseSensitive = prefs.getBoolean(context.getString(R.string.search_option_case_sensitive_key),
context.resources.getBoolean(R.bool.search_option_case_sensitive_default))
isRegex = prefs.getBoolean(context.getString(R.string.search_option_regex_key),
context.resources.getBoolean(R.bool.search_option_regex_default))
searchInTitles = prefs.getBoolean(context.getString(R.string.search_option_title_key),
context.resources.getBoolean(R.bool.search_option_title_default))
searchInUsernames = prefs.getBoolean(context.getString(R.string.search_option_username_key),
context.resources.getBoolean(R.bool.search_option_username_default))
searchInPasswords = prefs.getBoolean(context.getString(R.string.search_option_password_key),
context.resources.getBoolean(R.bool.search_option_password_default))
searchInUrls = prefs.getBoolean(context.getString(R.string.search_option_url_key),
context.resources.getBoolean(R.bool.search_option_url_default))
searchInExpired = prefs.getBoolean(context.getString(R.string.search_option_expired_key),
context.resources.getBoolean(R.bool.search_option_expired_default))
searchInNotes = prefs.getBoolean(context.getString(R.string.search_option_note_key),
context.resources.getBoolean(R.bool.search_option_note_default))
searchInOTP = prefs.getBoolean(context.getString(R.string.search_option_otp_key),
context.resources.getBoolean(R.bool.search_option_otp_default))
searchInOther = prefs.getBoolean(context.getString(R.string.search_option_other_key),
context.resources.getBoolean(R.bool.search_option_other_default))
searchInUUIDs = prefs.getBoolean(context.getString(R.string.search_option_uuid_key),
context.resources.getBoolean(R.bool.search_option_uuid_default))
searchInTags = prefs.getBoolean(context.getString(R.string.search_option_tag_key),
context.resources.getBoolean(R.bool.search_option_tag_default))
searchInCurrentGroup = prefs.getBoolean(context.getString(R.string.search_option_current_group_key),
context.resources.getBoolean(R.bool.search_option_current_group_default))
searchInSearchableGroup = prefs.getBoolean(context.getString(R.string.search_option_searchable_group_key),
context.resources.getBoolean(R.bool.search_option_searchable_group_default))
searchInRecycleBin = prefs.getBoolean(context.getString(R.string.search_option_recycle_bin_key),
context.resources.getBoolean(R.bool.search_option_recycle_bin_default))
searchInTemplates = prefs.getBoolean(context.getString(R.string.search_option_templates_key),
context.resources.getBoolean(R.bool.search_option_templates_default))
}
}
fun setDefaultSearchParameters(context: Context, searchParameters: SearchParameters) {
PreferenceManager.getDefaultSharedPreferences(context).edit().apply {
putBoolean(context.getString(R.string.search_option_case_sensitive_key),
searchParameters.caseSensitive)
putBoolean(context.getString(R.string.search_option_regex_key),
searchParameters.isRegex)
putBoolean(context.getString(R.string.search_option_title_key),
searchParameters.searchInTitles)
putBoolean(context.getString(R.string.search_option_username_key),
searchParameters.searchInUsernames)
putBoolean(context.getString(R.string.search_option_password_key),
searchParameters.searchInPasswords)
putBoolean(context.getString(R.string.search_option_url_key),
searchParameters.searchInUrls)
putBoolean(context.getString(R.string.search_option_expired_key),
searchParameters.searchInExpired)
putBoolean(context.getString(R.string.search_option_note_key),
searchParameters.searchInNotes)
putBoolean(context.getString(R.string.search_option_otp_key),
searchParameters.searchInOTP)
putBoolean(context.getString(R.string.search_option_other_key),
searchParameters.searchInOther)
putBoolean(context.getString(R.string.search_option_uuid_key),
searchParameters.searchInUUIDs)
putBoolean(context.getString(R.string.search_option_tag_key),
searchParameters.searchInTags)
putBoolean(context.getString(R.string.search_option_current_group_key),
searchParameters.searchInCurrentGroup)
putBoolean(context.getString(R.string.search_option_searchable_group_key),
searchParameters.searchInSearchableGroup)
putBoolean(context.getString(R.string.search_option_recycle_bin_key),
searchParameters.searchInRecycleBin)
putBoolean(context.getString(R.string.search_option_templates_key),
searchParameters.searchInTemplates)
apply()
}
}
fun isClipboardNotificationsEnable(context: Context): Boolean {
@@ -351,12 +545,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),
@@ -436,6 +624,7 @@ object PreferencesUtil {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.keyboard_search_share_key),
context.resources.getBoolean(R.bool.keyboard_search_share_default))
&& MagikeyboardService.activatedInSettings(context)
}
fun isKeyboardSaveSearchInfoEnable(context: Context): Boolean {
@@ -470,6 +659,12 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.keyboard_previous_database_credentials_default))
}
fun isKeyboardPreviousSearchEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.keyboard_previous_search_key),
context.resources.getBoolean(R.bool.keyboard_previous_search_default))
}
fun isKeyboardPreviousFillInEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.keyboard_previous_fill_in_key),
@@ -609,9 +804,6 @@ 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.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())
@@ -638,6 +830,7 @@ object PreferencesUtil {
context.getString(R.string.keyboard_key_vibrate_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_key_sound_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_database_credentials_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_fill_in_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.keyboard_previous_lock_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.autofill_close_database_key) -> editor.putBoolean(name, value.toBoolean())
@@ -653,6 +846,8 @@ object PreferencesUtil {
context.getString(R.string.setting_style_brightness_key) -> editor.putString(name, value)
context.getString(R.string.setting_icon_pack_choose_key) -> editor.putString(name, value)
context.getString(R.string.show_entry_colors_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.list_entries_show_username_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.list_groups_show_number_entries_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.show_otp_token_key) -> editor.putBoolean(name, value.toBoolean())
@@ -662,6 +857,14 @@ object PreferencesUtil {
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())
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.password_generator_consider_chars_key) -> editor.putString(name, value)
context.getString(R.string.password_generator_ignore_chars_key) -> editor.putString(name, value)
context.getString(R.string.passphrase_generator_word_count_key) -> editor.putInt(name, value.toInt())
context.getString(R.string.passphrase_generator_word_case_key) -> editor.putInt(name, value.toInt())
context.getString(R.string.passphrase_generator_separator_key) -> editor.putString(name, value)
context.getString(R.string.sort_node_key) -> editor.putString(name, value)
context.getString(R.string.sort_group_before_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.sort_ascending_key) -> editor.putBoolean(name, value.toBoolean())

View File

@@ -69,7 +69,18 @@ object UriUtil {
return null
return when {
isFileScheme(fileUri) -> fileUri.path?.let { FileOutputStream(it) }
isContentScheme(fileUri) -> contentResolver.openOutputStream(fileUri, "rwt")
isContentScheme(fileUri) -> {
try {
contentResolver.openOutputStream(fileUri, "wt")
} catch (e: FileNotFoundException) {
Log.e(TAG, "Unable to open stream in `wt` mode, retry in `rwt` mode.", e)
// https://issuetracker.google.com/issues/180526528
// Try with rwt to fix content provider issue
val outStream = contentResolver.openOutputStream(fileUri, "rwt")
Log.w(TAG, "`rwt` mode used.")
outStream
}
}
else -> null
}
}

View File

@@ -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)
}

View File

@@ -0,0 +1,156 @@
/*
* 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 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
}
}

View File

@@ -13,6 +13,7 @@ import androidx.core.view.isVisible
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.settings.PreferencesUtil
class SearchFiltersView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
@@ -30,7 +31,7 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
private var searchUsername: CompoundButton
private var searchPassword: CompoundButton
private var searchURL: CompoundButton
private var searchExpires: CompoundButton
private var searchExpired: CompoundButton
private var searchNotes: CompoundButton
private var searchOther: CompoundButton
private var searchUUID: CompoundButton
@@ -49,7 +50,7 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
this.searchInUsernames = searchUsername.isChecked
this.searchInPasswords = searchPassword.isChecked
this.searchInUrls = searchURL.isChecked
this.excludeExpired = !(searchExpires.isChecked)
this.searchInExpired = searchExpired.isChecked
this.searchInNotes = searchNotes.isChecked
this.searchInOther = searchOther.isChecked
this.searchInUUIDs = searchUUID.isChecked
@@ -69,12 +70,12 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchUsername.isChecked = value.searchInUsernames
searchPassword.isChecked = value.searchInPasswords
searchURL.isChecked = value.searchInUrls
searchExpires.isChecked = !value.excludeExpired
searchExpired.isChecked = value.searchInExpired
searchNotes.isChecked = value.searchInNotes
searchOther.isChecked = value.searchInOther
searchUUID.isChecked = value.searchInUUIDs
searchTag.isChecked = value.searchInTags
searchGroupSearchable.isChecked = value.searchInRecycleBin
searchGroupSearchable.isChecked = value.searchInSearchableGroup
searchRecycleBin.isChecked = value.searchInRecycleBin
searchTemplate.isChecked = value.searchInTemplates
mOnParametersChangeListener = tempListener
@@ -107,7 +108,7 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchUsername = findViewById(R.id.search_chip_username)
searchPassword = findViewById(R.id.search_chip_password)
searchURL = findViewById(R.id.search_chip_url)
searchExpires = findViewById(R.id.search_chip_expires)
searchExpired = findViewById(R.id.search_chip_expires)
searchNotes = findViewById(R.id.search_chip_note)
searchUUID = findViewById(R.id.search_chip_uuid)
searchOther = findViewById(R.id.search_chip_other)
@@ -116,6 +117,9 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchRecycleBin = findViewById(R.id.search_chip_recycle_bin)
searchTemplate = findViewById(R.id.search_chip_template)
// Set search
searchParameters = PreferencesUtil.getDefaultSearchParameters(context)
// Expand menu with button
searchExpandButton.setOnClickListener {
val isVisible = searchAdvanceFiltersContainer?.visibility == View.VISIBLE
@@ -153,8 +157,8 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchParameters.searchInUrls = isChecked
mOnParametersChangeListener?.invoke(searchParameters)
}
searchExpires.setOnCheckedChangeListener { _, isChecked ->
searchParameters.excludeExpired = !isChecked
searchExpired.setOnCheckedChangeListener { _, isChecked ->
searchParameters.searchInExpired = isChecked
mOnParametersChangeListener?.invoke(searchParameters)
}
searchNotes.setOnCheckedChangeListener { _, isChecked ->
@@ -249,4 +253,8 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
}
}
}
fun saveSearchParameters() {
PreferencesUtil.setDefaultSearchParameters(context, searchParameters)
}
}

View File

@@ -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)
}
}
}

View File

@@ -165,7 +165,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
setCopyButtonState(TextFieldView.ButtonState.GONE)
} else {
label = otpElement.type.name
value = otpElement.token
value = otpElement.tokenString
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { _, _ ->
mOnCopyActionClickListener?.invoke(Field(
@@ -175,7 +175,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
mLastOtpTokenView = this
mOtpRunnable = Runnable {
if (otpElement.shouldRefreshToken()) {
value = otpElement.token
value = otpElement.tokenString
}
if (mLastOtpTokenView == null) {
mOnOtpElementUpdated?.invoke(null)

View File

@@ -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
}
}

View File

@@ -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()
}

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -25,6 +25,9 @@
android:right="0dp"
android:top="12dp"
android:bottom="12dp"/>
<stroke android:width="1dp" android:color="@color/grey_blue_slight" />
<solid android:color="@color/grey_blue_slight"/>
<stroke
android:width="1dp"
android:color="@color/grey_blue_slight" />
<solid
android:color="@color/grey_blue_slight"/>
</shape>

View File

@@ -0,0 +1,31 @@
<?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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:radius="12dp" />
<padding
android:left="0dp"
android:right="0dp"
android:top="12dp"
android:bottom="12dp"/>
<stroke
android:width="1dp"
android:color="@color/grey_blue_slighter" />
</shape>

View File

@@ -138,8 +138,9 @@
<com.google.android.material.tabs.TabLayout
android:id="@+id/entry_content_tab"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="120dp"
android:layout_gravity="bottom|center_horizontal"
android:background="?attr/cardBackgroundTransparentColor"
app:tabIconTint="?android:attr/textColor"

View 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>

View 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>

View File

@@ -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
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
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_height="120dp"
android:background="?attr/colorPrimary" />
<androidx.cardview.widget.CardView
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>
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_marginTop="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
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" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/generate_password_button"
android:layout_margin="@dimen/button_margin"
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>
</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">
<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">
<TextView
android:id="@+id/length_label"
android:text="@string/length"
android:layout_height="match_parent"
android:layout_width="match_parent" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_length"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/length"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
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_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_height="wrap_content"
android:layout_below="@+id/length_label"
android:maxLines="1"
android:maxLength="3"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
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"/>
android:maxLength="3"
android:maxLines="1" />
</RelativeLayout>
<LinearLayout
android:id="@+id/RelativeLayout"
android:layout_height="match_parent"
<com.google.android.material.chip.ChipGroup
android:id="@+id/password_filters"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp">
android:layout_height="wrap_content"
app:chipSpacingVertical="16dp"
android:paddingTop="@dimen/default_margin"
android:paddingBottom="@dimen/default_margin">
<RelativeLayout
<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_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"
<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">
<CheckBox
android:id="@+id/cb_lowercase"
android:layout_width="wrap_content"
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
<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">
<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
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ignore_chars_filter"
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>
android:hint="@string/ignore_chars_filter"/>
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<RelativeLayout
<com.google.android.material.chip.ChipGroup
android:id="@+id/password_advanced"
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>
app:chipSpacingVertical="16dp"
android:paddingTop="@dimen/default_margin"
android:paddingBottom="@dimen/default_margin">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_space"
<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/space" />
<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_space"
android:layout_toRightOf="@+id/cb_space"
android:text="@string/visual_space" />
</RelativeLayout>
android:checked="true"
android:text="@string/exclude_ambiguous_chars"/>
<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_alignParentLeft="true"
android:layout_alignParentStart="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_brackets"
android:layout_toRightOf="@+id/cb_brackets"
android:text="@string/visual_brackets" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:id="@+id/cb_extended"
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: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>
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</ScrollView>
</LinearLayout>

View File

@@ -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>

View File

@@ -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>

View File

@@ -144,7 +144,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="5136" />
tools:text="513 651" />
<FrameLayout
android:layout_width="wrap_content"

View File

@@ -20,18 +20,79 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:layout_alignParentBottom="true"
android:paddingTop="4dp"
android:paddingBottom="8dp"
android:background="@color/grey_blue_deep"
android:orientation="vertical">
<LinearLayout
android:id="@+id/magikeyboard_entry_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginTop="4dp"
android:background="@drawable/key_border"
android:orientation="vertical">
<TextView
android:id="@+id/magikeyboard_entry_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textSize="16sp"
tools:text="Git account signup"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/white"/>
<LinearLayout
android:id="@+id/magikeyboard_database_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/magikeyboard_database_color"
android:layout_width="6dp"
android:layout_height="6dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_gravity="center"
android:contentDescription="@string/content_description_database_color"
android:src="@drawable/background_icon" />
<TextView
android:id="@+id/magikeyboard_database_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:textSize="11sp"
tools:text="Database A"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/grey_blue_slighter"/>
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/magikeyboard_package_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/grey_blue_deep"
android:gravity="center"
android:textColor="@color/white"/>
android:textSize="12sp"
tools:text="com.kunzisoft.keepass"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/grey_blue_slighter"/>
<com.kunzisoft.keepass.magikeyboard.KeyboardView
android:id="@+id/magikeyboard_view"
style="@style/KeepassDXStyle.Keyboard"

View File

@@ -34,12 +34,12 @@
android:id="@+id/keyboard_popup_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:contentDescription="@string/content_description_keyboard_close_fields"
android:layout_centerVertical="true"
android:layout_margin="4dp"
android:padding="8dp"
android:background="@drawable/key_background"
android:contentDescription="@string/content_description_keyboard_close_fields"
android:src="@drawable/ic_close_white_24dp" />
</RelativeLayout>

View File

@@ -17,19 +17,20 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/keyboard_popup_field_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/key_background"
android:layout_margin="5dp"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:layout_gravity="center"
android:layout_margin="4dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
tools:text="Sample"
android:textColor="@color/white"/>
</RelativeLayout>

View File

@@ -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"

View 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>

View 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>

View File

@@ -199,7 +199,7 @@
<string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</string>
<string name="set_autofill_service_title">Standarddienst für automatisches Ausfüllen festlegen</string>
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um Formulare schnell in anderen Apps auszufüllen</string>
<string name="autofill_select_entry">Eintrag auswählen…</string>
<string name="autofill_select_entry">Eintrag auswählen </string>
<string name="clipboard">Zwischenablage</string>
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
<string name="biometric_delete_all_key_summary">Alle Verschlüsselungsschlüssel löschen, die mit der modernen Entsperrerkennung zusammenhängen</string>
@@ -252,7 +252,7 @@
<string name="education_donation_summary">Mithelfen, um Stabilität und Sicherheit zu verbessern sowie weitere Funktionen zu ermöglichen.</string>
<string name="html_text_ad_free">Anders als viele andere Passwortmanager ist dieser &lt;strong&gt;werbefrei&lt;/strong&gt;, &lt;strong&gt;quelloffen&lt;/strong&gt; und unter einer &lt;strong&gt;Copyleft-Lizenz&lt;/strong&gt;. Es werden keine persönlichen Daten gesammelt, in welcher Form auch immer, unabhängig von der verwendeten Version (kostenlos oder Pro).</string>
<string name="html_text_buy_pro">Mit dem Kauf der Pro-Version erhalten Sie Zugriff auf diesen &lt;strong&gt;visuellen Stil&lt;/strong&gt; und unterstützen insbesondere &lt;strong&gt;die Umsetzung gemeinschaftlicher Projekte.&lt;/strong&gt;</string>
<string name="html_text_feature_generosity">Dieser &lt;strong&gt;visuelle Stil&lt;/strong&gt; wurde wegen Ihrer Großzügigkeit freigeschaltet.</string>
<string name="html_text_feature_generosity">Dieser <strong>visuelle Stil</strong> ist dank Ihrer Großzügigkeit verfügbar.</string>
<string name="html_text_donation">Um unsere Freiheit zu bewahren und immer aktiv zu bleiben, zählen wir auf Ihren &lt;strong&gt;Beitrag.&lt;/strong&gt;</string>
<string name="html_text_dev_feature">Diese Funktion ist &lt;strong&gt;in Entwicklung&lt;/strong&gt; und erfordert &lt;strong&gt;Ihren Beitrag&lt;/strong&gt;, um bald verfügbar zu sein.</string>
<string name="html_text_dev_feature_buy_pro">Durch den Kauf der &lt;strong&gt;Pro-Version&lt;/strong&gt;,</string>
@@ -317,7 +317,7 @@
<string name="show_recent_files_summary">Speicherort zuletzt verwendeter Datenbanken anzeigen</string>
<string name="hide_broken_locations_title">Defekte Datenbankverknüpfungen ausblenden</string>
<string name="hide_broken_locations_summary">Nicht funktionierende Verknüpfungen in der Liste der zuletzt verwendeten Datenbanken ausblenden</string>
<string name="do_not_kill_app">App nicht beenden </string>
<string name="do_not_kill_app">App nicht beenden </string>
<string name="lock_database_back_root_summary">Datenbank sperren, wenn auf dem Hauptbildschirm der Zurück-Button gedrückt wird</string>
<string name="clear_clipboard_notification_title">Beim Schließen löschen</string>
<string name="recycle_bin">Papierkorb</string>
@@ -455,7 +455,7 @@
<string name="warning_database_link_revoked">Zugriff auf die Datei durch den Dateimanager widerrufen</string>
<string name="error_label_exists">Diese Bezeichnung existiert bereits.</string>
<string name="keyboard_search_share_summary">Beim Teilen einer URL mit KeePassDX die Einträge nach dieser URL filtern</string>
<string name="keyboard_search_share_title">Gemeinsame Infos durchsuchen</string>
<string name="keyboard_search_share_title">Gemeinsame Infos auswählen</string>
<string name="autofill_block_restart">Starten Sie die Anwendung, die das Formular enthält, neu, um die Sperrung zu aktivieren.</string>
<string name="autofill_block">Automatisches Ausfüllen sperren</string>
<string name="autofill_web_domain_blocklist_summary">Liste der Domains, auf denen ein automatisches Ausfüllen verhindert wird</string>
@@ -470,8 +470,8 @@
<string name="keyboard_change">Tastatur wechseln</string>
<string name="keyboard_previous_fill_in_title">Auto-Key-Aktion</string>
<string name="keyboard_previous_database_credentials_title">Datenbank-Anmeldebildschirm</string>
<string name="keyboard_previous_fill_in_summary">Nach Ausführung der automatischen Tastenaktion automatisch zur vorherigen Tastatur wechseln</string>
<string name="keyboard_previous_database_credentials_summary">Automatisches Zurückschalten zur vorherigen Tastatur auf dem Datenbank-Anmeldebildschirm</string>
<string name="keyboard_previous_fill_in_summary">Nach dem Ausführen der automatischen Tastenaktion automatisch zur vorherigen Tastatur wechseln</string>
<string name="keyboard_previous_database_credentials_summary">Auf dem Datenbank-Anmeldebildschirm automatisch zur vorherigen Tastatur wechseln</string>
<string name="education_add_attachment_summary">Laden Sie einen Anhang für Ihren Eintrag hoch, um wichtige externe Daten zu speichern.</string>
<string name="content_description_credentials_information">Anmeldeinformationen</string>
<string name="data">Daten</string>
@@ -492,7 +492,7 @@
<string name="show_uuid_title">UUID anzeigen</string>
<string name="autofill_read_only_save">Das Speichern von Daten ist für eine als schreibgeschützt geöffnete Datenbank nicht zulässig.</string>
<string name="autofill_close_database_title">Datenbank schließen</string>
<string name="keyboard_previous_lock_summary">Nach dem Sperren der Datenbank automatisch zur vorherigen Tastatur zurückschalten</string>
<string name="keyboard_previous_lock_summary">Nach dem Sperren der Datenbank automatisch zur vorherigen Tastatur wechseln</string>
<string name="keyboard_previous_lock_title">Datenbank sperren</string>
<string name="notification">Benachrichtigung</string>
<string name="biometric_security_update_required">Biometrisches Sicherheitsupdate erforderlich.</string>
@@ -507,10 +507,10 @@
<string name="autofill_save_search_info_title">Suchinformationen speichern</string>
<string name="autofill_close_database_summary">Datenbank nach Auswahl eines Eintrags zum automatischen Ausfüllen schließen</string>
<string name="keyboard_save_search_info_summary">Nachdem eine URL mit KeePassDX geteilt und ein Eintrag gewählt wurde, wird versucht, sich diesen Eintrag für die weitere Nutzung zu merken</string>
<string name="keyboard_save_search_info_title">Gemeinsam genutzte Informationen speichern</string>
<string name="keyboard_save_search_info_title">Gemeinsame Informationen speichern</string>
<string name="warning_empty_recycle_bin">Sollen alle ausgewählten Knoten wirklich aus dem Papierkorb gelöscht werden\?</string>
<string name="error_field_name_already_exists">Der Feldname existiert bereits.</string>
<string name="advanced_unlock_prompt_store_credential_message">Warnung: Sie müssen sich immer noch an Ihr Masterpasswort erinnern, wenn Sie die moderne Entsperrerkennung verwenden.</string>
<string name="advanced_unlock_prompt_store_credential_message">Sie müssen sich immer noch an Ihre Anmeldedaten erinnern, wenn Sie die moderne Entsperrerkennung verwenden.</string>
<string name="open_advanced_unlock_prompt_store_credential">Zum Speichern der Anmeldeinformationen Dialog zum modernen Entsperren öffnen</string>
<string name="open_advanced_unlock_prompt_unlock_database">Dialog zum modernen Entsperren der Datenbank öffnen</string>
<string name="menu_keystore_remove_key">Schlüssel für modernes Entsperren löschen</string>
@@ -633,10 +633,33 @@
<string name="search_filters">Suchfilter</string>
<string name="current_group">Aktuelle Gruppe</string>
<string name="case_sensitive">Groß-/Kleinschreibung beachten</string>
<string name="menu_merge_from">Zusammenführen von </string>
<string name="menu_save_copy_to">Kopie speichern unter </string>
<string name="menu_merge_from">Zusammenführen von </string>
<string name="menu_save_copy_to">Kopie speichern unter </string>
<string name="content_description_nav_header">Navigationskopfzeile</string>
<string name="navigation_drawer_close">Navigationsleiste schließen</string>
<string name="navigation_drawer_open">Navigationsleiste öffnen</string>
<string name="expired">Abgelaufen</string>
<string name="warning_database_already_opened">Eine Datenbank ist bereits geöffnet, schließen Sie diese, um die neue zu öffnen</string>
<string name="advanced_unlock_keystore_warning">Diese Funktion speichert verschlüsselte Anmeldedaten im sicheren Schlüsselspeicher Ihres Geräts.
\n
\nAbhängig von der nativen API-Implementierung des Betriebssystems ist sie möglicherweise nicht voll funktionsfähig.
\nÜberprüfen Sie die Kompatibilität und Sicherheit des Schlüsselspeichers mit dem Hersteller Ihres Geräts und dem Ersteller des von Ihnen verwendeten ROMs.</string>
<string name="content_description_passphrase_word_count">Passphrase-Wortanzahl</string>
<string name="passphrase">Passphrase</string>
<string name="colorize_password_title">Passwörter einfärben</string>
<string name="colorize_password_summary">Passwortzeichen nach Typ einfärben</string>
<string name="keyboard_previous_search_title">Suchbildschirm</string>
<string name="keyboard_previous_search_summary">Auf dem Suchbildschirm automatisch zur vorherigen Tastatur wechseln</string>
<string name="entropy">Entropie: %1$s Bit</string>
<string name="entropy_high">Entropie: Hoch</string>
<string name="entropy_calculate">Entropie: Berechnen </string>
<string name="at_least_one_char">Mindestens ein Zeichen von jedem</string>
<string name="consider_chars_filter">Zeichen berücksichtigen</string>
<string name="word_separator">Trennzeichen</string>
<string name="ignore_chars_filter">Zeichen ignorieren</string>
<string name="lower_case">Kleinbuchstaben</string>
<string name="upper_case">GROẞBUCHSTABEN</string>
<string name="character_count">Anzahl der Zeichen: %1$d</string>
<string name="exclude_ambiguous_chars">Mehrdeutige Zeichen ausschließen</string>
<string name="title_case">Groß-/Kleinschreibung des Titels</string>
</resources>

View File

@@ -492,7 +492,7 @@
<string name="error_registration_read_only">Η αποθήκευση ενός νέου αντικειμένου δεν επιτρέπεται σε μια βάση δεδομένων μόνο για ανάγνωση</string>
<string name="error_field_name_already_exists">Το όνομα πεδίου υπάρχει ήδη.</string>
<string name="advanced_unlock_prompt_store_credential_title">Προηγμένο ξεκλείδωμα αναγνώρισης</string>
<string name="advanced_unlock_prompt_store_credential_message">Προειδοποίηση: Θα πρέπει να θυμάστε τον κύριο κωδικό πρόσβασης εάν χρησιμοποιείτε προηγμένο ξεκλείδωμα αναγνώρισης.</string>
<string name="advanced_unlock_prompt_store_credential_message">Πρέπει ακόμα να θυμάστε τα κύρια διαπιστευτήριά σας εάν χρησιμοποιείτε σύνθετη αναγνώριση ξεκλειδώματος.</string>
<string name="advanced_unlock_prompt_extract_credential_title">Ανοίξτε τη βάση δεδομένων με προηγμένο ξεκλείδωμα αναγνώρισης</string>
<string name="open_advanced_unlock_prompt_store_credential">Ανοίξτε τη προηγμένη προτροπή ξεκλειδώματος για αποθήκευση διαπιστευτηρίων</string>
<string name="open_advanced_unlock_prompt_unlock_database">Ανοίξτε τη προηγμένη προτροπή ξεκλειδώματος για να ξεκλειδώσετε τη βάση δεδομένων</string>
@@ -626,4 +626,27 @@
<string name="regex">Κοινή έκφραση</string>
<string name="menu_save_copy_to">Αποθήκευση αντιγράφου στο …</string>
<string name="expired">Ληγμένο</string>
<string name="warning_database_already_opened">Μια βάση δεδομένων είναι ήδη ανοιχτή, κλείστε την πρώτα για να ανοίξετε τη νέα</string>
<string name="advanced_unlock_keystore_warning">Αυτή η δυνατότητα θα αποθηκεύσει κρυπτογραφημένα δεδομένα διαπιστευτηρίων στο ασφαλές KeyStore της συσκευής σας.
\n
\nΑνάλογα με την εγγενή υλοποίηση API του λειτουργικού συστήματος, ενδέχεται να μην είναι πλήρως λειτουργικό.
\nΕλέγξτε τη συμβατότητα και την ασφάλεια του KeyStore με τον κατασκευαστή της συσκευής σας και τον δημιουργό της ROM που χρησιμοποιείτε.</string>
<string name="passphrase">Συνθηματική φράση</string>
<string name="colorize_password_summary">Χρωματίστε τους χαρακτήρες του κωδικού πρόσβασης ανά τύπο</string>
<string name="keyboard_previous_search_title">Οθόνη αναζήτησης</string>
<string name="keyboard_previous_search_summary">Αυτόματη εναλλαγή στο προηγούμενο πληκτρολόγιο στην οθόνη αναζήτησης</string>
<string name="entropy">Εντροπία: %1$s bit</string>
<string name="entropy_calculate">Εντροπία: Υπολογισμός…</string>
<string name="title_case">Γράμματα Τίτλου</string>
<string name="colorize_password_title">Χρωματίστε τους κωδικούς πρόσβασης</string>
<string name="entropy_high">Εντροπία: Υψηλή</string>
<string name="consider_chars_filter">Σκεφτείτε χαρακτήρες</string>
<string name="exclude_ambiguous_chars">Εξαιρέστε διφορούμενους χαρακτήρες</string>
<string name="content_description_passphrase_word_count">Πλήθος λέξεων συνθηματικής φράσης</string>
<string name="at_least_one_char">Τουλάχιστον ένας χαρακτήρας από το καθένα</string>
<string name="ignore_chars_filter">Αγνοήστε χαρακτήρες</string>
<string name="lower_case">πεζά γράμματα</string>
<string name="upper_case">ΚΕΦΑΛΑΙΑ ΓΡΑΜΜΑΤΑ</string>
<string name="word_separator">Διαχωριστής</string>
<string name="character_count">Αριθμός χαρακτήρων: %1$d</string>
</resources>

View File

@@ -462,8 +462,8 @@
<string name="autofill_web_domain_blocklist_title">Liste de blocage de domaine Web</string>
<string name="autofill_application_id_blocklist_summary">Liste de blocage qui empêche le remplissage automatique des applications</string>
<string name="autofill_application_id_blocklist_title">Liste de blocage dapplication</string>
<string name="keyboard_search_share_summary">Lorsqu\'une adresse web est partagée avec KeePassDX, filtrer automatiquement les entrées contenant le domaine</string>
<string name="keyboard_search_share_title">Rechercher les informations partagées</string>
<string name="keyboard_search_share_summary">Lorsque des infos sont partagées avec KeePassDX, filtre les entrées en utilisant ces infos pour alimenter le Magiclavier</string>
<string name="keyboard_search_share_title">Sélectionner les infos partagées</string>
<string name="filter">Filtre</string>
<string name="subdomain_search_summary">Recherche des domaines Web avec des contraintes de sous-domaines</string>
<string name="subdomain_search_title">Recherche de sous-domaine</string>
@@ -490,13 +490,13 @@
<string name="autofill_read_only_save">Lenregistrement des données nest pas autorisé pour une base de données ouverte en lecture seule.</string>
<string name="autofill_ask_to_save_data_summary">Demande de sauvegarde des données à la fin du remplissage d\'un formulaire</string>
<string name="autofill_ask_to_save_data_title">Demander à enregistrer des données</string>
<string name="autofill_save_search_info_summary">Essayer denregistrer les informations de recherche lors de la sélection manuelle dune entrée</string>
<string name="autofill_save_search_info_summary">Essaye denregistrer les informations de recherche lors de la sélection manuelle dune entrée</string>
<string name="autofill_save_search_info_title">Enregistrer les infos de recherche</string>
<string name="autofill_close_database_summary">Ferme la base de données après une sélection de remplissage automatique</string>
<string name="autofill_close_database_title">Fermer la base de données</string>
<string name="keyboard_previous_lock_summary">Revient automatiquement au clavier précédent après le verrouillage de la base de données</string>
<string name="keyboard_previous_lock_title">Verrouiller la base de données</string>
<string name="keyboard_save_search_info_summary">Essayer denregistrer l\'association entre une adresse web partagée à KeePassDX et l\'entrée sélectionnée manuellement</string>
<string name="keyboard_save_search_info_summary">Après avoir partagé des infos avec KeePassDX, lorsqu\'une entrée est sélectionnée, essaye d\'enregistrer les infos dans l\'entrée pour faciliter les utilisations futures</string>
<string name="keyboard_save_search_info_title">Enregistrer les infos partagées</string>
<string name="notification">Notification</string>
<string name="biometric_security_update_required">Mise à jour de sécurité biométrique requise.</string>
@@ -641,4 +641,22 @@
\n
\nSelon l\'implémentation de l\'API native du système d\'exploitation, il se peut qu\'elle ne soit pas entièrement fonctionnelle.
\nVérifiez la compatibilité et la sécurité du KeyStore auprès du fabricant de votre appareil et du créateur de la ROM que vous utilisez.</string>
<string name="content_description_passphrase_word_count">Nombre de mots</string>
<string name="keyboard_previous_search_title">Écran de recherche</string>
<string name="entropy">Entropie : %1$s bit</string>
<string name="entropy_high">Entropie : Haute</string>
<string name="at_least_one_char">Au moins un caractère de chaque</string>
<string name="exclude_ambiguous_chars">Exclure les caractères ambigus</string>
<string name="ignore_chars_filter">Ignorer les caractères</string>
<string name="lower_case">minuscule</string>
<string name="upper_case">MAJUSCULE</string>
<string name="title_case">1ère Lettre Majuscule</string>
<string name="keyboard_previous_search_summary">Retour automatique au clavier précédent sur l\'écran de recherche</string>
<string name="colorize_password_title">Coloriser les mots de passe</string>
<string name="entropy_calculate">Entropie : Calcule…</string>
<string name="passphrase">Phrase de passe</string>
<string name="colorize_password_summary">Coloriser les caractères du mot de passe par type</string>
<string name="consider_chars_filter">Considérer les caractères</string>
<string name="word_separator">Séparateur</string>
<string name="character_count">Nombre de caractères : %1$d</string>
</resources>

View File

@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--><resources>
<string name="about_description">Android implementacija KeePass upravitelja lozinki</string>
<string name="about_description">Android implementacija KeePass upravlja lozinki</string>
<string name="accept">Prihvati</string>
<string name="add_entry">Dodaj unos</string>
<string name="edit_entry">Uredi unos</string>
@@ -31,13 +31,13 @@
<string name="application">Aplikacija</string>
<string name="brackets">Zagrade</string>
<string name="extended_ASCII">Prošireni ASCII</string>
<string name="file_manager_install_description">Za stvaranje, otvaranje i spremanje datoteka baze podataka potreban je upravitelj datoteka koji prihvaća zahtjeve ACTION_CREATE_DOCUMENT i ACTION_OPEN_DOCUMENT.</string>
<string name="file_manager_install_description">Za stvaranje, otvaranje i spremanje datoteka baze podataka potreban je upravlj datoteka koji prihvaća zahtjeve ACTION_CREATE_DOCUMENT i ACTION_OPEN_DOCUMENT.</string>
<string name="allow">Dozvoli</string>
<string name="clipboard_cleared">Međuspremnik ispražnjen</string>
<string name="clipboard_error_title">Greška međuspremnika</string>
<string name="clipboard_error">Neki uređaji neće dopustiti aplikacijama korištenje međuspremnika.</string>
<string name="clipboard_error_clear">Nije moguće isprazniti međuspremnik</string>
<string name="clipboard_timeout_summary">Trajanje pohrane u međuspremniku (ako uređaj to podržava)</string>
<string name="clipboard_timeout_summary">Trajanje spremišta u međuspremniku (ako uređaj to podržava)</string>
<string name="content_description_background">Pozadina</string>
<string name="content_description_open_file">Otvori datoteku</string>
<string name="content_description_add_node">Dodaj čvor</string>
@@ -85,7 +85,7 @@
<string name="entry_url">URL</string>
<string name="entry_user_name">Korisničko ime</string>
<string name="error_nokeyfile">Odaberi datoteku ključa.</string>
<string name="error_pass_gen_type">Barem jedan tip generiranja lozinke mora biti odabran.</string>
<string name="error_pass_gen_type">Barem jedna vrsta generiranja lozinke mora biti odabrana.</string>
<string name="error_pass_match">Lozinke se ne poklapaju.</string>
<string name="error_wrong_length">Upiši pozitivan cijeli broj u polje „Duljina”.</string>
<string name="error_otp_secret_key">Tajni ključ mora biti u Base32 formatu.</string>
@@ -93,7 +93,7 @@
<string name="error_otp_digits">Token mora sadržavati %1$d do %2$d znamenki.</string>
<string name="field_name">Ime polja</string>
<string name="field_value">Vrijednost polja</string>
<string name="file_browser">Upravitelj datoteka</string>
<string name="file_browser">Upravlj datoteka</string>
<string name="generate_password">Generiraj lozinku</string>
<string name="hint_conf_pass">Potvrdi lozinku</string>
<string name="hint_generated_password">Generirana lozinka</string>
@@ -258,7 +258,7 @@
<string name="menu_file_selection_read_only">Zaštićeno od pisanja</string>
<string name="progress_title">Rad u tijeku …</string>
<string name="read_only">Zaštićeno od pisanja</string>
<string name="read_only_warning">Ovisno o upravitelju datoteka, KeePassDX možda neće moći pisati u tvoje spremište.</string>
<string name="read_only_warning">Ovisno o upravlju datoteka, KeePassDX možda neće moći pisati u tvoje spremište.</string>
<string name="contains_duplicate_uuid_procedure">Riješiti problem generiranjem novih UUID-ova za duplikate\?</string>
<string name="root">Korijen</string>
<string name="memory_usage_explanation">Količina memorije koju će koristiti funkcija za generiranje ključeva.</string>
@@ -372,7 +372,7 @@
<string name="html_text_dev_feature_upgrade">Redovito aktualiziraj aplikaciju instaliranjem najnovijih verzija.</string>
<string name="autofill_block_restart">Za aktiviranje blokiranja, ponovo pokreni aplikaciju koja sadrži obrazac.</string>
<string name="education_sort_summary">Odaberi način razvrstavanja unosa i grupa.</string>
<string name="warning_database_link_revoked">Pristup datoteci koju je opozvao upravitelj datoteka</string>
<string name="warning_database_link_revoked">Pristup datoteci opozvan od upravljača datoteka</string>
<string name="download_attachment">Preuzmi %1$s</string>
<string name="keyboard_entry_timeout_title">Istek vremena</string>
<string name="auto_focus_search_summary">Pokreni pretragu prilikom otvaranja baze podataka</string>
@@ -387,7 +387,7 @@
<string name="education_entry_new_field_title">Dodaj prilagođena polja</string>
<string name="education_lock_summary">Zaključaj bazu podataka brzo, aplikaciju možeš postaviti tako da bazu nakon nekog vremena zaključa i kad se ekran isključi.</string>
<string name="show_recent_files_summary">Prikaži mjesto nedavnih baza podataka</string>
<string name="education_advanced_unlock_summary">Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom.</string>
<string name="education_advanced_unlock_summary">Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom ili podacima za prijavu uređaja.</string>
<string name="html_text_donation">Kako bismo zadržali našu slobodu i uvijek bili aktivni, računamo na tvoj&lt;strong&gt;doprinos.&lt;/strong&gt;</string>
<string name="kdf_explanation">Za stvaranje ključa za algoritam šifriranja, glavni ključ se transformira pomoću funkcije za generiranje ključeva koja sadrži slučajnu komponentu.</string>
<string name="lock_database_back_root_summary">Zaključaj bazu podataka kad korisnik pritisne gumb za natrag na glavnom ekranu</string>
@@ -471,7 +471,7 @@
<string name="autofill_read_only_save">Spremanje podataka nije dopušteno za bazu podataka koja je otvorena u zaštićenom stanju.</string>
<string name="show_uuid_summary">Prikazuje UUID povezan s unosom ili grupom</string>
<string name="show_uuid_title">Prikaži UUID</string>
<string name="autofill_ask_to_save_data_summary">Zatraži spremanje podataka kad se obrazac provjeri</string>
<string name="autofill_ask_to_save_data_summary">Zatraži spremanje podataka kad se obrazac ispuni</string>
<string name="autofill_ask_to_save_data_title">Zatraži spremanje podataka</string>
<string name="autofill_save_search_info_title">Spremi podatke pretrage</string>
<string name="autofill_close_database_summary">Zatvori bazu podataka nakon odabira automatskog ispunjavanja</string>
@@ -488,9 +488,9 @@
<string name="search_mode">Modus pretrage</string>
<string name="error_field_name_already_exists">Ime polja već postoji.</string>
<string name="advanced_unlock_delete_all_key_warning">Izbrisati sve ključeve šifriranja povezane s naprednim prepoznavanjem otključavanja\?</string>
<string name="credential_before_click_advanced_unlock_button">Upiši lozinku i zatim pritisni ovaj gumb.</string>
<string name="credential_before_click_advanced_unlock_button">Upiši lozinku, zatim pritisni ovaj gumb.</string>
<string name="advanced_unlock_prompt_extract_credential_title">Otvori bazu podataka pomoću naprednog prepoznavanja otključavanja</string>
<string name="advanced_unlock_prompt_store_credential_message">Upozorenje: Ako koristiš prepoznavanje naprednog otključavanja morat ćeš i dalje znati glavnu lozinku.</string>
<string name="advanced_unlock_prompt_store_credential_message">Ako koristiš prepoznavanje naprednog otključavanja morat ćeš i dalje znati glavnu lozinku.</string>
<string name="menu_keystore_remove_key">Izbriši ključ naprednog otključavanja</string>
<string name="advanced_unlock_prompt_store_credential_title">Napredno prepoznavanje otključavanja</string>
<string name="advanced_unlock_prompt_not_initialized">Nije moguće pokrenuti prozor naprednog otključavanja.</string>
@@ -521,9 +521,9 @@
<string name="autofill_inline_suggestions_title">Umetnuti prijedlozi</string>
<string name="autofill_inline_suggestions_keyboard">Prijedlozi za automatsko popunjavanje su dodani.</string>
<string name="autofill_inline_suggestions_summary">Pokušaj prikazivanja prijedloga za automatsko popunjavanje izravno s kompatibilne tipkovnice</string>
<string name="warning_database_revoked">Pristup datoteci koju je opozvao upravljač datoteka. Zatvori bazu podataka i ponovo je otvori s njezinog mjesta.</string>
<string name="warning_database_revoked">Pristup datoteci opozvan od upravljača datoteka. Zatvori bazu podataka i ponovo je otvori s njezinog mjesta.</string>
<string name="warning_database_info_changed_options">Sjedini podatke, prepiši vanjske promjene spremanjem baze podataka ili je ponovo učitaj s najnovijim promjenama.</string>
<string name="warning_database_info_changed">Podaci u datoteci tvoje baze podataka izmijenjeni su izvan programa.</string>
<string name="warning_database_info_changed">Podaci u datoteci tvoje baze podataka izmijenjeni su izvan aplikacije.</string>
<string name="menu_reload_database">Ponovo učitaj podatke</string>
<string name="error_otp_type">Ovaj obrazac ne prepoznaje postojeću vrstu jednokratne lozinke. Provjera valjanosti možda više neće pravilno generirati token.</string>
<string name="unit_gibibyte">GiB</string>
@@ -594,7 +594,7 @@
<string name="menu_external_icon">Vanjska ikona</string>
<string name="seed">Tajna fraza</string>
<string name="hint_icon_name">Ime ikone</string>
<string name="warning_exact_alarm">Nisi dopustio/la programu da koristi točan alarm. Kao rezultat toga, značajke koje zahtijevaju tajmer neće biti obavljene s točnim vremenom.</string>
<string name="warning_exact_alarm">Nisi dopustio/la aplikaciji da koristi točan alarm. Zbog toga funkije koje zahtijevaju tajmer neće biti obavljene s točnim vremenom.</string>
<string name="permission">Dozvola</string>
<string name="tags">Oznake</string>
<string name="menu_merge_database">Sjedini podatke</string>
@@ -607,4 +607,40 @@
<string name="content_description_entry_foreground_color">Prednja boja unosa</string>
<string name="content_description_database_color">Boja baze podataka</string>
<string name="content_description_entry_background_color">Pozadinska boja unosa</string>
<string name="expired">Isteklo</string>
<string name="search_filters">Filtri pretrage</string>
<string name="current_group">Trenutačna grupa</string>
<string name="case_sensitive">Razlikovanje velikih/malih slova</string>
<string name="searchable">Pretraživo</string>
<string name="inherited">Naslijedi</string>
<string name="custom_data">Prilagođeni podaci</string>
<string name="auto_type_sequence">Slijed automatskog popunjavanja</string>
<string name="regex">Regularni izraz</string>
<string name="menu_merge_from">Sjedini iz …</string>
<string name="menu_save_copy_to">Spremi kopiju u …</string>
<string name="navigation_drawer_open">Ploča navigacije otvorena</string>
<string name="navigation_drawer_close">Ploča navigacije zatvorena</string>
<string name="advanced_unlock_keystore_warning">Ova će funckija spremiti šifrirane podatke za prijavu u sigurni KeyStore tvog uređaja.
\n
\nOvisno o izvornoj API implementaciji operacijskog sustava, funcionalsnost možda neće biti potpuna.
\nProvjeri kompatibilnost i sigurnost KeyStorea kod proizvođača tvog uređaja i kreatora ROM-a koji koristiš.</string>
<string name="warning_database_already_opened">Baza podataka je već otvorena. Za otvaranje nove, najprije je zatvori</string>
<string name="content_description_passphrase_word_count">Broj riječi dugačke lozinke</string>
<string name="colorize_password_title">Oboji lozinke</string>
<string name="colorize_password_summary">Oboje znakove lozinke prema vrsti</string>
<string name="passphrase">Dugačka lozinka</string>
<string name="entropy">Entropija: %1$s bit</string>
<string name="entropy_high">Entropija: visoka</string>
<string name="entropy_calculate">Entropija: Izračunaj …</string>
<string name="at_least_one_char">Barem jedan znak od svakog</string>
<string name="exclude_ambiguous_chars">Isključi višeznačne znakove</string>
<string name="consider_chars_filter">Uzmi u obzir znakove</string>
<string name="keyboard_previous_search_title">Ekran pretrage</string>
<string name="keyboard_previous_search_summary">Automatski se vrati na prethodnu tipkovnicu na ekranu pretrage</string>
<string name="word_separator">Znak razdvajanja</string>
<string name="ignore_chars_filter">Zanemari znakove</string>
<string name="lower_case">mala slova</string>
<string name="title_case">Velika Početna Slova</string>
<string name="character_count">Broj znakova: %1$d</string>
<string name="upper_case">VELIKA SLOVA</string>
</resources>

View File

@@ -168,7 +168,7 @@
<string name="parallelism_explanation">Het aantal parallellen (aantal threads) dat de afleidingsfunctie mag gebruiken.</string>
<string name="sort_menu">Sorteren</string>
<string name="sort_ascending">Laagste eerst ↓</string>
<string name="sort_groups_before">Groepen vóór losse items sorteren</string>
<string name="sort_groups_before">Groepen vooraan plaatsen</string>
<string name="sort_recycle_bin_bottom">Prullenbak onderaan plaatsen</string>
<string name="sort_title">Titel</string>
<string name="sort_username">Gebruikersnaam</string>
@@ -286,7 +286,7 @@
<string name="style_choose_summary">Thema gebruikt in de app</string>
<string name="icon_pack_choose_title">Pictogrammenpakket</string>
<string name="icon_pack_choose_summary">Gebruikt pictogrammenpakket</string>
<string name="build_label">Build %1$s</string>
<string name="build_label">Editie %1$s</string>
<string name="keyboard_name">Magikeyboard</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
<string name="keyboard_setting_label">Instellingen Magikeyboard</string>
@@ -517,7 +517,7 @@
<string name="advanced_unlock_invalid_key">Kan de geavanceerde ontgrendelingssleutel niet lezen. Verwijder deze en herhaal de herkenningsprocedure voor het ontgrendelen.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Databasegegevens uitpakken met geavanceerde ontgrendelingsgegevens</string>
<string name="advanced_unlock_prompt_extract_credential_title">Open database met geavanceerde ontgrendelingsherkenning</string>
<string name="advanced_unlock_prompt_store_credential_message">Waarschuwing: je moet nog steeds je hoofdwachtwoord onthouden als je geavanceerde ontgrendelingsherkenning gebruikt.</string>
<string name="advanced_unlock_prompt_store_credential_message">Je moet nog steeds je hoofdwachtwoord onthouden als je geavanceerde ontgrendelingsherkenning gebruikt.</string>
<string name="advanced_unlock_prompt_store_credential_title">Geavanceerde ontgrendelingsherkenning</string>
<string name="open_advanced_unlock_prompt_store_credential">Open de geavanceerde ontgrendelingsprompt om inloggegevens op te slaan</string>
<string name="open_advanced_unlock_prompt_unlock_database">Open de geavanceerde ontgrendelingsprompt om de database te ontgrendelen</string>
@@ -627,4 +627,9 @@
<string name="menu_merge_from">Samenvoegen van…</string>
<string name="navigation_drawer_open">Navigatiepaneel openen</string>
<string name="navigation_drawer_close">Navigatiepaneel sluiten</string>
<string name="advanced_unlock_keystore_warning">Deze functie slaat versleutelde gegevens op in de beveiligde KeyStore van uw apparaat.
\n
\nAfhankelijk van de specifieke API-implementatie van het besturingssysteem, is dit mogelijk niet volledig functioneel.
\nControleer de compatibiliteit en beveiliging van de KeyStore met de fabrikant van uw apparaat en de maker van de ROM die u gebruikt.</string>
<string name="warning_database_already_opened">Er is al een database geopend, sluit deze eerst alvorens een nieuwe te openen</string>
</resources>

View File

@@ -504,7 +504,7 @@
<string name="advanced_unlock_not_recognized">Nie można rozpoznać zaawansowanego wydruku odblokowującego</string>
<string name="advanced_unlock_invalid_key">Nie można odczytać zaawansowanego klucza odblokowującego. Usuń go i powtórz procedurę rozpoznawania odblokowania.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Wyodrębnij poświadczenia bazy danych z zaawansowanymi danymi odblokowującymi</string>
<string name="advanced_unlock_prompt_store_credential_message">Ostrzeżenie: Jeśli używasz zaawansowanego rozpoznawania odblokowania, nadal musisz zapamiętać hasło główne.</string>
<string name="advanced_unlock_prompt_store_credential_message">Jeśli używasz zaawansowanego rozpoznawania odblokowania, nadal musisz zapamiętać główne dane uwierzytelniające.</string>
<string name="advanced_unlock_prompt_store_credential_title">Zaawansowane rozpoznawanie odblokowania</string>
<string name="menu_keystore_remove_key">Usuń zaawansowany klucz odblokowujący</string>
<string name="education_advanced_unlock_summary">Połącz swoje hasło ze zeskanowanymi danymi biometrycznymi lub danymi logowania urządzenia, aby szybko odblokować bazę danych.</string>
@@ -627,4 +627,27 @@
<string name="searchable">Przeszukiwalne</string>
<string name="menu_save_copy_to">Zapisz kopię w…</string>
<string name="expired">Wygasłe</string>
<string name="warning_database_already_opened">Baza danych jest już otwarta, należy ją najpierw zamknąć, aby otworzyć nową</string>
<string name="advanced_unlock_keystore_warning">Ta funkcja umożliwia przechowywanie zaszyfrowanych danych uwierzytelniających w bezpiecznym magazynie kluczy urządzenia.
\n
\nW zależności od konkretnej implementacji interfejsu API systemu operacyjnego, może nie być w pełni funkcjonalna.
\nSprawdź kompatybilność i bezpieczeństwo magazynu kluczy u producenta urządzenia i twórcy używanego ROM-u.</string>
<string name="passphrase">Fraza hasła</string>
<string name="colorize_password_summary">Koloruj znaki hasła według typu</string>
<string name="keyboard_previous_search_title">Ekran wyszukiwania</string>
<string name="entropy">Entropia: %1$s bitowa</string>
<string name="entropy_high">Entropia: Wysoka</string>
<string name="entropy_calculate">Entropia: Oblicz…</string>
<string name="at_least_one_char">Co najmniej po jednym znaku z każdego</string>
<string name="exclude_ambiguous_chars">Wyklucz niejednoznaczne znaki</string>
<string name="consider_chars_filter">Rozważ znaki</string>
<string name="word_separator">Separator</string>
<string name="lower_case">małe litery</string>
<string name="upper_case">DUŻE LITERY</string>
<string name="title_case">Jak Nazwy Własne</string>
<string name="character_count">Liczba znaków: %1$d</string>
<string name="content_description_passphrase_word_count">Liczba słów frazy hasła</string>
<string name="ignore_chars_filter">Ignoruj znaki</string>
<string name="colorize_password_title">Koloruj hasła</string>
<string name="keyboard_previous_search_summary">Automatycznie przełączaj z powrotem do poprzedniej klawiatury na ekranie wyszukiwania</string>
</resources>

View File

@@ -521,7 +521,7 @@
<string name="advanced_unlock_invalid_key">Não é possível ler a chave de desbloqueio avançada. Por favor, apague-a e repita o procedimento de reconhecimento de desbloqueio.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Extraia credencial de banco de dados com dados de desbloqueio avançado</string>
<string name="advanced_unlock_prompt_extract_credential_title">Banco de dados aberto com reconhecimento avançado de desbloqueio</string>
<string name="advanced_unlock_prompt_store_credential_message">Aviso: você ainda precisa lembrar sua senha mestra se usar o reconhecimento de desbloqueio avançado.</string>
<string name="advanced_unlock_prompt_store_credential_message">Você ainda precisa lembrar sua credencial principal se usar o reconhecimento de desbloqueio avançado.</string>
<string name="advanced_unlock_prompt_store_credential_title">Reconhecimento de desbloqueio avançado</string>
<string name="open_advanced_unlock_prompt_store_credential">Abra o prompt de desbloqueio avançado para armazenar as credenciais</string>
<string name="open_advanced_unlock_prompt_unlock_database">Abra o prompt de desbloqueio avançado para desbloquear o banco de dados</string>
@@ -626,4 +626,27 @@
<string name="search_filters">Filtros de pesquisa</string>
<string name="case_sensitive">Sensível a maiúsculas e minúsculas</string>
<string name="current_group">Grupo atual</string>
<string name="content_description_passphrase_word_count">Número de palavras da senha</string>
<string name="warning_database_already_opened">Um banco de dados já está aberto, feche-o primeiro para abrir o novo</string>
<string name="keyboard_previous_search_title">Tela de busca</string>
<string name="entropy_calculate">Entropia: Calcular…</string>
<string name="at_least_one_char">Pelo menos um caractere de cada</string>
<string name="exclude_ambiguous_chars">Excluir caracteres ambíguos</string>
<string name="consider_chars_filter">Considerar caracteres</string>
<string name="upper_case">MAIÚSCULAS</string>
<string name="title_case">Capitalização de Título</string>
<string name="colorize_password_title">Colorir senhas</string>
<string name="colorize_password_summary">Colorir caracteres da senha por tipo</string>
<string name="passphrase">Frase secreta</string>
<string name="entropy">Entropia: %1$s bit</string>
<string name="entropy_high">Entropia: Alta</string>
<string name="keyboard_previous_search_summary">Voltar automaticamente para o teclado anterior na tela de pesquisa</string>
<string name="advanced_unlock_keystore_warning">Esse recurso armazenará dados de credenciais criptografados no KeyStore seguro do seu dispositivo.
\n
\nDependendo da implementação da API nativa do sistema operacional, pode não ser totalmente funcional.
\nVerifique a compatibilidade e segurança do KeyStore com o fabricante do seu dispositivo e o criador da ROM que você está usando.</string>
<string name="ignore_chars_filter">Ignorar caracteres</string>
<string name="word_separator">Separador</string>
<string name="lower_case">minúsculas</string>
<string name="character_count">Número de caracteres: %1$d</string>
</resources>

View File

@@ -455,7 +455,7 @@
<string name="advanced_unlock_invalid_key">Não foi possível ler a chave de desbloqueio avançada. Por favor, elimine-a e repita o procedimento de reconhecimento de desbloqueio.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Extrair credencial da base de dados com dados de desbloqueio avançados</string>
<string name="advanced_unlock_prompt_extract_credential_title">Abrir base de dados com reconhecimento de desbloqueio avançado</string>
<string name="advanced_unlock_prompt_store_credential_message">Advertência: ainda tem de se lembrar da sua palavra-passe principal se usar o reconhecimento de desbloqueio avançado.</string>
<string name="advanced_unlock_prompt_store_credential_message">Ainda te de se lembrar da sua credencial principal se usar o reconhecimento de desbloqueio avançado.</string>
<string name="advanced_unlock_prompt_store_credential_title">Reconhecimento de desbloqueio avançado</string>
<string name="open_advanced_unlock_prompt_store_credential">Abrir o alerta de desbloqueio avançado para armazenar as credenciais</string>
<string name="open_advanced_unlock_prompt_unlock_database">Abrir o alerta de desbloqueio avançado para desbloquear a base de dados</string>
@@ -604,4 +604,27 @@
<string name="inherited">Herdar</string>
<string name="menu_merge_from">Unir de…</string>
<string name="expired">Expirou</string>
<string name="passphrase">Frase-chave</string>
<string name="colorize_password_summary">Colorir caracteres da palavra-passe por tipo</string>
<string name="advanced_unlock_keystore_warning">Esta funcionalidade irá armazenar dados encriptados de credenciais na KeyStore segura do seu dispositivo.
\n
\nDependendo da implementação da API nativa do sistema operativo, esta pode não estar totalmente funcional.
\nVerifique a compatibilidade e segurança da KeyStore com o fabricante do seu dispositivo e com o criador da ROM que está a utilizar.</string>
<string name="keyboard_previous_search_title">Ecrã de pesquisa</string>
<string name="entropy_high">Entropia: alta</string>
<string name="entropy_calculate">Entropia: calcular…</string>
<string name="consider_chars_filter">Considerar caracteres</string>
<string name="word_separator">Separador</string>
<string name="ignore_chars_filter">Ignorar caracteres</string>
<string name="lower_case">minúsculas</string>
<string name="upper_case">MAIÚSCULAS</string>
<string name="title_case">Capitalização de Título</string>
<string name="character_count">Número de caracteres: %1$d</string>
<string name="entropy">Entropia: %1$s bit</string>
<string name="content_description_passphrase_word_count">Número de palavras da frase-chave</string>
<string name="colorize_password_title">Colorir palavras-passe</string>
<string name="warning_database_already_opened">Já está aberta uma base de dados, feche-a primeiro para poder abrir uma nova</string>
<string name="keyboard_previous_search_summary">Mudar automaticamente para o teclado anterior no ecrã de pesquisa</string>
<string name="at_least_one_char">Pelo menos um caractere de cada um</string>
<string name="exclude_ambiguous_chars">Excluir caracteres ambíguos</string>
</resources>

View File

@@ -500,7 +500,7 @@
<string name="advanced_unlock_invalid_key">Невозможно прочитать ключ расширенной разблокировки. Удалите его и повторите процедуру распознавания разблокировки.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Извлекать учётные данные базы с использованием расширенной разблокировки</string>
<string name="advanced_unlock_prompt_extract_credential_title">Открыть базу с расширенным распознаванием разблокировки</string>
<string name="advanced_unlock_prompt_store_credential_message">Предупреждение: даже при использовании расширенной разблокировки вам всё равно необходимо помнить главный пароль.</string>
<string name="advanced_unlock_prompt_store_credential_message">При использовании расширенной разблокировки вам всё равно необходимо помнить основные учётные данные.</string>
<string name="open_advanced_unlock_prompt_store_credential">Открыть запрос расширенной разблокировки для сохранения учётных данных</string>
<string name="open_advanced_unlock_prompt_unlock_database">Открыть запрос расширенной разблокировки для разблокировки базы</string>
<string name="advanced_unlock_delete_all_key_warning">Удалить все ключи шифрования, связанные с распознаванием расширенной разблокировки\?</string>
@@ -626,4 +626,27 @@
<string name="current_group">Текущая группа</string>
<string name="custom_data">Пользовательские данные</string>
<string name="expired">Срок действия истёк</string>
<string name="warning_database_already_opened">База уже открыта, закройте её, прежде чем открыть новую</string>
<string name="advanced_unlock_keystore_warning">Эта функция будет хранить зашифрованные учётные данные в безопасном хранилище ключей вашего устройства.
\n
\nВ зависимости от конкретной реализации API операционной системы, он может быть не полностью функциональным.
\nПроверьте совместимость и безопасность хранилища ключей через производителя устройства и разработчика используемого ПЗУ.</string>
<string name="passphrase">Кодовая фраза</string>
<string name="colorize_password_title">Раскрашивать пароли</string>
<string name="colorize_password_summary">Разделять цветом символы пароля по типу</string>
<string name="keyboard_previous_search_title">Экран поиска</string>
<string name="entropy">Энтропия: %1$s бит</string>
<string name="entropy_high">Энтропия: высокая</string>
<string name="word_separator">Разделитель</string>
<string name="character_count">Количество символов: %1$d</string>
<string name="ignore_chars_filter">Игнорировать символы</string>
<string name="title_case">С Заглавных</string>
<string name="at_least_one_char">Не менее, чем по одному символу из каждого</string>
<string name="consider_chars_filter">Включить символы</string>
<string name="content_description_passphrase_word_count">Количество слов в кодовой фразе</string>
<string name="keyboard_previous_search_summary">Автоматически переключаться на предыдущую клавиатуру на экране поиска</string>
<string name="lower_case">нижний регистр</string>
<string name="exclude_ambiguous_chars">Исключить неоднозначные символы</string>
<string name="entropy_calculate">Энтропия: вычисление…</string>
<string name="upper_case">ВЕРХНИЙ РЕГИСТР</string>
</resources>

View File

@@ -429,8 +429,8 @@
<string name="autofill_preference_title">Otomatik doldurma ayarları</string>
<string name="warning_database_link_revoked">Dosyaya erişim dosya yöneticisi tarafından iptal edildi</string>
<string name="error_label_exists">Bu etiket zaten var.</string>
<string name="keyboard_search_share_summary">Bir URL\'yi KeePassDX ile paylaşırken o URL domainini kullananları filtrele</string>
<string name="keyboard_search_share_title">Paylaşılan bilgileri ara</string>
<string name="keyboard_search_share_summary">KeePassDX ile bilgi paylaşırken, Magickeyboard\'a iletmek için bu bilgiyi kullanarak girdileri filtreleyin</string>
<string name="keyboard_search_share_title">Paylaşılan bilgileri seç</string>
<string name="filter">Filtre</string>
<string name="autofill_block_restart">Engellemeyi etkinleştirmek için formu içeren uygulamayı yeniden başlatın.</string>
<string name="autofill_block">Otomatik doldurmayı engelle</string>
@@ -474,7 +474,7 @@
<string name="autofill_close_database_title">Veri tabanını kapat</string>
<string name="keyboard_previous_lock_summary">Veri tabanını kilitledikten sonra otomatik olarak önceki klavyeye dön</string>
<string name="keyboard_previous_lock_title">Veri tabanını kilitle</string>
<string name="keyboard_save_search_info_summary">Bir URL\'yi KeePassDX ile paylaştıktan sonra, eğer bir girdi seçiliyse, o girdiyi sonraki kullanımlar için hatırla</string>
<string name="keyboard_save_search_info_summary">KeePassDX ile bilgi paylaştıktan sonra, bir girdi seçildiğinde, gelecekte daha kolay kullanmak için bilgileri girdiye kaydetmeyi deneyin</string>
<string name="keyboard_save_search_info_title">Paylaşılan bilgileri kaydet</string>
<string name="notification">Bildirim</string>
<string name="biometric_security_update_required">Biyometrik güvenlik güncellemesi gerekli.</string>
@@ -497,7 +497,7 @@
<string name="advanced_unlock_invalid_key">Gelişmiş kilit açma anahtarı okunamıyor. Lütfen silin ve kilit açma tanıma işlemini tekrarlayın.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Veri tabanı kimlik bilgilerini gelişmiş kilit açma özelliğiyle çıkarın</string>
<string name="advanced_unlock_prompt_extract_credential_title">Veri tabanını gelişmiş kilit açma tanıma ile aç</string>
<string name="advanced_unlock_prompt_store_credential_message">Uyarı: Gelişmiş kilit açma tanıma kullanırsanız, yine de ana parolanızı hatırlamanız gerekmektedir.</string>
<string name="advanced_unlock_prompt_store_credential_message">Gelişmiş kilit açma tanıma kullanırsanız, yine de ana kimlik bilgilerinizi hatırlamanız gerekmektedir.</string>
<string name="advanced_unlock_prompt_store_credential_title">Gelişmiş kilit açma tanıma</string>
<string name="open_advanced_unlock_prompt_store_credential">Kimlik bilgilerini saklamak için gelişmiş kilit açma istemini aç</string>
<string name="open_advanced_unlock_prompt_unlock_database">Veri tabanının kilidini açmak için gelişmiş kilit açma istemini aç</string>
@@ -621,4 +621,27 @@
<string name="custom_data">Özel veri</string>
<string name="menu_save_copy_to">Bir kopyasını şuraya kaydet…</string>
<string name="expired">Süresi doldu</string>
<string name="warning_database_already_opened">Bir veri tabanı zaten açık, yenisini açmak için önce onu kapatın</string>
<string name="advanced_unlock_keystore_warning">Bu özellik, şifrelenen kimlik bilgilerini aygıtınızın güvenli anahtar deposunda saklayacaktır.
\n
\nİşletim sisteminin yerel API uygulamasına bağlı olarak, tam olarak işlevsel olmayabilir.
\nAygıtınızın üreticisi ve kullandığınız ROM\'un geliştiricisi ile anahtar deposunun uyumluluğunu ve güvenliğini denetleyin.</string>
<string name="content_description_passphrase_word_count">Parola sözcük sayısı</string>
<string name="at_least_one_char">Her birinden en az bir karakter</string>
<string name="exclude_ambiguous_chars">Belirsiz karakterleri hariç tut</string>
<string name="lower_case">küçük harf</string>
<string name="passphrase">Parola</string>
<string name="colorize_password_title">Parolaları renklendir</string>
<string name="entropy">Entropi: %1$s bit</string>
<string name="entropy_high">Entropi: Yüksek</string>
<string name="colorize_password_summary">Parola karakterlerini türe göre renklendir</string>
<string name="keyboard_previous_search_title">Arama ekranı</string>
<string name="keyboard_previous_search_summary">Arama ekranında otomatik olarak önceki klavyeye geri dön</string>
<string name="consider_chars_filter">Karakterleri göz önünde bulundur</string>
<string name="ignore_chars_filter">Karakterleri yoksay</string>
<string name="character_count">Karakter sayısı: %1$d</string>
<string name="entropy_calculate">Entropi: Hesapla…</string>
<string name="word_separator">Ayırıcı</string>
<string name="upper_case">BÜYÜK HARF</string>
<string name="title_case">İlk Harfleri Büyük</string>
</resources>

View File

@@ -502,7 +502,7 @@
<string name="advanced_unlock_invalid_key">Не вдалося розпізнати ключ розширеного розблокування. Видаліть його та повторіть процедуру створення ключа.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Витягування облікових даних бази даних за допомогою даних розширеного розблокування</string>
<string name="advanced_unlock_prompt_extract_credential_title">Відкрити базу даних розширеним розпізнаванням розблокування</string>
<string name="advanced_unlock_prompt_store_credential_message">Попередження: Навіть якщо ви користуєтеся розширеним розблокуванням, вам однаково необхідно пам\'ятати головний пароль.</string>
<string name="advanced_unlock_prompt_store_credential_message">Навіть якщо ви користуєтеся розширеним розблокуванням, вам однаково необхідно пам\'ятати основні облікові дані.</string>
<string name="advanced_unlock_prompt_store_credential_title">Розпізнавання розширеного розблокування</string>
<string name="open_advanced_unlock_prompt_store_credential">Відкривати запит розширеного розблокування, щоб зберегти облікові дані</string>
<string name="open_advanced_unlock_prompt_unlock_database">Відкривати запит розширеного розблокування, щоб розблокувати базу даних</string>
@@ -626,4 +626,27 @@
<string name="auto_type_sequence">Послідовність автовведення</string>
<string name="menu_save_copy_to">Зберегти копію в …</string>
<string name="expired">Термін дії завершився</string>
<string name="warning_database_already_opened">База даних уже відкрита, спочатку закрийте її, щоб відкрити нову</string>
<string name="advanced_unlock_keystore_warning">Ця функція зберігатиме зашифровані облікові дані в захищеному сховищі ключів вашого пристрою.
\n
\nЗалежно від вбудованої реалізації API операційної системи, вона може бути не повністю функціональною.
\nПеревірте сумісність і безпеку сховища ключів із виробником вашого пристрою та творцем використовуваного ROM.</string>
<string name="passphrase">Парольна фраза</string>
<string name="colorize_password_title">Забарвлювати паролі</string>
<string name="colorize_password_summary">Забарвлювати символи пароля за типом</string>
<string name="keyboard_previous_search_summary">Автоматичний перехід до попередньої клавіатури на екрані пошуку</string>
<string name="entropy">Ентропія: %1$s біт</string>
<string name="at_least_one_char">Принаймні один символ з кожного</string>
<string name="ignore_chars_filter">Ігнорувати символи</string>
<string name="lower_case">нижній регістр</string>
<string name="keyboard_previous_search_title">Екран пошуку</string>
<string name="entropy_calculate">Ентропія: Обчислення…</string>
<string name="exclude_ambiguous_chars">Виключити неоднозначні символи</string>
<string name="content_description_passphrase_word_count">Кількість слів парольної фрази</string>
<string name="character_count">Кількість символів: %1$d</string>
<string name="entropy_high">Ентропія: висока</string>
<string name="consider_chars_filter">Включити символи</string>
<string name="title_case">Регістр Заголовка</string>
<string name="word_separator">Роздільник</string>
<string name="upper_case">ВЕРХНІЙ РЕГІСТР</string>
</resources>

View File

@@ -502,7 +502,7 @@
<string name="advanced_unlock_invalid_key">无法读取高级解锁密钥。请删除它,并重复解锁识别步骤。</string>
<string name="advanced_unlock_prompt_extract_credential_message">用高级解锁数据提取数据库凭据</string>
<string name="advanced_unlock_prompt_extract_credential_title">用高级解锁识别打开数据库</string>
<string name="advanced_unlock_prompt_store_credential_message">警告:即使您使用高级解锁识别,您仍然需要记住您的主密码</string>
<string name="advanced_unlock_prompt_store_credential_message">即使您使用高级解锁识别,您仍然需要记住您的主凭据</string>
<string name="advanced_unlock_prompt_store_credential_title">高级解锁识别</string>
<string name="open_advanced_unlock_prompt_store_credential">点击以打开高级解锁提示来存储凭证</string>
<string name="open_advanced_unlock_prompt_unlock_database">点击以使用高级识别解锁</string>
@@ -627,4 +627,27 @@
<string name="case_sensitive">区分大小写</string>
<string name="auto_type">自动输入</string>
<string name="expired">已过期</string>
<string name="warning_database_already_opened">已经打开一个数据库,请先关闭它再打开新数据库</string>
<string name="advanced_unlock_keystore_warning">此功能将把加密的凭据数据存储在你设备的安全 KeyStore 中。
\n
\n取决于操作系统的原生 API 实现,它可能无法完全正常工作。
\n请与设备制造商和你正使用的 ROM 的创建者一起检查 KeyStore 的兼容性和安全性。</string>
<string name="content_description_passphrase_word_count">密码口令单词计数</string>
<string name="colorize_password_title">给密码添加颜色</string>
<string name="entropy_calculate">熵:计算…</string>
<string name="at_least_one_char">每种至少有一个字符</string>
<string name="exclude_ambiguous_chars">排除模棱两可的字符</string>
<string name="consider_chars_filter">考虑字符</string>
<string name="word_separator">分隔符</string>
<string name="ignore_chars_filter">忽略字符</string>
<string name="lower_case">小写</string>
<string name="upper_case">大写</string>
<string name="title_case">标题大小写</string>
<string name="keyboard_previous_search_title">搜索屏幕</string>
<string name="passphrase">密码口令</string>
<string name="character_count">字符计数:%1$d</string>
<string name="keyboard_previous_search_summary">在搜索屏上自动切换回前一个键盘</string>
<string name="colorize_password_summary">按类型给密码字符加上颜色</string>
<string name="entropy_high">熵:高</string>
<string name="entropy">熵:%1$s bit</string>
</resources>

View File

@@ -14,7 +14,7 @@
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--><resources>
<string name="about">關於</string>
<string name="about_description">Android 版 KeePass 密碼管理軟體</string>
<string name="about_description">兼容KeePass密碼管理軟體的Android程式</string>
<string name="accept">接受</string>
<string name="account">帳戶</string>
<string name="add_entry">添加項目</string>
@@ -27,7 +27,7 @@
<string name="advanced_unlock_prompt_extract_credential_message">用高級解鎖資料提取資料庫憑證</string>
<string name="advanced_unlock_prompt_extract_credential_title">用高級解鎖開啟資料庫</string>
<string name="advanced_unlock_prompt_not_initialized">無法初始化高級解鎖提示。</string>
<string name="advanced_unlock_prompt_store_credential_message">警告:即使你使用高級解鎖,你仍然需要記住你的主密碼</string>
<string name="advanced_unlock_prompt_store_credential_message">即使你使用高級解鎖,你仍然需要記住你的解鎖憑證</string>
<string name="advanced_unlock_prompt_store_credential_title">高級解鎖</string>
<string name="advanced_unlock_scanning_error">高級解鎖出錯:%1$s</string>
<string name="advanced_unlock_tap_delete">點擊刪除高級解鎖密鑰</string>
@@ -40,15 +40,15 @@
<string name="allow_no_password_title">允許沒有主密鑰</string>
<string name="app_timeout">應用程式超時</string>
<string name="app_timeout_summary">鎖定資料庫前的閒置時間</string>
<string name="application">程式</string>
<string name="application_appearance">應用程式</string>
<string name="application">應用程式</string>
<string name="application_appearance">界面</string>
<string name="assign_master_key">分配主密鑰</string>
<string name="auto_focus_search_summary">開啟資料庫時就開始搜尋</string>
<string name="auto_focus_search_title">快速搜尋</string>
<string name="autofill">自動填入</string>
<string name="autofill_application_id_blocklist_summary">禁止應用程式自動填充的黑名單</string>
<string name="autofill_application_id_blocklist_title">應用程式黑名單</string>
<string name="autofill_ask_to_save_data_summary">當表單經過驗證後,詢問是否保存資料</string>
<string name="autofill_ask_to_save_data_summary">填寫完表格后詢問是否保存數據</string>
<string name="autofill_ask_to_save_data_title">詢問是否保存資料</string>
<string name="autofill_auto_search_summary">自動建議匹配的網址域名或應用程式 ID 的搜尋結果</string>
<string name="autofill_auto_search_title">自動搜尋</string>
@@ -79,7 +79,7 @@
<string name="biometric">生物辨識</string>
<string name="biometric_auto_open_prompt_summary">資料庫就緒後,應用將自動請求高級解鎖</string>
<string name="biometric_auto_open_prompt_title">自動開啟提示</string>
<string name="biometric_delete_all_key_summary">刪除全部生物辨識加密密鑰</string>
<string name="biometric_delete_all_key_summary">刪除所有和高級解鎖方式有關的加密密鑰</string>
<string name="biometric_delete_all_key_title">刪除加密密鑰</string>
<string name="biometric_security_update_required">需要生物辨識安全更新。</string>
<string name="biometric_unlock_enable_summary">通過生物辨識解鎖資料庫</string>
@@ -104,10 +104,10 @@
<string name="compression">壓縮</string>
<string name="compression_gzip">Gzip</string>
<string name="compression_none"></string>
<string name="configure_biometric">未登記生物辨識或設備憑證。</string>
<string name="configure_biometric">未登記任何生物辨識或設備憑證。</string>
<string name="contact">聯繫方式</string>
<string name="contains_duplicate_uuid">資料庫包含重複的 UUID。</string>
<string name="contains_duplicate_uuid_procedure">通過驗證此對話方塊KeePassDX 將修復問題(通過為重複項生成新的 UUID繼續</string>
<string name="contains_duplicate_uuid_procedure">通過為重複的數據生成新的 UUID (識別碼)來解決問題。是否繼續</string>
<string name="content">內容</string>
<string name="content_description_add_entry">添加項目</string>
<string name="content_description_add_group">添加群組</string>
@@ -121,7 +121,7 @@
<string name="content_description_keyfile_checkbox">密鑰檔案核取方塊</string>
<string name="content_description_node_children">子節點</string>
<string name="content_description_open_file">開啟檔案</string>
<string name="content_description_otp_information">一次性密碼</string>
<string name="content_description_otp_information">一次性密碼訊</string>
<string name="content_description_password_checkbox">密碼核取方塊</string>
<string name="content_description_password_length">密碼長度</string>
<string name="content_description_remove_field">刪除欄位</string>
@@ -172,7 +172,7 @@
<string name="download_finalization">最後步驟…</string>
<string name="download_initialization">初始化中…</string>
<string name="download_progression">進行中:%1$d%%</string>
<string name="edit_entry">編輯</string>
<string name="edit_entry">編輯</string>
<string name="education_add_attachment_summary">將附件上傳到你的條目以保存重要的外部資料。</string>
<string name="education_add_attachment_title">添加附件</string>
<string name="education_advanced_unlock_summary">將你的密碼連接到你掃瞄的生物特徵或設備憑證,以快速解鎖你的資料庫。</string>
@@ -185,15 +185,22 @@
<string name="education_entry_edit_title">編輯此條目</string>
<string name="education_entry_new_field_summary">添加一個新的欄位並添加為其添加一個值,此時可以選擇是否保護該欄位及其值。</string>
<string name="education_entry_new_field_title">添加自定義欄位</string>
<string name="education_field_copy_summary">已複製的欄位可以貼到任何地方。選擇需要的填寫方式。</string>
<string name="education_field_copy_summary">已複製的欄位可以貼到任何地方。
\n
\n選擇需要的表格填寫方式。</string>
<string name="education_field_copy_title">複製欄位</string>
<string name="education_generate_password_summary">依據表單中的標準進行簡單的定義,隨機為該條目生成一個強密碼,不在擔心忘記安全密碼。</string>
<string name="education_generate_password_title">建立一個強密碼</string>
<string name="education_lock_summary">可以設置在無操作和螢幕關閉的時候,鎖定資料庫。</string>
<string name="education_lock_title">鎖定資料庫</string>
<string name="education_new_node_summary">這些條目幫助管理數位化身份,還可以使用群組來管理資料庫中的條目。</string>
<string name="education_new_node_summary">這些條目幫助管理數位化身份
\n
\n還可以使用群組來管理資料庫中的條目。</string>
<string name="education_new_node_title">向資料庫添加項目</string>
<string name="education_read_only_summary">更改此會話的開啟模式。 「寫入保護(唯讀)」可防止對資料庫的意外更改。「可編輯」將允許隨心所欲地添加、刪除或者修改所有元素。</string>
<string name="education_read_only_summary">更改此會話的開啟模式。
\n
\n「寫入保護」「唯讀」可防止對資料庫的意外更改。
\n「可編輯」將你允許隨心所欲地添加、刪除或者修改所有元素。</string>
<string name="education_read_only_title">資料庫啟用寫入保護(唯讀)</string>
<string name="education_search_summary">輸入標題、用戶名或其他欄位的內容來搜尋密碼。</string>
<string name="education_search_title">搜尋條目</string>
@@ -203,7 +210,9 @@
<string name="education_setup_OTP_title">設置一次性密碼</string>
<string name="education_sort_summary">選擇條目和群組的排序方式。</string>
<string name="education_sort_title">條目排序</string>
<string name="education_unlock_summary">輸入密碼和/或一個密鑰檔來解鎖你的資料庫.</string>
<string name="education_unlock_summary">輸入密碼和/或一個密鑰檔來解鎖你的資料庫
\n
\n每次更改后將資料庫檔備份到安全的地方。</string>
<string name="education_unlock_title">解鎖資料庫</string>
<string name="email">電子郵件</string>
<string name="email_address">電子郵件地址</string>
@@ -243,7 +252,7 @@
<string name="error_arc4">Arcfour 流密碼不被支援。</string>
<string name="error_autofill_enable_service">無法啟用自動填入服務。</string>
<string name="error_can_not_handle_uri">KeePassDX 無法處理此 URI。</string>
<string name="error_copy_entry_here">不能複製項目至此</string>
<string name="error_copy_entry_here">不能在此處複製條目</string>
<string name="error_copy_group_here">不能在此處複製群組。</string>
<string name="error_create_database">無法建立資料庫。</string>
<string name="error_create_database_file">無法使用此密碼和密鑰檔創建資料庫。</string>
@@ -261,12 +270,12 @@
<string name="error_label_exists">此標籤已存在。</string>
<string name="error_load_database">無法載入資料庫。</string>
<string name="error_load_database_KDF_memory">無法載入密鑰。嘗試降低 KDF\"記憶體使用率\"。</string>
<string name="error_move_entry_here">不能移動目至此。</string>
<string name="error_move_group_here">不能把一個組移動到此處。</string>
<string name="error_move_entry_here">不能移動目至此</string>
<string name="error_move_group_here">不能將群組移至此處。</string>
<string name="error_no_name">請輸入用戶名。</string>
<string name="error_nokeyfile">選擇密鑰檔案。</string>
<string name="error_otp_counter">計數器必須介於 %1$d 和 %2$d 之間。</string>
<string name="error_otp_digits">權杖需含有 %1$d %2$d 數字。</string>
<string name="error_otp_digits">令牌需含有 %1$d %2$d 數字。</string>
<string name="error_otp_period">時段必須介於 %1$d 和 %2$d 秒之間。</string>
<string name="error_otp_secret_key">密鑰必須採用 Base32 格式。</string>
<string name="error_otp_type">現有的 OTP 類型未被此表單所辨識,其驗證可能不再正確生成令牌。</string>
@@ -282,7 +291,7 @@
<string name="error_string_key">每個字串必須具有欄位名稱。</string>
<string name="error_string_type">文本和請求的條目不匹配。</string>
<string name="error_upload_file">上傳檔案時發生錯誤。</string>
<string name="error_word_reserved">該字保留字,無法使用。</string>
<string name="error_word_reserved">該字詞是保留字,無法使用。</string>
<string name="error_wrong_length">請在長度欄位輸入一個正整數。</string>
<string name="export_app_properties_summary">建立一個檔案以匯出配置</string>
<string name="export_app_properties_title">導出配置</string>
@@ -291,7 +300,7 @@
<string name="field_name">欄位名</string>
<string name="field_value">欄位值</string>
<string name="file_browser">檔案管理器</string>
<string name="file_manager_install_description">需要支持 Intent 動作 ACTION_CREATE_DOCUMENT 和 ACTION_OPEN_DOCUMENT 的檔案管理器來建立、開啟和儲存資料庫檔案。</string>
<string name="file_manager_install_description">檔案管理器必須支援 ACTION_CREATE_DOCUMENT 和 ACTION_OPEN_DOCUMENT 的 intent才能用來建立、開啟和儲存資料庫檔案。</string>
<string name="file_name">檔案名稱</string>
<string name="file_not_found_content">找不到檔案,嘗試從檔案瀏覽器重新開啟它。</string>
<string name="filter">過濾</string>
@@ -334,9 +343,9 @@
<string name="import_app_properties_title">導入配置</string>
<string name="international_bank_account_number">IBAN</string>
<string name="invalid_algorithm">無效的演算法。</string>
<string name="invalid_credentials">效的密碼或密鑰檔</string>
<string name="invalid_credentials">法獲取身份驗證信息</string>
<string name="invalid_db_same_uuid">與%1$s的 UUID 相同的%2$s已經存在。</string>
<string name="invalid_db_sig">資料庫格式無法辨識</string>
<string name="invalid_db_sig">無法辨識資料庫格式。</string>
<string name="kdf_explanation">為生成加密演算法的密鑰,主密鑰使用隨機跳轉密鑰推算函數進行轉換。</string>
<string name="key_derivation_function">密鑰推算函數</string>
<string name="keyboard">鍵盤</string>
@@ -376,7 +385,7 @@
<string name="keyfile_is_empty">密鑰檔案是空白的。</string>
<string name="keystore_not_accessible">密鑰庫未能初始化。</string>
<string name="length">長度</string>
<string name="list_entries_show_username_summary">目清單中顯示使用者名稱</string>
<string name="list_entries_show_username_summary">目清單中顯示使用者名稱</string>
<string name="list_entries_show_username_title">顯示用戶名</string>
<string name="list_groups_show_number_entries_summary">顯示群組中的條目數</string>
<string name="list_groups_show_number_entries_title">顯示條目數</string>
@@ -407,7 +416,7 @@
<string name="max_history_size_title">最大大小</string>
<string name="membership">會員身份</string>
<string name="memory_usage">記憶體使用情況</string>
<string name="memory_usage_explanation">密鑰推算函數使用的記憶體位元組量</string>
<string name="memory_usage_explanation">密鑰推算函數使用的記憶體的大小</string>
<string name="menu_advanced_unlock_settings">高級解鎖</string>
<string name="menu_app_settings">應用程式設定</string>
<string name="menu_appearance_settings">外觀</string>
@@ -478,8 +487,8 @@
<string name="recycle_bin_summary">刪除群組和條目前先移至回收桶</string>
<string name="recycle_bin_title">回收桶使用情況</string>
<string name="registration_mode">註冊模式</string>
<string name="remember_database_locations_summary">記住資料庫位置</string>
<string name="remember_database_locations_title">資料庫儲存位置</string>
<string name="remember_database_locations_summary">跟蹤資料庫的存儲位置</string>
<string name="remember_database_locations_title">記住所有資料庫位置</string>
<string name="remember_keyfile_locations_summary">追蹤密鑰檔案位置</string>
<string name="remember_keyfile_locations_title">記住密鑰檔案位置</string>
<string name="reset_education_screens_summary">再次顯示全部新手引導信息</string>
@@ -508,7 +517,7 @@
<string name="settings_database_force_changing_master_key_summary">強制更改主密鑰(天)</string>
<string name="settings_database_force_changing_master_key_title">強制更換</string>
<string name="settings_database_recommend_changing_master_key_summary">建議更改主密鑰(天)</string>
<string name="settings_database_recommend_changing_master_key_title">建議更</string>
<string name="settings_database_recommend_changing_master_key_title">建議更</string>
<string name="show_otp_token_summary">在條目列表中顯示 OTP 領牌</string>
<string name="show_otp_token_title">顯示 OTP 令牌</string>
<string name="show_recent_files_summary">顯示最近使用的儲存庫位置</string>
@@ -566,7 +575,7 @@
<string name="version_label">版本 %1$s</string>
<string name="warning">警告</string>
<string name="warning_database_info_changed">資料庫檔案中包含的信息已在應用程式之外被修改。</string>
<string name="warning_database_info_changed_options">通過保存資料庫或用最的更重新加載資料庫來覆蓋外部修改。</string>
<string name="warning_database_info_changed_options">通過保存資料庫或用最的更重新加載資料庫來合并數據和覆蓋外部修改。</string>
<string name="warning_database_link_revoked">訪問檔案管理器撤消訪問權限的檔案</string>
<string name="warning_database_read_only">授予軟體檔案讀寫訪問權限以保存資料庫更改</string>
<string name="warning_database_revoked">檔案管理器撤銷了對此檔案的訪問,關閉資料庫並從其位置重新開啟它。</string>
@@ -574,7 +583,9 @@
<string name="warning_empty_keyfile_explanation">密鑰檔案的內容應該永不更改,在最好的情況下,應該包含隨機生成的資料。</string>
<string name="warning_empty_password">確定不使用密碼?</string>
<string name="warning_empty_recycle_bin">從回收桶永久刪除所有節點?</string>
<string name="warning_file_too_big">KeePass 資料庫應該只包含小的實用程式檔案(例如 PGP 密鑰檔案)。</string>
<string name="warning_file_too_big">KeePass 資料庫應該只包含實用的小檔案(例如 PGP 密鑰檔案)。
\n
\n否則你的資料庫可能會變得非常大而這會減少數據上傳的表現。</string>
<string name="warning_no_encryption_key">確定不使用加密密鑰?</string>
<string name="warning_password_encoding">避免在資料庫中保存編碼格式外字符的密碼(未辨識的字符將轉換為同一字符)。</string>
<string name="warning_permanently_delete_nodes">確定永久刪除已選項目?</string>
@@ -583,4 +594,36 @@
<string name="warning_sure_add_file">仍要新增檔案?</string>
<string name="warning_sure_remove_data">仍要移除此資料?</string>
<string name="wireless">Wi-Fi</string>
<string name="expired">過期</string>
<string name="tags">標籤</string>
<string name="warning_exact_alarm">因爲您尚未允許該應用程式使用精確鬧鐘。所以所有需要計時的功能將不會完全準時完成。</string>
<string name="warning_database_info_reloaded">重新加載資料庫會刪除所有在本地修改的數據。</string>
<string name="warning_keyfile_integrity">因為Android系統可以動態更改檔案數據所以不能保證檔案的哈希值。將檔案擴展名更改為 .bin 以確保正確的完整性。</string>
<string name="permission">許可</string>
<string name="content_description_entry_background_color">條目背景顏色</string>
<string name="content_description_entry_foreground_color">條目正面顏色</string>
<string name="content_description_database_color">資料庫顔色</string>
<string name="hint_icon_name">圖標名稱</string>
<string name="navigation_drawer_open">打開導航抽屜</string>
<string name="custom_data">自定義數據</string>
<string name="auto_type_sequence">自動輸入次序</string>
<string name="search_filters">搜索過濾器</string>
<string name="current_group">當前组别</string>
<string name="case_sensitive">區分大小寫</string>
<string name="menu_merge_database">合併數據</string>
<string name="menu_merge_from">合併自 …</string>
<string name="menu_save_copy_to">保存一份副本到…</string>
<string name="show_entry_colors_title">條目顔色</string>
<string name="show_entry_colors_summary">顯示條目中的前景色和背景色</string>
<string name="enable_keep_screen_on_title">保持螢幕開啟</string>
<string name="enable_keep_screen_on_summary">在查看條目時保持熒幕打開</string>
<string name="regex">正則表達式</string>
<string name="navigation_drawer_close">關閉導航抽屜</string>
<string name="searchable">可搜索</string>
<string name="inherited">繼承</string>
<string name="warning_database_already_opened">已經有一個資料庫打開了,先關閉它,才可以打開新的資料庫</string>
<string name="advanced_unlock_keystore_warning">此功能會將加密的身份驗證數據儲存在設備裏安全存儲 KeyStore 中。
\n
\n基於操作系統的原始 API 實現Keystore 有可能無法發揮其應有的作用。
\n請向設備的製造商和 ROM 的創建者咨詢 KeyStore 的兼容性和安全性。</string>
</resources>

View File

@@ -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. -->

View File

@@ -93,6 +93,7 @@
<color name="grey_slight">#202124</color>
<color name="grey_slight_transparent">#E0202124</color>
<color name="grey_black_slight">#3E4247</color>
<color name="grey_blue_slighter">#4F5F66</color>
<color name="grey_blue_slight">#3c474c</color>
<color name="grey_blue_deep">#263238</color>
<color name="blue_slight">#5E97F6</color>

View File

@@ -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>
@@ -135,13 +131,13 @@
<string name="keyboard_notification_entry_clear_close_key" translatable="false">keyboard_notification_entry_clear_close_key</string>
<bool name="keyboard_notification_entry_clear_close_default" translatable="false">true</bool>
<string name="keyboard_entry_timeout_key" translatable="false">keyboard_entry_timeout_key</string>
<string name="keyboard_entry_timeout_default" translatable="false">30000</string>
<string name="keyboard_entry_timeout_default" translatable="false">-1</string>
<string name="keyboard_selection_entry_key" translatable="false">keyboard_selection_entry_key</string>
<bool name="keyboard_selection_entry_default" translatable="false">false</bool>
<string name="keyboard_search_share_key" translatable="false">keyboard_search_share_key</string>
<bool name="keyboard_search_share_default" translatable="false">false</bool>
<bool name="keyboard_search_share_default" translatable="false">true</bool>
<string name="keyboard_save_search_info_key" translatable="false">keyboard_save_search_info_key</string>
<bool name="keyboard_save_search_info_default" translatable="false">true</bool>
<bool name="keyboard_save_search_info_default" translatable="false">false</bool>
<string name="keyboard_auto_go_action_key" translatable="false">keyboard_auto_go_action_key</string>
<bool name="keyboard_auto_go_action_default" translatable="false">true</bool>
<string name="keyboard_key_vibrate_key" translatable="false">keyboard_key_vibrate_key</string>
@@ -150,6 +146,8 @@
<bool name="keyboard_key_sound_default" translatable="false">false</bool>
<string name="keyboard_previous_database_credentials_key" translatable="false">keyboard_previous_database_credentials_key</string>
<bool name="keyboard_previous_database_credentials_default" translatable="false">false</bool>
<string name="keyboard_previous_search_key" translatable="false">keyboard_previous_search_key</string>
<bool name="keyboard_previous_search_default" translatable="false">true</bool>
<string name="keyboard_previous_fill_in_key" translatable="false">keyboard_previous_fill_in_key</string>
<bool name="keyboard_previous_fill_in_default" translatable="false">false</bool>
<string name="keyboard_previous_lock_key" translatable="false">keyboard_previous_lock_key</string>
@@ -184,6 +182,10 @@
<string name="setting_icon_pack_choose_key" translatable="false">setting_icon_pack_choose_key</string>
<string name="show_entry_colors_key" translatable="false">show_entry_colors_key</string>
<bool name="show_entry_colors_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="list_entries_show_username_key" translatable="false">list_entries_show_username_key</string>
<bool name="list_entries_show_username_default" translatable="false">true</bool>
<string name="list_groups_show_number_entries_key" translatable="false">list_groups_show_number_entries_key</string>
@@ -201,6 +203,58 @@
<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" />
<!-- Search settings -->
<string name="search_option_case_sensitive_key" translatable="false">search_option_case_sensitive_key</string>
<bool name="search_option_case_sensitive_default" translatable="false">false</bool>
<string name="search_option_regex_key" translatable="false">search_option_regex_key</string>
<bool name="search_option_regex_default" translatable="false">false</bool>
<string name="search_option_title_key" translatable="false">search_option_title_key</string>
<bool name="search_option_title_default" translatable="false">true</bool>
<string name="search_option_username_key" translatable="false">search_option_username_key</string>
<bool name="search_option_username_default" translatable="false">true</bool>
<string name="search_option_password_key" translatable="false">search_option_password_key</string>
<bool name="search_option_password_default" translatable="false">false</bool>
<string name="search_option_url_key" translatable="false">search_option_url_key</string>
<bool name="search_option_url_default" translatable="false">true</bool>
<string name="search_option_expired_key" translatable="false">search_option_expired_key</string>
<bool name="search_option_expired_default" translatable="false">true</bool>
<string name="search_option_note_key" translatable="false">search_option_note_key</string>
<bool name="search_option_note_default" translatable="false">true</bool>
<string name="search_option_otp_key" translatable="false">search_option_otp_key</string>
<bool name="search_option_otp_default" translatable="false">false</bool>
<string name="search_option_other_key" translatable="false">search_option_other_key</string>
<bool name="search_option_other_default" translatable="false">true</bool>
<string name="search_option_uuid_key" translatable="false">search_option_uuid_key</string>
<bool name="search_option_uuid_default" translatable="false">false</bool>
<string name="search_option_tag_key" translatable="false">search_option_tag_key</string>
<bool name="search_option_tag_default" translatable="false">false</bool>
<string name="search_option_current_group_key" translatable="false">search_option_current_group_key</string>
<bool name="search_option_current_group_default" translatable="false">false</bool>
<string name="search_option_searchable_group_key" translatable="false">search_option_searchable_group_key</string>
<bool name="search_option_searchable_group_default" translatable="false">true</bool>
<string name="search_option_recycle_bin_key" translatable="false">search_option_recycle_bin_key</string>
<bool name="search_option_recycle_bin_default" translatable="false">false</bool>
<string name="search_option_templates_key" translatable="false">search_option_templates_key</string>
<bool name="search_option_templates_default" translatable="false">false</bool>
<!-- 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 +419,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 +428,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 +443,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>

View File

@@ -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,12 +205,17 @@
<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>
<string name="invalid_db_sig">Could not recognize the database format.</string>
<string name="keyfile_is_empty">The keyfile is empty.</string>
<string name="length">Length</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="list_entries_show_username_title">Show usernames</string>
<string name="list_entries_show_username_summary">Displays usernames in entry lists</string>
<string name="list_groups_show_number_entries_title">Show number of entries</string>
@@ -223,8 +229,6 @@
<string name="creating_database">Creating database…</string>
<string name="loading_database">Loading database…</string>
<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="about">About</string>
<string name="menu_change_key_settings">Change master key</string>
<string name="copy_field">Copy of %1$s</string>
@@ -479,10 +483,10 @@
<string name="keyboard_selection_entry_summary">When viewing an entry in KeePassDX, populate Magikeyboard with that entry</string>
<string name="keyboard_notification_entry_title">Notification info</string>
<string name="keyboard_notification_entry_summary">Show a notification when an entry is available</string>
<string name="keyboard_search_share_title">Search shared info</string>
<string name="keyboard_search_share_summary">When sharing a URL to KeePassDX, filter the entries using that URL domain</string>
<string name="keyboard_search_share_title">Select shared info</string>
<string name="keyboard_search_share_summary">When sharing info to KeePassDX, filter the entries using this info to feed the Magikeyboard</string>
<string name="keyboard_save_search_info_title">Save shared info</string>
<string name="keyboard_save_search_info_summary">After sharing a URL to KeePassDX, when an entry is selected, try to remember that entry for further uses</string>
<string name="keyboard_save_search_info_summary">After sharing info to KeePassDX, when an entry is selected, try saving the info in the entry for easier future uses</string>
<string name="keyboard_notification_entry_clear_close_title">Clear at closing</string>
<string name="keyboard_notification_entry_clear_close_summary">Close the database when closing the notification</string>
<string name="keyboard_entry_timeout_title">Timeout</string>
@@ -500,6 +504,8 @@
<string name="keyboard_change">Switch keyboard</string>
<string name="keyboard_previous_database_credentials_title">Database credentials screen</string>
<string name="keyboard_previous_database_credentials_summary">Automatically switch back to the previous keyboard on the database credentials screen</string>
<string name="keyboard_previous_search_title">Search screen</string>
<string name="keyboard_previous_search_summary">Automatically switch back to the previous keyboard on the search screen</string>
<string name="keyboard_previous_fill_in_title">Auto key action</string>
<string name="keyboard_previous_fill_in_summary">Automatically switch back to the previous keyboard after executing \"Auto key action\"</string>
<string name="keyboard_previous_lock_title">Lock database</string>
@@ -603,6 +609,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>

View File

@@ -48,6 +48,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/text_appearance">

View File

@@ -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">

View File

@@ -84,6 +84,11 @@
android:title="@string/keyboard_previous_database_credentials_title"
android:summary="@string/keyboard_previous_database_credentials_summary"
android:defaultValue="@bool/keyboard_previous_database_credentials_default"/>
<SwitchPreference
android:key="@string/keyboard_previous_search_key"
android:title="@string/keyboard_previous_search_title"
android:summary="@string/keyboard_previous_search_summary"
android:defaultValue="@bool/keyboard_previous_search_default"/>
<SwitchPreference
android:key="@string/keyboard_previous_fill_in_key"
android:title="@string/keyboard_previous_fill_in_title"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.6.10'
ext.kotlin_version = '1.6.20'
ext.android_core_version = '1.7.0'
ext.android_appcompat_version = '1.4.1'
ext.android_material_version = '1.5.0'
@@ -10,7 +10,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath 'com.android.tools.build:gradle:7.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

View File

@@ -0,0 +1,8 @@
* Passphrase implementation #218
* Show visual password strength indicator with entropy #631 #869 #454 #1270
* Dynamically save password generator configuration #618 #696
* Add advanced password filters #1052 #448 #983 #271 #539
* Better search implementation #175 #1254 #1267
* Manage package name from Magikeyboard #1010 #1261
* Ask confirmation to lock if changes without save #970
* Fix small bugs #1282

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 KiB

Some files were not shown because too many files have changed in this diff Show More