Compare commits

...

18 Commits

Author SHA1 Message Date
J-Jamet
c88413f7f7 feat: Add UV behind database merge #2283 2025-12-04 12:35:42 +01:00
J-Jamet
7b1fb8a4bf fix: save instance state in protected view #2283 2025-12-04 11:32:07 +01:00
J-Jamet
3567fa797b fix: Copy protected field behind UV #2283 2025-12-03 15:04:35 +01:00
J-Jamet
eb41233e57 fix: ProtectField #2283 2025-12-03 12:43:27 +01:00
J-Jamet
b394a99e40 fix: Unprotect with User Verification #2283 2025-12-02 20:25:44 +01:00
J-Jamet
2bbb40e513 fix: Add ProtectedFieldView callback #2283 2025-12-02 19:36:46 +01:00
J-Jamet
09ef69e6ae fix: Add user verification device credential setting #2283 2025-12-02 16:55:42 +01:00
J-Jamet
762ac8f77b fix: Merge dialog #2283 2025-12-02 11:41:21 +01:00
J-Jamet
d28087d8d8 fix: User Verification dialog #2283 2025-12-02 10:06:00 +01:00
J-Jamet
17d4c363ac fix: Add User Verification to database settings #2283 2025-12-01 21:01:08 +01:00
J-Jamet
c754b6a049 Added a dialog to verify the password #2283 2025-12-01 18:40:38 +01:00
J-Jamet
9c6241afc9 fix: Behavior when ask device credential #2283 2025-12-01 13:20:06 +01:00
J-Jamet
f6774b6d51 feat: Dialog to ask device credential #2283 2025-11-29 15:36:53 +01:00
J-Jamet
108a61905e fix: Add condition for User Verification #2283 2025-11-29 13:30:11 +01:00
J-Jamet
d251788b1a fix: Add User Verification for Entry Edition #2283 2025-11-29 13:04:01 +01:00
J-Jamet
7ed8a44168 fix: Remove hide password setting #2283 2025-11-28 15:05:13 +01:00
J-Jamet
844b1dfc79 fix: Add main credential check method 2025-11-27 20:00:20 +01:00
J-Jamet
d087fcc930 fix: Add MainCredentialViewModel 2025-11-27 16:20:44 +01:00
105 changed files with 1702 additions and 770 deletions

View File

