mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
18 Commits
5fd25c6150
...
c88413f7f7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c88413f7f7 | ||
|
|
7b1fb8a4bf | ||
|
|
3567fa797b | ||
|
|
eb41233e57 | ||
|
|
b394a99e40 | ||
|
|
2bbb40e513 | ||
|
|
09ef69e6ae | ||
|
|
762ac8f77b | ||
|
|
d28087d8d8 | ||
|
|
17d4c363ac | ||
|
|
c754b6a049 | ||
|
|
9c6241afc9 | ||
|
|
f6774b6d51 | ||
|
|
108a61905e | ||
|
|
d251788b1a | ||
|
|
7ed8a44168 | ||
|
|
844b1dfc79 | ||
|
|
d087fcc930 |
@@ -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()
|
||||||
|
|||||||
@@ -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 ->
|
||||||
|
|||||||
@@ -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()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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 ->
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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ę.
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 <strong>manter a liberdade</strong>, <strong>corrigir erros</strong>, <strong>adicionar funcionalidades</strong> e <strong>para sermos sempre ativos</strong>, contamos com sua <strong>contribuição</strong>.</string>
|
<string name="html_about_contribution">Para <strong>manter a liberdade</strong>, <strong>corrigir erros</strong>, <strong>adicionar funcionalidades</strong> e <strong>para sermos sempre ativos</strong>, contamos com sua <strong>contribuição</strong>.</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>
|
||||||
|
|||||||
@@ -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 <strong>open source</strong> și <strong>fără reclame</strong>. \nEste furnizat ca atare, sub licență <strong>GPLv3</strong>, fără nicio garanție.</string>
|
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft este <strong>open source</strong> și <strong>fără reclame</strong>. \nEste furnizat ca atare, sub licență <strong>GPLv3</strong>, fără nicio garanție.</string>
|
||||||
<string name="html_about_contribution">Pentru a ne <strong>păstra libertatea</strong>, <strong>pentru a remedia erori</strong>, <strong>pentru a adăuga funcții</strong> și <strong>pentru a fi mereu activi</strong>, ne bazăm pe <strong>contribuția</strong> dvs.</string>
|
<string name="html_about_contribution">Pentru a ne <strong>păstra libertatea</strong>, <strong>pentru a remedia erori</strong>, <strong>pentru a adăuga funcții</strong> și <strong>pentru a fi mereu activi</strong>, ne bazăm pe <strong>contribuția</strong> 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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -193,8 +193,6 @@
|
|||||||
<string name="invalid_db_sig">S’u kuptua dot formati i bazës së të dhënave.</string>
|
<string name="invalid_db_sig">S’u 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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">Aç</string>
|
<string name="menu_open">Aç</string>
|
||||||
<string name="menu_search">Ara</string>
|
<string name="menu_search">Ara</string>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
46
art/ic_passkey.svg
Normal 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 |
45
art/ic_user_verification.svg
Normal file
45
art/ic_user_verification.svg
Normal 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
Reference in New Issue
Block a user