@@ -41,6 +41,9 @@ import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat import androidx.core.graphics.BlendModeCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
@@ -53,6 +56,11 @@ import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.TagsAdapter import com.kunzisoft.keepass.adapters.TagsAdapter
import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isUserVerificationNeeded
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.requestShowUnprotectField
import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
@@ -78,7 +86,10 @@ import com.kunzisoft.keepass.view.changeTitleColor
import com.kunzisoft.keepass.view.hideByFading import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.setTransparentNavigationBar import com.kunzisoft.keepass.view.setTransparentNavigationBar
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.view.showError
import com.kunzisoft.keepass.viewmodels.EntryViewModel import com.kunzisoft.keepass.viewmodels.EntryViewModel
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
import kotlinx.coroutines.launch
import java.util.EnumSet import java.util.EnumSet
import java.util.UUID import java.util.UUID
@@ -100,14 +111,10 @@ class EntryActivity : DatabaseLockActivity() {
private var loadingView: ProgressBar? = null private var loadingView: ProgressBar? = null
private val mEntryViewModel: EntryViewModel by viewModels() private val mEntryViewModel: EntryViewModel by viewModels()
private val mUserVerificationViewModel: UserVerificationViewModel by viewModels()
private val mEntryActivityEducation = EntryActivityEducation(this) private val mEntryActivityEducation = EntryActivityEducation(this)
private var mMainEntryId: NodeId<UUID>? = null
private var mHistoryPosition: Int = -1
private var mEntryIsHistory: Boolean = false
private var mEntryLoaded = false
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mAttachmentSelected: Attachment? = null private var mAttachmentSelected: Attachment? = null
@@ -210,7 +217,7 @@ class EntryActivity : DatabaseLockActivity() {
mEntryViewModel.loadEntry(mDatabase, mainEntryId, historyPosition) mEntryViewModel.loadEntry(mDatabase, mainEntryId, historyPosition)
} }
} catch (e: ClassCastException) { } catch (_: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key") Log.e(TAG, "Unable to retrieve the entry key")
} }
@@ -238,13 +245,9 @@ class EntryActivity : DatabaseLockActivity() {
mEntryViewModel.entryInfoHistory.observe(this) { entryInfoHistory -> mEntryViewModel.entryInfoHistory.observe(this) { entryInfoHistory ->
if (entryInfoHistory != null) { if (entryInfoHistory != null) {
this.mMainEntryId = entryInfoHistory.mainEntryId
// Manage history position // Manage history position
val historyPosition = entryInfoHistory.historyPosition val historyPosition = entryInfoHistory.historyPosition
this.mHistoryPosition = historyPosition
val entryIsHistory = historyPosition > -1 val entryIsHistory = historyPosition > -1
this.mEntryIsHistory = entryIsHistory
// Assign history dedicated view // Assign history dedicated view
historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
// TODO History badge // TODO History badge
@@ -279,7 +282,6 @@ class EntryActivity : DatabaseLockActivity() {
mForegroundColor = if (showEntryColors) entryInfo.foregroundColor else null mForegroundColor = if (showEntryColors) entryInfo.foregroundColor else null
loadingView?.hideByFading() loadingView?.hideByFading()
mEntryLoaded = true
} else { } else {
finish() finish()
} }
@@ -322,6 +324,73 @@ class EntryActivity : DatabaseLockActivity() {
) )
} }
} }
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mEntryViewModel.entryState.collect { entryState ->
when (entryState) {
is EntryViewModel.EntryState.Loading -> {}
is EntryViewModel.EntryState.RequestUnprotectField -> {
mDatabase?.let { database ->
requestShowUnprotectField(
userVerificationViewModel = mUserVerificationViewModel,
database = database,
protectedFieldView = entryState.protectedFieldView
)
}
mEntryViewModel.actionPerformed()
}
is EntryViewModel.EntryState.RequestCopyProtectedField -> {
mDatabase?.let { database ->
checkUserVerification(
userVerificationViewModel = mUserVerificationViewModel,
dataToVerify = UserVerificationData(
actionType = UserVerificationActionType.COPY_PROTECTED_FIELD,
database = database,
field = entryState.field,
)
)
}
mEntryViewModel.actionPerformed()
}
}
}
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mUserVerificationViewModel.userVerificationState.collect { uVState ->
when (uVState) {
is UserVerificationViewModel.UVState.Loading -> {}
is UserVerificationViewModel.UVState.OnUserVerificationCanceled -> {
coordinatorLayout?.showError(uVState.error)
mUserVerificationViewModel.onUserVerificationReceived()
}
is UserVerificationViewModel.UVState.OnUserVerificationSucceeded -> {
val data = uVState.dataToVerify
when (data.actionType) {
UserVerificationActionType.SHOW_PROTECTED_FIELD -> {
// Unprotect field by its view
data.protectedFieldView?.unprotect()
}
UserVerificationActionType.COPY_PROTECTED_FIELD -> {
// Copy field value
data.field?.let {
mEntryViewModel.copyToClipboard(it)
}
}
UserVerificationActionType.EDIT_ENTRY -> {
// Edit Entry
editEntry(data.database, data.entryId)
}
else -> {}
}
mUserVerificationViewModel.onUserVerificationReceived()
}
}
}
}
}
} }
override fun finishActivityIfReloadRequested(): Boolean { override fun finishActivityIfReloadRequested(): Boolean {
@@ -410,13 +479,13 @@ class EntryActivity : DatabaseLockActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
if (mEntryLoaded) { if (mEntryViewModel.entryLoaded) {
val inflater = menuInflater val inflater = menuInflater
inflater.inflate(R.menu.entry, menu) inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database, menu) inflater.inflate(R.menu.database, menu)
if (mEntryIsHistory && !mDatabaseReadOnly) { if (mEntryViewModel.entryIsHistory && !mDatabaseReadOnly) {
inflater.inflate(R.menu.entry_history, menu) inflater.inflate(R.menu.entry_history, menu)
} }
@@ -429,7 +498,7 @@ class EntryActivity : DatabaseLockActivity() {
} }
override fun onPrepareOptionsMenu(menu: Menu?): Boolean { override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
if (mEntryIsHistory || mDatabaseReadOnly) { if (mEntryViewModel.entryIsHistory || mDatabaseReadOnly) {
menu?.findItem(R.id.menu_save_database)?.isVisible = false menu?.findItem(R.id.menu_save_database)?.isVisible = false
menu?.findItem(R.id.menu_merge_database)?.isVisible = false menu?.findItem(R.id.menu_merge_database)?.isVisible = false
menu?.findItem(R.id.menu_edit)?.isVisible = false menu?.findItem(R.id.menu_edit)?.isVisible = false
@@ -474,34 +543,53 @@ class EntryActivity : DatabaseLockActivity() {
} }
} }
private fun editEntry(database: ContextualDatabase?, entryId: NodeId<*>?) {
database?.let { database ->
entryId?.let { entryId ->
EntryEditActivity.launch(
activity = this@EntryActivity,
database = database,
registrationType = EntryEditActivity.RegistrationType.UPDATE,
nodeId = entryId,
activityResultLauncher = mEntryActivityResultLauncher
)
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_edit -> { R.id.menu_edit -> {
mDatabase?.let { database -> if (mEntryViewModel.entryInfo?.isUserVerificationNeeded() == true) {
mMainEntryId?.let { entryId -> mDatabase?.let { database ->
EntryEditActivity.launch( checkUserVerification(
activity = this, userVerificationViewModel = mUserVerificationViewModel,
database = database, dataToVerify = UserVerificationData(
registrationType = EntryEditActivity.RegistrationType.UPDATE, actionType = UserVerificationActionType.EDIT_ENTRY,
nodeId = entryId, database = database,
activityResultLauncher = mEntryActivityResultLauncher entryId = mEntryViewModel.mainEntryId
)
) )
} }
} else {
editEntry(mDatabase, mEntryViewModel.mainEntryId)
} }
return true return true
} }
R.id.menu_restore_entry_history -> { R.id.menu_restore_entry_history -> {
mMainEntryId?.let { mainEntryId -> mEntryViewModel.mainEntryId?.let { mainEntryId ->
restoreEntryHistory( restoreEntryHistory(
mainEntryId, mainEntryId,
mHistoryPosition) mEntryViewModel.historyPosition
)
} }
} }
R.id.menu_delete_entry_history -> { R.id.menu_delete_entry_history -> {
mMainEntryId?.let { mainEntryId -> mEntryViewModel.mainEntryId?.let { mainEntryId ->
deleteEntryHistory( deleteEntryHistory(
mainEntryId, mainEntryId,
mHistoryPosition) mEntryViewModel.historyPosition
)
} }
} }
R.id.menu_save_database -> { R.id.menu_save_database -> {
@@ -521,7 +609,7 @@ class EntryActivity : DatabaseLockActivity() {
override fun finish() { override fun finish() {
// Transit data in previous Activity after an update // Transit data in previous Activity after an update
Intent().apply { Intent().apply {
putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mMainEntryId) putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntryViewModel.mainEntryId)
setResult(RESULT_OK, this) setResult(RESULT_OK, this)
} }
super.finish() super.finish()

View File

@@ -63,6 +63,8 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildSpecia
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveRegisterInfo import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveRegisterInfo
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
import com.kunzisoft.keepass.credentialprovider.TypeMode import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.requestShowUnprotectField
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
@@ -98,9 +100,11 @@ import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.hideByFading import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.setTransparentNavigationBar import com.kunzisoft.keepass.view.setTransparentNavigationBar
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.view.showError
import com.kunzisoft.keepass.view.updateLockPaddingStart import com.kunzisoft.keepass.view.updateLockPaddingStart
import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel import com.kunzisoft.keepass.viewmodels.ColorPickerViewModel
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.EnumSet import java.util.EnumSet
import java.util.UUID import java.util.UUID
@@ -129,6 +133,7 @@ class EntryEditActivity : DatabaseLockActivity(),
private var mTemplatesSelectorAdapter: TemplatesSelectorAdapter? = null private var mTemplatesSelectorAdapter: TemplatesSelectorAdapter? = null
private val mColorPickerViewModel: ColorPickerViewModel by viewModels() private val mColorPickerViewModel: ColorPickerViewModel by viewModels()
private val mUserVerificationViewModel: UserVerificationViewModel by viewModels()
private var mAllowCustomFields = false private var mAllowCustomFields = false
private var mAllowOTP = false private var mAllowOTP = false
@@ -383,23 +388,52 @@ class EntryEditActivity : DatabaseLockActivity(),
lifecycleScope.launch { lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) { repeatOnLifecycle(Lifecycle.State.STARTED) {
mEntryEditViewModel.uiState.collect { uiState -> mEntryEditViewModel.entryEditState.collect { uiState ->
when (uiState) { when (uiState) {
EntryEditViewModel.UIState.Loading -> {} is EntryEditViewModel.EntryEditState.Loading -> {}
EntryEditViewModel.UIState.ShowOverwriteMessage -> { is EntryEditViewModel.EntryEditState.ShowOverwriteMessage -> {
if (mEntryEditViewModel.warningOverwriteDataAlreadyApproved.not()) { AlertDialog.Builder(this@EntryEditActivity)
AlertDialog.Builder(this@EntryEditActivity) .setTitle(R.string.warning_overwrite_data_title)
.setTitle(R.string.warning_overwrite_data_title) .setMessage(R.string.warning_overwrite_data_description)
.setMessage(R.string.warning_overwrite_data_description) .setNegativeButton(android.R.string.cancel) { _, _ ->
.setNegativeButton(android.R.string.cancel) { _, _ -> mEntryEditViewModel.backPressedAlreadyApproved = true
mEntryEditViewModel.backPressedAlreadyApproved = true onCancelSpecialMode()
onCancelSpecialMode() }
} .setPositiveButton(android.R.string.ok) { _, _ -> }
.setPositiveButton(android.R.string.ok) { _, _ -> .create().show()
mEntryEditViewModel.warningOverwriteDataAlreadyApproved = true mEntryEditViewModel.actionPerformed()
} }
.create().show() is EntryEditViewModel.EntryEditState.RequestUnprotectField -> {
mDatabase?.let { database ->
requestShowUnprotectField(
userVerificationViewModel = mUserVerificationViewModel,
database = database,
protectedFieldView = uiState.protectedFieldView
)
} }
mEntryEditViewModel.actionPerformed()
}
}
}
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mUserVerificationViewModel.userVerificationState.collect { uVState ->
when (uVState) {
is UserVerificationViewModel.UVState.Loading -> {}
is UserVerificationViewModel.UVState.OnUserVerificationCanceled -> {
coordinatorLayout?.showError(uVState.error)
mUserVerificationViewModel.onUserVerificationReceived()
}
is UserVerificationViewModel.UVState.OnUserVerificationSucceeded -> {
when (uVState.dataToVerify.actionType) {
UserVerificationActionType.SHOW_PROTECTED_FIELD -> {
uVState.dataToVerify.protectedFieldView?.unprotect()
}
else -> {}
}
mUserVerificationViewModel.onUserVerificationReceived()
} }
} }
} }
@@ -448,7 +482,7 @@ class EntryEditActivity : DatabaseLockActivity(),
searchAction = { searchAction = {
// Nothing when search retrieved // Nothing when search retrieved
}, },
selectionAction = { intentSender, typeMode, searchInfo -> selectionAction = { _, typeMode, _ ->
when(typeMode) { when(typeMode) {
TypeMode.DEFAULT -> {} TypeMode.DEFAULT -> {}
TypeMode.MAGIKEYBOARD -> TypeMode.MAGIKEYBOARD ->

View File

@@ -52,6 +52,9 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.MaterialTimePicker
@@ -72,10 +75,13 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.removeModes
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.TypeMode import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isUserVerificationNeeded
import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
@@ -117,10 +123,14 @@ import com.kunzisoft.keepass.view.applyWindowInsets
import com.kunzisoft.keepass.view.hideByFading import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.setTransparentNavigationBar import com.kunzisoft.keepass.view.setTransparentNavigationBar
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.view.showError
import com.kunzisoft.keepass.view.toastError import com.kunzisoft.keepass.view.toastError
import com.kunzisoft.keepass.view.updateLockPaddingStart import com.kunzisoft.keepass.view.updateLockPaddingStart
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
import com.kunzisoft.keepass.viewmodels.GroupViewModel import com.kunzisoft.keepass.viewmodels.GroupViewModel
import com.kunzisoft.keepass.viewmodels.MainCredentialViewModel
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
import kotlinx.coroutines.launch
import org.joda.time.LocalDateTime import org.joda.time.LocalDateTime
import java.util.EnumSet import java.util.EnumSet
@@ -130,8 +140,7 @@ class GroupActivity : DatabaseLockActivity(),
GroupFragment.NodesActionMenuListener, GroupFragment.NodesActionMenuListener,
GroupFragment.OnScrollListener, GroupFragment.OnScrollListener,
GroupFragment.GroupRefreshedListener, GroupFragment.GroupRefreshedListener,
SortDialogFragment.SortSelectionListener, SortDialogFragment.SortSelectionListener {
MainCredentialDialogFragment.AskMainCredentialDialogListener {
// Views // Views
private var header: ViewGroup? = null private var header: ViewGroup? = null
@@ -156,6 +165,8 @@ class GroupActivity : DatabaseLockActivity(),
private val mGroupViewModel: GroupViewModel by viewModels() private val mGroupViewModel: GroupViewModel by viewModels()
private val mGroupEditViewModel: GroupEditViewModel by viewModels() private val mGroupEditViewModel: GroupEditViewModel by viewModels()
private val mMainCredentialViewModel: MainCredentialViewModel by viewModels()
private val mUserVerificationViewModel: UserVerificationViewModel by viewModels()
private val mGroupActivityEducation = GroupActivityEducation(this) private val mGroupActivityEducation = GroupActivityEducation(this)
@@ -356,14 +367,22 @@ class GroupActivity : DatabaseLockActivity(),
SettingsActivity.launch(this@GroupActivity, true) SettingsActivity.launch(this@GroupActivity, true)
} }
R.id.menu_merge_from -> { R.id.menu_merge_from -> {
mExternalFileHelper?.openDocument() checkUserVerification(
userVerificationViewModel = mUserVerificationViewModel,
dataToVerify = UserVerificationData(
actionType = UserVerificationActionType.MERGE_FROM_DATABASE,
database = mDatabase
)
)
} }
R.id.menu_save_copy_to -> { R.id.menu_save_copy_to -> {
mExternalFileHelper?.createDocument( checkUserVerification(
getString(R.string.database_file_name_default) + userVerificationViewModel = mUserVerificationViewModel,
"_" + dataToVerify = UserVerificationData(
LocalDateTime.now().toString() + actionType = UserVerificationActionType.SAVE_DATABASE_COPY_TO,
mDatabase?.defaultFileExtension) database = mDatabase
)
)
} }
R.id.menu_lock_all -> { R.id.menu_lock_all -> {
lockAndExit() lockAndExit()
@@ -548,6 +567,58 @@ class GroupActivity : DatabaseLockActivity(),
} }
} }
} }
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mMainCredentialViewModel.uiState.collect { credentialState ->
when (credentialState) {
is MainCredentialViewModel.UIState.Loading -> {}
is MainCredentialViewModel.UIState.OnMainCredentialEntered -> {
mergeDatabaseFrom(credentialState.databaseUri, credentialState.mainCredential)
mMainCredentialViewModel.onActionReceived()
}
is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> {
mMainCredentialViewModel.onActionReceived()
}
}
}
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mUserVerificationViewModel.userVerificationState.collect { uVState ->
when (uVState) {
is UserVerificationViewModel.UVState.Loading -> {}
is UserVerificationViewModel.UVState.OnUserVerificationCanceled -> {
coordinatorLayout?.showError(uVState.error)
mUserVerificationViewModel.onUserVerificationReceived()
}
is UserVerificationViewModel.UVState.OnUserVerificationSucceeded -> {
val data = uVState.dataToVerify
when (data.actionType) {
UserVerificationActionType.EDIT_ENTRY -> {
editEntry(uVState.dataToVerify.database, uVState.dataToVerify.entryId)
}
UserVerificationActionType.MERGE_FROM_DATABASE -> {
mExternalFileHelper?.openDocument()
}
UserVerificationActionType.SAVE_DATABASE_COPY_TO -> {
mExternalFileHelper?.createDocument(
getString(R.string.database_file_name_default) +
"_" +
LocalDateTime.now().toString() +
uVState.dataToVerify.database?.defaultFileExtension
)
}
else -> {}
}
mUserVerificationViewModel.onUserVerificationReceived()
}
}
}
}
}
} }
override fun viewToInvalidateTimeout(): View? { override fun viewToInvalidateTimeout(): View? {
@@ -668,14 +739,6 @@ class GroupActivity : DatabaseLockActivity(),
result: ActionRunnable.Result result: ActionRunnable.Result
) { ) {
super.onDatabaseActionFinished(database, actionTask, result) super.onDatabaseActionFinished(database, actionTask, result)
var entry: Entry? = null
try {
entry = result.data?.getNewEntry(database)
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve entry action for selection", e)
}
when (actionTask) { when (actionTask) {
ACTION_DATABASE_UPDATE_ENTRY_TASK -> { ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
if (result.isSuccess) { if (result.isSuccess) {
@@ -687,7 +750,13 @@ class GroupActivity : DatabaseLockActivity(),
searchAction = { searchAction = {
// Search not used // Search not used
}, },
selectionAction = { intentSenderMode, typeMode, searchInfo -> selectionAction = { _, typeMode, _ ->
var entry: Entry? = null
try {
entry = result.data?.getNewEntry(database)
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve entry action for selection", e)
}
when (typeMode) { when (typeMode) {
TypeMode.DEFAULT -> {} TypeMode.DEFAULT -> {}
TypeMode.MAGIKEYBOARD -> entry?.let { TypeMode.MAGIKEYBOARD -> entry?.let {
@@ -701,19 +770,17 @@ class GroupActivity : DatabaseLockActivity(),
} }
} }
}, },
registrationAction = { intentSenderMode, typeMode, searchInfo -> registrationAction = { _, _, _ ->
// Save not used // Save not used
} }
) )
} }
coordinatorError?.showActionErrorIfNeeded(result)
// Reload the group
loadGroup()
finishNodeAction()
} }
} }
coordinatorError?.showActionErrorIfNeeded(result)
// Reload the group
loadGroup()
finishNodeAction()
} }
private fun manageIntent(intent: Intent?) { private fun manageIntent(intent: Intent?) {
@@ -860,7 +927,7 @@ class GroupActivity : DatabaseLockActivity(),
searchAction = { searchAction = {
// Nothing here, a search is simply performed // Nothing here, a search is simply performed
}, },
selectionAction = { intentSenderMode, typeMode, searchInfo -> selectionAction = { _, typeMode, searchInfo ->
when (typeMode) { when (typeMode) {
TypeMode.DEFAULT -> {} TypeMode.DEFAULT -> {}
TypeMode.MAGIKEYBOARD -> { TypeMode.MAGIKEYBOARD -> {
@@ -994,6 +1061,20 @@ class GroupActivity : DatabaseLockActivity(),
).containsSearchInfo(searchInfo) ).containsSearchInfo(searchInfo)
} }
private fun editEntry(database: ContextualDatabase?, entryId: NodeId<*>?) {
database?.let {
entryId?.let {
EntryEditActivity.launch(
activity = this@GroupActivity,
database = database,
registrationType = EntryEditActivity.RegistrationType.UPDATE,
nodeId = entryId,
activityResultLauncher = mEntryActivityResultLauncher
)
}
}
}
private fun finishNodeAction() { private fun finishNodeAction() {
actionNodeMode?.finish() actionNodeMode?.finish()
} }
@@ -1043,13 +1124,18 @@ class GroupActivity : DatabaseLockActivity(),
launchDialogForGroupUpdate(node as Group) launchDialogForGroupUpdate(node as Group)
} }
Type.ENTRY -> { Type.ENTRY -> {
EntryEditActivity.launch( if ((node as Entry).getEntryInfo(database).isUserVerificationNeeded()) {
activity = this@GroupActivity, checkUserVerification(
database = database, userVerificationViewModel = mUserVerificationViewModel,
registrationType = EntryEditActivity.RegistrationType.UPDATE, dataToVerify = UserVerificationData(
nodeId = (node as Entry).nodeId, actionType = UserVerificationActionType.EDIT_ENTRY,
activityResultLauncher = mEntryActivityResultLauncher database = database,
) entryId = node.nodeId
)
)
} else {
editEntry(database, node.nodeId)
}
} }
} }
return true return true
@@ -1133,20 +1219,6 @@ class GroupActivity : DatabaseLockActivity(),
return true return true
} }
override fun onAskMainCredentialDialogPositiveClick(
databaseUri: Uri?,
mainCredential: MainCredential
) {
databaseUri?.let {
mergeDatabaseFrom(it, mainCredential)
}
}
override fun onAskMainCredentialDialogNegativeClick(
databaseUri: Uri?,
mainCredential: MainCredential
) { }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@@ -1644,13 +1716,13 @@ class GroupActivity : DatabaseLockActivity(),
context = activity, context = activity,
database = database, database = database,
searchInfo = searchInfo, searchInfo = searchInfo,
onItemsFound = { openedDatabase, items -> onItemsFound = { _, items ->
when (typeMode) { when (typeMode) {
TypeMode.DEFAULT -> {} TypeMode.DEFAULT -> {}
TypeMode.MAGIKEYBOARD -> { TypeMode.MAGIKEYBOARD -> {
MagikeyboardService.performSelection( MagikeyboardService.performSelection(
items = items, items = items,
actionPopulateKeyboard = { entryInfo -> actionPopulateKeyboard = { _ ->
activity.buildSpecialModeResponseAndSetResult(items) activity.buildSpecialModeResponseAndSetResult(items)
onValidateSpecialMode() onValidateSpecialMode()
}, },

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2025 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.os.Bundle
import android.text.InputFilter
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.MasterCredential
import com.kunzisoft.keepass.utils.UriUtil.openUrl
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
class CheckDatabaseCredentialDialogFragment : DatabaseDialogFragment() {
private val userVerificationViewModel: UserVerificationViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
val rootView = inflater.inflate(R.layout.fragment_check_database_credential, null)
val editText = rootView.findViewById<TextView>(R.id.setup_check_password_edit_text)
editText.filters = arrayOf<InputFilter>(InputFilter.LengthFilter(
MasterCredential.CHECK_KEY_PASSWORD_LENGTH)
)
builder.setView(rootView)
.setPositiveButton(R.string.check) { _, _ ->
userVerificationViewModel.checkMainCredential(
editText.text.toString()
)
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
userVerificationViewModel.onUserVerificationFailed()
dismiss()
}
rootView.findViewById<View>(R.id.user_verification_information)?.setOnClickListener {
activity.openUrl(R.string.user_verification_explanation_url)
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
companion object {
fun getInstance(): CheckDatabaseCredentialDialogFragment {
val fragment = CheckDatabaseCredentialDialogFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
}
}

View File

@@ -20,45 +20,27 @@
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.database.MainCredential import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
import com.kunzisoft.keepass.utils.getParcelableCompat import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.view.MainCredentialView import com.kunzisoft.keepass.view.MainCredentialView
import com.kunzisoft.keepass.viewmodels.MainCredentialViewModel
class MainCredentialDialogFragment : DatabaseDialogFragment() { class MainCredentialDialogFragment : DatabaseDialogFragment() {
private var mainCredentialView: MainCredentialView? = null private var mainCredentialView: MainCredentialView? = null
private var mListener: AskMainCredentialDialogListener? = null
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
interface AskMainCredentialDialogListener { private val mMainCredentialViewModel: MainCredentialViewModel by activityViewModels()
fun onAskMainCredentialDialogPositiveClick(databaseUri: Uri?, mainCredential: MainCredential)
fun onAskMainCredentialDialogNegativeClick(databaseUri: Uri?, mainCredential: MainCredential)
}
override fun onAttach(activity: Context) {
super.onAttach(activity)
try {
mListener = activity as AskMainCredentialDialogListener
} catch (e: ClassCastException) {
throw ClassCastException(activity.toString()
+ " must implement " + AskMainCredentialDialogListener::class.java.name)
}
}
override fun onDetach() {
mListener = null
super.onDetach()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity -> activity?.let { activity ->
@@ -76,30 +58,35 @@ class MainCredentialDialogFragment : DatabaseDialogFragment() {
databaseUri?.let { databaseUri?.let {
root.findViewById<TextView>(R.id.title_database)?.text = root.findViewById<TextView>(R.id.title_database)?.text =
it.getDocumentFile(requireContext())?.name it.getDocumentFile(requireContext())?.name
}
builder.setView(root) builder.setView(root)
// Add action buttons // Add action buttons
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
mListener?.onAskMainCredentialDialogPositiveClick( mMainCredentialViewModel.validateMainCredential(
databaseUri, databaseUri = databaseUri,
retrieveMainCredential() mainCredential = retrieveMainCredential()
) )
} }
.setNegativeButton(android.R.string.cancel) { _, _ -> .setNegativeButton(android.R.string.cancel) { _, _ ->
mListener?.onAskMainCredentialDialogNegativeClick( mMainCredentialViewModel.cancelMainCredential(
databaseUri, databaseUri = databaseUri
retrieveMainCredential()
) )
} }
mExternalFileHelper = ExternalFileHelper(this)
mExternalFileHelper = ExternalFileHelper(this) mExternalFileHelper?.buildOpenDocument { uri ->
mExternalFileHelper?.buildOpenDocument { uri -> if (uri != null) {
if (uri != null) { mainCredentialView?.populateKeyFileView(uri)
mainCredentialView?.populateKeyFileView(uri) }
} }
mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)
} ?: run {
mMainCredentialViewModel.cancelMainCredential(
databaseUri = null,
error = FileNotFoundDatabaseException()
)
dismissAllowingStateLoss()
} }
mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)
return builder.create() return builder.create()
} }

View File

@@ -116,6 +116,9 @@ class EntryEditFragment: DatabaseFragment() {
setOnForegroundColorClickListener { setOnForegroundColorClickListener {
mEntryEditViewModel.requestForegroundColorSelection(templateView.getForegroundColor()) mEntryEditViewModel.requestForegroundColorSelection(templateView.getForegroundColor())
} }
setOnUnprotectClickListener { _, textEditFieldView ->
mEntryEditViewModel.requestUnprotectField(textEditFieldView)
}
setOnCustomEditionActionClickListener { field -> setOnCustomEditionActionClickListener { field ->
mEntryEditViewModel.requestCustomFieldEdition(field) mEntryEditViewModel.requestCustomFieldEdition(field)
} }

View File

@@ -16,13 +16,10 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.database.helper.getLocalizedName
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString
import com.kunzisoft.keepass.utils.UUIDUtils.asHexString import com.kunzisoft.keepass.utils.UUIDUtils.asHexString
import com.kunzisoft.keepass.view.TemplateView import com.kunzisoft.keepass.view.TemplateView
@@ -50,8 +47,6 @@ class EntryFragment: DatabaseFragment() {
private lateinit var uuidContainerView: View private lateinit var uuidContainerView: View
private lateinit var uuidReferenceView: TextView private lateinit var uuidReferenceView: TextView
private var mClipboardHelper: ClipboardHelper? = null
private val mEntryViewModel: EntryViewModel by activityViewModels() private val mEntryViewModel: EntryViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater, override fun onCreateView(inflater: LayoutInflater,
@@ -66,10 +61,6 @@ class EntryFragment: DatabaseFragment() {
savedInstanceState: Bundle?) { savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
context?.let { context ->
mClipboardHelper = ClipboardHelper(context)
}
rootView = view rootView = view
// Hide only the first time // Hide only the first time
if (savedInstanceState == null) { if (savedInstanceState == null) {
@@ -152,16 +143,14 @@ class EntryFragment: DatabaseFragment() {
private fun assignEntryInfo(entryInfo: EntryInfo?) { private fun assignEntryInfo(entryInfo: EntryInfo?) {
// Set copy buttons // Set copy buttons
templateView.apply { templateView.apply {
setOnUnprotectClickListener { protectedFieldView ->
mEntryViewModel.requestUnprotectField(protectedFieldView)
}
setOnAskCopySafeClickListener { setOnAskCopySafeClickListener {
showClipboardDialog() showClipboardDialog()
} }
setOnCopyActionClickListener { field, protectedFieldView ->
setOnCopyActionClickListener { field -> mEntryViewModel.requestCopyField(field, protectedFieldView)
mClipboardHelper?.timeoutCopyToClipboard(
TemplateField.getLocalizedName(context, field.name),
field.protectedValue.stringValue,
field.protectedValue.isProtected
)
} }
} }
@@ -242,14 +231,14 @@ class EntryFragment: DatabaseFragment() {
fun firstEntryFieldCopyView(): View? { fun firstEntryFieldCopyView(): View? {
return try { return try {
templateView.getActionImageView() templateView.getActionImageView()
} catch (e: Exception) { } catch (_: Exception) {
null null
} }
} }
fun launchEntryCopyEducationAction() { fun launchEntryCopyEducationAction() {
val appNameString = getString(R.string.app_name) val appNameString = getString(R.string.app_name)
mClipboardHelper?.timeoutCopyToClipboard(appNameString, appNameString) mEntryViewModel.copyToClipboard(appNameString)
} }
companion object { companion object {

View File

@@ -0,0 +1,25 @@
package com.kunzisoft.keepass.credentialprovider
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.view.ProtectedFieldView
data class UserVerificationData(
val actionType: UserVerificationActionType,
val database: ContextualDatabase? = null,
val entryId: NodeId<*>? = null,
val field: Field? = null,
val protectedFieldView: ProtectedFieldView? = null,
val preferenceKey: String? = null
)
enum class UserVerificationActionType {
LAUNCH_PASSKEY_CEREMONY,
SHOW_PROTECTED_FIELD,
COPY_PROTECTED_FIELD,
EDIT_ENTRY,
EDIT_DATABASE_SETTING,
MERGE_FROM_DATABASE,
SAVE_DATABASE_COPY_TO
}

View File

@@ -0,0 +1,209 @@
package com.kunzisoft.keepass.credentialprovider
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.CheckDatabaseCredentialDialogFragment
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.settings.PreferencesUtil.isUserVerificationDeviceCredential
import com.kunzisoft.keepass.utils.getEnumExtra
import com.kunzisoft.keepass.utils.putEnumExtra
import com.kunzisoft.keepass.view.ProtectedFieldView
import com.kunzisoft.keepass.view.toastError
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
class UserVerificationHelper {
companion object {
private const val EXTRA_USER_VERIFICATION = "com.kunzisoft.keepass.extra.userVerification"
private const val EXTRA_USER_VERIFIED_WITH_AUTH = "com.kunzisoft.keepass.extra.userVerifiedWithAuth"
/**
* Allowed authenticators for the User Verification
*/
const val ALLOWED_AUTHENTICATORS = BIOMETRIC_WEAK or DEVICE_CREDENTIAL
/**
* Check if the device supports the biometric prompt for User Verification
*/
fun Context.isAuthenticatorsAllowed(): Boolean {
return BiometricManager.from(this)
.canAuthenticate(ALLOWED_AUTHENTICATORS) == BIOMETRIC_SUCCESS
}
/**
* Add the User Verification to the intent
*/
fun Intent.addUserVerification(
userVerification: UserVerificationRequirement,
userVerifiedWithAuth: Boolean
) {
putEnumExtra(EXTRA_USER_VERIFICATION, userVerification)
putExtra(EXTRA_USER_VERIFIED_WITH_AUTH, userVerifiedWithAuth)
}
/**
* Define if the User is verified with authentification from the intent
*/
fun Intent.getUserVerifiedWithAuth(): Boolean {
return getBooleanExtra(EXTRA_USER_VERIFIED_WITH_AUTH, true)
}
/**
* Remove the User Verification from the intent
*/
fun Intent.removeUserVerification() {
removeExtra(EXTRA_USER_VERIFICATION)
}
/**
* Remove the User verified with auth from the intent
*/
fun Intent.removeUserVerifiedWithAuth() {
removeExtra(EXTRA_USER_VERIFIED_WITH_AUTH)
}
/**
* Get the User Verification from the intent
*/
fun Intent.isUserVerificationNeeded(userVerificationPreferred: Boolean): Boolean {
val userVerification: UserVerificationRequirement =
getEnumExtra<UserVerificationRequirement>(EXTRA_USER_VERIFICATION)
?: UserVerificationRequirement.PREFERRED
return (userVerification == UserVerificationRequirement.REQUIRED
|| (userVerificationPreferred
&& userVerification == UserVerificationRequirement.PREFERRED)
)
}
/**
* Check if the User needs to be verified for this entry
*/
fun EntryInfo.isUserVerificationNeeded(): Boolean {
// Apply to any entry with protected content
// Not only this.passkey != null
return true
}
fun Fragment.checkUserVerification(
userVerificationViewModel: UserVerificationViewModel,
dataToVerify: UserVerificationData
) {
activity?.checkUserVerification(userVerificationViewModel, dataToVerify)
}
fun FragmentActivity.requestShowUnprotectField(
userVerificationViewModel: UserVerificationViewModel,
database: ContextualDatabase,
protectedFieldView: ProtectedFieldView
) {
if (protectedFieldView.isCurrentlyProtected()) {
checkUserVerification(
userVerificationViewModel = userVerificationViewModel,
dataToVerify = UserVerificationData(
actionType = UserVerificationActionType.SHOW_PROTECTED_FIELD,
database = database,
protectedFieldView = protectedFieldView
)
)
} else {
protectedFieldView.protect()
}
}
/**
* Displays a dialog to verify the user
*/
fun FragmentActivity.checkUserVerification(
userVerificationViewModel: UserVerificationViewModel,
dataToVerify: UserVerificationData
) {
if (isAuthenticatorsAllowed() && isUserVerificationDeviceCredential(this)) {
showUserVerificationDeviceCredential(userVerificationViewModel, dataToVerify)
} else if (dataToVerify.database != null) {
showUserVerificationDatabaseCredential(userVerificationViewModel, dataToVerify)
}
}
/**
* Displays a dialog for entering the device credential to be checked
*/
fun FragmentActivity.showUserVerificationDeviceCredential(
userVerificationViewModel: UserVerificationViewModel,
dataToVerify: UserVerificationData
) {
BiometricPrompt(
this, ContextCompat.getMainExecutor(this),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
when (errorCode) {
BiometricPrompt.ERROR_CANCELED,
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
BiometricPrompt.ERROR_USER_CANCELED -> {
// No operation
Log.i("UserVerification", "$errString")
}
else -> {
toastError(SecurityException("Authentication error: $errString"))
}
}
userVerificationViewModel.onUserVerificationFailed(dataToVerify)
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
userVerificationViewModel.onUserVerificationSucceeded(dataToVerify)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
toastError(SecurityException(getString(R.string.device_unlock_not_recognized)))
userVerificationViewModel.onUserVerificationFailed(dataToVerify)
}
}).authenticate(
BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.user_verification_required_title))
.setSubtitle(getString(R.string.user_verification_required_description))
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
.setConfirmationRequired(false)
.build()
)
}
/**
* Displays a dialog for entering the database credential to be checked
*/
fun FragmentActivity.showUserVerificationDatabaseCredential(
userVerificationViewModel: UserVerificationViewModel,
dataToVerify: UserVerificationData
) {
userVerificationViewModel.dataToVerify = dataToVerify
val fragmentTag = "checkDatabaseCredentialDialog"
var fragment: CheckDatabaseCredentialDialogFragment? =
supportFragmentManager.findFragmentByTag(fragmentTag)
as? CheckDatabaseCredentialDialogFragment?
if (fragment == null) {
fragment = CheckDatabaseCredentialDialogFragment.getInstance()
fragment.show(this.supportFragmentManager, fragmentTag)
}
}
}
}

View File

@@ -31,9 +31,9 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.biometric.BiometricPrompt import androidx.lifecycle.Lifecycle
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity import com.kunzisoft.keepass.activities.GroupActivity
@@ -45,26 +45,27 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addTypeMode
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.TypeMode import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.addUserVerification
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.getUserVerifiedWithAuth
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.isUserVerificationNeeded
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.ALLOWED_AUTHENTICATORS
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAuthCode import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAuthCode
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addUserVerification
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getUserVerificationCondition
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getUserVerifiedWithAuth
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.isAuthenticatorsAllowed
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeUserVerification
import com.kunzisoft.keepass.credentialprovider.viewmodel.CredentialLauncherViewModel import com.kunzisoft.keepass.credentialprovider.viewmodel.CredentialLauncherViewModel
import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewModel import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewModel
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.model.AppOrigin import com.kunzisoft.keepass.model.AppOrigin
import com.kunzisoft.keepass.model.SearchInfo import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.settings.PreferencesUtil.isUserVerificationPreferred
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode
import com.kunzisoft.keepass.utils.LOCK_ACTION
import com.kunzisoft.keepass.view.toastError import com.kunzisoft.keepass.view.toastError
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
@@ -72,6 +73,7 @@ import java.util.UUID
class PasskeyLauncherActivity : DatabaseLockActivity() { class PasskeyLauncherActivity : DatabaseLockActivity() {
private val passkeyLauncherViewModel: PasskeyLauncherViewModel by viewModels() private val passkeyLauncherViewModel: PasskeyLauncherViewModel by viewModels()
private val userVerificationViewModel: UserVerificationViewModel by viewModels()
private var mPasskeySelectionActivityResultLauncher: ActivityResultLauncher<Intent>? = private var mPasskeySelectionActivityResultLauncher: ActivityResultLauncher<Intent>? =
this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
@@ -92,60 +94,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// To manage https://github.com/Kunzisoft/KeePassDX/issues/2283
val userVerificationCondition = intent.getUserVerificationCondition()
if (userVerificationCondition) {
if (isAuthenticatorsAllowed().not()) {
intent.removeUserVerification()
sendBroadcast(Intent(LOCK_ACTION))
}
}
// super.onCreate must be after UserVerification to allow database lock
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// Biometric must be after super.onCreate
if (userVerificationCondition) {
if (isAuthenticatorsAllowed()) {
BiometricPrompt(
this, ContextCompat.getMainExecutor(this),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
when (errorCode) {
BiometricPrompt.ERROR_CANCELED,
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
BiometricPrompt.ERROR_USER_CANCELED -> {
// No operation
Log.i(TAG, "$errString")
}
else -> {
toastError(SecurityException("Authentication error: $errString"))
}
}
passkeyLauncherViewModel.cancelResult()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
passkeyLauncherViewModel.launchAction(userVerified = true, intent, mSpecialMode)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
toastError(SecurityException(getString(R.string.device_unlock_not_recognized)))
passkeyLauncherViewModel.cancelResult()
}
}).authenticate(
BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.user_verification_required))
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
.setConfirmationRequired(false)
.build()
)
}
}
lifecycleScope.launch { lifecycleScope.launch {
// Initialize the parameters // Initialize the parameters
@@ -225,11 +174,58 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
} }
} }
} }
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
userVerificationViewModel.userVerificationState.collect { uiState ->
when (uiState) {
is UserVerificationViewModel.UVState.Loading -> {}
is UserVerificationViewModel.UVState.OnUserVerificationSucceeded -> {
val data = uiState.dataToVerify
when (data.actionType) {
UserVerificationActionType.LAUNCH_PASSKEY_CEREMONY -> {
passkeyLauncherViewModel.launchActionIfNeeded(
userVerified = true,
intent = intent,
specialMode = mSpecialMode,
database = uiState.dataToVerify.database
)
}
else -> {}
}
userVerificationViewModel.onUserVerificationReceived()
}
is UserVerificationViewModel.UVState.OnUserVerificationCanceled -> {
toastError(uiState.error)
passkeyLauncherViewModel.cancelResult()
userVerificationViewModel.onUserVerificationReceived()
}
}
}
}
}
} }
override fun onUnknownDatabaseRetrieved(database: ContextualDatabase?) { override fun onUnknownDatabaseRetrieved(database: ContextualDatabase?) {
super.onUnknownDatabaseRetrieved(database) super.onUnknownDatabaseRetrieved(database)
passkeyLauncherViewModel.launchActionIfNeeded(intent, mSpecialMode, database) // To manage https://github.com/Kunzisoft/KeePassDX/issues/2283
val userVerificationNeeded = intent.isUserVerificationNeeded(
userVerificationPreferred = isUserVerificationPreferred(this)
) && intent.getUserVerifiedWithAuth().not()
if (userVerificationNeeded) {
checkUserVerification(
userVerificationViewModel = userVerificationViewModel,
dataToVerify = UserVerificationData(
actionType = UserVerificationActionType.LAUNCH_PASSKEY_CEREMONY,
database = database
)
)
} else {
passkeyLauncherViewModel.launchActionIfNeeded(
intent = intent,
specialMode = mSpecialMode,
database = database
)
}
} }
override fun onDatabaseActionFinished( override fun onDatabaseActionFinished(

View File

@@ -50,7 +50,6 @@ import com.kunzisoft.keepass.credentialprovider.activity.PasskeyLauncherActivity
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationOptions import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationOptions
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.isAuthenticatorsAllowed
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.DatabaseTaskProvider import com.kunzisoft.keepass.database.DatabaseTaskProvider
import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException
@@ -67,7 +66,6 @@ class PasskeyProviderService : CredentialProviderService() {
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
private var mDatabase: ContextualDatabase? = null private var mDatabase: ContextualDatabase? = null
private lateinit var defaultIcon: Icon private lateinit var defaultIcon: Icon
private lateinit var relaunchIcon: Icon
private var isAutoSelectAllowed: Boolean = false private var isAutoSelectAllowed: Boolean = false
override fun onCreate() { override fun onCreate() {
@@ -86,11 +84,6 @@ class PasskeyProviderService : CredentialProviderService() {
setTintBlendMode(BlendMode.DST) setTintBlendMode(BlendMode.DST)
} }
relaunchIcon = Icon.createWithResource(
this@PasskeyProviderService,
R.drawable.ic_clock_loader_red_24dp
)
isAutoSelectAllowed = isPasskeyAutoSelectEnable(this) isAutoSelectAllowed = isPasskeyAutoSelectEnable(this)
} }
@@ -165,84 +158,70 @@ class PasskeyProviderService : CredentialProviderService() {
database = mDatabase, database = mDatabase,
searchInfo = searchInfo, searchInfo = searchInfo,
onItemsFound = { database, items -> onItemsFound = { database, items ->
manageUserVerification( Log.d(TAG, "Add pending intent for passkey selection with found items")
passkeyEntries = passkeyEntries, for (passkeyEntry in items) {
searchInfo = searchInfo, PasskeyLauncherActivity.getPendingIntent(
option = option, context = applicationContext,
userVerification = userVerification specialMode = SpecialMode.SELECTION,
) { nodeId = passkeyEntry.id,
Log.d(TAG, "Add pending intent for passkey selection with found items") appOrigin = passkeyEntry.appOrigin,
for (passkeyEntry in items) { userVerification = userVerification,
PasskeyLauncherActivity.getPendingIntent( userVerifiedWithAuth = false
context = applicationContext, )?.let { usagePendingIntent ->
specialMode = SpecialMode.SELECTION, val passkey = passkeyEntry.passkey
nodeId = passkeyEntry.id, passkeyEntries.add(
appOrigin = passkeyEntry.appOrigin, PublicKeyCredentialEntry(
userVerification = userVerification, context = applicationContext,
userVerifiedWithAuth = false username = passkey?.username ?: "Unknown",
)?.let { usagePendingIntent -> icon = passkeyEntry.buildIcon(
val passkey = passkeyEntry.passkey this@PasskeyProviderService,
passkeyEntries.add( database
PublicKeyCredentialEntry( )?.apply {
context = applicationContext, setTintBlendMode(BlendMode.DST)
username = passkey?.username ?: "Unknown", } ?: defaultIcon,
icon = passkeyEntry.buildIcon( pendingIntent = usagePendingIntent,
this@PasskeyProviderService, beginGetPublicKeyCredentialOption = option,
database displayName = passkeyEntry.getVisualTitle(),
)?.apply { isAutoSelectAllowed = isAutoSelectAllowed
setTintBlendMode(BlendMode.DST)
} ?: defaultIcon,
pendingIntent = usagePendingIntent,
beginGetPublicKeyCredentialOption = option,
displayName = passkeyEntry.getVisualTitle(),
isAutoSelectAllowed = isAutoSelectAllowed
)
) )
} )
} }
} }
callback(passkeyEntries) callback(passkeyEntries)
}, },
onItemNotFound = { _ -> onItemNotFound = { _ ->
manageUserVerification( Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId")
passkeyEntries = passkeyEntries, if (credentialIdList.isEmpty()) {
searchInfo = searchInfo, Log.d(TAG, "Add pending intent for passkey selection in opened database")
option = option, PasskeyLauncherActivity.getPendingIntent(
userVerification = userVerification, context = applicationContext,
) { specialMode = SpecialMode.SELECTION,
Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId") searchInfo = searchInfo,
if (credentialIdList.isEmpty()) { userVerification = userVerification,
Log.d(TAG, "Add pending intent for passkey selection in opened database") userVerifiedWithAuth = false
PasskeyLauncherActivity.getPendingIntent( )?.let { pendingIntent ->
context = applicationContext, passkeyEntries.add(
specialMode = SpecialMode.SELECTION, PublicKeyCredentialEntry(
searchInfo = searchInfo, context = applicationContext,
userVerification = userVerification, username = getString(R.string.passkey_database_username),
userVerifiedWithAuth = false displayName = getString(R.string.passkey_selection_description),
)?.let { pendingIntent -> icon = defaultIcon,
passkeyEntries.add( pendingIntent = pendingIntent,
PublicKeyCredentialEntry( beginGetPublicKeyCredentialOption = option,
context = applicationContext, lastUsedTime = Instant.now(),
username = getString(R.string.passkey_database_username), isAutoSelectAllowed = isAutoSelectAllowed
displayName = getString(R.string.passkey_selection_description),
icon = defaultIcon,
pendingIntent = pendingIntent,
beginGetPublicKeyCredentialOption = option,
lastUsedTime = Instant.now(),
isAutoSelectAllowed = isAutoSelectAllowed
)
)
}
callback(passkeyEntries)
} else {
throw IOException(
getString(
R.string.error_passkey_credential_id,
relyingPartyId,
credentialIdList
) )
) )
} }
callback(passkeyEntries)
} else {
throw IOException(
getString(
R.string.error_passkey_credential_id,
relyingPartyId,
credentialIdList
)
)
} }
}, },
onDatabaseClosed = { onDatabaseClosed = {
@@ -272,42 +251,6 @@ class PasskeyProviderService : CredentialProviderService() {
) )
} }
/**
* To easily manage user verification condition
*/
private fun manageUserVerification(
passkeyEntries: MutableList<CredentialEntry>,
searchInfo: SearchInfo,
option: BeginGetPublicKeyCredentialOption,
userVerification: UserVerificationRequirement,
standardAction: () -> Unit
) {
if (userVerification == UserVerificationRequirement.REQUIRED && isAuthenticatorsAllowed().not()) {
PasskeyLauncherActivity.getPendingIntent(
context = applicationContext,
specialMode = SpecialMode.SELECTION,
searchInfo = searchInfo,
userVerification = userVerification,
userVerifiedWithAuth = true
)?.let { pendingIntent ->
passkeyEntries.add(
PublicKeyCredentialEntry(
context = applicationContext,
username = getString(R.string.passkey_database_username),
displayName = getString(R.string.passkey_relaunch_database_description),
icon = relaunchIcon,
pendingIntent = pendingIntent,
beginGetPublicKeyCredentialOption = option,
lastUsedTime = Instant.now(),
isAutoSelectAllowed = isAutoSelectAllowed
)
)
}
} else {
standardAction()
}
}
override fun onBeginCreateCredentialRequest( override fun onBeginCreateCredentialRequest(
request: BeginCreateCredentialRequest, request: BeginCreateCredentialRequest,
cancellationSignal: CancellationSignal, cancellationSignal: CancellationSignal,

View File

@@ -30,10 +30,6 @@ import android.security.keystore.KeyProperties
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
import androidx.credentials.CreatePublicKeyCredentialRequest import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.CreatePublicKeyCredentialResponse import androidx.credentials.CreatePublicKeyCredentialResponse
import androidx.credentials.GetPublicKeyCredentialOption import androidx.credentials.GetPublicKeyCredentialOption
@@ -60,7 +56,6 @@ import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredential
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists.getOriginFromPrivilegedAllowLists import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists.getOriginFromPrivilegedAllowLists
import com.kunzisoft.keepass.model.AndroidOrigin import com.kunzisoft.keepass.model.AndroidOrigin
import com.kunzisoft.keepass.model.AppOrigin import com.kunzisoft.keepass.model.AppOrigin
@@ -68,9 +63,7 @@ import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Passkey import com.kunzisoft.keepass.model.Passkey
import com.kunzisoft.keepass.utils.AppUtil import com.kunzisoft.keepass.utils.AppUtil
import com.kunzisoft.keepass.utils.StringUtil.toHexString import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.getEnumExtra
import com.kunzisoft.keepass.utils.getParcelableExtraCompat import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.utils.putEnumExtra
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.IOException import java.io.IOException
@@ -98,8 +91,6 @@ object PasskeyHelper {
private const val EXTRA_APP_ORIGIN = "com.kunzisoft.keepass.extra.appOrigin" private const val EXTRA_APP_ORIGIN = "com.kunzisoft.keepass.extra.appOrigin"
private const val EXTRA_TIMESTAMP = "com.kunzisoft.keepass.extra.timestamp" private const val EXTRA_TIMESTAMP = "com.kunzisoft.keepass.extra.timestamp"
private const val EXTRA_AUTHENTICATION_CODE = "com.kunzisoft.keepass.extra.authenticationCode" private const val EXTRA_AUTHENTICATION_CODE = "com.kunzisoft.keepass.extra.authenticationCode"
private const val EXTRA_USER_VERIFICATION = "com.kunzisoft.keepass.extra.userVerification"
private const val EXTRA_USER_VERIFIED_WITH_AUTH = "com.kunzisoft.keepass.extra.userVerifiedWithAuth"
private const val SEPARATOR = "_" private const val SEPARATOR = "_"
@@ -116,60 +107,6 @@ object PasskeyHelper {
private val internalSecureRandom: SecureRandom = SecureRandom() private val internalSecureRandom: SecureRandom = SecureRandom()
/**
* Add the User Verification to the intent
*/
fun Intent.addUserVerification(
userVerification: UserVerificationRequirement,
userVerifiedWithAuth: Boolean
) {
putEnumExtra(EXTRA_USER_VERIFICATION, userVerification)
putExtra(EXTRA_USER_VERIFIED_WITH_AUTH, userVerifiedWithAuth)
}
/**
* Get the User Verification from the intent
*/
fun Intent.getUserVerificationCondition(): Boolean {
return (getEnumExtra<UserVerificationRequirement>(EXTRA_USER_VERIFICATION)
?: UserVerificationRequirement.PREFERRED) == UserVerificationRequirement.REQUIRED
}
/**
* Define if the User is verified with authentification from the intent
*/
fun Intent.getUserVerifiedWithAuth(): Boolean {
return getBooleanExtra(EXTRA_USER_VERIFIED_WITH_AUTH, true)
}
/**
* Remove the User Verification from the intent
*/
fun Intent.removeUserVerification() {
removeExtra(EXTRA_USER_VERIFICATION)
}
/**
* Remove the User verified with auth from the intent
*/
fun Intent.removeUserVerifiedWithAuth() {
removeExtra(EXTRA_USER_VERIFIED_WITH_AUTH)
}
/**
* Allowed authenticators for the User Verification
*/
const val ALLOWED_AUTHENTICATORS = BIOMETRIC_WEAK or DEVICE_CREDENTIAL
/**
* Check if the device supports the biometric prompt for User Verification
*/
fun Context.isAuthenticatorsAllowed(): Boolean {
return BiometricManager.from(this)
.canAuthenticate(ALLOWED_AUTHENTICATORS) == BIOMETRIC_SUCCESS
}
/** /**
* Add an authentication code generated by an entry to the intent * Add an authentication code generated by an entry to the intent
*/ */

View File

@@ -24,7 +24,6 @@ import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredential
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildCreatePublicKeyCredentialResponse import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildCreatePublicKeyCredentialResponse
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyPublicKeyCredential import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyPublicKeyCredential
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.checkSecurity import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.checkSecurity
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getUserVerificationCondition
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getVerifiedGETClientDataResponse import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getVerifiedGETClientDataResponse
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeAppOrigin import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeAppOrigin
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removePasskey import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removePasskey
@@ -152,13 +151,14 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
} }
} }
fun launchAction( fun launchActionIfNeeded(
userVerified: Boolean, userVerified: Boolean,
intent: Intent, intent: Intent,
specialMode: SpecialMode, specialMode: SpecialMode,
database: ContextualDatabase?
) { ) {
this.mUserVerified = userVerified this.mUserVerified = userVerified
super.launchActionIfNeeded(intent, specialMode, mDatabase) launchActionIfNeeded(intent, specialMode, database)
} }
override fun launchActionIfNeeded( override fun launchActionIfNeeded(
@@ -166,15 +166,9 @@ class PasskeyLauncherViewModel(application: Application): CredentialLauncherView
specialMode: SpecialMode, specialMode: SpecialMode,
database: ContextualDatabase? database: ContextualDatabase?
) { ) {
if (intent.getUserVerificationCondition()) { // Launch with database when a nodeId is present
if (database != null) { if ((database != null && database.loaded) || intent.retrieveNodeId() == null) {
onDatabaseRetrieved(database) super.launchActionIfNeeded(intent, specialMode, database)
}
} else {
// Launch with database when a nodeId is present
if ((database != null && database.loaded) || intent.retrieveNodeId() == null) {
super.launchActionIfNeeded(intent, specialMode, database)
}
} }
} }

View File

@@ -238,7 +238,7 @@ class DatabaseTaskProvider(
try { try {
context.unregisterReceiver(databaseTaskBroadcastReceiver) context.unregisterReceiver(databaseTaskBroadcastReceiver)
} catch (e: IllegalArgumentException) { } catch (_: IllegalArgumentException) {
// If receiver not register, do nothing // If receiver not register, do nothing
} }
} }
@@ -319,7 +319,6 @@ class DatabaseTaskProvider(
databaseUri: Uri, databaseUri: Uri,
mainCredential: MainCredential mainCredential: MainCredential
) { ) {
start(Bundle().apply { start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri) putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential) putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)

View File

@@ -33,6 +33,7 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.GroupActivity import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseAction import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.credentialprovider.activity.HardwareKeyActivity
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.MainCredential import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.database.ProgressMessage import com.kunzisoft.keepass.database.ProgressMessage
@@ -61,7 +62,6 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.hardware.HardwareKey import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.credentialprovider.activity.HardwareKeyActivity
import com.kunzisoft.keepass.model.CipherEncryptDatabase import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -917,7 +917,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
private fun buildDatabaseAssignCredentialActionTask( private fun buildDatabaseAssignCredentialActionTask(
intent: Intent, intent: Intent,
database: ContextualDatabase, database: ContextualDatabase
): ActionRunnable? { ): ActionRunnable? {
return if (intent.hasExtra(DATABASE_URI_KEY) return if (intent.hasExtra(DATABASE_URI_KEY)
&& intent.hasExtra(MAIN_CREDENTIAL_KEY) && intent.hasExtra(MAIN_CREDENTIAL_KEY)

View File

@@ -302,7 +302,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
biometricUnlockEnablePreference.isChecked = false biometricUnlockEnablePreference.isChecked = false
warningMessage(activity, keystoreWarning = true, deleteKeys = true) { warningMessage(activity, keystoreWarning = true, deleteKeys = true) {
biometricUnlockEnablePreference.isChecked = true biometricUnlockEnablePreference.isChecked = true
deviceCredentialUnlockEnablePreference?.isChecked = false deviceCredentialUnlockEnablePreference.isChecked = false
} }
} else { } else {
biometricUnlockEnablePreference.isChecked = false biometricUnlockEnablePreference.isChecked = false
@@ -349,7 +349,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
deviceCredentialUnlockEnablePreference.isChecked = false deviceCredentialUnlockEnablePreference.isChecked = false
warningMessage(activity, keystoreWarning = true, deleteKeys = true) { warningMessage(activity, keystoreWarning = true, deleteKeys = true) {
deviceCredentialUnlockEnablePreference.isChecked = true deviceCredentialUnlockEnablePreference.isChecked = true
biometricUnlockEnablePreference?.isChecked = false biometricUnlockEnablePreference.isChecked = false
} }
} else { } else {
deviceCredentialUnlockEnablePreference.isChecked = false deviceCredentialUnlockEnablePreference.isChecked = false
@@ -412,7 +412,6 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
warningAlertDialog = AlertDialog.Builder(activity) warningAlertDialog = AlertDialog.Builder(activity)
.setMessage(message) .setMessage(message)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(resources.getString(android.R.string.ok) .setPositiveButton(resources.getString(android.R.string.ok)
) { _, _ -> ) { _, _ ->
validate?.invoke() validate?.invoke()
@@ -524,27 +523,23 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
override fun onDisplayPreferenceDialog(preference: Preference) { override fun onDisplayPreferenceDialog(preference: Preference) {
var otherDialogFragment = false
var dialogFragment: DialogFragment? = null var dialogFragment: DialogFragment? = null
// Main Preferences
when (preference.key) { when (preference.key) {
getString(R.string.app_timeout_key), getString(R.string.app_timeout_key),
getString(R.string.clipboard_timeout_key), getString(R.string.clipboard_timeout_key),
getString(R.string.temp_device_unlock_timeout_key) -> { getString(R.string.temp_device_unlock_timeout_key) -> {
dialogFragment = DurationDialogFragmentCompat.newInstance(preference.key) dialogFragment = DurationDialogFragmentCompat.newInstance(preference.key)
} }
else -> otherDialogFragment = true else -> {}
} }
if (dialogFragment != null) { if (dialogFragment != null) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
dialogFragment.setTargetFragment(this, 0) dialogFragment.setTargetFragment(this, 0)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT) dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
} } else {
// Could not be handled here. Try with the super method. // Could not be handled here. Try with the super method.
else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference) super.onDisplayPreferenceDialog(preference)
} }
} }

View File

@@ -42,6 +42,9 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
import com.kunzisoft.keepass.credentialprovider.UserVerificationActionType
import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.credentialprovider.UserVerificationHelper.Companion.checkUserVerification
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
@@ -74,11 +77,16 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.getParcelableCompat import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.utils.getSerializableCompat import com.kunzisoft.keepass.utils.getSerializableCompat
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
import com.kunzisoft.keepass.viewmodels.SettingsViewModel
import com.kunzisoft.keepass.viewmodels.UserVerificationViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetrieval { class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetrieval {
private val mSettingsViewModel: SettingsViewModel by activityViewModels()
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels() private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
private val mUserVerificationViewModel: UserVerificationViewModel by activityViewModels()
private val mDatabase: ContextualDatabase? private val mDatabase: ContextualDatabase?
get() = mDatabaseViewModel.database get() = mDatabaseViewModel.database
private var mDatabaseReadOnly: Boolean = false private var mDatabaseReadOnly: Boolean = false
@@ -171,6 +179,51 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
} }
} }
} }
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mUserVerificationViewModel.userVerificationState.collect { state ->
when (state) {
is UserVerificationViewModel.UVState.Loading -> {}
is UserVerificationViewModel.UVState.OnUserVerificationCanceled -> {
mSettingsViewModel.showError(state.error)
mUserVerificationViewModel.onUserVerificationReceived()
}
is UserVerificationViewModel.UVState.OnUserVerificationSucceeded -> {
val data = state.dataToVerify
when (data.actionType) {
UserVerificationActionType.EDIT_DATABASE_SETTING -> {
val database = data.database
val preferenceKey = data.preferenceKey
if (database != null && preferenceKey != null) {
// Main Preferences
when (preferenceKey) {
// Master Key
getString(R.string.settings_database_change_credentials_key) -> {
SetMainCredentialDialogFragment
.getInstance(database.allowNoMasterKey)
.show(parentFragmentManager, "passwordDialog")
}
else -> {}
}
// TODO Settings in compose
@Suppress("DEPRECATION")
mSettingsViewModel.dialogFragment?.let { dialogFragment ->
dialogFragment.setTargetFragment(
this@NestedDatabaseSettingsFragment, 0
)
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
}
mSettingsViewModel.dialogFragment = null
}
}
else -> {}
}
mUserVerificationViewModel.onUserVerificationReceived()
}
}
}
}
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -325,7 +378,6 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
} }
// Change the recycle bin group // Change the recycle bin group
recycleBinGroupPref?.setOnPreferenceClickListener { recycleBinGroupPref?.setOnPreferenceClickListener {
true true
} }
// Recycle Bin group // Recycle Bin group
@@ -431,11 +483,18 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
} }
private fun onCreateDatabaseMasterKeyPreference(database: ContextualDatabase) { private fun onCreateDatabaseMasterKeyPreference(database: ContextualDatabase) {
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply { val changeCredentialKey = getString(R.string.settings_database_change_credentials_key)
findPreference<Preference>(changeCredentialKey)?.apply {
isEnabled = if (!mDatabaseReadOnly) { isEnabled = if (!mDatabaseReadOnly) {
onPreferenceClickListener = Preference.OnPreferenceClickListener { onPreferenceClickListener = Preference.OnPreferenceClickListener {
SetMainCredentialDialogFragment.getInstance(database.allowNoMasterKey) checkUserVerification(
.show(parentFragmentManager, "passwordDialog") mUserVerificationViewModel,
UserVerificationData(
actionType = UserVerificationActionType.EDIT_DATABASE_SETTING,
database = database,
preferenceKey = changeCredentialKey
)
)
false false
} }
true true
@@ -462,7 +521,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
// To reassign color listener after orientation change // To reassign color listener after orientation change
val chromaDialog = parentFragmentManager.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat? val chromaDialog = parentFragmentManager.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat?
chromaDialog?.onColorSelectedListener = colorSelectedListener chromaDialog?.onColorSelectedListener = colorSelectedListener
} catch (e: Exception) {} } catch (_: Exception) {}
return view return view
} }
@@ -730,9 +789,15 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
} }
if (dialogFragment != null && !mDatabaseReadOnly) { if (dialogFragment != null && !mDatabaseReadOnly) {
@Suppress("DEPRECATION") mSettingsViewModel.dialogFragment = dialogFragment
dialogFragment.setTargetFragment(this, 0) checkUserVerification(
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT) mUserVerificationViewModel,
UserVerificationData(
actionType = UserVerificationActionType.EDIT_DATABASE_SETTING,
database = mDatabase,
preferenceKey = preference.key
)
)
} }
// Could not be handled here. Try with the super method. // Could not be handled here. Try with the super method.
else if (otherDialogFragment) { else if (otherDialogFragment) {
@@ -742,7 +807,6 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
context?.let { context -> context?.let { context ->
mDatabaseAutoSaveEnabled = PreferencesUtil.isAutoSaveDatabaseEnabled(context) mDatabaseAutoSaveEnabled = PreferencesUtil.isAutoSaveDatabaseEnabled(context)
} }

View File

@@ -30,11 +30,17 @@ import com.kunzisoft.keepass.activities.dialogs.UnderDevelopmentFeatureDialogFra
abstract class NestedSettingsFragment : PreferenceFragmentCompat() { abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
enum class Screen { enum class Screen {
APPLICATION, FORM_FILLING, DEVICE_UNLOCK, APPEARANCE, DATABASE, DATABASE_SECURITY, DATABASE_MASTER_KEY APPLICATION,
FORM_FILLING,
DEVICE_UNLOCK,
APPEARANCE,
DATABASE,
DATABASE_SECURITY,
DATABASE_MASTER_KEY
} }
fun getScreen(): Screen { fun getScreen(): Screen {
return Screen.values()[requireArguments().getInt(TAG_KEY)] return Screen.entries[requireArguments().getInt(TAG_KEY)]
} }
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -50,8 +56,7 @@ abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
preferenceInDev.setOnPreferenceClickListener { preference -> preferenceInDev.setOnPreferenceClickListener { preference ->
try { // don't check if we can try { // don't check if we can
(preference as TwoStatePreference).isChecked = false (preference as TwoStatePreference).isChecked = false
} catch (ignored: Exception) { } catch (_: Exception) {}
}
UnderDevelopmentFeatureDialogFragment().show(parentFragmentManager, "underDevFeatureDialog") UnderDevelopmentFeatureDialogFragment().show(parentFragmentManager, "underDevFeatureDialog")
false false
} }

View File

@@ -132,12 +132,6 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.hide_templates_default)) context.resources.getBoolean(R.bool.hide_templates_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 { fun colorizePassword(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.colorize_password_key), return prefs.getBoolean(context.getString(R.string.colorize_password_key),
@@ -696,6 +690,18 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.passkeys_close_database_default)) context.resources.getBoolean(R.bool.passkeys_close_database_default))
} }
fun isUserVerificationDeviceCredential(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.user_verification_device_credential_key),
context.resources.getBoolean(R.bool.user_verification_device_credential_default))
}
fun isUserVerificationPreferred(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.user_verification_preferred_key),
context.resources.getBoolean(R.bool.user_verification_preferred_default))
}
fun isPasskeyBackupEligibilityEnable(context: Context): Boolean { fun isPasskeyBackupEligibilityEnable(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.passkeys_backup_eligibility_key), return prefs.getBoolean(context.getString(R.string.passkeys_backup_eligibility_key),
@@ -882,7 +888,6 @@ object PreferencesUtil {
context.getString(R.string.show_entry_colors_key) -> editor.putBoolean(name, value.toBoolean()) context.getString(R.string.show_entry_colors_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.hide_expired_entries_key) -> editor.putBoolean(name, value.toBoolean()) context.getString(R.string.hide_expired_entries_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.hide_templates_key) -> editor.putBoolean(name, value.toBoolean()) context.getString(R.string.hide_templates_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.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_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.list_groups_show_number_entries_key) -> editor.putBoolean(name, value.toBoolean())

View File

@@ -28,9 +28,13 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment
@@ -41,6 +45,9 @@ import com.kunzisoft.keepass.database.MainCredential
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.view.showError
import com.kunzisoft.keepass.viewmodels.SettingsViewModel
import kotlinx.coroutines.launch
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.Properties import java.util.Properties
@@ -49,6 +56,8 @@ open class SettingsActivity
MainPreferenceFragment.Callback, MainPreferenceFragment.Callback,
SetMainCredentialDialogFragment.AssignMainCredentialDialogListener { SetMainCredentialDialogFragment.AssignMainCredentialDialogListener {
private val mSettingsViewModel: SettingsViewModel by viewModels()
private var backupManager: BackupManager? = null private var backupManager: BackupManager? = null
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
@@ -118,7 +127,7 @@ open class SettingsActivity
if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty()) if (savedInstanceState?.getString(TITLE_KEY).isNullOrEmpty())
toolbar?.setTitle(R.string.settings) toolbar?.setTitle(R.string.settings)
else else
toolbar?.title = savedInstanceState?.getString(TITLE_KEY) toolbar?.title = savedInstanceState.getString(TITLE_KEY)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
@@ -145,6 +154,20 @@ open class SettingsActivity
// Eat state // Eat state
intent.removeExtra(FRAGMENT_ARG) intent.removeExtra(FRAGMENT_ARG)
} }
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
mSettingsViewModel.settingsState.collect { settingsState ->
when (settingsState) {
is SettingsViewModel.SettingsState.Wait -> {}
is SettingsViewModel.SettingsState.ShowError -> {
coordinatorLayout?.showError(settingsState.error)
mSettingsViewModel.errorShown()
}
}
}
}
}
} }
/** /**

View File

@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.view
import android.content.Context import android.content.Context
import android.text.Editable import android.text.Editable
import android.text.InputType
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.text.TextWatcher import android.text.TextWatcher
@@ -51,7 +50,6 @@ class PasswordEditView @JvmOverloads constructor(context: Context,
private var mViewHint: String = "" private var mViewHint: String = ""
private var mMaxLines: Int = 3 private var mMaxLines: Int = 3
private var mShowPassword: Boolean = false
private var mPasswordTextWatchers: MutableList<TextWatcher> = mutableListOf() private var mPasswordTextWatchers: MutableList<TextWatcher> = mutableListOf()
private var mPasswordTextWatcher: TextWatcher? = null private var mPasswordTextWatcher: TextWatcher? = null
@@ -65,8 +63,6 @@ class PasswordEditView @JvmOverloads constructor(context: Context,
mViewHint = getString(R.styleable.PasswordView_passwordHint) mViewHint = getString(R.styleable.PasswordView_passwordHint)
?: context.getString(R.string.password) ?: context.getString(R.string.password)
mMaxLines = getInteger(R.styleable.PasswordView_passwordMaxLines, mMaxLines) mMaxLines = getInteger(R.styleable.PasswordView_passwordMaxLines, mMaxLines)
mShowPassword = getBoolean(R.styleable.PasswordView_passwordVisible,
!PreferencesUtil.hideProtectedValue(context))
} finally { } finally {
recycle() recycle()
} }
@@ -76,16 +72,12 @@ class PasswordEditView @JvmOverloads constructor(context: Context,
inflater?.inflate(R.layout.view_password_edit, this) inflater?.inflate(R.layout.view_password_edit, this)
passwordInputLayout = findViewById(R.id.password_edit_input_layout) passwordInputLayout = findViewById(R.id.password_edit_input_layout)
passwordInputLayout?.hint = mViewHint passwordInputLayout.hint = mViewHint
passwordText = findViewById(R.id.password_edit_text) passwordText = findViewById(R.id.password_edit_text)
if (mShowPassword) { passwordText.maxLines = mMaxLines
passwordText?.inputType = passwordText.inputType or passwordText.applyFontVisibility()
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
}
passwordText?.maxLines = mMaxLines
passwordText?.applyFontVisibility()
passwordStrengthProgress = findViewById(R.id.password_edit_strength_progress) passwordStrengthProgress = findViewById(R.id.password_edit_strength_progress)
passwordStrengthProgress?.apply { passwordStrengthProgress.apply {
setIndicatorColor(PasswordEntropy.Strength.RISKY.color) setIndicatorColor(PasswordEntropy.Strength.RISKY.color)
progress = 0 progress = 0
max = 100 max = 100
@@ -93,7 +85,7 @@ class PasswordEditView @JvmOverloads constructor(context: Context,
passwordEntropy = findViewById(R.id.password_edit_entropy) passwordEntropy = findViewById(R.id.password_edit_entropy)
mPasswordEntropyCalculator = PasswordEntropy { mPasswordEntropyCalculator = PasswordEntropy {
passwordText?.text?.toString()?.let { firstPassword -> passwordText.text?.toString()?.let { firstPassword ->
getEntropyStrength(firstPassword) getEntropyStrength(firstPassword)
} }
} }
@@ -119,7 +111,7 @@ class PasswordEditView @JvmOverloads constructor(context: Context,
PasswordGenerator.colorizedPassword(editable) PasswordGenerator.colorizedPassword(editable)
} }
} }
passwordText?.addTextChangedListener(mPasswordTextWatcher) passwordText.addTextChangedListener(mPasswordTextWatcher)
} }
private fun getEntropyStrength(passwordText: String) { private fun getEntropyStrength(passwordText: String) {

View File

@@ -0,0 +1,14 @@
package com.kunzisoft.keepass.view
import android.view.View.OnClickListener
interface ProtectedFieldView {
fun setProtection(
protection: Boolean,
isCurrentlyProtected: Boolean,
onUnprotectClickListener: OnClickListener?
)
fun isCurrentlyProtected(): Boolean
fun protect()
fun unprotect()
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2025 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.os.Parcel
import android.os.Parcelable
import android.os.Parcelable.Creator
import android.util.AttributeSet
import android.widget.RelativeLayout
import com.kunzisoft.keepass.utils.readBooleanCompat
import com.kunzisoft.keepass.utils.writeBooleanCompat
abstract class ProtectedTextFieldView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0)
: RelativeLayout(context, attrs, defStyle),
GenericTextFieldView, ProtectedFieldView {
var isProtected: Boolean = false
private set
private var mIsCurrentlyProtected: Boolean = true
// Only to fix rebuild view from template
var onSaveInstanceState: (() -> Unit)? = null
override fun isCurrentlyProtected(): Boolean {
return mIsCurrentlyProtected
}
override fun protect() {
mIsCurrentlyProtected = true
changeProtectedValueParameters()
}
override fun unprotect() {
mIsCurrentlyProtected = false
changeProtectedValueParameters()
}
override fun setProtection(
protection: Boolean,
isCurrentlyProtected: Boolean,
onUnprotectClickListener: OnClickListener?
) {
this.isProtected = protection
this.mIsCurrentlyProtected = isCurrentlyProtected
if (isProtected) {
changeProtectedValueParameters()
}
}
protected abstract fun changeProtectedValueParameters()
override fun onSaveInstanceState(): Parcelable? {
onSaveInstanceState?.invoke()
return ProtectionState(super.onSaveInstanceState()).apply {
this.isCurrentlyProtected = isCurrentlyProtected()
}
}
override fun onRestoreInstanceState(state: Parcelable?) {
when (state) {
is ProtectionState -> {
super.onRestoreInstanceState(state.superState)
mIsCurrentlyProtected = state.isCurrentlyProtected
}
else -> super.onRestoreInstanceState(state)
}
}
internal class ProtectionState : BaseSavedState {
var isCurrentlyProtected: Boolean = true
constructor(superState: Parcelable?) : super(superState)
private constructor(parcel: Parcel) : super(parcel) {
isCurrentlyProtected = parcel.readBooleanCompat()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeBooleanCompat(isCurrentlyProtected)
}
companion object CREATOR : Creator<ProtectionState> {
override fun createFromParcel(parcel: Parcel): ProtectionState {
return ProtectionState(parcel)
}
override fun newArray(size: Int): Array<ProtectionState?> {
return arrayOfNulls(size)
}
}
}
}

View File

@@ -31,6 +31,7 @@ import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.KeyboardUtil.hideKeyboard import com.kunzisoft.keepass.utils.KeyboardUtil.hideKeyboard
import com.kunzisoft.keepass.utils.readListCompat
import com.kunzisoft.keepass.utils.readParcelableCompat import com.kunzisoft.keepass.utils.readParcelableCompat
@@ -45,11 +46,12 @@ abstract class TemplateAbstractView<
private var mTemplate: Template? = null private var mTemplate: Template? = null
protected var mEntryInfo: EntryInfo? = null protected var mEntryInfo: EntryInfo? = null
// To keep unprotected views during orientation change
protected var mUnprotectedFields = mutableListOf<Field>()
private var mViewFields = mutableListOf<ViewField>() private var mViewFields = mutableListOf<ViewField>()
protected var mFontInVisibility: Boolean = PreferencesUtil.fieldFontIsInVisibility(context) protected var mFontInVisibility: Boolean = PreferencesUtil.fieldFontIsInVisibility(context)
protected var mHideProtectedValue: Boolean = PreferencesUtil.hideProtectedValue(context)
protected var headerContainerView: ViewGroup protected var headerContainerView: ViewGroup
protected var entryIconView: ImageView protected var entryIconView: ImageView
protected var backgroundColorView: View protected var backgroundColorView: View
@@ -121,9 +123,6 @@ abstract class TemplateAbstractView<
} }
private fun buildTemplate() { private fun buildTemplate() {
// Retrieve preferences
mHideProtectedValue = PreferencesUtil.hideProtectedValue(context)
// Build each template section // Build each template section
titleContainerView.removeAllViews() titleContainerView.removeAllViews()
templateContainerView.removeAllViews() templateContainerView.removeAllViews()
@@ -284,10 +283,6 @@ abstract class TemplateAbstractView<
this.mFontInVisibility = fontInVisibility this.mFontInVisibility = fontInVisibility
} }
fun setHideProtectedValue(hideProtectedValue: Boolean) {
this.mHideProtectedValue = hideProtectedValue
}
fun setEntryInfo(entryInfo: EntryInfo?) { fun setEntryInfo(entryInfo: EntryInfo?) {
mEntryInfo = entryInfo mEntryInfo = entryInfo
buildTemplateAndPopulateInfo() buildTemplateAndPopulateInfo()
@@ -578,7 +573,7 @@ abstract class TemplateAbstractView<
} }
return if (!isStandardFieldName(customField.name)) { return if (!isStandardFieldName(customField.name)) {
customFieldsContainerView.visibility = View.VISIBLE customFieldsContainerView.visibility = VISIBLE
if (getIndexViewFieldByName(customField.name) >= 0) { if (getIndexViewFieldByName(customField.name) >= 0) {
// Update a custom field with a new value, // Update a custom field with a new value,
// new field name must be the same as old field name // new field name must be the same as old field name
@@ -683,6 +678,16 @@ abstract class TemplateAbstractView<
putCustomField(Field(otpField.name, otpField.protectedValue)) putCustomField(Field(otpField.name, otpField.protectedValue))
} }
fun saveUnprotectedFieldState(field: Field, isCurrentlyProtected: Boolean) {
try {
if (!isCurrentlyProtected) {
mUnprotectedFields.add(field)
} else {
mUnprotectedFields.remove(field)
}
} catch (_: Exception) {}
}
override fun onRestoreInstanceState(state: Parcelable?) { override fun onRestoreInstanceState(state: Parcelable?) {
//begin boilerplate code so parent classes can restore state //begin boilerplate code so parent classes can restore state
if (state !is SavedState) { if (state !is SavedState) {
@@ -691,6 +696,7 @@ abstract class TemplateAbstractView<
} else { } else {
mTemplate = state.template mTemplate = state.template
mEntryInfo = state.entryInfo mEntryInfo = state.entryInfo
mUnprotectedFields = state.unprotectedFields
onRestoreEntryInstanceState(state) onRestoreEntryInstanceState(state)
buildTemplateAndPopulateInfo() buildTemplateAndPopulateInfo()
super.onRestoreInstanceState(state.superState) super.onRestoreInstanceState(state.superState)
@@ -706,6 +712,7 @@ abstract class TemplateAbstractView<
retrieveDefaultValues = false) retrieveDefaultValues = false)
saveState.template = this.mTemplate saveState.template = this.mTemplate
saveState.entryInfo = this.mEntryInfo saveState.entryInfo = this.mEntryInfo
saveState.unprotectedFields = this.mUnprotectedFields
onSaveEntryInstanceState(saveState) onSaveEntryInstanceState(saveState)
return saveState return saveState
} }
@@ -715,6 +722,7 @@ abstract class TemplateAbstractView<
protected class SavedState : BaseSavedState { protected class SavedState : BaseSavedState {
var template: Template? = null var template: Template? = null
var entryInfo: EntryInfo? = null var entryInfo: EntryInfo? = null
var unprotectedFields = mutableListOf<Field>()
// TODO Move // TODO Move
var tempDateTimeViewId: Int? = null var tempDateTimeViewId: Int? = null
@@ -723,6 +731,7 @@ abstract class TemplateAbstractView<
private constructor(parcel: Parcel) : super(parcel) { private constructor(parcel: Parcel) : super(parcel) {
template = parcel.readParcelableCompat() ?: template template = parcel.readParcelableCompat() ?: template
entryInfo = parcel.readParcelableCompat() ?: entryInfo entryInfo = parcel.readParcelableCompat() ?: entryInfo
parcel.readListCompat<Field>(unprotectedFields)
val dateTimeViewId = parcel.readInt() val dateTimeViewId = parcel.readInt()
if (dateTimeViewId != -1) if (dateTimeViewId != -1)
tempDateTimeViewId = dateTimeViewId tempDateTimeViewId = dateTimeViewId
@@ -732,6 +741,7 @@ abstract class TemplateAbstractView<
super.writeToParcel(out, flags) super.writeToParcel(out, flags)
out.writeParcelable(template, flags) out.writeParcelable(template, flags)
out.writeParcelable(entryInfo, flags) out.writeParcelable(entryInfo, flags)
out.writeList(unprotectedFields)
out.writeInt(tempDateTimeViewId ?: -1) out.writeInt(tempDateTimeViewId ?: -1)
} }

View File

@@ -18,9 +18,9 @@ import com.kunzisoft.keepass.database.element.template.TemplateAttributeAction
import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.database.helper.getLocalizedName import com.kunzisoft.keepass.database.helper.getLocalizedName
import com.kunzisoft.keepass.database.helper.isStandardPasswordName import com.kunzisoft.keepass.database.helper.isStandardPasswordName
import com.kunzisoft.keepass.model.AppOriginEntryField
import com.kunzisoft.keepass.model.DataDate import com.kunzisoft.keepass.model.DataDate
import com.kunzisoft.keepass.model.DataTime import com.kunzisoft.keepass.model.DataTime
import com.kunzisoft.keepass.model.AppOriginEntryField
import com.kunzisoft.keepass.model.PasskeyEntryFields import com.kunzisoft.keepass.model.PasskeyEntryFields
import com.kunzisoft.keepass.otp.OtpEntryFields import com.kunzisoft.keepass.otp.OtpEntryFields
@@ -35,6 +35,11 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
@IdRes @IdRes
private var mTempDateTimeViewId: Int? = null private var mTempDateTimeViewId: Int? = null
private var mOnUnprotectClickListener: ((Field, ProtectedFieldView) -> Unit)? = null
fun setOnUnprotectClickListener(listener: ((Field, ProtectedFieldView) -> Unit)?) {
this.mOnUnprotectClickListener = listener
}
private var mOnCustomEditionActionClickListener: ((Field) -> Unit)? = null private var mOnCustomEditionActionClickListener: ((Field) -> Unit)? = null
fun setOnCustomEditionActionClickListener(listener: ((Field) -> Unit)?) { fun setOnCustomEditionActionClickListener(listener: ((Field) -> Unit)?) {
this.mOnCustomEditionActionClickListener = listener this.mOnCustomEditionActionClickListener = listener
@@ -80,9 +85,9 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
if (color != null) { if (color != null) {
backgroundColorView.background.colorFilter = BlendModeColorFilterCompat backgroundColorView.background.colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP) .createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)
backgroundColorView.visibility = View.VISIBLE backgroundColorView.visibility = VISIBLE
} else { } else {
backgroundColorView.visibility = View.GONE backgroundColorView.visibility = GONE
} }
} }
@@ -103,9 +108,9 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
if (color != null) { if (color != null) {
foregroundColorView.background.colorFilter = BlendModeColorFilterCompat foregroundColorView.background.colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP) .createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP)
foregroundColorView.visibility = View.VISIBLE foregroundColorView.visibility = VISIBLE
} else { } else {
foregroundColorView.visibility = View.GONE foregroundColorView.visibility = GONE
} }
} }
@@ -113,14 +118,25 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
headerContainerView.isVisible = true headerContainerView.isVisible = true
} }
override fun buildLinearTextView(templateAttribute: TemplateAttribute, override fun buildLinearTextView(
field: Field): TextEditFieldView? { templateAttribute: TemplateAttribute,
field: Field
): TextEditFieldView? {
return context?.let { return context?.let {
(if (TemplateField.isStandardPasswordName(context, templateAttribute.label)) (if (TemplateField.isStandardPasswordName(context, templateAttribute.label))
PasswordTextEditFieldView(it) PasswordTextEditFieldView(it)
else TextEditFieldView(it)).apply { else TextEditFieldView(it)).apply {
// hiddenProtectedValue (mHideProtectedValue) don't work with TextInputLayout // hiddenProtectedValue (mHideProtectedValue) don't work with TextInputLayout
setProtection(field.protectedValue.isProtected) setProtection(
protection = field.protectedValue.isProtected,
isCurrentlyProtected = mUnprotectedFields.contains(field).not()
) {
mOnUnprotectClickListener?.invoke(field, this)
}
// Trick to bypass the onSaveInstanceState in rebuild child
onSaveInstanceState = {
saveUnprotectedFieldState(field, isCurrentlyProtected())
}
default = templateAttribute.default default = templateAttribute.default
setMaxChars(templateAttribute.options.getNumberChars()) setMaxChars(templateAttribute.options.getNumberChars())
setMaxLines(templateAttribute.options.getNumberLines()) setMaxLines(templateAttribute.options.getNumberLines())
@@ -129,7 +145,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
textDirection = TEXT_DIRECTION_LTR textDirection = TEXT_DIRECTION_LTR
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO
} }
} }
} }
@@ -143,7 +159,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
default = templateAttribute.default default = templateAttribute.default
setActionClick(templateAttribute, field, this) setActionClick(templateAttribute, field, this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO
} }
} }
} }
@@ -157,7 +173,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
label = templateAttribute.alias label = templateAttribute.alias
?: TemplateField.getLocalizedName(context, field.name) ?: TemplateField.getLocalizedName(context, field.name)
val fieldValue = field.protectedValue.stringValue val fieldValue = field.protectedValue.stringValue
value = if (fieldValue.isEmpty()) templateAttribute.default else fieldValue value = fieldValue.ifEmpty { templateAttribute.default }
// TODO edition and password generator at same time // TODO edition and password generator at same time
when (templateAttribute.action) { when (templateAttribute.action) {
TemplateAttributeAction.NONE -> { TemplateAttributeAction.NONE -> {
@@ -187,7 +203,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
val value = field.protectedValue.toString().trim() val value = field.protectedValue.toString().trim()
type = dateInstantType type = dateInstantType
activation = value.isNotEmpty() activation = value.isNotEmpty()
} catch (e: Exception) { } catch (_: Exception) {
type = dateInstantType type = dateInstantType
activation = false activation = false
} }

View File

@@ -25,12 +25,17 @@ class TemplateView @JvmOverloads constructor(context: Context,
: TemplateAbstractView<TextFieldView, TextFieldView, DateTimeFieldView> : TemplateAbstractView<TextFieldView, TextFieldView, DateTimeFieldView>
(context, attrs, defStyle) { (context, attrs, defStyle) {
private var mOnUnprotectClickListener: ((ProtectedFieldView) -> Unit)? = null
fun setOnUnprotectClickListener(listener: ((ProtectedFieldView) -> Unit)?) {
this.mOnUnprotectClickListener = listener
}
private var mOnAskCopySafeClickListener: (() -> Unit)? = null private var mOnAskCopySafeClickListener: (() -> Unit)? = null
fun setOnAskCopySafeClickListener(listener: (() -> Unit)? = null) { fun setOnAskCopySafeClickListener(listener: (() -> Unit)? = null) {
this.mOnAskCopySafeClickListener = listener this.mOnAskCopySafeClickListener = listener
} }
private var mOnCopyActionClickListener: ((Field) -> Unit)? = null private var mOnCopyActionClickListener: ((Field, ProtectedFieldView) -> Unit)? = null
fun setOnCopyActionClickListener(listener: ((Field) -> Unit)? = null) { fun setOnCopyActionClickListener(listener: ((Field, ProtectedFieldView) -> Unit)? = null) {
this.mOnCopyActionClickListener = listener this.mOnCopyActionClickListener = listener
} }
@@ -58,7 +63,16 @@ class TemplateView @JvmOverloads constructor(context: Context,
PasskeyTextFieldView(it) PasskeyTextFieldView(it)
else TextFieldView(it)).apply { else TextFieldView(it)).apply {
applyFontVisibility(mFontInVisibility) applyFontVisibility(mFontInVisibility)
setProtection(field.protectedValue.isProtected, mHideProtectedValue) setProtection(
protection = field.protectedValue.isProtected,
isCurrentlyProtected = mUnprotectedFields.contains(field).not()
) {
mOnUnprotectClickListener?.invoke(this)
}
// Trick to bypass the onSaveInstanceState in rebuild child
onSaveInstanceState = {
saveUnprotectedFieldState(field, isCurrentlyProtected())
}
label = templateAttribute.alias label = templateAttribute.alias
?: TemplateField.getLocalizedName(context, field.name) ?: TemplateField.getLocalizedName(context, field.name)
setMaxChars(templateAttribute.options.getNumberChars()) setMaxChars(templateAttribute.options.getNumberChars())
@@ -78,7 +92,15 @@ class TemplateView @JvmOverloads constructor(context: Context,
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { label, value -> setCopyButtonClickListener { label, value ->
mOnCopyActionClickListener mOnCopyActionClickListener
?.invoke(Field(label, ProtectedString(true, value))) ?.invoke(
Field(
name = label,
value = ProtectedString(
enableProtection = true,
string = value
)
), this
)
} }
} else { } else {
setCopyButtonState(TextFieldView.ButtonState.GONE) setCopyButtonState(TextFieldView.ButtonState.GONE)
@@ -89,7 +111,15 @@ class TemplateView @JvmOverloads constructor(context: Context,
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { label, value -> setCopyButtonClickListener { label, value ->
mOnCopyActionClickListener mOnCopyActionClickListener
?.invoke(Field(label, ProtectedString(false, value))) ?.invoke(
Field(
name = label,
value = ProtectedString(
enableProtection = false,
string = value
)
), this
)
} }
} }
} }
@@ -114,7 +144,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
try { try {
val value = field.protectedValue.toString().trim() val value = field.protectedValue.toString().trim()
activation = value.isNotEmpty() activation = value.isNotEmpty()
} catch (e: Exception) { } catch (_: Exception) {
activation = false activation = false
} }
} }
@@ -177,9 +207,15 @@ class TemplateView @JvmOverloads constructor(context: Context,
value = otpElement.tokenString value = otpElement.tokenString
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { _, _ -> setCopyButtonClickListener { _, _ ->
mOnCopyActionClickListener?.invoke(Field( mOnCopyActionClickListener?.invoke(
otpElement.type.name, Field(
ProtectedString(false, otpElement.token))) name = otpElement.type.name,
value = ProtectedString(
enableProtection = false,
string = otpElement.token
)
), this
)
} }
textDirection = TEXT_DIRECTION_LTR textDirection = TEXT_DIRECTION_LTR
mLastOtpTokenView = this mLastOtpTokenView = this

View File

@@ -6,12 +6,13 @@ import android.text.InputFilter
import android.text.InputType import android.text.InputType
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import android.text.method.PasswordTransformationMethod
import android.text.method.SingleLineTransformationMethod
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatImageButton
@@ -21,12 +22,11 @@ import androidx.core.view.isVisible
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.settings.PreferencesUtil
open class TextEditFieldView @JvmOverloads constructor(context: Context, open class TextEditFieldView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyle: Int = 0) defStyle: Int = 0)
: RelativeLayout(context, attrs, defStyle), GenericTextFieldView { : ProtectedTextFieldView(context, attrs, defStyle) {
private var labelViewId = ViewCompat.generateViewId() private var labelViewId = ViewCompat.generateViewId()
private var valueViewId = ViewCompat.generateViewId() private var valueViewId = ViewCompat.generateViewId()
@@ -169,14 +169,29 @@ open class TextEditFieldView @JvmOverloads constructor(context: Context,
} }
} }
fun setProtection(protection: Boolean) { override fun setProtection(
if (protection) { protection: Boolean,
isCurrentlyProtected: Boolean,
onUnprotectClickListener: OnClickListener?
) {
super.setProtection(protection, isCurrentlyProtected, onUnprotectClickListener)
if (isProtected) {
labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
val visibilityTag = if (PreferencesUtil.hideProtectedValue(context)) // FIXME Called by itself during orientation change
InputType.TYPE_TEXT_VARIATION_PASSWORD /*
else labelView.setEndIconOnClickListener {
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD onUnprotectClickListener?.onClick(this@TextEditFieldView)
valueView.inputType = valueView.inputType or visibilityTag }*/
}
}
override fun changeProtectedValueParameters() {
if (isCurrentlyProtected()) {
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
valueView.transformationMethod = PasswordTransformationMethod.getInstance()
} else {
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
valueView.transformationMethod = SingleLineTransformationMethod.getInstance()
} }
} }

View File

@@ -26,7 +26,6 @@ import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.View import android.view.View
import android.widget.RelativeLayout
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatImageButton
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
@@ -42,7 +41,7 @@ import com.kunzisoft.keepass.utils.AppUtil.openExternalApp
open class TextFieldView @JvmOverloads constructor(context: Context, open class TextFieldView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyle: Int = 0) defStyle: Int = 0)
: RelativeLayout(context, attrs, defStyle), GenericTextFieldView { : ProtectedTextFieldView(context, attrs, defStyle) {
protected var labelViewId = ViewCompat.generateViewId() protected var labelViewId = ViewCompat.generateViewId()
private var valueViewId = ViewCompat.generateViewId() private var valueViewId = ViewCompat.generateViewId()
@@ -204,25 +203,29 @@ open class TextFieldView @JvmOverloads constructor(context: Context,
} }
} }
fun setProtection(protection: Boolean, hiddenProtectedValue: Boolean = false) { override fun setProtection(
showButton.isVisible = protection protection: Boolean,
showButton.isSelected = hiddenProtectedValue isCurrentlyProtected: Boolean,
showButton.setOnClickListener { onUnprotectClickListener: OnClickListener?
showButton.isSelected = !showButton.isSelected ) {
changeProtectedValueParameters() super.setProtection(protection, isCurrentlyProtected, onUnprotectClickListener)
showButton.isVisible = isProtected
if (isProtected) {
showButton.setOnClickListener {
onUnprotectClickListener?.onClick(this@TextFieldView)
}
} }
changeProtectedValueParameters()
invalidate()
} }
protected fun changeProtectedValueParameters() { override fun changeProtectedValueParameters() {
valueView.apply { valueView.apply {
if (showButton.isVisible) { if (showButton.isVisible) {
applyHiddenStyle(showButton.isSelected) applyHiddenStyle(isCurrentlyProtected())
} else { } else {
linkify() linkify()
} }
} }
invalidate()
} }
private fun linkify() { private fun linkify() {

View File

@@ -238,15 +238,13 @@ fun View.updateLockPaddingStart() {
} }
} }
fun Context.toastError(e: Throwable) { fun Context.toastError(e: Throwable?) {
Toast.makeText( val message = if (e is LocalizedException)
applicationContext, e.getLocalizedMessage(resources)
if (e is LocalizedException) else e?.localizedMessage
e.getLocalizedMessage(resources) message?.let {
else Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()
e.localizedMessage, }
Toast.LENGTH_LONG
).show()
} }
fun Context.showActionErrorIfNeeded(result: ActionRunnable.Result) { fun Context.showActionErrorIfNeeded(result: ActionRunnable.Result) {
@@ -259,6 +257,15 @@ fun Context.showActionErrorIfNeeded(result: ActionRunnable.Result) {
} }
} }
fun CoordinatorLayout.showError(error: Throwable?) {
val message = if (error is LocalizedException) {
error.getLocalizedMessage(resources) ?: error.message
} else error?.message
message?.let {
Snackbar.make(this, message, Snackbar.LENGTH_LONG).asError().show()
}
}
fun CoordinatorLayout.showActionErrorIfNeeded(result: ActionRunnable.Result) { fun CoordinatorLayout.showActionErrorIfNeeded(result: ActionRunnable.Result) {
if (!result.isSuccess) { if (!result.isSuccess) {
result.exception?.getLocalizedMessage(resources)?.let { errorMessage -> result.exception?.getLocalizedMessage(resources)?.let { errorMessage ->

View File

@@ -20,6 +20,7 @@ import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.utils.IOActionTask import com.kunzisoft.keepass.utils.IOActionTask
import com.kunzisoft.keepass.view.ProtectedFieldView
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.util.UUID import java.util.UUID
@@ -37,7 +38,6 @@ class EntryEditViewModel: NodeEditViewModel() {
// To show dialog only one time // To show dialog only one time
var backPressedAlreadyApproved = false var backPressedAlreadyApproved = false
var warningOverwriteDataAlreadyApproved = false
// Useful to not relaunch a current action // Useful to not relaunch a current action
private var actionLocked: Boolean = false private var actionLocked: Boolean = false
@@ -81,8 +81,8 @@ class EntryEditViewModel: NodeEditViewModel() {
val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded
private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>() private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>()
private val mUiState = MutableStateFlow<UIState>(UIState.Loading) private val mEntryEditState = MutableStateFlow<EntryEditState>(EntryEditState.Loading)
val uiState: StateFlow<UIState> = mUiState val entryEditState: StateFlow<EntryEditState> = mEntryEditState
fun loadTemplateEntry(database: ContextualDatabase?) { fun loadTemplateEntry(database: ContextualDatabase?) {
loadTemplateEntry(database, mEntryId, mParentId, mRegisterInfo) loadTemplateEntry(database, mEntryId, mParentId, mRegisterInfo)
@@ -125,7 +125,7 @@ class EntryEditViewModel: NodeEditViewModel() {
mEntryId = null mEntryId = null
_templatesEntry.value = templatesEntry _templatesEntry.value = templatesEntry
if (templatesEntry?.overwrittenData == true) { if (templatesEntry?.overwrittenData == true) {
mUiState.value = UIState.ShowOverwriteMessage mEntryEditState.value = EntryEditState.ShowOverwriteMessage
} }
} }
).execute() ).execute()
@@ -293,6 +293,10 @@ class EntryEditViewModel: NodeEditViewModel() {
_onPasswordSelected.value = passwordField _onPasswordSelected.value = passwordField
} }
fun requestUnprotectField(fieldView: ProtectedFieldView) {
mEntryEditState.value = EntryEditState.RequestUnprotectField(fieldView)
}
fun requestCustomFieldEdition(customField: Field) { fun requestCustomFieldEdition(customField: Field) {
_requestCustomFieldEdition.value = customField _requestCustomFieldEdition.value = customField
} }
@@ -348,6 +352,10 @@ class EntryEditViewModel: NodeEditViewModel() {
_onBinaryPreviewLoaded.value = AttachmentPosition(entryAttachmentState, viewPosition) _onBinaryPreviewLoaded.value = AttachmentPosition(entryAttachmentState, viewPosition)
} }
fun actionPerformed() {
mEntryEditState.value = EntryEditState.Loading
}
data class TemplatesEntry( data class TemplatesEntry(
val isTemplate: Boolean, val isTemplate: Boolean,
val templates: List<Template>, val templates: List<Template>,
@@ -362,9 +370,12 @@ class EntryEditViewModel: NodeEditViewModel() {
data class AttachmentUpload(val attachmentToUploadUri: Uri, val attachment: Attachment) data class AttachmentUpload(val attachmentToUploadUri: Uri, val attachment: Attachment)
data class AttachmentPosition(val entryAttachmentState: EntryAttachmentState, val viewPosition: Float) data class AttachmentPosition(val entryAttachmentState: EntryAttachmentState, val viewPosition: Float)
sealed class UIState { sealed class EntryEditState {
object Loading: UIState() object Loading: EntryEditState()
object ShowOverwriteMessage: UIState() object ShowOverwriteMessage: EntryEditState()
data class RequestUnprotectField(
val protectedFieldView: ProtectedFieldView
): EntryEditState()
} }
companion object { companion object {

View File

@@ -19,25 +19,43 @@
*/ */
package com.kunzisoft.keepass.viewmodels package com.kunzisoft.keepass.viewmodels
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Field
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.template.Template import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.database.helper.getLocalizedName
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.utils.IOActionTask import com.kunzisoft.keepass.utils.IOActionTask
import com.kunzisoft.keepass.view.ProtectedFieldView
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID import java.util.UUID
class EntryViewModel: ViewModel() { class EntryViewModel(application: Application): AndroidViewModel(application) {
private var mMainEntryId: NodeId<UUID>? = null var mainEntryId: NodeId<UUID>? = null
private var mHistoryPosition: Int = -1 private set
var entryInfo: EntryInfo? = null
private set
var historyPosition: Int = -1
private set
var entryIsHistory: Boolean = false
private set
var entryLoaded = false
private set
private var mClipboardHelper: ClipboardHelper = ClipboardHelper(application)
val entryInfoHistory : LiveData<EntryInfoHistory?> get() = _entryInfoHistory val entryInfoHistory : LiveData<EntryInfoHistory?> get() = _entryInfoHistory
private val _entryInfoHistory = MutableLiveData<EntryInfoHistory?>() private val _entryInfoHistory = MutableLiveData<EntryInfoHistory?>()
@@ -59,13 +77,16 @@ class EntryViewModel: ViewModel() {
val historySelected : LiveData<EntryHistory> get() = _historySelected val historySelected : LiveData<EntryHistory> get() = _historySelected
private val _historySelected = SingleLiveEvent<EntryHistory>() private val _historySelected = SingleLiveEvent<EntryHistory>()
private val mEntryState = MutableStateFlow<EntryState>(EntryState.Loading)
val entryState: StateFlow<EntryState> = mEntryState
fun loadDatabase(database: ContextualDatabase?) { fun loadDatabase(database: ContextualDatabase?) {
loadEntry(database, mMainEntryId, mHistoryPosition) loadEntry(database, mainEntryId, historyPosition)
} }
fun loadEntry(database: ContextualDatabase?, mainEntryId: NodeId<UUID>?, historyPosition: Int = -1) { fun loadEntry(database: ContextualDatabase?, mainEntryId: NodeId<UUID>?, historyPosition: Int = -1) {
this.mMainEntryId = mainEntryId this.mainEntryId = mainEntryId
this.mHistoryPosition = historyPosition this.historyPosition = historyPosition
if (database != null && mainEntryId != null) { if (database != null && mainEntryId != null) {
IOActionTask( IOActionTask(
@@ -104,6 +125,13 @@ class EntryViewModel: ViewModel() {
} }
}, },
{ entryInfoHistory -> { entryInfoHistory ->
if (entryInfoHistory != null) {
this.mainEntryId = entryInfoHistory.mainEntryId
this.entryInfo = entryInfoHistory.entryInfo
this.historyPosition = historyPosition
this.entryIsHistory = historyPosition > -1
this.entryLoaded = true
}
_entryInfoHistory.value = entryInfoHistory _entryInfoHistory.value = entryInfoHistory
_entryHistory.value = entryInfoHistory?.entryHistory _entryHistory.value = entryInfoHistory?.entryHistory
} }
@@ -111,6 +139,18 @@ class EntryViewModel: ViewModel() {
} }
} }
fun requestUnprotectField(fieldView: ProtectedFieldView) {
mEntryState.value = EntryState.RequestUnprotectField(fieldView)
}
fun requestCopyField(field: Field, fieldView: ProtectedFieldView) {
// Only request the User Verification if the field is protected and not shown
if (field.protectedValue.isProtected && fieldView.isCurrentlyProtected())
mEntryState.value = EntryState.RequestCopyProtectedField(field)
else
copyToClipboard(field)
}
fun onOtpElementUpdated(optElement: OtpElement?) { fun onOtpElementUpdated(optElement: OtpElement?) {
_onOtpElementUpdated.value = optElement _onOtpElementUpdated.value = optElement
} }
@@ -131,6 +171,22 @@ class EntryViewModel: ViewModel() {
_sectionSelected.value = section _sectionSelected.value = section
} }
fun copyToClipboard(field: Field) {
mClipboardHelper.timeoutCopyToClipboard(
TemplateField.getLocalizedName(getApplication(), field.name),
field.protectedValue.stringValue,
field.protectedValue.isProtected
)
}
fun copyToClipboard(text: String) {
mClipboardHelper.timeoutCopyToClipboard(text, text)
}
fun actionPerformed() {
mEntryState.value = EntryState.Loading
}
data class EntryInfoHistory(var mainEntryId: NodeId<UUID>, data class EntryInfoHistory(var mainEntryId: NodeId<UUID>,
var historyPosition: Int, var historyPosition: Int,
val template: Template, val template: Template,
@@ -152,6 +208,16 @@ class EntryViewModel: ViewModel() {
} }
} }
sealed class EntryState {
object Loading: EntryState()
data class RequestUnprotectField(
val protectedFieldView: ProtectedFieldView
): EntryState()
data class RequestCopyProtectedField(
val field: Field
): EntryState()
}
companion object { companion object {
private val TAG = EntryViewModel::class.java.name private val TAG = EntryViewModel::class.java.name
} }

View File

@@ -0,0 +1,48 @@
package com.kunzisoft.keepass.viewmodels
import android.net.Uri
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.database.MainCredential
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel for the Main Credential Dialog
* Easily retrieves main credential from the database identified by its URI
*/
class MainCredentialViewModel: ViewModel() {
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
val uiState: StateFlow<UIState> = mUiState
fun validateMainCredential(
databaseUri: Uri,
mainCredential: MainCredential
) {
mUiState.value = UIState.OnMainCredentialEntered(databaseUri, mainCredential)
}
fun cancelMainCredential(
databaseUri: Uri?,
error: Throwable? = null
) {
mUiState.value = UIState.OnMainCredentialCanceled(databaseUri, error)
}
fun onActionReceived() {
mUiState.value = UIState.Loading
}
sealed class UIState {
object Loading: UIState()
data class OnMainCredentialEntered(
val databaseUri: Uri,
val mainCredential: MainCredential
): UIState()
data class OnMainCredentialCanceled(
val databaseUri: Uri?,
val error: Throwable?
): UIState()
}
}

View File

@@ -0,0 +1,31 @@
package com.kunzisoft.keepass.viewmodels
import android.app.Application
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class SettingsViewModel(application: Application): AndroidViewModel(application) {
private val mSettingsState = MutableStateFlow<SettingsState>(SettingsState.Wait)
val settingsState: StateFlow<SettingsState> = mSettingsState
var dialogFragment: DialogFragment? = null
fun showError(error: Throwable?) {
mSettingsState.value = SettingsState.ShowError(error)
}
fun errorShown() {
mSettingsState.value = SettingsState.Wait
}
sealed class SettingsState {
object Wait: SettingsState()
data class ShowError(
val error: Throwable? = null
): SettingsState()
}
}

View File

@@ -0,0 +1,58 @@
package com.kunzisoft.keepass.viewmodels
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.credentialprovider.UserVerificationData
import com.kunzisoft.keepass.database.element.MasterCredential.CREATOR.getCheckKey
import com.kunzisoft.keepass.database.exception.InvalidCredentialsDatabaseException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel for the User Verification
*/
class UserVerificationViewModel: ViewModel() {
private val mUVState = MutableStateFlow<UVState>(UVState.Loading)
val userVerificationState: StateFlow<UVState> = mUVState
var dataToVerify: UserVerificationData? = null
fun checkMainCredential(checkString: String) {
// Check the password part
val data = dataToVerify
if (data?.database?.checkKey(getCheckKey(checkString)) == true)
onUserVerificationSucceeded(data)
else {
onUserVerificationFailed(dataToVerify, InvalidCredentialsDatabaseException())
}
dataToVerify = null
}
fun onUserVerificationSucceeded(dataToVerify: UserVerificationData) {
mUVState.value = UVState.OnUserVerificationSucceeded(dataToVerify)
}
fun onUserVerificationFailed(
dataToVerify: UserVerificationData? = null,
error: Throwable? = null
) {
this.dataToVerify = dataToVerify
mUVState.value = UVState.OnUserVerificationCanceled(dataToVerify, error)
}
fun onUserVerificationReceived() {
mUVState.value = UVState.Loading
}
sealed class UVState {
object Loading: UVState()
data class OnUserVerificationSucceeded(
val dataToVerify: UserVerificationData
): UVState()
data class OnUserVerificationCanceled(
val dataToVerify: UserVerificationData?,
val error: Throwable?
): UVState()
}
}

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="960"
android:viewportWidth="960">
<path
android:fillColor="#E50808"
android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM253,707L480,480L480,160Q346,160 253,253Q160,346 160,480Q160,544 184,603Q208,662 253,707Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,4C9.95,4 9.063,4.363 8.338,5.088C7.613,5.813 7.25,6.7 7.25,7.75C7.25,8.8 7.613,9.687 8.338,10.412C9.063,11.137 9.95,11.5 11,11.5C12.05,11.5 12.937,11.137 13.662,10.412C14.387,9.687 14.75,8.8 14.75,7.75C14.75,6.7 14.387,5.813 13.662,5.088C12.937,4.363 12.05,4 11,4zM11,13.5C10.567,13.5 10.147,13.521 9.738,13.563C9.33,13.604 8.908,13.667 8.475,13.75C8.47,13.754 8.049,13.845 7.912,13.875C6.887,14.125 5.816,14.5 4.699,15C4.199,15.233 3.791,15.575 3.475,16.025C3.158,16.475 3,17.017 3,17.65L3,20L6,20L13.682,20C13.429,19.394 13.287,18.729 13.287,18.031C13.287,16.501 13.971,15.144 15.033,14.205C14.724,14.11 14.389,13.949 14.088,13.875C14.08,13.877 13.525,13.75 13.525,13.75C13.301,13.707 13.089,13.684 12.871,13.652C12.721,13.63 12.57,13.603 12.42,13.586C12.368,13.58 12.313,13.568 12.262,13.563C11.853,13.521 11.433,13.5 11,13.5zM18.414,14.445C16.433,14.445 14.826,16.05 14.826,18.031C14.826,20.012 16.433,21.619 18.414,21.619C20.395,21.619 22,20.012 22,18.031C22,16.05 20.395,14.445 18.414,14.445zM19.848,16.314C19.956,16.314 20.064,16.355 20.146,16.438L20.443,16.736C20.608,16.9 20.607,17.168 20.443,17.332L18.145,19.633C17.98,19.797 17.711,19.797 17.547,19.633L16.391,18.475C16.226,18.31 16.226,18.043 16.391,17.879L16.689,17.58C16.854,17.416 17.121,17.416 17.285,17.58L17.846,18.139L19.549,16.438C19.631,16.355 19.739,16.314 19.848,16.314z"
android:fillColor="#7D7D7D"/>
</vector>

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2025 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/>.
-->
<ScrollView 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:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:padding="@dimen/default_margin"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="noExcludeDescendants"
tools:targetApi="o">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/user_verification_information"
android:text="@string/user_verification_required_title"
style="@style/KeepassDXStyle.Title"/>
<ImageView
android:id="@+id/user_verification_information"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:src="@drawable/ic_info_white_24dp"
style="@style/KeepassDXStyle.ImageButton.Simple"
android:contentDescription="@string/content_description_user_verification_information"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/user_verification_required_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/card_view_margin_horizontal"
android:layout_marginLeft="@dimen/card_view_margin_horizontal"
android:layout_marginEnd="@dimen/card_view_margin_horizontal"
android:layout_marginRight="@dimen/card_view_margin_horizontal"
android:text="@string/user_verification_database_credential"
android:textColor="?attr/colorSecondary"/>
<!-- Password -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/setup_check_password_input_layout"
android:layout_margin="@dimen/card_view_margin_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:endIconMode="password_toggle"
app:endIconTint="?attr/colorSecondary">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/setup_check_password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusedByDefault="true"
android:inputType="textPassword"
android:importantForAccessibility="no"
android:importantForAutofill="no"
android:hint="@string/first_chars" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</ScrollView>

View File

@@ -75,8 +75,7 @@
<com.kunzisoft.keepass.view.PasswordEditView <com.kunzisoft.keepass.view.PasswordEditView
android:id="@+id/password_view" android:id="@+id/password_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"/>
app:passwordVisible="false"/>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_repeat_input_layout" android:id="@+id/password_repeat_input_layout"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -61,7 +61,6 @@
<string name="list_size_summary">حجم النص في قائمة العناصر</string> <string name="list_size_summary">حجم النص في قائمة العناصر</string>
<string name="loading_database">يحمل قاعدة البيانات…</string> <string name="loading_database">يحمل قاعدة البيانات…</string>
<string name="lowercase">حروف صغيرة</string> <string name="lowercase">حروف صغيرة</string>
<string name="hide_password_summary">أخفِ كلمات السر (***) افتراضيًا</string>
<string name="about">عن التطبيق</string> <string name="about">عن التطبيق</string>
<string name="menu_change_key_settings">تغيير المفتاح الرئيسي</string> <string name="menu_change_key_settings">تغيير المفتاح الرئيسي</string>
<string name="settings">الإعدادات</string> <string name="settings">الإعدادات</string>
@@ -143,13 +142,11 @@
<string name="list_entries_show_username_summary">يعرض اسماء المستخدمين في قوائم المدخلات</string> <string name="list_entries_show_username_summary">يعرض اسماء المستخدمين في قوائم المدخلات</string>
<string name="hint_generated_password">كلمة السر مولّدة</string> <string name="hint_generated_password">كلمة السر مولّدة</string>
<string name="hint_keyfile">ملف المفتاح</string> <string name="hint_keyfile">ملف المفتاح</string>
<string name="hide_password_title">أخفِ كلمات السر</string>
<string name="copy_field">نُسخة من %1$s</string> <string name="copy_field">نُسخة من %1$s</string>
<string name="menu_copy">نسخ</string> <string name="menu_copy">نسخ</string>
<string name="menu_move">نقل</string> <string name="menu_move">نقل</string>
<string name="menu_paste">لصق</string> <string name="menu_paste">لصق</string>
<string name="menu_cancel">ألغِ</string> <string name="menu_cancel">ألغِ</string>
<string name="menu_hide_password">أخفِ كلمة السر</string>
<string name="menu_showpass">أظهر كلمة السر</string> <string name="menu_showpass">أظهر كلمة السر</string>
<string name="menu_url">الانتقال الى الرابط</string> <string name="menu_url">الانتقال الى الرابط</string>
<string name="menu_file_selection_read_only">محمي من التعديل</string> <string name="menu_file_selection_read_only">محمي من التعديل</string>

View File

@@ -142,7 +142,6 @@
<string name="menu_paste">Mubadilə buferindən əlavə et</string> <string name="menu_paste">Mubadilə buferindən əlavə et</string>
<string name="menu_delete">Sil</string> <string name="menu_delete">Sil</string>
<string name="menu_cancel">Ləğv et</string> <string name="menu_cancel">Ləğv et</string>
<string name="menu_hide_password">Şifrəni gizlət</string>
<string name="menu_lock">Məlumat bazasını kilidlə</string> <string name="menu_lock">Məlumat bazasını kilidlə</string>
<string name="menu_save_database">Məlumatları yadda saxla</string> <string name="menu_save_database">Məlumatları yadda saxla</string>
<string name="menu_merge_database">Məlumatları birləşdir</string> <string name="menu_merge_database">Məlumatları birləşdir</string>
@@ -448,8 +447,6 @@
<string name="invalid_db_sig">Məlumat bazasının formatını tanımaq mümkün olmadı.</string> <string name="invalid_db_sig">Məlumat bazasının formatını tanımaq mümkün olmadı.</string>
<string name="keyfile_is_empty">Açar faylı boşdur.</string> <string name="keyfile_is_empty">Açar faylı boşdur.</string>
<string name="length">Uzunluq</string> <string name="length">Uzunluq</string>
<string name="hide_password_title">Şifrələri gizlət</string>
<string name="hide_password_summary">Şifrələri standart olaraq (***) ilə maskala</string>
<string name="colorize_password_title">Şifrələri rəngləndir</string> <string name="colorize_password_title">Şifrələri rəngləndir</string>
<string name="colorize_password_summary">Şifrə hərflərini (simvollarını) növə görə rəngləndir</string> <string name="colorize_password_summary">Şifrə hərflərini (simvollarını) növə görə rəngləndir</string>
<string name="list_entries_show_username_title">İstifadəçi adlarını göstər</string> <string name="list_entries_show_username_title">İstifadəçi adlarını göstər</string>

View File

@@ -29,7 +29,6 @@
<string name="menu_open">Otvori</string> <string name="menu_open">Otvori</string>
<string name="menu_save_database">Sačuvaj podatake</string> <string name="menu_save_database">Sačuvaj podatake</string>
<string name="menu_lock">Zaključaj bazu podataka</string> <string name="menu_lock">Zaključaj bazu podataka</string>
<string name="menu_hide_password">Sakrij lozinku</string>
<string name="menu_cancel">Otkaži</string> <string name="menu_cancel">Otkaži</string>
<string name="menu_delete">Izbriši</string> <string name="menu_delete">Izbriši</string>
<string name="menu_paste">Nalepi</string> <string name="menu_paste">Nalepi</string>
@@ -47,8 +46,6 @@
<string name="copy_field">Kopija od %1$s</string> <string name="copy_field">Kopija od %1$s</string>
<string name="menu_change_key_settings">Promeni glavni ključ</string> <string name="menu_change_key_settings">Promeni glavni ključ</string>
<string name="about">O aplikaciji</string> <string name="about">O aplikaciji</string>
<string name="hide_password_summary">Podrazumevaj maskiranje lozinki sa (***)</string>
<string name="hide_password_title">Sakrij lozinke</string>
<string name="lowercase">Mala slova</string> <string name="lowercase">Mala slova</string>
<string name="loading_database">Učitavanje baze podataka…</string> <string name="loading_database">Učitavanje baze podataka…</string>
<string name="creating_database">Kreiranje baze podataka…</string> <string name="creating_database">Kreiranje baze podataka…</string>

View File

@@ -216,8 +216,6 @@
<string name="keyfile_is_empty">Файл ключа пусты.</string> <string name="keyfile_is_empty">Файл ключа пусты.</string>
<string name="length">Даўжыня</string> <string name="length">Даўжыня</string>
<string name="nodes">Вузлы</string> <string name="nodes">Вузлы</string>
<string name="hide_password_title">Схаваць паролі</string>
<string name="hide_password_summary">Маскіраваць паролі (***) па змаўчанні</string>
<string name="colorize_password_title">Размаляваць паролі</string> <string name="colorize_password_title">Размаляваць паролі</string>
<string name="colorize_password_summary">Размаляваць сімвалы пароля па тыпу</string> <string name="colorize_password_summary">Размаляваць сімвалы пароля па тыпу</string>
<string name="list_entries_show_username_title">Паказаць імёны карыстальнікаў</string> <string name="list_entries_show_username_title">Паказаць імёны карыстальнікаў</string>
@@ -258,7 +256,6 @@
<string name="menu_paste">Уставіць</string> <string name="menu_paste">Уставіць</string>
<string name="menu_delete">Выдаліць</string> <string name="menu_delete">Выдаліць</string>
<string name="menu_cancel">Адмена</string> <string name="menu_cancel">Адмена</string>
<string name="menu_hide_password">Схаваць пароль</string>
<string name="menu_lock">Заблакаваць базу дадзеных</string> <string name="menu_lock">Заблакаваць базу дадзеных</string>
<string name="menu_save_database">Захаваць дадзеныя</string> <string name="menu_save_database">Захаваць дадзеныя</string>
<string name="menu_merge_database">Аб\'яднаць дадзеныя</string> <string name="menu_merge_database">Аб\'яднаць дадзеныя</string>

View File

@@ -71,10 +71,8 @@
<string name="unlock">Отключване</string> <string name="unlock">Отключване</string>
<string name="unavailable_feature_hardware">Необходимият хардуер не може да бъде намерен.</string> <string name="unavailable_feature_hardware">Необходимият хардуер не може да бъде намерен.</string>
<string name="hardware_key">Хардуерен ключ</string> <string name="hardware_key">Хардуерен ключ</string>
<string name="hide_password_summary">Скриване на паролите (***) по подразбиране</string>
<string name="select_database_file">Отключване на хранилище</string> <string name="select_database_file">Отключване на хранилище</string>
<string name="content_description_hardware_key_checkbox">Отметка на поле с хардуерен ключ</string> <string name="content_description_hardware_key_checkbox">Отметка на поле с хардуерен ключ</string>
<string name="hide_password_title">Скриване на пароли</string>
<string name="hint_pass">Парола</string> <string name="hint_pass">Парола</string>
<string name="education_select_database_title">Отворете съществуващо хранилище</string> <string name="education_select_database_title">Отворете съществуващо хранилище</string>
<string name="content_description_keyfile_checkbox">Отметка на поле за файл с ключ</string> <string name="content_description_keyfile_checkbox">Отметка на поле за файл с ключ</string>
@@ -157,7 +155,6 @@
<string name="template">Шаблон</string> <string name="template">Шаблон</string>
<string name="menu_move">Преместване</string> <string name="menu_move">Преместване</string>
<string name="menu_cancel">Отказ</string> <string name="menu_cancel">Отказ</string>
<string name="menu_hide_password">Скриване на парола</string>
<string name="auto_focus_search_summary">Търсене при отключване на хранилище</string> <string name="auto_focus_search_summary">Търсене при отключване на хранилище</string>
<string name="saving_database">Запазване на хранилището…</string> <string name="saving_database">Запазване на хранилището…</string>
<string name="command_execution">Изпълнение на команда…</string> <string name="command_execution">Изпълнение на команда…</string>

View File

@@ -182,7 +182,6 @@
<string name="invalid_db_same_uuid">একই UUID সহ %1$s %2$s ইতিমধ্যেই বিদ্যমান।</string> <string name="invalid_db_same_uuid">একই UUID সহ %1$s %2$s ইতিমধ্যেই বিদ্যমান।</string>
<string name="passphrase">পাসফ্রেজ</string> <string name="passphrase">পাসফ্রেজ</string>
<string name="keyfile_is_empty">কী ফাইলটি খালি।</string> <string name="keyfile_is_empty">কী ফাইলটি খালি।</string>
<string name="hide_password_title">পাসওয়ার্ড লুকান</string>
<string name="list_size_summary">উপাদান তালিকায় পাঠ্যের আকার</string> <string name="list_size_summary">উপাদান তালিকায় পাঠ্যের আকার</string>
<string name="creating_database">ডাটাবেস তৈরি করা হচ্ছে…</string> <string name="creating_database">ডাটাবেস তৈরি করা হচ্ছে…</string>
<string name="loading_database">ডাটাবেস লোড হচ্ছে…</string> <string name="loading_database">ডাটাবেস লোড হচ্ছে…</string>
@@ -316,7 +315,6 @@
<string name="field_value">ক্ষেত্রের মান</string> <string name="field_value">ক্ষেত্রের মান</string>
<string name="corrupted_file">দূষিত ফাইল।</string> <string name="corrupted_file">দূষিত ফাইল।</string>
<string name="show_uuid_title">UUID দেখান</string> <string name="show_uuid_title">UUID দেখান</string>
<string name="hide_password_summary">ডিফল্টরূপে মাস্ক পাসওয়ার্ড (***)</string>
<string name="list_entries_show_username_title">ব্যবহারকারীর নাম দেখান</string> <string name="list_entries_show_username_title">ব্যবহারকারীর নাম দেখান</string>
<string name="show_uuid_summary">একটি এন্ট্রি বা একটি গ্রুপের সাথে সংযুক্ত UUID প্রদর্শন করে</string> <string name="show_uuid_summary">একটি এন্ট্রি বা একটি গ্রুপের সাথে সংযুক্ত UUID প্রদর্শন করে</string>
<string name="menu_reload_database">ডেটা পুনরায় লোড করুন</string> <string name="menu_reload_database">ডেটা পুনরায় লোড করুন</string>
@@ -330,7 +328,6 @@
<string name="menu_merge_database">ডেটা মার্জ করুন</string> <string name="menu_merge_database">ডেটা মার্জ করুন</string>
<string name="menu_merge_from">থেকে মার্জ করুন…</string> <string name="menu_merge_from">থেকে মার্জ করুন…</string>
<string name="menu_showpass">পাসওয়ার্ড দেখাও</string> <string name="menu_showpass">পাসওয়ার্ড দেখাও</string>
<string name="menu_hide_password">পাসওয়ার্ড লুকান</string>
<string name="menu_lock">ডাটাবেস লক করুন</string> <string name="menu_lock">ডাটাবেস লক করুন</string>
<string name="menu_url">URL-এ যান</string> <string name="menu_url">URL-এ যান</string>
<string name="menu_empty_recycle_bin">রিসাইকেল বিন খালি করুন</string> <string name="menu_empty_recycle_bin">রিসাইকেল বিন খালি করুন</string>

View File

@@ -84,8 +84,6 @@
<string name="list_size_summary">Mida del text a la llista de grups</string> <string name="list_size_summary">Mida del text a la llista de grups</string>
<string name="loading_database">Carregant base de dades…</string> <string name="loading_database">Carregant base de dades…</string>
<string name="lowercase">Minúscules</string> <string name="lowercase">Minúscules</string>
<string name="hide_password_title">Emmascara contrasenya</string>
<string name="hide_password_summary">Amaga les contrasenyes per defecte</string>
<string name="about">Sobre</string> <string name="about">Sobre</string>
<string name="menu_change_key_settings">Canvia Clau Mestra</string> <string name="menu_change_key_settings">Canvia Clau Mestra</string>
<string name="settings">Paràmetres</string> <string name="settings">Paràmetres</string>
@@ -93,7 +91,6 @@
<string name="menu_delete">Esborra</string> <string name="menu_delete">Esborra</string>
<string name="menu_donate">Donar</string> <string name="menu_donate">Donar</string>
<string name="menu_edit">Editar</string> <string name="menu_edit">Editar</string>
<string name="menu_hide_password">Amaga contrasenya</string>
<string name="menu_lock">Bloca la base de dades</string> <string name="menu_lock">Bloca la base de dades</string>
<string name="menu_open">Obre</string> <string name="menu_open">Obre</string>
<string name="menu_search">Cerca</string> <string name="menu_search">Cerca</string>

View File

@@ -90,8 +90,6 @@
<string name="list_size_summary">Velikost textu v seznamu prvků</string> <string name="list_size_summary">Velikost textu v seznamu prvků</string>
<string name="loading_database">Načítám databázi…</string> <string name="loading_database">Načítám databázi…</string>
<string name="lowercase">Malá písmena</string> <string name="lowercase">Malá písmena</string>
<string name="hide_password_title">Skrýt hesla</string>
<string name="hide_password_summary">Ve výchozím stavu zobrazit (***) místo hesla</string>
<string name="about">O aplikaci</string> <string name="about">O aplikaci</string>
<string name="menu_change_key_settings">Změnit hlavní klíč</string> <string name="menu_change_key_settings">Změnit hlavní klíč</string>
<string name="settings">Nastavení</string> <string name="settings">Nastavení</string>
@@ -99,7 +97,6 @@
<string name="menu_delete">Smazat</string> <string name="menu_delete">Smazat</string>
<string name="menu_donate">Přispět darem</string> <string name="menu_donate">Přispět darem</string>
<string name="menu_edit">Upravit</string> <string name="menu_edit">Upravit</string>
<string name="menu_hide_password">Skrýt heslo</string>
<string name="menu_lock">Zamknout databázi</string> <string name="menu_lock">Zamknout databázi</string>
<string name="menu_open">Otevřít</string> <string name="menu_open">Otevřít</string>
<string name="menu_search">Hledat</string> <string name="menu_search">Hledat</string>

View File

@@ -89,8 +89,6 @@
<string name="list_size_summary">Tekststørrelse i elementlisten</string> <string name="list_size_summary">Tekststørrelse i elementlisten</string>
<string name="loading_database">Indlæser database…</string> <string name="loading_database">Indlæser database…</string>
<string name="lowercase">Små bogstaver</string> <string name="lowercase">Små bogstaver</string>
<string name="hide_password_title">Skjul adgangskoder</string>
<string name="hide_password_summary">Skjul adgangskoder (***) som standard</string>
<string name="about">Om</string> <string name="about">Om</string>
<string name="menu_change_key_settings">Skift hovednøgle</string> <string name="menu_change_key_settings">Skift hovednøgle</string>
<string name="settings">Indstillinger</string> <string name="settings">Indstillinger</string>
@@ -98,7 +96,6 @@
<string name="menu_delete">Slet</string> <string name="menu_delete">Slet</string>
<string name="menu_donate">Donér</string> <string name="menu_donate">Donér</string>
<string name="menu_edit">Rediger</string> <string name="menu_edit">Rediger</string>
<string name="menu_hide_password">Skjul adgangskode</string>
<string name="menu_lock">Lås database</string> <string name="menu_lock">Lås database</string>
<string name="menu_open">Åbn</string> <string name="menu_open">Åbn</string>
<string name="menu_search">Søg</string> <string name="menu_search">Søg</string>

View File

@@ -99,8 +99,6 @@
<string name="list_size_summary">Schriftgröße der Listenelemente</string> <string name="list_size_summary">Schriftgröße der Listenelemente</string>
<string name="loading_database">Datenbank wird geladen </string> <string name="loading_database">Datenbank wird geladen </string>
<string name="lowercase">Kleinbuchstaben</string> <string name="lowercase">Kleinbuchstaben</string>
<string name="hide_password_title">Passwörter verbergen</string>
<string name="hide_password_summary">Passwörter mit (***) maskieren</string>
<string name="about">Über</string> <string name="about">Über</string>
<string name="menu_change_key_settings">Hauptschlüssel ändern</string> <string name="menu_change_key_settings">Hauptschlüssel ändern</string>
<string name="settings">Einstellungen</string> <string name="settings">Einstellungen</string>
@@ -108,7 +106,6 @@
<string name="menu_delete">Löschen</string> <string name="menu_delete">Löschen</string>
<string name="menu_donate">Spenden</string> <string name="menu_donate">Spenden</string>
<string name="menu_edit">Bearbeiten</string> <string name="menu_edit">Bearbeiten</string>
<string name="menu_hide_password">Passwort verbergen</string>
<string name="menu_lock">Datenbank sperren</string> <string name="menu_lock">Datenbank sperren</string>
<string name="menu_open">Öffnen</string> <string name="menu_open">Öffnen</string>
<string name="menu_search">Suche</string> <string name="menu_search">Suche</string>

View File

@@ -92,8 +92,6 @@
<string name="list_size_summary">Μέγεθος κειμένου στη λίστα στοιχείων</string> <string name="list_size_summary">Μέγεθος κειμένου στη λίστα στοιχείων</string>
<string name="loading_database">Φόρτωση βάσης δεδομένων…</string> <string name="loading_database">Φόρτωση βάσης δεδομένων…</string>
<string name="lowercase">Μικρά</string> <string name="lowercase">Μικρά</string>
<string name="hide_password_title">Απόκρυψη κωδικών πρόσβασης</string>
<string name="hide_password_summary">Μάσκα κωδικούς πρόσβασης (***) από προεπιλογή</string>
<string name="about">Σχετικά με</string> <string name="about">Σχετικά με</string>
<string name="menu_change_key_settings">Αλλαγή Κύριου Κλειδιού</string> <string name="menu_change_key_settings">Αλλαγή Κύριου Κλειδιού</string>
<string name="settings">Ρυθμίσεις</string> <string name="settings">Ρυθμίσεις</string>
@@ -101,7 +99,6 @@
<string name="menu_delete">Διαγραφή</string> <string name="menu_delete">Διαγραφή</string>
<string name="menu_donate">Δωρεά</string> <string name="menu_donate">Δωρεά</string>
<string name="menu_edit">Επεξεργασία</string> <string name="menu_edit">Επεξεργασία</string>
<string name="menu_hide_password">Απόκρυψη κωδικού</string>
<string name="menu_lock">Κλείδωμα βάσης δεδομένων</string> <string name="menu_lock">Κλείδωμα βάσης δεδομένων</string>
<string name="menu_open">Άνοιγμα</string> <string name="menu_open">Άνοιγμα</string>
<string name="menu_search">Αναζήτηση</string> <string name="menu_search">Αναζήτηση</string>

View File

@@ -219,8 +219,6 @@
<string name="invalid_algorithm">Wrong algorithm.</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_same_uuid">%1$s with the same UUID %2$s already exists.</string>
<string name="keyfile_is_empty">The keyfile is empty.</string> <string name="keyfile_is_empty">The keyfile is empty.</string>
<string name="hide_password_title">Hide passwords</string>
<string name="list_entries_show_username_title">Show usernames</string>
<string name="error_otp_period">Period must be between %1$d and %2$d seconds.</string> <string name="error_otp_period">Period must be between %1$d and %2$d seconds.</string>
<string name="error_string_type">This text does not match the requested item.</string> <string name="error_string_type">This text does not match the requested item.</string>
<string name="error_registration_read_only">Saving a new item is not allowed in a read-only database.</string> <string name="error_registration_read_only">Saving a new item is not allowed in a read-only database.</string>
@@ -231,7 +229,6 @@
<string name="error_file_to_big">The file you are trying to upload is too big.</string> <string name="error_file_to_big">The file you are trying to upload is too big.</string>
<string name="error_upload_file">An error occurred while uploading the file data.</string> <string name="error_upload_file">An error occurred while uploading the file data.</string>
<string name="error_location_unknown">Database location is unknown, database action cannot be performed.</string> <string name="error_location_unknown">Database location is unknown, database action cannot be performed.</string>
<string name="hide_password_summary">Mask passwords (***) by default</string>
<string name="list_entries_show_username_summary">Displays usernames in entry lists</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> <string name="list_groups_show_number_entries_title">Show number of entries</string>
<string name="show_otp_token_summary">Displays OTP tokens in the list of entries</string> <string name="show_otp_token_summary">Displays OTP tokens in the list of entries</string>

View File

@@ -41,7 +41,6 @@
<string name="menu_reload_database">Reŝargi datumbazon</string> <string name="menu_reload_database">Reŝargi datumbazon</string>
<string name="menu_save_database">Konservi datumbazon</string> <string name="menu_save_database">Konservi datumbazon</string>
<string name="menu_lock">Ŝlosi datumbazon</string> <string name="menu_lock">Ŝlosi datumbazon</string>
<string name="menu_hide_password">Kaŝi pasvorton</string>
<string name="menu_cancel">Nuligi</string> <string name="menu_cancel">Nuligi</string>
<string name="menu_delete">Forigi</string> <string name="menu_delete">Forigi</string>
<string name="menu_paste">Algui</string> <string name="menu_paste">Algui</string>
@@ -56,8 +55,6 @@
<string name="settings">Agordoj</string> <string name="settings">Agordoj</string>
<string name="copy_field">Kopio de %1$s</string> <string name="copy_field">Kopio de %1$s</string>
<string name="about">Pri</string> <string name="about">Pri</string>
<string name="hide_password_summary">Kaŝi pasvortojn sub (***) defaŭlte</string>
<string name="hide_password_title">Kaŝi pasvortojn</string>
<string name="loading_database">Datumbaza ŝarĝado…</string> <string name="loading_database">Datumbaza ŝarĝado…</string>
<string name="creating_database">Datumbaza kreado…</string> <string name="creating_database">Datumbaza kreado…</string>
<string name="list_entries_show_username_summary">Vidigi uzantnomojn en elementaj listoj</string> <string name="list_entries_show_username_summary">Vidigi uzantnomojn en elementaj listoj</string>

View File

@@ -84,8 +84,6 @@
<string name="list_size_summary">Tamaño del texto en la lista de elementos</string> <string name="list_size_summary">Tamaño del texto en la lista de elementos</string>
<string name="loading_database">Cargando base de datos…</string> <string name="loading_database">Cargando base de datos…</string>
<string name="lowercase">Minúsculas</string> <string name="lowercase">Minúsculas</string>
<string name="hide_password_title">Ocultar contraseñas</string>
<string name="hide_password_summary">Oculta contraseñas (***) por defecto</string>
<string name="about">Acerca de</string> <string name="about">Acerca de</string>
<string name="menu_change_key_settings">Cambiar contraseña maestra</string> <string name="menu_change_key_settings">Cambiar contraseña maestra</string>
<string name="settings">Configuración</string> <string name="settings">Configuración</string>
@@ -93,7 +91,6 @@
<string name="menu_delete">Eliminar</string> <string name="menu_delete">Eliminar</string>
<string name="menu_donate">Donar</string> <string name="menu_donate">Donar</string>
<string name="menu_edit">Editar</string> <string name="menu_edit">Editar</string>
<string name="menu_hide_password">Ocultar contraseña</string>
<string name="menu_lock">Bloquear la base de datos</string> <string name="menu_lock">Bloquear la base de datos</string>
<string name="menu_open">Abrir</string> <string name="menu_open">Abrir</string>
<string name="menu_search">Buscar</string> <string name="menu_search">Buscar</string>

View File

@@ -197,8 +197,6 @@
<string name="hint_keyfile">Võtmefail</string> <string name="hint_keyfile">Võtmefail</string>
<string name="hint_length">Pikkus</string> <string name="hint_length">Pikkus</string>
<string name="password">Salasõna</string> <string name="password">Salasõna</string>
<string name="hide_password_title">Peida salasõnad</string>
<string name="hide_password_summary">Vakimisi peida salasõnad (***) maski taha</string>
<string name="colorize_password_title">Värvi salasõnad</string> <string name="colorize_password_title">Värvi salasõnad</string>
<string name="error_registration_read_only">Uue kirje salvestamine pole võimalik andmebaasis, milles on vaid lugemisõigus.</string> <string name="error_registration_read_only">Uue kirje salvestamine pole võimalik andmebaasis, milles on vaid lugemisõigus.</string>
<string name="error_database_uri_null">Andmebaasi ühtset ressursiidentifikaatorit ei õnnestu laadida.</string> <string name="error_database_uri_null">Andmebaasi ühtset ressursiidentifikaatorit ei õnnestu laadida.</string>
@@ -213,7 +211,6 @@
<string name="list_groups_show_number_entries_title">Näita kirjete arvu</string> <string name="list_groups_show_number_entries_title">Näita kirjete arvu</string>
<string name="show_uuid_title">Näita UUID\'d</string> <string name="show_uuid_title">Näita UUID\'d</string>
<string name="show_uuid_summary">Näita kirje või grupiga seotud UUID\'d</string> <string name="show_uuid_summary">Näita kirje või grupiga seotud UUID\'d</string>
<string name="menu_hide_password">Peida salasõna</string>
<string name="menu_showpass">Näita salasõna</string> <string name="menu_showpass">Näita salasõna</string>
<string name="error_cancel_by_user">Katkestatud kasutaja poolt.</string> <string name="error_cancel_by_user">Katkestatud kasutaja poolt.</string>
<string name="error_driver_required">%1$s draiver on vajalik.</string> <string name="error_driver_required">%1$s draiver on vajalik.</string>

View File

@@ -93,8 +93,6 @@
<string name="list_size_summary">Testuaren tamaina taldearen listan</string> <string name="list_size_summary">Testuaren tamaina taldearen listan</string>
<string name="loading_database">Datubasea kargatzen…</string> <string name="loading_database">Datubasea kargatzen…</string>
<string name="lowercase">minuskulak</string> <string name="lowercase">minuskulak</string>
<string name="hide_password_title">Pasahitza estali</string>
<string name="hide_password_summary">Pasahitza estali modu lehenetsian</string>
<string name="about">Honi buruz</string> <string name="about">Honi buruz</string>
<string name="menu_change_key_settings">Gako nagusia aldatu</string> <string name="menu_change_key_settings">Gako nagusia aldatu</string>
<string name="settings">Ezarpenak</string> <string name="settings">Ezarpenak</string>
@@ -102,7 +100,6 @@
<string name="menu_delete">Ezabatu</string> <string name="menu_delete">Ezabatu</string>
<string name="menu_donate">Dirua eman</string> <string name="menu_donate">Dirua eman</string>
<string name="menu_edit">Editatu</string> <string name="menu_edit">Editatu</string>
<string name="menu_hide_password">Pasahitza ezkutatu</string>
<string name="menu_lock">Datu-basea blokeatu</string> <string name="menu_lock">Datu-basea blokeatu</string>
<string name="menu_open">Ireki</string> <string name="menu_open">Ireki</string>
<string name="menu_search">Bilatu</string> <string name="menu_search">Bilatu</string>

View File

@@ -79,7 +79,6 @@
<string name="menu_open">باز</string> <string name="menu_open">باز</string>
<string name="menu_save_database">ذخیره پایگاه داده</string> <string name="menu_save_database">ذخیره پایگاه داده</string>
<string name="menu_lock">بانک اطلاعاتی قفل</string> <string name="menu_lock">بانک اطلاعاتی قفل</string>
<string name="menu_hide_password">پنهان کردن رمز عبور</string>
<string name="menu_cancel">لغو</string> <string name="menu_cancel">لغو</string>
<string name="menu_delete">حذف</string> <string name="menu_delete">حذف</string>
<string name="menu_paste">جاگذاری</string> <string name="menu_paste">جاگذاری</string>
@@ -97,8 +96,6 @@
<string name="copy_field">کپی %1$s</string> <string name="copy_field">کپی %1$s</string>
<string name="menu_change_key_settings">کلید اصلی را تغییر دهید</string> <string name="menu_change_key_settings">کلید اصلی را تغییر دهید</string>
<string name="about">در باره</string> <string name="about">در باره</string>
<string name="hide_password_summary">رمزهای عبور ماسک (***) به طور پیش فرض</string>
<string name="hide_password_title">پنهان کردن رمزهای عبور</string>
<string name="lowercase">ترجمه</string> <string name="lowercase">ترجمه</string>
<string name="loading_database">پایگاه داده بارگذاری…</string> <string name="loading_database">پایگاه داده بارگذاری…</string>
<string name="creating_database">ایجاد پایگاه داده…</string> <string name="creating_database">ایجاد پایگاه داده…</string>

View File

@@ -92,8 +92,6 @@
<string name="list_size_summary">Tekstin koko ryhmälistauksessa</string> <string name="list_size_summary">Tekstin koko ryhmälistauksessa</string>
<string name="loading_database">Ladataan tietokantaa…</string> <string name="loading_database">Ladataan tietokantaa…</string>
<string name="lowercase">pienet kirjaimet</string> <string name="lowercase">pienet kirjaimet</string>
<string name="hide_password_title">Piilota salasana</string>
<string name="hide_password_summary">Piilota salasanat oletuksena</string>
<string name="about">Tietoa</string> <string name="about">Tietoa</string>
<string name="menu_change_key_settings">Vaihda pääsalasanaa</string> <string name="menu_change_key_settings">Vaihda pääsalasanaa</string>
<string name="settings">Asetukset</string> <string name="settings">Asetukset</string>
@@ -101,7 +99,6 @@
<string name="menu_delete">Poista</string> <string name="menu_delete">Poista</string>
<string name="menu_donate">Lahjoita</string> <string name="menu_donate">Lahjoita</string>
<string name="menu_edit">Muokkaa</string> <string name="menu_edit">Muokkaa</string>
<string name="menu_hide_password">Piilota salasana</string>
<string name="menu_lock">Lukitse tietokanta</string> <string name="menu_lock">Lukitse tietokanta</string>
<string name="menu_open">Avaa</string> <string name="menu_open">Avaa</string>
<string name="menu_search">Etsi</string> <string name="menu_search">Etsi</string>

View File

@@ -218,8 +218,6 @@
<string name="invalid_db_sig">Hindi makilala ang format ng database.</string> <string name="invalid_db_sig">Hindi makilala ang format ng database.</string>
<string name="keyfile_is_empty">Walang laman ang keyfile.</string> <string name="keyfile_is_empty">Walang laman ang keyfile.</string>
<string name="length">Haba</string> <string name="length">Haba</string>
<string name="hide_password_title">Itago ang mga password</string>
<string name="hide_password_summary">I-mask ang mga password (***) bilang default</string>
<string name="colorize_password_title">Kulayan ang mga password</string> <string name="colorize_password_title">Kulayan ang mga password</string>
<string name="colorize_password_summary">Kulayan ang mga password character ayon sa uri</string> <string name="colorize_password_summary">Kulayan ang mga password character ayon sa uri</string>
<string name="list_entries_show_username_title">Ipakita ang mga username</string> <string name="list_entries_show_username_title">Ipakita ang mga username</string>
@@ -258,7 +256,6 @@
<string name="menu_paste">I-paste</string> <string name="menu_paste">I-paste</string>
<string name="menu_delete">Burahin</string> <string name="menu_delete">Burahin</string>
<string name="menu_cancel">Kanselahin</string> <string name="menu_cancel">Kanselahin</string>
<string name="menu_hide_password">Itago ang password</string>
<string name="menu_lock">I-lock ang database</string> <string name="menu_lock">I-lock ang database</string>
<string name="menu_save_database">I-save ang data</string> <string name="menu_save_database">I-save ang data</string>
<string name="menu_merge_database">I-merge ang data</string> <string name="menu_merge_database">I-merge ang data</string>

View File

@@ -98,8 +98,6 @@
<string name="list_size_summary">Taille du texte dans les éléments de liste</string> <string name="list_size_summary">Taille du texte dans les éléments de liste</string>
<string name="loading_database">Chargement de la base de données…</string> <string name="loading_database">Chargement de la base de données…</string>
<string name="lowercase">Minuscules</string> <string name="lowercase">Minuscules</string>
<string name="hide_password_title">Masquer les mots de passe</string>
<string name="hide_password_summary">Masque les mots de passe (***) par défaut</string>
<string name="about">À propos</string> <string name="about">À propos</string>
<string name="menu_change_key_settings">Modifier la clé principale</string> <string name="menu_change_key_settings">Modifier la clé principale</string>
<string name="copy_field">%1$s copié</string> <string name="copy_field">%1$s copié</string>
@@ -108,7 +106,6 @@
<string name="menu_delete">Supprimer</string> <string name="menu_delete">Supprimer</string>
<string name="menu_donate">Faire un don</string> <string name="menu_donate">Faire un don</string>
<string name="menu_edit">Modifier</string> <string name="menu_edit">Modifier</string>
<string name="menu_hide_password">Masquer le mot de passe</string>
<string name="menu_lock">Verrouiller la base de données</string> <string name="menu_lock">Verrouiller la base de données</string>
<string name="menu_open">Ouvrir</string> <string name="menu_open">Ouvrir</string>
<string name="menu_search">Rechercher</string> <string name="menu_search">Rechercher</string>

View File

@@ -188,7 +188,6 @@
<string name="corrupted_file">Ficheiro corrompido.</string> <string name="corrupted_file">Ficheiro corrompido.</string>
<string name="menu_keystore_remove_key">Borrar a clave do desbloqueo avanzado</string> <string name="menu_keystore_remove_key">Borrar a clave do desbloqueo avanzado</string>
<string name="loading_database">A carregar base de datos…</string> <string name="loading_database">A carregar base de datos…</string>
<string name="hide_password_summary">Mascarar contrasinais (***) por predefinición</string>
<string name="list_size_summary">Tamaño do texto na lista de elementos</string> <string name="list_size_summary">Tamaño do texto na lista de elementos</string>
<string name="creating_database">A crear a base de datos…</string> <string name="creating_database">A crear a base de datos…</string>
<string name="menu_move">Mover</string> <string name="menu_move">Mover</string>
@@ -209,7 +208,6 @@
<string name="colorize_password_title">Colorear contrasinais</string> <string name="colorize_password_title">Colorear contrasinais</string>
<string name="colorize_password_summary">Colorear caracteres dos contrasinais por tipo</string> <string name="colorize_password_summary">Colorear caracteres dos contrasinais por tipo</string>
<string name="settings">Configuracións</string> <string name="settings">Configuracións</string>
<string name="menu_hide_password">Ocultar contrasinal</string>
<string name="menu_lock">Bloquear base de datos</string> <string name="menu_lock">Bloquear base de datos</string>
<string name="menu_save_database">Gardar datos</string> <string name="menu_save_database">Gardar datos</string>
<string name="no_results">Sen resultados da procura</string> <string name="no_results">Sen resultados da procura</string>
@@ -278,7 +276,6 @@
<string name="list_entries_show_username_title">Mostrar nomes de usuario</string> <string name="list_entries_show_username_title">Mostrar nomes de usuario</string>
<string name="hint_keyfile">Ficheiro clave</string> <string name="hint_keyfile">Ficheiro clave</string>
<string name="hint_pass">Contrasinal</string> <string name="hint_pass">Contrasinal</string>
<string name="hide_password_title">Ocultar contrasinais</string>
<string name="lowercase">Minúsculas</string> <string name="lowercase">Minúsculas</string>
<string name="about">Sobre</string> <string name="about">Sobre</string>
<string name="copy_field">Copia de %1$s</string> <string name="copy_field">Copia de %1$s</string>

View File

@@ -113,7 +113,6 @@
<string name="list_groups_show_number_entries_summary">Prikazuje broj unosa u grupi</string> <string name="list_groups_show_number_entries_summary">Prikazuje broj unosa u grupi</string>
<string name="creating_database">Stvaranje baze podataka …</string> <string name="creating_database">Stvaranje baze podataka …</string>
<string name="loading_database">Učitavanje baze podataka …</string> <string name="loading_database">Učitavanje baze podataka …</string>
<string name="hide_password_title">Sakrij lozinke</string>
<string name="menu_change_key_settings">Promjeni glavni ključ</string> <string name="menu_change_key_settings">Promjeni glavni ključ</string>
<string name="settings">Postavke</string> <string name="settings">Postavke</string>
<string name="menu_app_settings">Postavke aplikacije</string> <string name="menu_app_settings">Postavke aplikacije</string>
@@ -127,7 +126,6 @@
<string name="menu_copy">Kopiraj</string> <string name="menu_copy">Kopiraj</string>
<string name="menu_paste">Umetni</string> <string name="menu_paste">Umetni</string>
<string name="menu_delete">Izbriši</string> <string name="menu_delete">Izbriši</string>
<string name="menu_hide_password">Sakrij lozinku</string>
<string name="menu_lock">Zaključaj bazu podataka</string> <string name="menu_lock">Zaključaj bazu podataka</string>
<string name="menu_save_database">Spremi podatke</string> <string name="menu_save_database">Spremi podatke</string>
<string name="menu_open">Otvori</string> <string name="menu_open">Otvori</string>
@@ -249,7 +247,6 @@
<string name="list_size_title">Veličina elemenata popisa</string> <string name="list_size_title">Veličina elemenata popisa</string>
<string name="list_size_summary">Veličina teksta u popisu elemenata</string> <string name="list_size_summary">Veličina teksta u popisu elemenata</string>
<string name="lowercase">Mala slova</string> <string name="lowercase">Mala slova</string>
<string name="hide_password_summary">Standardno sakrij lozinke (***)</string>
<string name="about">O aplikaciji</string> <string name="about">O aplikaciji</string>
<string name="copy_field">Kopija od %1$s</string> <string name="copy_field">Kopija od %1$s</string>
<string name="menu_move">Premjesti</string> <string name="menu_move">Premjesti</string>

View File

@@ -92,8 +92,6 @@
<string name="list_size_summary">Szövegméret az elemlistában</string> <string name="list_size_summary">Szövegméret az elemlistában</string>
<string name="loading_database">Adatbázis betöltése…</string> <string name="loading_database">Adatbázis betöltése…</string>
<string name="lowercase">Kisbetűk</string> <string name="lowercase">Kisbetűk</string>
<string name="hide_password_title">Jelszavak elrejtése</string>
<string name="hide_password_summary">Jelszavak alapértelmezett elrejtése (***)</string>
<string name="about">Névjegy</string> <string name="about">Névjegy</string>
<string name="menu_change_key_settings">Mesterkulcs cseréje</string> <string name="menu_change_key_settings">Mesterkulcs cseréje</string>
<string name="settings">Beállítások</string> <string name="settings">Beállítások</string>
@@ -101,7 +99,6 @@
<string name="menu_delete">Törlés</string> <string name="menu_delete">Törlés</string>
<string name="menu_donate">Támogatás</string> <string name="menu_donate">Támogatás</string>
<string name="menu_edit">Szerkesztés</string> <string name="menu_edit">Szerkesztés</string>
<string name="menu_hide_password">Jelszó elrejtése</string>
<string name="menu_lock">Adatbázis zárolása</string> <string name="menu_lock">Adatbázis zárolása</string>
<string name="menu_open">Megnyitás</string> <string name="menu_open">Megnyitás</string>
<string name="menu_search">Keresés</string> <string name="menu_search">Keresés</string>

View File

@@ -14,7 +14,6 @@
<string name="menu_open">Buka</string> <string name="menu_open">Buka</string>
<string name="menu_save_database">Simpan data</string> <string name="menu_save_database">Simpan data</string>
<string name="menu_lock">Basis Data Terkunci</string> <string name="menu_lock">Basis Data Terkunci</string>
<string name="menu_hide_password">Sembunyikan Kata Sandi</string>
<string name="menu_cancel">Batal</string> <string name="menu_cancel">Batal</string>
<string name="menu_delete">Hapus</string> <string name="menu_delete">Hapus</string>
<string name="menu_paste">Tempel</string> <string name="menu_paste">Tempel</string>
@@ -32,8 +31,6 @@
<string name="copy_field">Salinan dari %1$s</string> <string name="copy_field">Salinan dari %1$s</string>
<string name="menu_change_key_settings">Ubah Kunci Utama</string> <string name="menu_change_key_settings">Ubah Kunci Utama</string>
<string name="about">Tentang</string> <string name="about">Tentang</string>
<string name="hide_password_summary">Secara otomatis tutupi kata sandi (***)</string>
<string name="hide_password_title">Sembunyikan Kata Sandi</string>
<string name="lowercase">Huruf Kecil</string> <string name="lowercase">Huruf Kecil</string>
<string name="loading_database">Memuat basis data…</string> <string name="loading_database">Memuat basis data…</string>
<string name="creating_database">Pembuatan basis data…</string> <string name="creating_database">Pembuatan basis data…</string>

View File

@@ -94,8 +94,6 @@
<string name="list_size_summary">Dimensione del testo nell\'elenco del gruppo</string> <string name="list_size_summary">Dimensione del testo nell\'elenco del gruppo</string>
<string name="loading_database">Caricamento del database…</string> <string name="loading_database">Caricamento del database…</string>
<string name="lowercase">Minuscole</string> <string name="lowercase">Minuscole</string>
<string name="hide_password_title">Nascondi le password</string>
<string name="hide_password_summary">Maschera le password (***) in modo predefinito</string>
<string name="about">Informazioni</string> <string name="about">Informazioni</string>
<string name="menu_change_key_settings">Modifica chiave principale</string> <string name="menu_change_key_settings">Modifica chiave principale</string>
<string name="settings">Impostazioni</string> <string name="settings">Impostazioni</string>
@@ -103,7 +101,6 @@
<string name="menu_delete">Elimina</string> <string name="menu_delete">Elimina</string>
<string name="menu_donate">Dona</string> <string name="menu_donate">Dona</string>
<string name="menu_edit">Modifica</string> <string name="menu_edit">Modifica</string>
<string name="menu_hide_password">Nascondi password</string>
<string name="menu_lock">Blocca database</string> <string name="menu_lock">Blocca database</string>
<string name="menu_open">Apri</string> <string name="menu_open">Apri</string>
<string name="menu_search">Cerca</string> <string name="menu_search">Cerca</string>

View File

@@ -90,8 +90,6 @@
<string name="list_size_summary">גודל טקסט ברשימת הרכיבים</string> <string name="list_size_summary">גודל טקסט ברשימת הרכיבים</string>
<string name="loading_database">טוען מסד נתונים…</string> <string name="loading_database">טוען מסד נתונים…</string>
<string name="lowercase">אות קטנה</string> <string name="lowercase">אות קטנה</string>
<string name="hide_password_title">הסתר סיסמאות</string>
<string name="hide_password_summary">הסווה סיסמאות (***) כברירת מחדל</string>
<string name="about">אודות</string> <string name="about">אודות</string>
<string name="menu_change_key_settings">שנה מפתח ראשי</string> <string name="menu_change_key_settings">שנה מפתח ראשי</string>
<string name="settings">העדפות</string> <string name="settings">העדפות</string>
@@ -99,7 +97,6 @@
<string name="menu_delete">מחק</string> <string name="menu_delete">מחק</string>
<string name="menu_donate">תרום</string> <string name="menu_donate">תרום</string>
<string name="menu_edit">ערוך</string> <string name="menu_edit">ערוך</string>
<string name="menu_hide_password">הסתר סיסמה</string>
<string name="menu_lock">נעל מסד נתונים</string> <string name="menu_lock">נעל מסד נתונים</string>
<string name="menu_open">פתח</string> <string name="menu_open">פתח</string>
<string name="menu_search">חיפוש</string> <string name="menu_search">חיפוש</string>

View File

@@ -159,8 +159,6 @@
<string name="creating_database">データベースを作成しています…</string> <string name="creating_database">データベースを作成しています…</string>
<string name="loading_database">データベースを読み込んでいます…</string> <string name="loading_database">データベースを読み込んでいます…</string>
<string name="lowercase">小文字</string> <string name="lowercase">小文字</string>
<string name="hide_password_title">パスワードを非表示にする</string>
<string name="hide_password_summary">デフォルトでパスワードを隠します(***と表示)</string>
<string name="about">概要</string> <string name="about">概要</string>
<string name="menu_change_key_settings">マスターキーを変更</string> <string name="menu_change_key_settings">マスターキーを変更</string>
<string name="copy_field">%1$s のコピー</string> <string name="copy_field">%1$s のコピー</string>
@@ -178,7 +176,6 @@
<string name="menu_paste">貼り付け</string> <string name="menu_paste">貼り付け</string>
<string name="menu_delete">削除</string> <string name="menu_delete">削除</string>
<string name="menu_cancel">キャンセル</string> <string name="menu_cancel">キャンセル</string>
<string name="menu_hide_password">パスワードを非表示にする</string>
<string name="menu_lock">データベースをロック</string> <string name="menu_lock">データベースをロック</string>
<string name="menu_save_database">保存する</string> <string name="menu_save_database">保存する</string>
<string name="menu_open">開く</string> <string name="menu_open">開く</string>

View File

@@ -100,8 +100,6 @@
<string name="list_size_summary">요소 목록 텍스트 크기</string> <string name="list_size_summary">요소 목록 텍스트 크기</string>
<string name="loading_database">데이터베이스 로딩 중…</string> <string name="loading_database">데이터베이스 로딩 중…</string>
<string name="lowercase">소문자</string> <string name="lowercase">소문자</string>
<string name="hide_password_title">비밀번호 숨기기</string>
<string name="hide_password_summary">기본 비밀번호를 (***) 로 가리기</string>
<string name="about">정보</string> <string name="about">정보</string>
<string name="menu_change_key_settings">마스터 키 바꾸기</string> <string name="menu_change_key_settings">마스터 키 바꾸기</string>
<string name="copy_field">%1$s의 사본</string> <string name="copy_field">%1$s의 사본</string>
@@ -116,7 +114,6 @@
<string name="menu_paste">붙여넣기</string> <string name="menu_paste">붙여넣기</string>
<string name="menu_delete">삭제</string> <string name="menu_delete">삭제</string>
<string name="menu_cancel">취소</string> <string name="menu_cancel">취소</string>
<string name="menu_hide_password">비밀번호 숨기기</string>
<string name="menu_lock">데이터베이스 잠그기</string> <string name="menu_lock">데이터베이스 잠그기</string>
<string name="menu_open">열기</string> <string name="menu_open">열기</string>
<string name="menu_search">검색</string> <string name="menu_search">검색</string>

View File

@@ -81,7 +81,6 @@
<string name="entry_confpassword">Patvirtinti slaptažodį</string> <string name="entry_confpassword">Patvirtinti slaptažodį</string>
<string name="invalid_db_sig">Nepavyko atpažinti duomenų bazės formato.</string> <string name="invalid_db_sig">Nepavyko atpažinti duomenų bazės formato.</string>
<string name="sort_db">Natūrali tvarka</string> <string name="sort_db">Natūrali tvarka</string>
<string name="menu_hide_password">Slėpti slaptažodį</string>
<string name="app_timeout">Neveiklumas</string> <string name="app_timeout">Neveiklumas</string>
<string name="clipboard_error_clear">Nepavyko išvalyti iškarpinės</string> <string name="clipboard_error_clear">Nepavyko išvalyti iškarpinės</string>
<string name="list_size_summary">Teksto dydis grupės sąraše</string> <string name="list_size_summary">Teksto dydis grupės sąraše</string>
@@ -92,7 +91,6 @@
<string name="search">Įrašo pavadinimas/aprašymas</string> <string name="search">Įrašo pavadinimas/aprašymas</string>
<string name="menu_change_key_settings">Pakeisti pagrindinį raktą</string> <string name="menu_change_key_settings">Pakeisti pagrindinį raktą</string>
<string name="entry_accessed">Naudota</string> <string name="entry_accessed">Naudota</string>
<string name="hide_password_title">Maskuoti slaptažodį</string>
<string name="space">Tarpas</string> <string name="space">Tarpas</string>
<string name="special">Specialus</string> <string name="special">Specialus</string>
<string name="uppercase">Didžiosios raidės</string> <string name="uppercase">Didžiosios raidės</string>
@@ -100,7 +98,6 @@
<string name="minus">Minusas</string> <string name="minus">Minusas</string>
<string name="underline">Pabraukimas</string> <string name="underline">Pabraukimas</string>
<string name="default_checkbox">Naudoti šią duomenų bazę kaip numatytąją</string> <string name="default_checkbox">Naudoti šią duomenų bazę kaip numatytąją</string>
<string name="hide_password_summary">Slėpti slaptažodžius pagal nutylėjimą</string>
<string name="invalid_algorithm">Neteisingas algoritmas.</string> <string name="invalid_algorithm">Neteisingas algoritmas.</string>
<string name="error_invalid_path">Pasirūpininkite, kad kelias būtų teisingas.</string> <string name="error_invalid_path">Pasirūpininkite, kad kelias būtų teisingas.</string>
<string name="education_unlock_summary">Įveskite slaptažodį ir/ar rakto failą, kad atrakintumėte savo duomenų bazę. <string name="education_unlock_summary">Įveskite slaptažodį ir/ar rakto failą, kad atrakintumėte savo duomenų bazę.

View File

@@ -89,8 +89,6 @@
<string name="list_size_summary">Teksta izmērs ierakstos un grupu sarakstos</string> <string name="list_size_summary">Teksta izmērs ierakstos un grupu sarakstos</string>
<string name="loading_database">Ielādēt datu bāzi…</string> <string name="loading_database">Ielādēt datu bāzi…</string>
<string name="lowercase">Mazie burti</string> <string name="lowercase">Mazie burti</string>
<string name="hide_password_title">Sēpt paroles</string>
<string name="hide_password_summary">Sēpt paroles *****</string>
<string name="about">Par</string> <string name="about">Par</string>
<string name="menu_change_key_settings">Mainīt galveno paroli</string> <string name="menu_change_key_settings">Mainīt galveno paroli</string>
<string name="settings">Iestatījumi</string> <string name="settings">Iestatījumi</string>
@@ -98,7 +96,6 @@
<string name="menu_delete">Dzēst</string> <string name="menu_delete">Dzēst</string>
<string name="menu_donate">Ziedot</string> <string name="menu_donate">Ziedot</string>
<string name="menu_edit">Rediģēt</string> <string name="menu_edit">Rediģēt</string>
<string name="menu_hide_password">Paslēpt paroli</string>
<string name="menu_lock">Bloķēt datu bāzi</string> <string name="menu_lock">Bloķēt datu bāzi</string>
<string name="menu_open">Atvērt</string> <string name="menu_open">Atvērt</string>
<string name="menu_search">Meklēšana</string> <string name="menu_search">Meklēšana</string>

View File

@@ -21,7 +21,6 @@
<string name="menu_search">തിരയുക</string> <string name="menu_search">തിരയുക</string>
<string name="menu_security_settings">സുരക്ഷാ ക്രമീകരണങ്ങൾ</string> <string name="menu_security_settings">സുരക്ഷാ ക്രമീകരണങ്ങൾ</string>
<string name="menu_database_settings">ഡാറ്റാബേസ് ക്രമീകരണങ്ങൾ</string> <string name="menu_database_settings">ഡാറ്റാബേസ് ക്രമീകരണങ്ങൾ</string>
<string name="hide_password_summary">സ്ഥിരസ്ഥിതിയായി പാസ്‌വേഡുകൾ (***) മാസ്ക് ചെയ്യുക</string>
<string name="invalid_algorithm">തെറ്റായ അൽഗോരിതം.</string> <string name="invalid_algorithm">തെറ്റായ അൽഗോരിതം.</string>
<string name="password">പാസ്സ്‌വേഡ്</string> <string name="password">പാസ്സ്‌വേഡ്</string>
<string name="hint_pass">പാസ്സ്‌വേഡ്</string> <string name="hint_pass">പാസ്സ്‌വേഡ്</string>
@@ -52,7 +51,6 @@
<string name="menu_open">തുറക്കുക</string> <string name="menu_open">തുറക്കുക</string>
<string name="menu_save_database">ഡാറ്റാബേസ് സംരക്ഷിക്കുക</string> <string name="menu_save_database">ഡാറ്റാബേസ് സംരക്ഷിക്കുക</string>
<string name="menu_lock">ഡാറ്റാബേസ് ലോക്ക് ചെയ്യുക</string> <string name="menu_lock">ഡാറ്റാബേസ് ലോക്ക് ചെയ്യുക</string>
<string name="menu_hide_password">പാസ്‌വേഡുകൾ മറയ്‌ക്കുക</string>
<string name="menu_cancel">റദ്ദാക്കുക</string> <string name="menu_cancel">റദ്ദാക്കുക</string>
<string name="menu_delete">ഇല്ലാതാക്കുക</string> <string name="menu_delete">ഇല്ലാതാക്കുക</string>
<string name="menu_move">നീക്കുക</string> <string name="menu_move">നീക്കുക</string>
@@ -62,7 +60,6 @@
<string name="menu_app_settings">അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങൾ</string> <string name="menu_app_settings">അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങൾ</string>
<string name="settings">ക്രമീകരണങ്ങൾ</string> <string name="settings">ക്രമീകരണങ്ങൾ</string>
<string name="menu_change_key_settings">മാസ്റ്റർ കീ മാറ്റുക</string> <string name="menu_change_key_settings">മാസ്റ്റർ കീ മാറ്റുക</string>
<string name="hide_password_title">പാസ്‌വേഡുകൾ മറയ്‌ക്കുക</string>
<string name="loading_database">ഡാറ്റാബേസ് ലോഡുചെയ്യുന്നു…</string> <string name="loading_database">ഡാറ്റാബേസ് ലോഡുചെയ്യുന്നു…</string>
<string name="creating_database">ഡാറ്റാബേസ് സൃഷ്ടിക്കുന്നു…</string> <string name="creating_database">ഡാറ്റാബേസ് സൃഷ്ടിക്കുന്നു…</string>
<string name="list_entries_show_username_title">ഉപയോക്തൃനാമങ്ങൾ കാണിക്കുക</string> <string name="list_entries_show_username_title">ഉപയോക്തൃനാമങ്ങൾ കാണിക്കുക</string>

View File

@@ -101,8 +101,6 @@
<string name="list_size_summary">Tekststørrelse i elemenetlisten</string> <string name="list_size_summary">Tekststørrelse i elemenetlisten</string>
<string name="loading_database">Laster database…</string> <string name="loading_database">Laster database…</string>
<string name="lowercase">Små bokstaver</string> <string name="lowercase">Små bokstaver</string>
<string name="hide_password_title">Masker passord</string>
<string name="hide_password_summary">Skjul passord som forvalg</string>
<string name="about">Om</string> <string name="about">Om</string>
<string name="menu_change_key_settings">Endre hovednøkkel</string> <string name="menu_change_key_settings">Endre hovednøkkel</string>
<string name="copy_field">Kopi av%1$s</string> <string name="copy_field">Kopi av%1$s</string>
@@ -117,7 +115,6 @@
<string name="menu_paste">Lim inn</string> <string name="menu_paste">Lim inn</string>
<string name="menu_delete">Slett</string> <string name="menu_delete">Slett</string>
<string name="menu_cancel">Avbryt</string> <string name="menu_cancel">Avbryt</string>
<string name="menu_hide_password">Skjul passord</string>
<string name="menu_lock">Lås database</string> <string name="menu_lock">Lås database</string>
<string name="menu_open">Åpne</string> <string name="menu_open">Åpne</string>
<string name="menu_search">Søk</string> <string name="menu_search">Søk</string>

View File

@@ -85,8 +85,6 @@
<string name="list_size_summary">Tekstgrootte in de itemslijst</string> <string name="list_size_summary">Tekstgrootte in de itemslijst</string>
<string name="loading_database">Database laden…</string> <string name="loading_database">Database laden…</string>
<string name="lowercase">Kleine letters</string> <string name="lowercase">Kleine letters</string>
<string name="hide_password_title">Wachtwoorden verbergen</string>
<string name="hide_password_summary">Wachtwoorden maskeren (***)</string>
<string name="about">Over</string> <string name="about">Over</string>
<string name="menu_change_key_settings">Hoofdsleutel wijzigen</string> <string name="menu_change_key_settings">Hoofdsleutel wijzigen</string>
<string name="settings">Instellingen</string> <string name="settings">Instellingen</string>
@@ -94,7 +92,6 @@
<string name="menu_delete">Verwijderen</string> <string name="menu_delete">Verwijderen</string>
<string name="menu_donate">Doneren</string> <string name="menu_donate">Doneren</string>
<string name="menu_edit">Bewerken</string> <string name="menu_edit">Bewerken</string>
<string name="menu_hide_password">Wachtwoord verbergen</string>
<string name="menu_lock">Database vergrendelen</string> <string name="menu_lock">Database vergrendelen</string>
<string name="menu_open">Openen</string> <string name="menu_open">Openen</string>
<string name="menu_search">Zoeken</string> <string name="menu_search">Zoeken</string>

View File

@@ -81,8 +81,6 @@
<string name="list_size_summary">Tekststorleik i gruppelista</string> <string name="list_size_summary">Tekststorleik i gruppelista</string>
<string name="loading_database">Lastar databasen …</string> <string name="loading_database">Lastar databasen …</string>
<string name="lowercase">Små bokstavar</string> <string name="lowercase">Små bokstavar</string>
<string name="hide_password_title">Skjul passord</string>
<string name="hide_password_summary">Skjul passord som forval</string>
<string name="about">Om</string> <string name="about">Om</string>
<string name="menu_change_key_settings">Endra hovudnøkkelen</string> <string name="menu_change_key_settings">Endra hovudnøkkelen</string>
<string name="settings">Innstillingar</string> <string name="settings">Innstillingar</string>
@@ -90,7 +88,6 @@
<string name="menu_delete">Slett</string> <string name="menu_delete">Slett</string>
<string name="menu_donate">Doner</string> <string name="menu_donate">Doner</string>
<string name="menu_edit">Endra</string> <string name="menu_edit">Endra</string>
<string name="menu_hide_password">Skjul passord</string>
<string name="menu_lock">Lås database</string> <string name="menu_lock">Lås database</string>
<string name="menu_open">Opne</string> <string name="menu_open">Opne</string>
<string name="menu_search">Søk</string> <string name="menu_search">Søk</string>

View File

@@ -87,7 +87,6 @@
<string name="menu_open">ਖੋਲ੍ਹੋ</string> <string name="menu_open">ਖੋਲ੍ਹੋ</string>
<string name="menu_save_database">ਡਾਟਾਬੇਸ ਸੰਭਾਲੋ</string> <string name="menu_save_database">ਡਾਟਾਬੇਸ ਸੰਭਾਲੋ</string>
<string name="menu_lock">ਡਾਟਾਬੇਸ ਲਾਕ ਕਰੋ</string> <string name="menu_lock">ਡਾਟਾਬੇਸ ਲਾਕ ਕਰੋ</string>
<string name="menu_hide_password">ਪਾਸਵਰਡ ਲੁਕਾਓ</string>
<string name="menu_cancel">ਰੱਦ ਕਰੋ</string> <string name="menu_cancel">ਰੱਦ ਕਰੋ</string>
<string name="menu_delete">ਹਟਾਓ</string> <string name="menu_delete">ਹਟਾਓ</string>
<string name="menu_paste">ਚੇਪੋ</string> <string name="menu_paste">ਚੇਪੋ</string>
@@ -105,8 +104,6 @@
<string name="copy_field">%1$s ਦੀ ਕਾਪੀ</string> <string name="copy_field">%1$s ਦੀ ਕਾਪੀ</string>
<string name="menu_change_key_settings">ਮਾਸਟਰ ਕੁੰਜੀ ਬਦਲੋ</string> <string name="menu_change_key_settings">ਮਾਸਟਰ ਕੁੰਜੀ ਬਦਲੋ</string>
<string name="about">ਇਸ ਬਾਰੇ</string> <string name="about">ਇਸ ਬਾਰੇ</string>
<string name="hide_password_summary">ਪਾਸਵਰਡਾਂ ਨੂੰ ਮੂਲ ਰੂਪ ਵਿੱਚ ਲੁਕਾਓ (***)</string>
<string name="hide_password_title">ਪਾਸਵਰਡ ਲੁਕਾਓ</string>
<string name="lowercase">ਛੋਟੇ ਅੱਖਰ (ਅੰਗਰੇਜ਼ੀ)</string> <string name="lowercase">ਛੋਟੇ ਅੱਖਰ (ਅੰਗਰੇਜ਼ੀ)</string>
<string name="loading_database">…ਡਾਟਾਬੇਸ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string> <string name="loading_database">…ਡਾਟਾਬੇਸ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ</string>
<string name="creating_database">…ਡਾਟਾਬੇਸ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ</string> <string name="creating_database">…ਡਾਟਾਬੇਸ ਬਣਾਇਆ ਜਾ ਰਿਹਾ ਹੈ</string>

View File

@@ -81,8 +81,6 @@
<string name="list_size_summary">Rozmiar tekstu na liście elementów</string> <string name="list_size_summary">Rozmiar tekstu na liście elementów</string>
<string name="loading_database">Wczytywanie bazy danych…</string> <string name="loading_database">Wczytywanie bazy danych…</string>
<string name="lowercase">Małe litery</string> <string name="lowercase">Małe litery</string>
<string name="hide_password_title">Ukryj hasła</string>
<string name="hide_password_summary">Domyślnie maskuj hasła (***)</string>
<string name="about">O aplikacji</string> <string name="about">O aplikacji</string>
<string name="menu_change_key_settings">Zmień klucz główny</string> <string name="menu_change_key_settings">Zmień klucz główny</string>
<string name="settings">Ustawienia</string> <string name="settings">Ustawienia</string>
@@ -90,7 +88,6 @@
<string name="menu_delete">Usuń</string> <string name="menu_delete">Usuń</string>
<string name="menu_donate">Wspomóż</string> <string name="menu_donate">Wspomóż</string>
<string name="menu_edit">Edytuj</string> <string name="menu_edit">Edytuj</string>
<string name="menu_hide_password">Ukryj hasło</string>
<string name="menu_lock">Zablokuj bazę danych</string> <string name="menu_lock">Zablokuj bazę danych</string>
<string name="menu_open">Otwórz</string> <string name="menu_open">Otwórz</string>
<string name="menu_search">Szukaj</string> <string name="menu_search">Szukaj</string>

View File

@@ -83,8 +83,6 @@
<string name="list_size_summary">Tamanho do texto na lista de grupos</string> <string name="list_size_summary">Tamanho do texto na lista de grupos</string>
<string name="loading_database">Carregando banco de dados…</string> <string name="loading_database">Carregando banco de dados…</string>
<string name="lowercase">Letras minúsculas</string> <string name="lowercase">Letras minúsculas</string>
<string name="hide_password_title">Esconder senhas</string>
<string name="hide_password_summary">Mascarar senhas (***) por padrão</string>
<string name="about">Sobre</string> <string name="about">Sobre</string>
<string name="menu_change_key_settings">Modificar a chave-mestra</string> <string name="menu_change_key_settings">Modificar a chave-mestra</string>
<string name="settings">Configurações</string> <string name="settings">Configurações</string>
@@ -92,7 +90,6 @@
<string name="menu_delete">Deletar</string> <string name="menu_delete">Deletar</string>
<string name="menu_donate">Doar</string> <string name="menu_donate">Doar</string>
<string name="menu_edit">Editar</string> <string name="menu_edit">Editar</string>
<string name="menu_hide_password">Esconder senha</string>
<string name="menu_lock">Bloquear banco de dados</string> <string name="menu_lock">Bloquear banco de dados</string>
<string name="menu_open">Abrir</string> <string name="menu_open">Abrir</string>
<string name="menu_search">Buscar</string> <string name="menu_search">Buscar</string>

View File

@@ -93,8 +93,6 @@
<string name="list_size_summary">Tamanho do texto na lista de grupos</string> <string name="list_size_summary">Tamanho do texto na lista de grupos</string>
<string name="loading_database">A carregar a base de dados…</string> <string name="loading_database">A carregar a base de dados…</string>
<string name="lowercase">Minúsculas</string> <string name="lowercase">Minúsculas</string>
<string name="hide_password_title">Ocultar palavras-passe</string>
<string name="hide_password_summary">Mascarar palavras-passe (***) por predefinição</string>
<string name="about">Sobre</string> <string name="about">Sobre</string>
<string name="menu_change_key_settings">Alterar chave mestra</string> <string name="menu_change_key_settings">Alterar chave mestra</string>
<string name="settings">Configurações</string> <string name="settings">Configurações</string>
@@ -102,7 +100,6 @@
<string name="menu_delete">Eliminar</string> <string name="menu_delete">Eliminar</string>
<string name="menu_donate">Donativos</string> <string name="menu_donate">Donativos</string>
<string name="menu_edit">Editar</string> <string name="menu_edit">Editar</string>
<string name="menu_hide_password">Ocultar palavra-chave</string>
<string name="menu_lock">Bloquear base de dados</string> <string name="menu_lock">Bloquear base de dados</string>
<string name="menu_open">Abrir</string> <string name="menu_open">Abrir</string>
<string name="menu_search">Pesquisar</string> <string name="menu_search">Pesquisar</string>

View File

@@ -142,7 +142,6 @@
<string name="menu_search">Pesquisar</string> <string name="menu_search">Pesquisar</string>
<string name="menu_open">Abrir</string> <string name="menu_open">Abrir</string>
<string name="menu_lock">Bloquear base de dados</string> <string name="menu_lock">Bloquear base de dados</string>
<string name="menu_hide_password">Ocultar palavra-chave</string>
<string name="menu_edit">Editar</string> <string name="menu_edit">Editar</string>
<string name="menu_donate">Donativos</string> <string name="menu_donate">Donativos</string>
<string name="menu_delete">Eliminar</string> <string name="menu_delete">Eliminar</string>
@@ -246,8 +245,6 @@
<string name="auto_focus_search_summary">Solicitar uma pesquisa quando abrir a base de dados</string> <string name="auto_focus_search_summary">Solicitar uma pesquisa quando abrir a base de dados</string>
<string name="auto_focus_search_title">Pesquisa rápida</string> <string name="auto_focus_search_title">Pesquisa rápida</string>
<string name="about">Sobre</string> <string name="about">Sobre</string>
<string name="hide_password_summary">Mascarar palavras-passe (***) por predefinição</string>
<string name="hide_password_title">Ocultar palavras-passe</string>
<string name="html_about_contribution">Para &lt;strong&gt;manter a liberdade&lt;/strong&gt;, &lt;strong&gt;corrigir erros&lt;/strong&gt;, &lt;strong&gt;adicionar funcionalidades&lt;/strong&gt; e &lt;strong&gt;para sermos sempre ativos&lt;/strong&gt;, contamos com sua &lt;strong&gt;contribuição&lt;/strong&gt;.</string> <string name="html_about_contribution">Para &lt;strong&gt;manter a liberdade&lt;/strong&gt;, &lt;strong&gt;corrigir erros&lt;/strong&gt;, &lt;strong&gt;adicionar funcionalidades&lt;/strong&gt; e &lt;strong&gt;para sermos sempre ativos&lt;/strong&gt;, contamos com sua &lt;strong&gt;contribuição&lt;/strong&gt;.</string>
<string name="homepage">Página inicial</string> <string name="homepage">Página inicial</string>
<string name="feedback">Comentários</string> <string name="feedback">Comentários</string>

View File

@@ -163,7 +163,6 @@
<string name="menu_paste">Lipește</string> <string name="menu_paste">Lipește</string>
<string name="menu_delete">Șterge</string> <string name="menu_delete">Șterge</string>
<string name="menu_cancel">Anulare</string> <string name="menu_cancel">Anulare</string>
<string name="menu_hide_password">Ascunde parola</string>
<string name="menu_lock">Blocare bază de date</string> <string name="menu_lock">Blocare bază de date</string>
<string name="menu_save_database">Salvare date</string> <string name="menu_save_database">Salvare date</string>
<string name="menu_open">Deschidere</string> <string name="menu_open">Deschidere</string>
@@ -180,8 +179,6 @@
<string name="no_results">Nu există rezultate de căutare</string> <string name="no_results">Nu există rezultate de căutare</string>
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft este &lt;strong&gt;open source&lt;/strong&gt; și &lt;strong&gt;fără reclame&lt;/strong&gt;. \nEste furnizat ca atare, sub licență &lt;strong&gt;GPLv3&lt;/strong&gt;, fără nicio garanție.</string> <string name="html_about_licence">KeePassDX © %1$d Kunzisoft este &lt;strong&gt;open source&lt;/strong&gt; și &lt;strong&gt;fără reclame&lt;/strong&gt;. \nEste furnizat ca atare, sub licență &lt;strong&gt;GPLv3&lt;/strong&gt;, fără nicio garanție.</string>
<string name="html_about_contribution">Pentru a ne &lt;strong&gt;păstra libertatea&lt;/strong&gt;, &lt;strong&gt;pentru a remedia erori&lt;/strong&gt;, &lt;strong&gt;pentru a adăuga funcții&lt;/strong&gt; și &lt;strong&gt;pentru a fi mereu activi&lt;/strong&gt;, ne bazăm pe &lt;strong&gt;contribuția&lt;/strong&gt; dvs.</string> <string name="html_about_contribution">Pentru a ne &lt;strong&gt;păstra libertatea&lt;/strong&gt;, &lt;strong&gt;pentru a remedia erori&lt;/strong&gt;, &lt;strong&gt;pentru a adăuga funcții&lt;/strong&gt; și &lt;strong&gt;pentru a fi mereu activi&lt;/strong&gt;, ne bazăm pe &lt;strong&gt;contribuția&lt;/strong&gt; dvs.</string>
<string name="hide_password_title">Ascundeți parolele</string>
<string name="hide_password_summary">Mascați parolele (***) în mod implicit</string>
<string name="about">Despre</string> <string name="about">Despre</string>
<string name="no_url_handler">Instalați un browser web pentru a deschide această adresă URL.</string> <string name="no_url_handler">Instalați un browser web pentru a deschide această adresă URL.</string>
<string name="select_database_file">Deschideți seiful existent</string> <string name="select_database_file">Deschideți seiful existent</string>

View File

@@ -93,8 +93,6 @@
<string name="list_size_summary">Размер текста элементов списка</string> <string name="list_size_summary">Размер текста элементов списка</string>
<string name="loading_database">Загрузка базы…</string> <string name="loading_database">Загрузка базы…</string>
<string name="lowercase">Строчные</string> <string name="lowercase">Строчные</string>
<string name="hide_password_title">Скрывать пароли</string>
<string name="hide_password_summary">Скрывать пароли за (***) по умолчанию</string>
<string name="about">О программе</string> <string name="about">О программе</string>
<string name="menu_change_key_settings">Изменить главный пароль</string> <string name="menu_change_key_settings">Изменить главный пароль</string>
<string name="settings">Настройки</string> <string name="settings">Настройки</string>
@@ -102,7 +100,6 @@
<string name="menu_delete">Удалить</string> <string name="menu_delete">Удалить</string>
<string name="menu_donate">Помочь</string> <string name="menu_donate">Помочь</string>
<string name="menu_edit">Изменить</string> <string name="menu_edit">Изменить</string>
<string name="menu_hide_password">Скрыть пароль</string>
<string name="menu_lock">Заблокировать базу</string> <string name="menu_lock">Заблокировать базу</string>
<string name="menu_open">Открыть</string> <string name="menu_open">Открыть</string>
<string name="menu_search">Поиск</string> <string name="menu_search">Поиск</string>

View File

@@ -83,8 +83,6 @@
<string name="list_size_summary">Veľkosť textu v zozname prvkov</string> <string name="list_size_summary">Veľkosť textu v zozname prvkov</string>
<string name="loading_database">Načítava sa databáza…</string> <string name="loading_database">Načítava sa databáza…</string>
<string name="lowercase">Malé písmená</string> <string name="lowercase">Malé písmená</string>
<string name="hide_password_title">Skryť heslá</string>
<string name="hide_password_summary">Predvolene maskovať heslá (***)</string>
<string name="about">O Programe</string> <string name="about">O Programe</string>
<string name="menu_change_key_settings">Zmeniť hlavný Kľúč</string> <string name="menu_change_key_settings">Zmeniť hlavný Kľúč</string>
<string name="settings">Nastavenia</string> <string name="settings">Nastavenia</string>
@@ -92,7 +90,6 @@
<string name="menu_delete">Zmazať</string> <string name="menu_delete">Zmazať</string>
<string name="menu_donate">Darovať</string> <string name="menu_donate">Darovať</string>
<string name="menu_edit">Upraviť</string> <string name="menu_edit">Upraviť</string>
<string name="menu_hide_password">Skryť heslo</string>
<string name="menu_lock">Zamknúť databázu</string> <string name="menu_lock">Zamknúť databázu</string>
<string name="menu_open">Otvoriť</string> <string name="menu_open">Otvoriť</string>
<string name="menu_search">Hľadať</string> <string name="menu_search">Hľadať</string>

View File

@@ -193,8 +193,6 @@
<string name="invalid_db_sig">Su kuptua dot formati i bazës së të dhënave.</string> <string name="invalid_db_sig">Su kuptua dot formati i bazës së të dhënave.</string>
<string name="keyfile_is_empty">Kartela e kyçit është e zbrazët.</string> <string name="keyfile_is_empty">Kartela e kyçit është e zbrazët.</string>
<string name="length">Gjatësi</string> <string name="length">Gjatësi</string>
<string name="hide_password_title">Fshihi fjalëkalimet</string>
<string name="hide_password_summary">Si parazgjedhje, maskoji fjalëkalimet (***)</string>
<string name="colorize_password_title">Ngjyrosi fjalëkalimet</string> <string name="colorize_password_title">Ngjyrosi fjalëkalimet</string>
<string name="list_entries_show_username_title">Shfaq emra përdoruesi</string> <string name="list_entries_show_username_title">Shfaq emra përdoruesi</string>
<string name="list_groups_show_number_entries_title">Shfaq numër zërash</string> <string name="list_groups_show_number_entries_title">Shfaq numër zërash</string>
@@ -211,7 +209,6 @@
<string name="menu_master_key_settings">Rregullime kyçi të përgjithshëm</string> <string name="menu_master_key_settings">Rregullime kyçi të përgjithshëm</string>
<string name="menu_copy">Kopjoje</string> <string name="menu_copy">Kopjoje</string>
<string name="menu_cancel">Anuloje</string> <string name="menu_cancel">Anuloje</string>
<string name="menu_hide_password">Fshihe fjalëkalimin</string>
<string name="menu_lock">Kyçe bazën e të dhënave</string> <string name="menu_lock">Kyçe bazën e të dhënave</string>
<string name="menu_save_database">Ruaji të dhënat</string> <string name="menu_save_database">Ruaji të dhënat</string>
<string name="menu_reload_database">Ringarko të dhënat</string> <string name="menu_reload_database">Ringarko të dhënat</string>

View File

@@ -255,8 +255,6 @@
<string name="passphrase">Дугачка лозинка</string> <string name="passphrase">Дугачка лозинка</string>
<string name="invalid_credentials">Није могуће прочитати податке за пријављивање.</string> <string name="invalid_credentials">Није могуће прочитати податке за пријављивање.</string>
<string name="invalid_db_same_uuid">%1$s са истим УУИД %2$s већ постоји.</string> <string name="invalid_db_same_uuid">%1$s са истим УУИД %2$s већ постоји.</string>
<string name="hide_password_title">Сакриј лозинке</string>
<string name="hide_password_summary">Подразумевај маскирање лозинки са (***)</string>
<string name="colorize_password_title">Обојите лозинке</string> <string name="colorize_password_title">Обојите лозинке</string>
<string name="colorize_password_summary">Обојите знакове лозинке по типу</string> <string name="colorize_password_summary">Обојите знакове лозинке по типу</string>
<string name="list_entries_show_username_title">Прикажи корисничка имена</string> <string name="list_entries_show_username_title">Прикажи корисничка имена</string>
@@ -287,7 +285,6 @@
<string name="master_key_settings_summary">Промена, обнова</string> <string name="master_key_settings_summary">Промена, обнова</string>
<string name="menu_move">Премести</string> <string name="menu_move">Премести</string>
<string name="menu_cancel">Откажи</string> <string name="menu_cancel">Откажи</string>
<string name="menu_hide_password">Сакриј лозинку</string>
<string name="menu_lock">Закључај базу података</string> <string name="menu_lock">Закључај базу података</string>
<string name="menu_save_database">Сачувај податке</string> <string name="menu_save_database">Сачувај податке</string>
<string name="menu_merge_database">Обједини податке</string> <string name="menu_merge_database">Обједини податке</string>

View File

@@ -92,8 +92,6 @@
<string name="list_size_summary">Textstorlek i grupplistan</string> <string name="list_size_summary">Textstorlek i grupplistan</string>
<string name="loading_database">Laddar databas…</string> <string name="loading_database">Laddar databas…</string>
<string name="lowercase">Gemener</string> <string name="lowercase">Gemener</string>
<string name="hide_password_title">Dölj lösenord</string>
<string name="hide_password_summary">Döljer lösenord (***) som standard</string>
<string name="about">Om</string> <string name="about">Om</string>
<string name="menu_change_key_settings">Byt huvudnyckel</string> <string name="menu_change_key_settings">Byt huvudnyckel</string>
<string name="settings">Inställningar</string> <string name="settings">Inställningar</string>
@@ -101,7 +99,6 @@
<string name="menu_delete">Radera</string> <string name="menu_delete">Radera</string>
<string name="menu_donate">Donera</string> <string name="menu_donate">Donera</string>
<string name="menu_edit">Redigera</string> <string name="menu_edit">Redigera</string>
<string name="menu_hide_password">Dölj lösenord</string>
<string name="menu_lock">Lås databas</string> <string name="menu_lock">Lås databas</string>
<string name="menu_open">Öppna</string> <string name="menu_open">Öppna</string>
<string name="menu_search">Sök</string> <string name="menu_search">Sök</string>

View File

@@ -278,8 +278,6 @@
<string name="hint_group_name">குழு பெயர்</string> <string name="hint_group_name">குழு பெயர்</string>
<string name="invalid_db_same_uuid">அதே uuid %2$s உடன் %1$s ஏற்கனவே உள்ளது.</string> <string name="invalid_db_same_uuid">அதே uuid %2$s உடன் %1$s ஏற்கனவே உள்ளது.</string>
<string name="invalid_db_sig">தரவுத்தள வடிவமைப்பை அடையாளம் காண முடியவில்லை.</string> <string name="invalid_db_sig">தரவுத்தள வடிவமைப்பை அடையாளம் காண முடியவில்லை.</string>
<string name="hide_password_title">கடவுச்சொற்களை மறைக்கவும்</string>
<string name="hide_password_summary">இயல்புநிலையாக கடவுச்சொற்களை (***) மறைக்கவும்</string>
<string name="colorize_password_title">கடவுச்சொற்களை வண்ணமயமாக்குங்கள்</string> <string name="colorize_password_title">கடவுச்சொற்களை வண்ணமயமாக்குங்கள்</string>
<string name="colorize_password_summary">தட்டச்சு மூலம் கடவுச்சொல் எழுத்துக்களை வண்ணமயமாக்குங்கள்</string> <string name="colorize_password_summary">தட்டச்சு மூலம் கடவுச்சொல் எழுத்துக்களை வண்ணமயமாக்குங்கள்</string>
<string name="list_groups_show_number_entries_summary">ஒரு குழுவில் உள்ளீடுகளின் எண்ணிக்கையைக் காட்டுகிறது</string> <string name="list_groups_show_number_entries_summary">ஒரு குழுவில் உள்ளீடுகளின் எண்ணிக்கையைக் காட்டுகிறது</string>
@@ -298,7 +296,6 @@
<string name="menu_security_settings_summary">குறியாக்கம், முக்கிய வழித்தோன்றல் செயல்பாடு</string> <string name="menu_security_settings_summary">குறியாக்கம், முக்கிய வழித்தோன்றல் செயல்பாடு</string>
<string name="menu_master_key_settings">முதன்மை விசை அமைப்புகள்</string> <string name="menu_master_key_settings">முதன்மை விசை அமைப்புகள்</string>
<string name="menu_paste">ஒட்டு</string> <string name="menu_paste">ஒட்டு</string>
<string name="menu_hide_password">கடவுச்சொல்லை மறைக்கவும்</string>
<string name="menu_merge_database">தேதி செல்கிறது</string> <string name="menu_merge_database">தேதி செல்கிறது</string>
<string name="menu_open_file_read_and_write">மாற்றியமைக்கக்கூடிய</string> <string name="menu_open_file_read_and_write">மாற்றியமைக்கக்கூடிய</string>
<string name="menu_restore_entry_history">வரலாற்றை மீட்டமை</string> <string name="menu_restore_entry_history">வரலாற்றை மீட்டமை</string>

View File

@@ -20,7 +20,6 @@
<string name="menu_app_settings">การตั้งค่าแอป</string> <string name="menu_app_settings">การตั้งค่าแอป</string>
<string name="homepage">หน้าหลัก</string> <string name="homepage">หน้าหลัก</string>
<string name="security">ความปลอดภัย</string> <string name="security">ความปลอดภัย</string>
<string name="hide_password_summary">ปิดบังรหัสผ่านเป็น (***) โดยค่าเรื่มต้น</string>
<string name="app_timeout_summary">ระยะเวลาที่ไม่ได้ใช้งานก่อนที่จะทำการล็อกฐานข้อมูล</string> <string name="app_timeout_summary">ระยะเวลาที่ไม่ได้ใช้งานก่อนที่จะทำการล็อกฐานข้อมูล</string>
<string name="settings">การตั้งค่า</string> <string name="settings">การตั้งค่า</string>
<string name="hint_pass">รหัสผ่าน</string> <string name="hint_pass">รหัสผ่าน</string>
@@ -32,7 +31,6 @@
<string name="error_pass_gen_type">จำเป็นต้องเลือกชนิดของรหัสผ่านที่จะสร้างอย่างน้อยหนึ่งอย่าง</string> <string name="error_pass_gen_type">จำเป็นต้องเลือกชนิดของรหัสผ่านที่จะสร้างอย่างน้อยหนึ่งอย่าง</string>
<string name="generate_password">สร้างรหัสผ่าน</string> <string name="generate_password">สร้างรหัสผ่าน</string>
<string name="password">รหัสผ่าน</string> <string name="password">รหัสผ่าน</string>
<string name="hide_password_title">ซ่อนรหัสผ่าน</string>
<string name="hint_conf_pass">ยืนยันรหัสผ่าน</string> <string name="hint_conf_pass">ยืนยันรหัสผ่าน</string>
<string name="contact">ติดต่อ</string> <string name="contact">ติดต่อ</string>
<string name="select_database_file">เปิดคลังรหัสผ่านที่มีอยู่แล้ว</string> <string name="select_database_file">เปิดคลังรหัสผ่านที่มีอยู่แล้ว</string>
@@ -269,7 +267,6 @@
<string name="list_groups_show_number_entries_summary">แสดงจำนวนรายการในกลุ่ม</string> <string name="list_groups_show_number_entries_summary">แสดงจำนวนรายการในกลุ่ม</string>
<string name="list_size_title">ขนาดของรายการ</string> <string name="list_size_title">ขนาดของรายการ</string>
<string name="creating_database">กําลังสร้างฐานข้อมูล…</string> <string name="creating_database">กําลังสร้างฐานข้อมูล…</string>
<string name="menu_hide_password">ซ่อนรหัสผ่าน</string>
<string name="menu_keystore_remove_key">ลบกุญแจปลดล็อกของอุปกรณ์</string> <string name="menu_keystore_remove_key">ลบกุญแจปลดล็อกของอุปกรณ์</string>
<string name="about">เกี่ยวกับ</string> <string name="about">เกี่ยวกับ</string>
<string name="menu_move">ย้าย</string> <string name="menu_move">ย้าย</string>

View File

@@ -101,8 +101,6 @@
<string name="list_size_summary">Öge listesindeki metin boyutu</string> <string name="list_size_summary">Öge listesindeki metin boyutu</string>
<string name="loading_database">Veri tabanı yükleniyor…</string> <string name="loading_database">Veri tabanı yükleniyor…</string>
<string name="lowercase">Küçük harf</string> <string name="lowercase">Küçük harf</string>
<string name="hide_password_title">Parolaları gizle</string>
<string name="hide_password_summary">Parolaları öntanımlı olarak (***) ile maskele</string>
<string name="about">Hakkında</string> <string name="about">Hakkında</string>
<string name="menu_change_key_settings">Ana anahtarı değiştir</string> <string name="menu_change_key_settings">Ana anahtarı değiştir</string>
<string name="copy_field">%1$s kopyalandı</string> <string name="copy_field">%1$s kopyalandı</string>
@@ -117,7 +115,6 @@
<string name="menu_paste">Yapıştır</string> <string name="menu_paste">Yapıştır</string>
<string name="menu_delete">Sil</string> <string name="menu_delete">Sil</string>
<string name="menu_cancel">İptal</string> <string name="menu_cancel">İptal</string>
<string name="menu_hide_password">Parolayı gizle</string>
<string name="menu_lock">Veri tabanını kilitle</string> <string name="menu_lock">Veri tabanını kilitle</string>
<string name="menu_open"></string> <string name="menu_open"></string>
<string name="menu_search">Ara</string> <string name="menu_search">Ara</string>

View File

@@ -18,7 +18,6 @@
<string name="menu_showpass">Серсүзне күрсәтү</string> <string name="menu_showpass">Серсүзне күрсәтү</string>
<string name="menu_search">Эзләү</string> <string name="menu_search">Эзләү</string>
<string name="menu_open">Ачу</string> <string name="menu_open">Ачу</string>
<string name="menu_hide_password">Серсүзне яшерү</string>
<string name="menu_move">Күчү</string> <string name="menu_move">Күчү</string>
<string name="menu_cancel">Баш тарту</string> <string name="menu_cancel">Баш тарту</string>
<string name="menu_delete">Бетерү</string> <string name="menu_delete">Бетерү</string>
@@ -27,7 +26,6 @@
<string name="settings">Көйләүләр</string> <string name="settings">Көйләүләр</string>
<string name="menu_change_key_settings">Мастер ачкычны үзгәртү</string> <string name="menu_change_key_settings">Мастер ачкычны үзгәртү</string>
<string name="about">Турында</string> <string name="about">Турында</string>
<string name="hide_password_title">Серсүзләрне яшерү</string>
<string name="list_entries_show_username_title">Кулланучы исемнәрен күрсәтү</string> <string name="list_entries_show_username_title">Кулланучы исемнәрен күрсәтү</string>
<string name="password">Серсүз</string> <string name="password">Серсүз</string>
<string name="hint_pass">Серсүз</string> <string name="hint_pass">Серсүз</string>

View File

@@ -83,8 +83,6 @@
<string name="list_size_summary">Розмір тексту у переліку груп</string> <string name="list_size_summary">Розмір тексту у переліку груп</string>
<string name="loading_database">Завантаження бази даних…</string> <string name="loading_database">Завантаження бази даних…</string>
<string name="lowercase">Малі літери</string> <string name="lowercase">Малі літери</string>
<string name="hide_password_title">Ховати паролі</string>
<string name="hide_password_summary">Типово ховати паролі за (***)</string>
<string name="about">Про KeePassDX</string> <string name="about">Про KeePassDX</string>
<string name="menu_change_key_settings">Змінити головний ключ</string> <string name="menu_change_key_settings">Змінити головний ключ</string>
<string name="settings">Налаштування</string> <string name="settings">Налаштування</string>
@@ -92,7 +90,6 @@
<string name="menu_delete">Видалити</string> <string name="menu_delete">Видалити</string>
<string name="menu_donate">Підтримати</string> <string name="menu_donate">Підтримати</string>
<string name="menu_edit">Змінити</string> <string name="menu_edit">Змінити</string>
<string name="menu_hide_password">Сховати пароль</string>
<string name="menu_lock">Заблокувати базу даних</string> <string name="menu_lock">Заблокувати базу даних</string>
<string name="menu_open">Відкрити</string> <string name="menu_open">Відкрити</string>
<string name="menu_search">Пошук</string> <string name="menu_search">Пошук</string>

View File

@@ -231,8 +231,6 @@
<string name="invalid_db_sig">Không thể nhận dạng được định dạng cơ sở dữ liệu.</string> <string name="invalid_db_sig">Không thể nhận dạng được định dạng cơ sở dữ liệu.</string>
<string name="keyfile_is_empty">Tệp khóa trống.</string> <string name="keyfile_is_empty">Tệp khóa trống.</string>
<string name="length">Chiều dài</string> <string name="length">Chiều dài</string>
<string name="hide_password_title">Ẩn mật khẩu</string>
<string name="hide_password_summary">Che dấu mật khẩu (***) theo mặc định</string>
<string name="colorize_password_title">Tô màu mật khẩu</string> <string name="colorize_password_title">Tô màu mật khẩu</string>
<string name="colorize_password_summary">Tô màu các ký tự mật khẩu theo loại</string> <string name="colorize_password_summary">Tô màu các ký tự mật khẩu theo loại</string>
<string name="list_entries_show_username_title">Hiển thị tên người dùng</string> <string name="list_entries_show_username_title">Hiển thị tên người dùng</string>
@@ -271,7 +269,6 @@
<string name="menu_paste">Dán</string> <string name="menu_paste">Dán</string>
<string name="menu_delete">Xóa</string> <string name="menu_delete">Xóa</string>
<string name="menu_cancel">Hủy</string> <string name="menu_cancel">Hủy</string>
<string name="menu_hide_password">Ẩn mật khẩu</string>
<string name="menu_lock">Khóa cơ sở dữ liệu</string> <string name="menu_lock">Khóa cơ sở dữ liệu</string>
<string name="menu_save_database">Lưu dữ liệu</string> <string name="menu_save_database">Lưu dữ liệu</string>
<string name="menu_merge_database">Hợp nhất dữ liệu</string> <string name="menu_merge_database">Hợp nhất dữ liệu</string>

View File

@@ -82,8 +82,6 @@
<string name="list_size_summary">列表文字大小</string> <string name="list_size_summary">列表文字大小</string>
<string name="loading_database">正在加载数据库…</string> <string name="loading_database">正在加载数据库…</string>
<string name="lowercase">小写</string> <string name="lowercase">小写</string>
<string name="hide_password_title">隐藏密码</string>
<string name="hide_password_summary">默认使用星号(***)隐藏密码</string>
<string name="about">关于</string> <string name="about">关于</string>
<string name="menu_change_key_settings">更改主密钥</string> <string name="menu_change_key_settings">更改主密钥</string>
<string name="settings">设置</string> <string name="settings">设置</string>
@@ -91,7 +89,6 @@
<string name="menu_delete">删除</string> <string name="menu_delete">删除</string>
<string name="menu_donate">捐赠</string> <string name="menu_donate">捐赠</string>
<string name="menu_edit">编辑</string> <string name="menu_edit">编辑</string>
<string name="menu_hide_password">隐藏密码</string>
<string name="menu_lock">锁定数据库</string> <string name="menu_lock">锁定数据库</string>
<string name="menu_open">打开</string> <string name="menu_open">打开</string>
<string name="menu_search">搜索</string> <string name="menu_search">搜索</string>

View File

@@ -304,8 +304,6 @@
<string name="hide_broken_locations_title">隱藏已損壞的資料庫路徑</string> <string name="hide_broken_locations_title">隱藏已損壞的資料庫路徑</string>
<string name="hide_expired_entries_summary">過期條目將被隱藏</string> <string name="hide_expired_entries_summary">過期條目將被隱藏</string>
<string name="hide_expired_entries_title">隱藏過期的條目</string> <string name="hide_expired_entries_title">隱藏過期的條目</string>
<string name="hide_password_summary">預設隱藏密碼</string>
<string name="hide_password_title">密碼遮罩</string>
<string name="hint_conf_pass">確認密碼</string> <string name="hint_conf_pass">確認密碼</string>
<string name="hint_generated_password">生成密碼</string> <string name="hint_generated_password">生成密碼</string>
<string name="hint_group_name">群組名</string> <string name="hint_group_name">群組名</string>
@@ -424,7 +422,6 @@
<string name="menu_external_icon">外部圖示</string> <string name="menu_external_icon">外部圖示</string>
<string name="menu_file_selection_read_only">唯讀</string> <string name="menu_file_selection_read_only">唯讀</string>
<string name="menu_form_filling_settings">表格填入</string> <string name="menu_form_filling_settings">表格填入</string>
<string name="menu_hide_password">隱藏密碼</string>
<string name="menu_keystore_remove_key">刪除裝置解鎖密鑰</string> <string name="menu_keystore_remove_key">刪除裝置解鎖密鑰</string>
<string name="menu_lock">鎖定資料庫</string> <string name="menu_lock">鎖定資料庫</string>
<string name="menu_master_key_settings">主密鑰設定</string> <string name="menu_master_key_settings">主密鑰設定</string>

View File

@@ -30,7 +30,6 @@
<declare-styleable name="PasswordView"> <declare-styleable name="PasswordView">
<attr name="passwordHint" format="string" /> <attr name="passwordHint" format="string" />
<attr name="passwordMaxLines" format="integer" /> <attr name="passwordMaxLines" format="integer" />
<attr name="passwordVisible" format="boolean" />
</declare-styleable> </declare-styleable>
<!-- Specific keyboard attributes --> <!-- Specific keyboard attributes -->

View File

@@ -45,6 +45,7 @@
<string name="magic_keyboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Magikeyboard</string> <string name="magic_keyboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Magikeyboard</string>
<string name="clipboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Clipboard</string> <string name="clipboard_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Clipboard</string>
<string name="passkeys_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Passkeys</string> <string name="passkeys_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Passkeys</string>
<string name="user_verification_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Passkeys#UserVerification</string>
<string name="autofill_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Autofill</string> <string name="autofill_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/Autofill</string>
<string name="file_manager_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/File-Manager-and-Sync</string> <string name="file_manager_explanation_url" translatable="false">https://github.com/Kunzisoft/KeePassDX/wiki/File-Manager-and-Sync</string>
<string name="html_rose">--,--`--,{@</string> <string name="html_rose">--,--`--,{@</string>
@@ -68,6 +69,8 @@
<bool name="allow_no_password_default" translatable="false">false</bool> <bool name="allow_no_password_default" translatable="false">false</bool>
<string name="delete_entered_password_key" translatable="false">delete_entered_password_key</string> <string name="delete_entered_password_key" translatable="false">delete_entered_password_key</string>
<bool name="delete_entered_password_default" translatable="false">true</bool> <bool name="delete_entered_password_default" translatable="false">true</bool>
<string name="user_verification_device_credential_key" translatable="false">user_verification_device_credential_key</string>
<bool name="user_verification_device_credential_default" translatable="false">true</bool>
<string name="enable_auto_save_database_key" translatable="false">enable_auto_save_database_key</string> <string name="enable_auto_save_database_key" translatable="false">enable_auto_save_database_key</string>
<bool name="enable_auto_save_database_default" translatable="false">true</bool> <bool name="enable_auto_save_database_default" translatable="false">true</bool>
<string name="enable_keep_screen_on_key" translatable="false">enable_keep_screen_on_key</string> <string name="enable_keep_screen_on_key" translatable="false">enable_keep_screen_on_key</string>
@@ -76,8 +79,6 @@
<bool name="enable_screenshot_mode_key_default" translatable="false">false</bool> <bool name="enable_screenshot_mode_key_default" translatable="false">false</bool>
<string name="auto_focus_search_key" translatable="false">auto_focus_search_key</string> <string name="auto_focus_search_key" translatable="false">auto_focus_search_key</string>
<bool name="auto_focus_search_default" translatable="false">false</bool> <bool name="auto_focus_search_default" translatable="false">false</bool>
<string name="subdomain_search_key" translatable="false">subdomain_search_key</string>
<bool name="subdomain_search_default" translatable="false">false</bool>
<string name="app_timeout_key" translatable="false">app_timeout_key</string> <string name="app_timeout_key" translatable="false">app_timeout_key</string>
<string name="lock_database_screen_off_key" translatable="false">lock_database_screen_off_key</string> <string name="lock_database_screen_off_key" translatable="false">lock_database_screen_off_key</string>
<bool name="lock_database_screen_off_default" translatable="false">true</bool> <bool name="lock_database_screen_off_default" translatable="false">true</bool>
@@ -143,6 +144,10 @@
<bool name="passkeys_backup_eligibility_default" translatable="false">true</bool> <bool name="passkeys_backup_eligibility_default" translatable="false">true</bool>
<string name="passkeys_backup_state_key" translatable="false">passkeys_backup_state_key</string> <string name="passkeys_backup_state_key" translatable="false">passkeys_backup_state_key</string>
<bool name="passkeys_backup_state_default" translatable="false">true</bool> <bool name="passkeys_backup_state_default" translatable="false">true</bool>
<string name="user_verification_preferred_key" translatable="false">user_verification_preferred_key</string>
<bool name="user_verification_preferred_default" translatable="false">false</bool>
<string name="subdomain_search_key" translatable="false">subdomain_search_key</string>
<bool name="subdomain_search_default" translatable="false">false</bool>
<string name="keyboard_notification_entry_key" translatable="false">keyboard_notification_entry_key</string> <string name="keyboard_notification_entry_key" translatable="false">keyboard_notification_entry_key</string>
<bool name="keyboard_notification_entry_default" translatable="false">true</bool> <bool name="keyboard_notification_entry_default" translatable="false">true</bool>
<string name="keyboard_notification_entry_clear_close_key" translatable="false">keyboard_notification_entry_clear_close_key</string> <string name="keyboard_notification_entry_clear_close_key" translatable="false">keyboard_notification_entry_clear_close_key</string>
@@ -199,8 +204,6 @@
<bool name="hide_expired_entries_default" translatable="false">false</bool> <bool name="hide_expired_entries_default" translatable="false">false</bool>
<string name="hide_templates_key" translatable="false">hide_templates_key</string> <string name="hide_templates_key" translatable="false">hide_templates_key</string>
<bool name="hide_templates_default" translatable="false">true</bool> <bool name="hide_templates_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> <string name="colorize_password_key" translatable="false">colorize_password_key</string>
<bool name="colorize_password_default" translatable="false">true</bool> <bool name="colorize_password_default" translatable="false">true</bool>
<string name="list_entries_show_username_key" translatable="false">list_entries_show_username_key</string> <string name="list_entries_show_username_key" translatable="false">list_entries_show_username_key</string>

View File

@@ -214,7 +214,7 @@
<string name="error_empty_key">Key cannot be empty.</string> <string name="error_empty_key">Key cannot be empty.</string>
<string name="field_name">Field name</string> <string name="field_name">Field name</string>
<string name="field_value">Field value</string> <string name="field_value">Field value</string>
<string name="file_not_found_content">Could not find file. Try reopening it from your file browser.</string> <string name="file_not_found_content">Could not find file</string>
<string name="corrupted_file">Corrupted file.</string> <string name="corrupted_file">Corrupted file.</string>
<string name="file_browser">File manager</string> <string name="file_browser">File manager</string>
<string name="generate_password">Generate password</string> <string name="generate_password">Generate password</string>
@@ -235,8 +235,6 @@
<string name="keyfile_is_empty">The keyfile is empty.</string> <string name="keyfile_is_empty">The keyfile is empty.</string>
<string name="length">Length</string> <string name="length">Length</string>
<string name="nodes">Nodes</string> <string name="nodes">Nodes</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_title">Colorize passwords</string>
<string name="colorize_password_summary">Colorize password characters by type</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_title">Show usernames</string>
@@ -277,7 +275,6 @@
<string name="menu_paste">Paste</string> <string name="menu_paste">Paste</string>
<string name="menu_delete">Delete</string> <string name="menu_delete">Delete</string>
<string name="menu_cancel">Cancel</string> <string name="menu_cancel">Cancel</string>
<string name="menu_hide_password">Hide password</string>
<string name="menu_lock">Lock database</string> <string name="menu_lock">Lock database</string>
<string name="menu_save_database">Save data</string> <string name="menu_save_database">Save data</string>
<string name="menu_merge_database">Merge data</string> <string name="menu_merge_database">Merge data</string>
@@ -447,6 +444,10 @@
<string name="autofill_explanation_summary">Configure autofilling to quickly fill out forms in other apps</string> <string name="autofill_explanation_summary">Configure autofilling to quickly fill out forms in other apps</string>
<string name="autofill_select_entry">Select entry…</string> <string name="autofill_select_entry">Select entry…</string>
<string name="autofill_preference_title">Autofill settings</string> <string name="autofill_preference_title">Autofill settings</string>
<string name="user_verification_device_credential_title">Device credential</string>
<string name="user_verification_device_credential_summary">Use the device credential as user verification if available</string>
<string name="user_verification_preferred_title">Preferred User Verification</string>
<string name="user_verification_preferred_summary">Perform a user verification when the relying party requests \"preferred\"</string>
<string name="password_size_title">Generated password size</string> <string name="password_size_title">Generated password size</string>
<string name="password_size_summary">Sets default size of the generated passwords</string> <string name="password_size_summary">Sets default size of the generated passwords</string>
<string name="list_password_generator_options_title">Password characters</string> <string name="list_password_generator_options_title">Password characters</string>
@@ -767,7 +768,6 @@
<string name="passkey_selection_description">Select an existing passkey</string> <string name="passkey_selection_description">Select an existing passkey</string>
<string name="passkey_database_username">KeePassDX Database</string> <string name="passkey_database_username">KeePassDX Database</string>
<string name="passkey_locked_database_description">Select to unlock</string> <string name="passkey_locked_database_description">Select to unlock</string>
<string name="passkey_relaunch_database_description">Reauthenticate (No Device Auth)</string>
<string name="passkey_username">Passkey Username</string> <string name="passkey_username">Passkey Username</string>
<string name="passkey_private_key">Passkey Private Key</string> <string name="passkey_private_key">Passkey Private Key</string>
<string name="passkey_credential_id">Passkey Credential Id</string> <string name="passkey_credential_id">Passkey Credential Id</string>
@@ -777,5 +777,10 @@
<string name="passkey_backup_state">Passkey Backup State</string> <string name="passkey_backup_state">Passkey Backup State</string>
<string name="error_passkey_result">Unable to return the passkey</string> <string name="error_passkey_result">Unable to return the passkey</string>
<string name="error_passkey_credential_id">No passkey found with relying party %1$s and credentialIds %2$s</string> <string name="error_passkey_credential_id">No passkey found with relying party %1$s and credentialIds %2$s</string>
<string name="user_verification_required">User verification required</string> <string name="content_description_user_verification_information">User verification info</string>
<string name="user_verification_required_title">User Verification</string>
<string name="user_verification_required_description">Access to this data requires verification</string>
<string name="user_verification_database_credential">Enter the first four characters of your database password</string>
<string name="first_chars">First chars</string>
<string name="check">Check</string>
</resources> </resources>

View File

@@ -288,8 +288,7 @@
<!-- Dialog --> <!-- Dialog -->
<style name="KeepassDXStyle.Light.Dialog" parent="ThemeOverlay.Material3.Dialog.Alert"> <style name="KeepassDXStyle.Light.Dialog" parent="ThemeOverlay.Material3.Dialog.Alert">
<item name="android:windowBackground">?attr/dialogBackgroundColor</item> <item name="android:colorBackground">?attr/dialogBackgroundColor</item>
<item name="background">?attr/dialogBackgroundColor</item>
<item name="android:windowSoftInputMode">adjustResize</item> <item name="android:windowSoftInputMode">adjustResize</item>
<item name="dialogCornerRadius">@dimen/dialog_radius</item> <item name="dialogCornerRadius">@dimen/dialog_radius</item>
<item name="buttonBarNegativeButtonStyle">@style/KeepassDXStyle.Light.Dialog.NegativeButtonStyle</item> <item name="buttonBarNegativeButtonStyle">@style/KeepassDXStyle.Light.Dialog.NegativeButtonStyle</item>
@@ -303,8 +302,7 @@
</style> </style>
<style name="KeepassDXStyle.Night.Dialog" parent="ThemeOverlay.Material3.Dialog.Alert"> <style name="KeepassDXStyle.Night.Dialog" parent="ThemeOverlay.Material3.Dialog.Alert">
<item name="android:windowBackground">?attr/dialogBackgroundColor</item> <item name="android:colorBackground">?attr/dialogBackgroundColor</item>
<item name="background">?attr/dialogBackgroundColor</item>
<item name="android:windowSoftInputMode">adjustResize</item> <item name="android:windowSoftInputMode">adjustResize</item>
<item name="dialogCornerRadius">@dimen/dialog_radius</item> <item name="dialogCornerRadius">@dimen/dialog_radius</item>
<item name="buttonBarNegativeButtonStyle">@style/KeepassDXStyle.Night.Dialog.NegativeButtonStyle</item> <item name="buttonBarNegativeButtonStyle">@style/KeepassDXStyle.Night.Dialog.NegativeButtonStyle</item>

View File

@@ -45,6 +45,11 @@
android:title="@string/show_entry_colors_title" android:title="@string/show_entry_colors_title"
android:summary="@string/show_entry_colors_summary" android:summary="@string/show_entry_colors_summary"
android:defaultValue="@bool/show_entry_colors_default"/> android:defaultValue="@bool/show_entry_colors_default"/>
<SwitchPreferenceCompat
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>
@@ -64,22 +69,6 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory
android:title="@string/password">
<SwitchPreferenceCompat
android:key="@string/hide_password_key"
android:title="@string/hide_password_title"
android:summary="@string/hide_password_summary"
android:defaultValue="@bool/hide_password_default"/>
<SwitchPreferenceCompat
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 <PreferenceCategory
android:title="@string/text_appearance"> android:title="@string/text_appearance">

View File

@@ -32,6 +32,11 @@
android:title="@string/delete_entered_password_title" android:title="@string/delete_entered_password_title"
android:summary="@string/delete_entered_password_summary" android:summary="@string/delete_entered_password_summary"
android:defaultValue="@bool/delete_entered_password_default"/> android:defaultValue="@bool/delete_entered_password_default"/>
<SwitchPreferenceCompat
android:key="@string/user_verification_device_credential_key"
android:title="@string/user_verification_device_credential_title"
android:summary="@string/user_verification_device_credential_summary"
android:defaultValue="@bool/user_verification_device_credential_default"/>
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="@string/enable_auto_save_database_key" android:key="@string/enable_auto_save_database_key"
android:title="@string/enable_auto_save_database_title" android:title="@string/enable_auto_save_database_title"

View File

@@ -20,12 +20,31 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory <PreferenceCategory
android:title="@string/general"> android:key="@string/credential_provider_key"
android:title="@string/credential_provider">
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="@string/subdomain_search_key" android:key="@string/settings_credential_provider_enable_key"
android:title="@string/subdomain_search_title" android:title="@string/set_credential_provider_service_title"
android:summary="@string/subdomain_search_summary" android:defaultValue="@bool/settings_credential_provider_enable_default"/>
android:defaultValue="@bool/subdomain_search_default"/> <SwitchPreferenceCompat
android:key="@string/user_verification_preferred_key"
android:title="@string/user_verification_preferred_title"
android:summary="@string/user_verification_preferred_summary"
android:defaultValue="@bool/user_verification_preferred_default"/>
<Preference
android:key="@string/passkeys_explanation_key"
android:icon="@drawable/prefs_info_24dp"
android:summary="@string/passkeys_explanation_summary"/>
<Preference
android:key="@string/settings_passkeys_key"
android:title="@string/passkeys_preference_title" />
<Preference
android:key="@string/autofill_explanation_key"
android:icon="@drawable/prefs_info_24dp"
android:summary="@string/autofill_explanation_summary"/>
<Preference
android:key="@string/settings_autofill_key"
android:title="@string/autofill_preference_title" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
@@ -43,26 +62,12 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="@string/credential_provider_key" android:title="@string/general">
android:title="@string/credential_provider">
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="@string/settings_credential_provider_enable_key" android:key="@string/subdomain_search_key"
android:title="@string/set_credential_provider_service_title" android:title="@string/subdomain_search_title"
android:defaultValue="@bool/settings_credential_provider_enable_default"/> android:summary="@string/subdomain_search_summary"
<Preference android:defaultValue="@bool/subdomain_search_default"/>
android:key="@string/passkeys_explanation_key"
android:icon="@drawable/prefs_info_24dp"
android:summary="@string/passkeys_explanation_summary"/>
<Preference
android:key="@string/settings_passkeys_key"
android:title="@string/passkeys_preference_title" />
<Preference
android:key="@string/autofill_explanation_key"
android:icon="@drawable/prefs_info_24dp"
android:summary="@string/autofill_explanation_summary"/>
<Preference
android:key="@string/settings_autofill_key"
android:title="@string/autofill_preference_title" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

46
art/ic_passkey.svg Normal file
View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
fill="none"
viewBox="0 0 24 24"
id="Passkey--Streamline-Outlined-Material"
height="24"
width="24"
version="1.1"
sodipodi:docname="ic_passkey.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs9" />
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="25.721009"
inkscape:cx="10.886043"
inkscape:cy="11.372027"
inkscape:window-width="1865"
inkscape:window-height="1005"
inkscape:window-x="849"
inkscape:window-y="1469"
inkscape:window-maximized="1"
inkscape:current-layer="Passkey--Streamline-Outlined-Material" />
<desc
id="desc2">
Passkey Streamline Icon: https://streamlinehq.com
</desc>
<path
fill="#000000"
d="M3 20v-2.35c0 -0.63335 0.158335 -1.175 0.475 -1.625 0.316665 -0.45 0.725 -0.79165 1.225 -1.025 1.11665 -0.5 2.1875 -0.875 3.2125 -1.125S9.96665 13.5 11 13.5c0.43335 0 0.85415 0.02085 1.2625 0.0625s0.82915 0.10415 1.2625 0.1875c-0.08335 0.96665 0.09585 1.87915 0.5375 2.7375C14.50415 17.34585 15.15 18.01665 16 18.5v1.5H3Zm16 3.675 -1.5 -1.5v-4.65c-0.73335 -0.21665 -1.33335 -0.62915 -1.8 -1.2375 -0.46665 -0.60835 -0.7 -1.3125 -0.7 -2.1125 0 -0.96665 0.34165 -1.79165 1.025 -2.475 0.68335 -0.68335 1.50835 -1.025 2.475 -1.025s1.79165 0.34165 2.475 1.025c0.68335 0.68335 1.025 1.50835 1.025 2.475 0 0.75 -0.2125 1.41665 -0.6375 2 -0.425 0.58335 -0.9625 1 -1.6125 1.25l1.25 1.25 -1.5 1.5 1.5 1.5 -2 2ZM11 11.5c-1.05 0 -1.9375 -0.3625 -2.6625 -1.0875 -0.725 -0.725 -1.0875 -1.6125 -1.0875 -2.6625s0.3625 -1.9375 1.0875 -2.6625C9.0625 4.3625 9.95 4 11 4s1.9375 0.3625 2.6625 1.0875c0.725 0.725 1.0875 1.6125 1.0875 2.6625s-0.3625 1.9375 -1.0875 2.6625C12.9375 11.1375 12.05 11.5 11 11.5Zm7.5 3.175c0.28335 0 0.52085 -0.09585 0.7125 -0.2875S19.5 13.95835 19.5 13.675c0 -0.28335 -0.09585 -0.52085 -0.2875 -0.7125s-0.42915 -0.2875 -0.7125 -0.2875c-0.28335 0 -0.52085 0.09585 -0.7125 0.2875S17.5 13.39165 17.5 13.675c0 0.28335 0.09585 0.52085 0.2875 0.7125s0.42915 0.2875 0.7125 0.2875Z"
stroke-width="0.5"
id="path4"
style="fill:#ffffff;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
version="1.1"
id="Capa_1"
width="24"
height="24"
viewBox="0 0 24 24"
xml:space="preserve"
sodipodi:docname="ic_user_verification.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs9" /><sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="11.313709"
inkscape:cx="14.672466"
inkscape:cy="29.742679"
inkscape:window-width="1865"
inkscape:window-height="1005"
inkscape:window-x="849"
inkscape:window-y="1469"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<g
id="g4"
transform="matrix(0.04926836,0,0,0.04926836,2,2)">
<path
id="rect6102"
style="fill:#ffffff;fill-opacity:1;stroke-width:19.2718"
d="M 182.67302 40.594004 C 161.36123 40.594004 143.35545 47.959647 128.64018 62.674922 C 113.92489 77.390207 106.55926 95.395978 106.55926 116.70776 C 106.55926 138.01955 113.92489 156.02533 128.64018 170.7406 C 143.35545 185.4559 161.36123 192.82152 182.67302 192.82152 C 203.98481 192.82152 221.99056 185.4559 236.70586 170.7406 C 251.42113 156.02533 258.78678 138.01955 258.78678 116.70776 C 258.78678 95.395978 251.42113 77.390207 236.70586 62.674922 C 221.99056 47.959647 203.98481 40.594004 182.67302 40.594004 z M 182.67302 233.41552 C 173.87734 233.41552 165.35216 233.83872 157.06391 234.68409 C 148.77566 235.52943 140.21083 236.79801 131.41516 238.48977 C 131.32956 238.57537 122.77898 240.40892 119.9981 241.0269 C 99.19372 246.10112 77.450632 253.71256 54.786048 263.86103 C 44.637578 268.59731 36.357481 275.53976 29.930149 284.67338 C 23.50282 293.807 20.297002 304.80093 20.297002 317.65601 L 20.297002 365.34604 L 81.188008 365.34604 L 237.10228 365.34604 C 231.97732 353.03923 229.09448 339.55171 229.09448 325.38631 C 229.09448 294.33677 242.97366 266.78778 264.53495 247.72649 C 258.25368 245.79499 251.46723 242.51941 245.34794 241.0269 C 245.18295 241.0669 233.93088 238.48977 233.93088 238.48977 C 229.37611 237.61371 225.06904 237.15674 220.65061 236.50764 C 217.59924 236.0587 214.54817 235.50028 211.49317 235.1598 C 210.44767 235.0407 209.31971 234.78992 208.28213 234.68409 C 199.99385 233.83872 191.4687 233.41552 182.67302 233.41552 z M 333.15626 252.60253 C 292.94556 252.60253 260.33284 285.17466 260.33284 325.38631 C 260.33284 365.59794 292.95126 398.20974 333.15626 398.20974 C 373.36125 398.20974 405.94004 365.59129 405.94004 325.38631 C 405.93909 285.17466 373.36694 252.60253 333.15626 252.60253 z M 362.25391 290.54048 C 364.45101 290.54048 366.65335 291.37209 368.31923 293.03797 L 374.3449 299.10328 C 377.68235 302.43504 377.6757 307.86254 374.3449 311.19427 L 327.68558 357.89323 C 324.35383 361.22498 318.8867 361.22498 315.55495 357.89323 L 292.08654 334.38518 C 288.7548 331.05344 288.7548 325.62594 292.08654 322.29419 L 298.15186 316.22888 C 301.48361 312.89712 306.9111 312.89712 310.24285 316.22888 L 321.62027 327.56665 L 356.1886 293.03797 C 357.85446 291.37209 360.05682 290.54048 362.25391 290.54048 z " />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

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