diff --git a/CHANGELOG b/CHANGELOG
index db2e8b4d3..970fc7563 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,15 @@
+KeePassDX(3.3.0)
+ * Quick search and dynamic filters #163 #462 #521
+ * Keep search context #1141
+ * Add searchable groups #905 #1006
+ * Search with regular expression #175
+ * Merge from file and save as copy #1221 #1204 #840
+ * Fix custom data #1236
+ * Fix education hints #1192
+ * Fix save and app instance in selection mode
+ * New UI and fix styles
+ * Add "Simple" and "Reply" themes
+
KeePassDX(3.2.0)
* Manage data merge #840 #977
* Manage Tags #633
diff --git a/app/build.gradle b/app/build.gradle
index dd8c95f4b..540967035 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -12,8 +12,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 15
targetSdkVersion 31
- versionCode = 97
- versionName = "3.2.0"
+ versionCode = 102
+ versionName = "3.3.0"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -69,10 +69,14 @@ android {
buildConfigField "boolean", "FULL_VERSION", "false"
buildConfigField "boolean", "CLOSED_STORE", "true"
buildConfigField "String[]", "STYLES_DISABLED",
- "{\"KeepassDXStyle_Blue\"," +
+ "{\"KeepassDXStyle_Simple\"," +
+ "\"KeepassDXStyle_Simple_Night\"," +
+ "\"KeepassDXStyle_Blue\"," +
"\"KeepassDXStyle_Blue_Night\"," +
"\"KeepassDXStyle_Red\"," +
"\"KeepassDXStyle_Red_Night\"," +
+ "\"KeepassDXStyle_Reply\"," +
+ "\"KeepassDXStyle_Reply_Night\"," +
"\"KeepassDXStyle_Purple\"," +
"\"KeepassDXStyle_Purple_Dark\"}"
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
@@ -104,17 +108,18 @@ def room_version = "2.4.1"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+ implementation "com.android.support:multidex:1.0.3"
implementation "androidx.appcompat:appcompat:$android_appcompat_version"
- implementation 'androidx.preference:preference-ktx:1.1.1'
+ implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.1.0'
- implementation 'androidx.media:media:1.4.3'
+ implementation 'androidx.media:media:1.5.0'
// Lifecycle - LiveData - ViewModel - Coroutines
implementation "androidx.core:core-ktx:$android_core_version"
- implementation 'androidx.fragment:fragment-ktx:1.4.0'
+ implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation "com.google.android.material:material:$android_material_version"
// Token auto complete
// From sources until https://github.com/splitwise/TokenAutoComplete/pull/422 fixed
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fef19294a..ac25f88fe 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -40,7 +40,6 @@
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
index 10edbbba9..2f5118359 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
@@ -66,7 +66,6 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
-import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.UuidUtil
import com.kunzisoft.keepass.view.changeControlColor
@@ -92,6 +91,8 @@ class EntryActivity : DatabaseLockActivity() {
private val mEntryViewModel: EntryViewModel by viewModels()
+ private val mEntryActivityEducation = EntryActivityEducation(this)
+
private var mMainEntryId: NodeId? = null
private var mHistoryPosition: Int = -1
private var mEntryIsHistory: Boolean = false
@@ -370,7 +371,6 @@ class EntryActivity : DatabaseLockActivity() {
super.onCreateOptionsMenu(menu)
if (mEntryLoaded) {
val inflater = menuInflater
- MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database, menu)
@@ -381,11 +381,7 @@ class EntryActivity : DatabaseLockActivity() {
// Show education views
Handler(Looper.getMainLooper()).post {
- performedNextEducation(
- EntryActivityEducation(
- this
- ), menu
- )
+ performedNextEducation(menu)
}
}
return true
@@ -411,31 +407,30 @@ class EntryActivity : DatabaseLockActivity() {
return super.onPrepareOptionsMenu(menu)
}
- private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
- menu: Menu) {
+ private fun performedNextEducation(menu: Menu) {
val entryFragment = supportFragmentManager.findFragmentByTag(ENTRY_FRAGMENT_TAG)
as? EntryFragment?
val entryFieldCopyView: View? = entryFragment?.firstEntryFieldCopyView()
val entryCopyEducationPerformed = entryFieldCopyView != null
- && entryActivityEducation.checkAndPerformedEntryCopyEducation(
+ && mEntryActivityEducation.checkAndPerformedEntryCopyEducation(
entryFieldCopyView,
{
entryFragment.launchEntryCopyEducationAction()
},
{
- performedNextEducation(entryActivityEducation, menu)
+ performedNextEducation(menu)
})
if (!entryCopyEducationPerformed) {
val menuEditView = toolbar?.findViewById(R.id.menu_edit)
// entryEditEducationPerformed
- menuEditView != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
+ menuEditView != null && mEntryActivityEducation.checkAndPerformedEntryEditEducation(
menuEditView,
{
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
},
{
- performedNextEducation(entryActivityEducation, menu)
+ performedNextEducation(menu)
}
)
}
@@ -443,10 +438,6 @@ class EntryActivity : DatabaseLockActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
- R.id.menu_contribute -> {
- MenuUtil.onContributionItemSelected(this)
- return true
- }
R.id.menu_edit -> {
mDatabase?.let { database ->
mMainEntryId?.let { entryId ->
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
index 3afc45150..ee789d4a5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
@@ -113,7 +113,7 @@ class EntryEditActivity : DatabaseLockActivity(),
private var mExternalFileHelper: ExternalFileHelper? = null
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
// Education
- private var entryEditActivityEducation: EntryEditActivityEducation? = null
+ private var mEntryEditActivityEducation = EntryEditActivityEducation(this)
private var mIconSelectionActivityResultLauncher = IconPickerActivity.registerIconSelectionForResult(this) { icon ->
mEntryEditViewModel.selectIcon(icon)
@@ -183,8 +183,6 @@ class EntryEditActivity : DatabaseLockActivity(),
}
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
- // Verify the education views
- entryEditActivityEducation = EntryEditActivityEducation(this)
// Lock button
lockView?.setOnClickListener { lockAndExit() }
@@ -538,10 +536,8 @@ class EntryEditActivity : DatabaseLockActivity(),
super.onCreateOptionsMenu(menu)
if (mEntryLoaded) {
menuInflater.inflate(R.menu.entry_edit, menu)
- entryEditActivityEducation?.let {
- Handler(Looper.getMainLooper()).post {
- performedNextEducation(it)
- }
+ Handler(Looper.getMainLooper()).post {
+ performedNextEducation()
}
}
return true
@@ -568,19 +564,19 @@ class EntryEditActivity : DatabaseLockActivity(),
return super.onPrepareOptionsMenu(menu)
}
- private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
+ private fun performedNextEducation() {
val entryEditFragment = supportFragmentManager.findFragmentById(R.id.entry_edit_content)
as? EntryEditFragment?
val generatePasswordView = entryEditFragment?.getActionImageView()
val generatePasswordEductionPerformed = generatePasswordView != null
- && entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
+ && mEntryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
generatePasswordView,
{
entryEditFragment.launchGeneratePasswordEductionAction()
},
{
- performedNextEducation(entryEditActivityEducation)
+ performedNextEducation()
}
)
@@ -589,33 +585,33 @@ class EntryEditActivity : DatabaseLockActivity(),
val addNewFieldEducationPerformed = mAllowCustomFields
&& addNewFieldView != null
&& addNewFieldView.isVisible
- && entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
+ && mEntryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
addNewFieldView,
{
addNewCustomField()
},
{
- performedNextEducation(entryEditActivityEducation)
+ performedNextEducation()
}
)
if (!addNewFieldEducationPerformed) {
val attachmentView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_attachment)
val addAttachmentEducationPerformed = attachmentView != null
&& attachmentView.isVisible
- && entryEditActivityEducation.checkAndPerformedAttachmentEducation(
+ && mEntryEditActivityEducation.checkAndPerformedAttachmentEducation(
attachmentView,
{
addNewAttachment()
},
{
- performedNextEducation(entryEditActivityEducation)
+ performedNextEducation()
}
)
if (!addAttachmentEducationPerformed) {
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
setupOtpView != null
&& setupOtpView.isVisible
- && entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
+ && mEntryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
setupOtpView,
{
setupOtp()
@@ -662,8 +658,8 @@ class EntryEditActivity : DatabaseLockActivity(),
override fun acceptPassword(passwordField: Field) {
mEntryEditViewModel.selectPassword(passwordField)
- entryEditActivityEducation?.let {
- Handler(Looper.getMainLooper()).post { performedNextEducation(it) }
+ Handler(Looper.getMainLooper()).post {
+ performedNextEducation()
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt
index 446a9e127..d9e2ba675 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt
@@ -42,7 +42,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
-import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
+import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
@@ -69,7 +69,7 @@ import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel
import java.io.FileNotFoundException
class FileDatabaseSelectActivity : DatabaseModeActivity(),
- AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
+ SetMainCredentialDialogFragment.AssignMainCredentialDialogListener {
// Views
private lateinit var coordinatorLayout: CoordinatorLayout
@@ -78,6 +78,8 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
private val databaseFilesViewModel: DatabaseFilesViewModel by viewModels()
+ private val mFileDatabaseSelectActivityEducation = FileDatabaseSelectActivityEducation(this)
+
// Adapter to manage database history list
private var mAdapterDatabaseHistory: FileDatabaseHistoryAdapter? = null
@@ -124,7 +126,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
mExternalFileHelper?.buildCreateDocument("application/x-keepass") { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) {
- AssignMasterKeyDialogFragment.getInstance(true)
+ SetMainCredentialDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog")
} else {
val error = getString(R.string.error_create_database)
@@ -132,7 +134,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
Log.e(TAG, error)
}
}
- openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
+ openDatabaseButtonView = findViewById(R.id.open_database_button)
openDatabaseButtonView?.setOpenDocumentClickListener(mExternalFileHelper)
// History list
@@ -291,7 +293,7 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
}
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) {
- PasswordActivity.launch(this,
+ MainCredentialActivity.launch(this,
databaseUri,
keyFile,
{ exception ->
@@ -392,39 +394,40 @@ class FileDatabaseSelectActivity : DatabaseModeActivity(),
MenuUtil.defaultMenuInflater(menuInflater, menu)
}
- Handler(Looper.getMainLooper()).post { performedNextEducation(FileDatabaseSelectActivityEducation(this)) }
+ Handler(Looper.getMainLooper()).post {
+ performedNextEducation()
+ }
return true
}
- private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
+ private fun performedNextEducation() {
// If no recent files
val createDatabaseEducationPerformed =
createDatabaseButtonView != null
&& createDatabaseButtonView!!.visibility == View.VISIBLE
- && mAdapterDatabaseHistory != null
- && mAdapterDatabaseHistory!!.itemCount == 0
- && fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
+ && mFileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
createDatabaseButtonView!!,
{
createNewFile()
},
{
// But if the user cancel, it can also select a database
- performedNextEducation(fileDatabaseSelectActivityEducation)
+ performedNextEducation()
})
if (!createDatabaseEducationPerformed) {
// selectDatabaseEducationPerformed
openDatabaseButtonView != null
- && fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
- openDatabaseButtonView!!,
- { tapTargetView ->
- tapTargetView?.let {
- mExternalFileHelper?.openDocument()
- }
- },
- {}
- )
+ && mFileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
+ openDatabaseButtonView!!,
+ { tapTargetView ->
+ tapTargetView?.let {
+ mExternalFileHelper?.openDocument()
+ }
+ },
+ {
+
+ })
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
index 0391243dd..5b14b5a37 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt
@@ -26,6 +26,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.PorterDuff
+import android.net.Uri
import android.os.*
import android.util.Log
import android.view.Menu
@@ -36,30 +37,38 @@ import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
+import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.GravityCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isVisible
+import androidx.drawerlayout.widget.DrawerLayout
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.fragments.GroupFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
+import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.BreadcrumbAdapter
-import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
+import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.search.SearchHelper
+import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.model.GroupInfo
+import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
@@ -67,14 +76,16 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
import com.kunzisoft.keepass.settings.PreferencesUtil
+import com.kunzisoft.keepass.settings.SettingsActivity
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
-import com.kunzisoft.keepass.utils.MenuUtil
+import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.*
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
import com.kunzisoft.keepass.viewmodels.GroupViewModel
import org.joda.time.DateTime
+
class GroupActivity : DatabaseLockActivity(),
DatePickerDialog.OnDateSetListener,
TimePickerDialog.OnTimeSetListener,
@@ -82,21 +93,22 @@ class GroupActivity : DatabaseLockActivity(),
GroupFragment.NodesActionMenuListener,
GroupFragment.OnScrollListener,
GroupFragment.GroupRefreshedListener,
- SortDialogFragment.SortSelectionListener {
+ SortDialogFragment.SortSelectionListener,
+ MainCredentialDialogFragment.AskMainCredentialDialogListener {
// Views
- private var rootContainerView: ViewGroup? = null
+ private var drawerLayout: DrawerLayout? = null
+ private var databaseNavView: NavigationDatabaseView? = null
private var coordinatorLayout: CoordinatorLayout? = null
private var lockView: View? = null
private var toolbar: Toolbar? = null
private var databaseNameContainer: ViewGroup? = null
+ private var databaseModifiedView: ImageView? = null
private var databaseColorView: ImageView? = null
private var databaseNameView: TextView? = null
- private var searchContainer: ViewGroup? = null
- private var searchNumbers: TextView? = null
- private var searchString: TextView? = null
+ private var searchView: SearchView? = null
+ private var searchFiltersView: SearchFiltersView? = null
private var toolbarBreadcrumb: Toolbar? = null
- private var searchTitleView: View? = null
private var toolbarAction: ToolbarAction? = null
private var numberChildrenView: TextView? = null
private var addNodeButtonView: AddNodeButtonView? = null
@@ -106,8 +118,12 @@ class GroupActivity : DatabaseLockActivity(),
private val mGroupViewModel: GroupViewModel by viewModels()
private val mGroupEditViewModel: GroupEditViewModel by viewModels()
+ private val mGroupActivityEducation = GroupActivityEducation(this)
+
private var mBreadcrumbAdapter: BreadcrumbAdapter? = null
+ private var mSearchMenuItem: MenuItem? = null
+
private var mGroupFragment: GroupFragment? = null
private var mRecyclingBinEnabled = false
private var mRecyclingBinIsCurrentGroup = false
@@ -115,15 +131,88 @@ class GroupActivity : DatabaseLockActivity(),
private var actionNodeMode: ActionMode? = null
- // Nodes
- private var mCurrentGroupState: GroupState? = null
- private var mRootGroup: Group? = null
- private var mCurrentGroup: Group? = null
+ // Manage merge
+ private var mExternalFileHelper: ExternalFileHelper? = null
+
+ // Manage group
+ private var mSearchState: SearchState? = null
+ private var mMainGroupState: GroupState? = null // Group state, not a search
+ private var mRootGroup: Group? = null // Root group in the tree
+ private var mMainGroup: Group? = null // Main group currently in memory
+ private var mCurrentGroup: Group? = null // Group currently visible (search or main group)
private var mPreviousGroupsIds = mutableListOf()
private var mOldGroupToUpdate: Group? = null
- private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null
- private var mOnSuggestionListener: SearchView.OnSuggestionListener? = null
+ private var mLockSearchListeners = false
+ private val mOnSearchQueryTextListener = object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ onQueryTextChange(query)
+ // Collapse the search filters
+ searchFiltersView?.closeAdvancedFilters()
+ // Close the keyboard
+ WindowInsetsControllerCompat(window, window.decorView)
+ .hide(WindowInsetsCompat.Type.ime())
+ return true
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ if (newText != null && !mLockSearchListeners) {
+ mSearchState?.let { searchState ->
+ searchState.searchParameters.searchQuery = newText
+ loadSearchGroup(searchState)
+ }
+ }
+ return true
+ }
+ }
+ private val mOnSearchFiltersChangeListener = object : ((SearchParameters) -> Unit) {
+ override fun invoke(searchParameters: SearchParameters) {
+ mSearchState?.let { searchState ->
+ searchParameters.searchQuery = searchState.searchParameters.searchQuery
+ searchState.searchParameters = searchParameters
+ loadSearchGroup(searchState)
+ }
+ }
+ }
+ private val mOnSearchActionExpandListener = object : MenuItem.OnActionExpandListener {
+ override fun onMenuItemActionExpand(p0: MenuItem?): Boolean {
+ searchFiltersView?.visibility = View.VISIBLE
+ searchView?.setOnQueryTextListener(mOnSearchQueryTextListener)
+ searchFiltersView?.onParametersChangeListener = mOnSearchFiltersChangeListener
+
+ addSearch()
+ //loadGroup()
+ return true
+ }
+
+ override fun onMenuItemActionCollapse(p0: MenuItem?): Boolean {
+ searchFiltersView?.onParametersChangeListener = null
+ searchView?.setOnQueryTextListener(null)
+ searchFiltersView?.visibility = View.GONE
+
+ removeSearch()
+ loadGroup()
+ return true
+ }
+ }
+
+ private fun addSearch() {
+ finishNodeAction()
+ if (mSearchState == null) {
+ mSearchState = SearchState(searchFiltersView?.searchParameters
+ ?: SearchParameters(), 0)
+ }
+ }
+
+ private fun removeSearch() {
+ finishNodeAction()
+ mSearchState = null
+ intent.removeExtra(AUTO_SEARCH_KEY)
+ if (Intent.ACTION_SEARCH == intent.action) {
+ intent.action = Intent.ACTION_DEFAULT
+ intent.removeExtra(SearchManager.QUERY)
+ }
+ }
private var mIconSelectionActivityResultLauncher = IconPickerActivity.registerIconSelectionForResult(this) { icon ->
// To create tree dialog for icon
@@ -142,19 +231,18 @@ class GroupActivity : DatabaseLockActivity(),
setContentView(layoutInflater.inflate(R.layout.activity_group, null))
// Initialize views
- rootContainerView = findViewById(R.id.activity_group_container_view)
+ drawerLayout = findViewById(R.id.drawer_layout)
+ databaseNavView = findViewById(R.id.database_nav_view)
coordinatorLayout = findViewById(R.id.group_coordinator)
numberChildrenView = findViewById(R.id.group_numbers)
addNodeButtonView = findViewById(R.id.add_node_button)
toolbar = findViewById(R.id.toolbar)
databaseNameContainer = findViewById(R.id.database_name_container)
+ databaseModifiedView = findViewById(R.id.database_modified)
databaseColorView = findViewById(R.id.database_color)
databaseNameView = findViewById(R.id.database_name)
- searchContainer = findViewById(R.id.search_container)
- searchNumbers = findViewById(R.id.search_numbers)
- searchString = findViewById(R.id.search_string)
+ searchFiltersView = findViewById(R.id.search_filters)
toolbarBreadcrumb = findViewById(R.id.toolbar_breadcrumb)
- searchTitleView = findViewById(R.id.search_title)
breadcrumbListView = findViewById(R.id.breadcrumb_list)
toolbarAction = findViewById(R.id.toolbar_action)
lockView = findViewById(R.id.lock_button)
@@ -166,12 +254,66 @@ class GroupActivity : DatabaseLockActivity(),
toolbar?.title = ""
setSupportActionBar(toolbar)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ val toggle = ActionBarDrawerToggle(
+ this, drawerLayout, toolbar,
+ R.string.navigation_drawer_open, R.string.navigation_drawer_close
+ )
+ drawerLayout?.addDrawerListener(toggle)
+ toggle.syncState()
+
+ // Manage 'merge from" and "save to"
+ mExternalFileHelper = ExternalFileHelper(this)
+ mExternalFileHelper?.buildOpenDocument { uri ->
+ launchDialogToAskMainCredential(uri)
+ }
+ mExternalFileHelper?.buildCreateDocument("application/x-keepass") { uri ->
+ uri?.let {
+ saveDatabaseTo(it)
+ }
+ }
+
+ // Menu in drawer
+ databaseNavView?.apply {
+ inflateMenu(R.menu.settings)
+ inflateMenu(R.menu.database_extra)
+ inflateMenu(R.menu.about)
+ setNavigationItemSelectedListener { menuItem ->
+ when (menuItem.itemId) {
+ R.id.menu_app_settings -> {
+ // To avoid flickering when launch settings in a LockingActivity
+ SettingsActivity.launch(this@GroupActivity, true)
+ }
+ R.id.menu_merge_from -> {
+ mExternalFileHelper?.openDocument()
+ }
+ R.id.menu_save_copy_to -> {
+ mExternalFileHelper?.createDocument(
+ getString(R.string.database_file_name_default) +
+ getString(R.string.database_file_name_copy) +
+ mDatabase?.defaultFileExtension)
+ }
+ R.id.menu_lock_all -> {
+ lockAndExit()
+ }
+ R.id.menu_contribute -> {
+ UriUtil.gotoUrl(this@GroupActivity, R.string.contribution_url)
+ }
+ R.id.menu_about -> {
+ startActivity(Intent(this@GroupActivity, AboutActivity::class.java))
+ }
+ }
+ false
+ }
+ }
+
+ searchFiltersView?.closeAdvancedFilters()
mBreadcrumbAdapter = BreadcrumbAdapter(this).apply {
// Open group on breadcrumb click
onItemClickListener = { node, _ ->
// If last item & not a virtual root group
- val currentGroup = mCurrentGroup
+ val currentGroup = mMainGroup
if (currentGroup != null && node == currentGroup
&& (currentGroup != mDatabase?.rootGroup
|| mDatabase?.rootGroupIsVirtual == false)
@@ -188,7 +330,7 @@ class GroupActivity : DatabaseLockActivity(),
}
}
onLongItemClickListener = { node, position ->
- val currentGroup = mCurrentGroup
+ val currentGroup = mMainGroup
if (currentGroup != null && node == currentGroup
&& (currentGroup != mDatabase?.rootGroup
|| mDatabase?.rootGroupIsVirtual == false)
@@ -244,94 +386,42 @@ class GroupActivity : DatabaseLockActivity(),
GROUP_FRAGMENT_TAG
).commit()
- // Observe group
+ // Observe main group
+ mGroupViewModel.mainGroup.observe(this) {
+ val mainGroup = it.group
+ mMainGroup = mainGroup
+ mRecyclingBinIsCurrentGroup = it.isRecycleBin
+ // Save group state
+ mMainGroupState = GroupState(mainGroup.nodeId, it.showFromPosition)
+ // Update last access time.
+ mainGroup.touch(modified = false, touchParents = false)
+ }
+
+ // Observe current group (main or search group visible)
mGroupViewModel.group.observe(this) {
val currentGroup = it.group
-
mCurrentGroup = currentGroup
- mRecyclingBinIsCurrentGroup = it.isRecycleBin
+ if (currentGroup.isVirtual) {
+ val searchParameters = it.searchParameters
+ mSearchState = SearchState(searchParameters, it.showFromPosition)
+ }
+ // Main and search groups in activity are managed with another variables
+ // to keep values during orientation
- if (!currentGroup.isVirtual) {
- // Save group id if real group
- mCurrentGroupState = GroupState(currentGroup.nodeId, it.showFromPosition)
-
- // Update last access time.
- currentGroup.touch(modified = false, touchParents = false)
-
- // Add listeners to the add buttons
- addNodeButtonView?.setAddGroupClickListener {
- launchDialogForGroupCreation(currentGroup)
- }
- addNodeButtonView?.setAddEntryClickListener {
- mDatabase?.let { database ->
- EntrySelectionHelper.doSpecialAction(intent,
- {
- mCurrentGroup?.nodeId?.let { currentParentGroupId ->
- mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher ->
- EntryEditActivity.launchToCreate(
- this@GroupActivity,
- database,
- currentParentGroupId,
- resultLauncher
- )
- }
- }
- },
- {
- // Search not used
- },
- { searchInfo ->
- EntryEditActivity.launchToCreateForSave(
- this@GroupActivity,
- database,
- currentGroup.nodeId,
- searchInfo
- )
- onLaunchActivitySpecialMode()
- },
- { searchInfo ->
- EntryEditActivity.launchForKeyboardSelectionResult(
- this@GroupActivity,
- database,
- currentGroup.nodeId,
- searchInfo
- )
- onLaunchActivitySpecialMode()
- },
- { searchInfo, autofillComponent ->
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- EntryEditActivity.launchForAutofillResult(
- this@GroupActivity,
- database,
- mAutofillActivityResultLauncher,
- autofillComponent,
- currentGroup.nodeId,
- searchInfo
- )
- onLaunchActivitySpecialMode()
- } else {
- onCancelSpecialMode()
- }
- },
- { searchInfo ->
- EntryEditActivity.launchToCreateForRegistration(
- this@GroupActivity,
- database,
- currentGroup.nodeId,
- searchInfo
- )
- onLaunchActivitySpecialMode()
- }
- )
- }
- }
+ // Expand the search view if defined in settings
+ if (mRequestStartupSearch
+ && PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) {
+ // To request search only one time
+ mRequestStartupSearch = false
+ mSearchMenuItem?.expandActionView()
}
loadingView?.hideByFading()
}
mGroupViewModel.firstPositionVisible.observe(this) { firstPositionVisible ->
- mCurrentGroupState?.firstVisibleItem = firstPositionVisible
+ mSearchState?.firstVisibleItem = firstPositionVisible
+ mMainGroupState?.firstVisibleItem = firstPositionVisible
}
mGroupEditViewModel.requestIconSelection.observe(this) { iconImage ->
@@ -358,44 +448,125 @@ class GroupActivity : DatabaseLockActivity(),
mGroupEditViewModel.onGroupCreated.observe(this) { groupInfo ->
if (groupInfo.title.isNotEmpty()) {
- mCurrentGroup?.let { currentGroup ->
- createGroup(currentGroup, groupInfo)
+ mMainGroup?.let { parentGroup ->
+ createGroup(parentGroup, groupInfo)
}
}
}
mGroupEditViewModel.onGroupUpdated.observe(this) { groupInfo ->
if (groupInfo.title.isNotEmpty()) {
- mOldGroupToUpdate?.let { oldGroupToUpdate ->
- updateGroup(oldGroupToUpdate, groupInfo)
+ groupInfo.id?.let { groupId ->
+ mDatabase?.getGroupById(NodeIdUUID(groupId))?.let { oldGroupToUpdate ->
+ updateGroup(oldGroupToUpdate, groupInfo)
+ }
+ }
+ }
+ }
+
+ // Add listeners to the add buttons
+ addNodeButtonView?.setAddGroupClickListener {
+ mMainGroup?.let { currentGroup ->
+ launchDialogForGroupCreation(currentGroup)
+ }
+ }
+ addNodeButtonView?.setAddEntryClickListener {
+ mDatabase?.let { database ->
+ mMainGroup?.let { currentGroup ->
+ EntrySelectionHelper.doSpecialAction(intent,
+ {
+ mMainGroup?.nodeId?.let { currentParentGroupId ->
+ mGroupFragment?.mEntryActivityResultLauncher?.let { resultLauncher ->
+ EntryEditActivity.launchToCreate(
+ this@GroupActivity,
+ database,
+ currentParentGroupId,
+ resultLauncher
+ )
+ }
+ }
+ },
+ {
+ // Search not used
+ },
+ { searchInfo ->
+ EntryEditActivity.launchToCreateForSave(
+ this@GroupActivity,
+ database,
+ currentGroup.nodeId,
+ searchInfo
+ )
+ onLaunchActivitySpecialMode()
+ },
+ { searchInfo ->
+ EntryEditActivity.launchForKeyboardSelectionResult(
+ this@GroupActivity,
+ database,
+ currentGroup.nodeId,
+ searchInfo
+ )
+ onLaunchActivitySpecialMode()
+ },
+ { searchInfo, autofillComponent ->
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ EntryEditActivity.launchForAutofillResult(
+ this@GroupActivity,
+ database,
+ mAutofillActivityResultLauncher,
+ autofillComponent,
+ currentGroup.nodeId,
+ searchInfo
+ )
+ onLaunchActivitySpecialMode()
+ } else {
+ onCancelSpecialMode()
+ }
+ },
+ { searchInfo ->
+ EntryEditActivity.launchToCreateForRegistration(
+ this@GroupActivity,
+ database,
+ currentGroup.nodeId,
+ searchInfo
+ )
+ onLaunchActivitySpecialMode()
+ }
+ )
}
}
}
}
override fun viewToInvalidateTimeout(): View? {
- return rootContainerView
+ return drawerLayout
}
- private fun loadGroup(database: Database?) {
+ private fun loadMainGroup(groupState: GroupState) {
+ mGroupViewModel.loadMainGroup(mDatabase,
+ groupState.groupId,
+ groupState.firstVisibleItem)
+ }
+
+ private fun loadSearchGroup(searchState: SearchState) {
+ mGroupViewModel.loadSearchGroup(mDatabase,
+ searchState.searchParameters,
+ mMainGroupState?.groupId,
+ searchState.firstVisibleItem)
+ }
+
+ private fun loadGroup() {
+ val searchState = mSearchState
+ val currentGroupState = mMainGroupState
when {
- Intent.ACTION_SEARCH == intent.action -> {
+ searchState != null -> {
finishNodeAction()
- val searchString =
- intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
- mGroupViewModel.loadGroupFromSearch(
- database,
- searchString,
- PreferencesUtil.omitBackup(this)
- )
+ loadSearchGroup(searchState)
}
- mCurrentGroupState == null -> {
- mRootGroup?.let { rootGroup ->
- mGroupViewModel.loadGroup(database, rootGroup, 0)
- }
+ currentGroupState != null -> {
+ loadMainGroup(currentGroupState)
}
else -> {
- mGroupViewModel.loadGroup(database, mCurrentGroupState)
+ loadMainGroup(GroupState(null, 0))
}
}
}
@@ -409,12 +580,28 @@ class GroupActivity : DatabaseLockActivity(),
&& database?.isRecycleBinEnabled == true
mRootGroup = database?.rootGroup
- loadGroup(database)
+ loadGroup()
- // Search suggestion
+ // Update view
database?.let {
- databaseNameView?.text = if (it.name.isNotEmpty()) it.name else getString(R.string.database)
+ mBreadcrumbAdapter?.iconDrawableFactory = it.iconDrawableFactory
+ }
+ refreshDatabaseViews()
+ invalidateOptionsMenu()
+ }
+
+ private fun refreshDatabaseViews() {
+ mDatabase?.let {
+ val databaseName = it.name.ifEmpty { getString(R.string.database) }
+ databaseNavView?.setDatabaseName(databaseName)
+ databaseNameView?.text = databaseName
+ databaseNavView?.setDatabasePath(it.fileUri?.toString())
+ databaseNavView?.setDatabaseVersion(it.version)
+ val modified = it.dataModifiedSinceLastLoading
+ databaseNavView?.setDatabaseModifiedSinceLastLoading(modified)
+ databaseModifiedView?.isVisible = modified
val customColor = it.customColor
+ databaseNavView?.setDatabaseColor(customColor)
if (customColor != null) {
databaseColorView?.visibility = View.VISIBLE
databaseColorView?.setColorFilter(
@@ -424,25 +611,7 @@ class GroupActivity : DatabaseLockActivity(),
} else {
databaseColorView?.visibility = View.GONE
}
- mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, it)
- mBreadcrumbAdapter?.iconDrawableFactory = it.iconDrawableFactory
- mOnSuggestionListener = object : SearchView.OnSuggestionListener {
- override fun onSuggestionClick(position: Int): Boolean {
- mSearchSuggestionAdapter?.let { searchAdapter ->
- searchAdapter.getEntryFromPosition(position)?.let { entry ->
- onNodeClick(database, entry)
- }
- }
- return true
- }
-
- override fun onSuggestionSelect(position: Int): Boolean {
- return true
- }
- }
}
-
- invalidateOptionsMenu()
}
override fun onDatabaseActionFinished(
@@ -503,7 +672,7 @@ class GroupActivity : DatabaseLockActivity(),
ACTION_DATABASE_UPDATE_GROUP_TASK -> {
if (result.isSuccess) {
try {
- if (mCurrentGroup == newNodes[0] as Group)
+ if (mMainGroup == newNodes[0] as Group)
reloadCurrentGroup()
} catch (e: Exception) {
Log.e(
@@ -540,12 +709,21 @@ class GroupActivity : DatabaseLockActivity(),
private fun manageIntent(intent: Intent?) {
intent?.let {
if (intent.extras?.containsKey(GROUP_STATE_KEY) == true) {
- mCurrentGroupState = intent.getParcelableExtra(GROUP_STATE_KEY)
+ mMainGroupState = intent.getParcelableExtra(GROUP_STATE_KEY)
intent.removeExtra(GROUP_STATE_KEY)
}
// To transform KEY_SEARCH_INFO in ACTION_SEARCH
transformSearchInfoIntent(intent)
- loadGroup(mDatabase)
+ // Get search query
+ if (intent.action == Intent.ACTION_SEARCH) {
+ val stringQuery = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
+ intent.action = Intent.ACTION_DEFAULT
+ intent.removeExtra(SearchManager.QUERY)
+ mSearchState = SearchState(SearchParameters().apply {
+ searchQuery = stringQuery
+ }, mSearchState?.firstVisibleItem ?: 0)
+ }
+ loadGroup()
}
}
@@ -566,36 +744,24 @@ class GroupActivity : DatabaseLockActivity(),
}
override fun onGroupRefreshed() {
- mCurrentGroup?.let { currentGroup ->
- assignGroupViewElements(currentGroup)
- }
- }
-
- private fun assignGroupViewElements(group: Group?) {
+ val group = mCurrentGroup
// Assign title
if (group?.isVirtual == true) {
- searchContainer?.visibility = View.VISIBLE
- val title = group.title
- searchString?.text = if (title.isNotEmpty()) title else ""
- searchNumbers?.text = group.numberOfChildEntries.toString()
- databaseNameContainer?.visibility = View.GONE
- toolbarBreadcrumb?.navigationIcon = null
- toolbarBreadcrumb?.collapse()
+ searchFiltersView?.setNumbers(group.numberOfChildEntries)
+ searchFiltersView?.setCurrentGroupText(mMainGroup?.title ?: "")
+ searchFiltersView?.availableOther(mDatabase?.allowEntryCustomFields() ?: false)
+ searchFiltersView?.availableTags(mDatabase?.allowTags() ?: false)
+ searchFiltersView?.enableTags(mDatabase?.tagPool?.isNotEmpty() ?: false)
+ searchFiltersView?.availableSearchableGroup(mDatabase?.allowCustomSearchableGroup() ?: false)
+ searchFiltersView?.availableTemplates(mDatabase?.allowTemplatesGroup ?: false)
+ searchFiltersView?.enableTemplates(mDatabase?.templatesGroup != null)
} else {
- searchContainer?.visibility = View.GONE
- databaseNameContainer?.visibility = View.VISIBLE
- // Refresh breadcrumb
- if (toolbarBreadcrumb?.isVisible != true) {
- toolbarBreadcrumb?.expand {
- setBreadcrumbNode(group)
- }
- } else {
- // Add breadcrumb
- setBreadcrumbNode(group)
- }
+ // Add breadcrumb
+ setBreadcrumbNode(group)
+ refreshDatabaseViews()
+ invalidateOptionsMenu()
}
initAddButton(group)
- invalidateOptionsMenu()
}
private fun setBreadcrumbNode(group: Group?) {
@@ -638,12 +804,12 @@ class GroupActivity : DatabaseLockActivity(),
val group = node as Group
// Save the last not virtual group and it's position
if (mCurrentGroup?.isVirtual == false) {
- mCurrentGroupState?.let {
+ mMainGroupState?.let {
mPreviousGroupsIds.add(it)
}
}
// Open child group
- mGroupViewModel.loadGroup(database, group, 0)
+ loadMainGroup(GroupState(group.nodeId, 0))
} catch (e: ClassCastException) {
Log.e(TAG, "Node can't be cast in Group")
@@ -672,29 +838,13 @@ class GroupActivity : DatabaseLockActivity(),
finish()
},
{ searchInfo ->
- // Recheck search, only to fix #783 because workflow allows to open multiple search elements
- SearchHelper.checkAutoSearchInfo(this,
- database,
- searchInfo,
- { openedDatabase, _ ->
- // Item in search, don't save
- entrySelectedForKeyboardSelection(openedDatabase, entryVersioned)
- },
- {
- // Item not found, save it if required
- if (!database.isReadOnly
- && searchInfo != null
- && PreferencesUtil.isKeyboardSaveSearchInfoEnable(this@GroupActivity)
- ) {
- updateEntryWithSearchInfo(database, entryVersioned, searchInfo)
- }
- entrySelectedForKeyboardSelection(database, entryVersioned)
- },
- {
- // Normally not append
- finish()
- }
- )
+ if (!database.isReadOnly
+ && searchInfo != null
+ && PreferencesUtil.isKeyboardSaveSearchInfoEnable(this@GroupActivity)
+ ) {
+ updateEntryWithSearchInfo(database, entryVersioned, searchInfo)
+ }
+ entrySelectedForKeyboardSelection(database, entryVersioned)
},
{ searchInfo, _ ->
if (!database.isReadOnly
@@ -776,14 +926,16 @@ class GroupActivity : DatabaseLockActivity(),
searchInfo: SearchInfo
) {
val newEntry = Entry(entry)
- newEntry.setEntryInfo(database, newEntry.getEntryInfo(
+ val entryInfo = newEntry.getEntryInfo(
database,
raw = true,
removeTemplateConfiguration = false
- ).apply {
- saveSearchInfo(database, searchInfo)
- })
- updateEntry(entry, newEntry)
+ )
+ val modification = entryInfo.saveSearchInfo(database, searchInfo)
+ newEntry.setEntryInfo(database, entryInfo)
+ if (modification) {
+ updateEntry(entry, newEntry)
+ }
}
override fun onDateSet(datePicker: DatePicker?, year: Int, month: Int, day: Int) {
@@ -886,6 +1038,11 @@ class GroupActivity : DatabaseLockActivity(),
.show(supportFragmentManager, GroupEditDialogFragment.TAG_CREATE_GROUP)
}
+ private fun launchDialogToAskMainCredential(uri: Uri?) {
+ MainCredentialDialogFragment.getInstance(uri)
+ .show(supportFragmentManager, MainCredentialDialogFragment.TAG_ASK_MAIN_CREDENTIAL)
+ }
+
override fun onCopyMenuClick(
database: Database,
nodes: List
@@ -914,13 +1071,13 @@ class GroupActivity : DatabaseLockActivity(),
when (pasteMode) {
GroupFragment.PasteMode.PASTE_FROM_COPY -> {
// Copy
- mCurrentGroup?.let { newParent ->
+ mMainGroup?.let { newParent ->
copyNodes(nodes, newParent)
}
}
GroupFragment.PasteMode.PASTE_FROM_MOVE -> {
// Move
- mCurrentGroup?.let { newParent ->
+ mMainGroup?.let { newParent ->
moveNodes(nodes, newParent)
}
}
@@ -941,6 +1098,16 @@ class GroupActivity : DatabaseLockActivity(),
return true
}
+ override fun onAskMainCredentialDialogPositiveClick(databaseUri: Uri?,
+ mainCredential: MainCredential) {
+ databaseUri?.let {
+ mergeDatabaseFrom(it, mainCredential)
+ }
+ }
+
+ override fun onAskMainCredentialDialogNegativeClick(databaseUri: Uri?,
+ mainCredential: MainCredential) { }
+
override fun onResume() {
super.onResume()
@@ -950,8 +1117,6 @@ class GroupActivity : DatabaseLockActivity(),
} else {
View.GONE
}
- // Refresh suggestions to change preferences
- mSearchSuggestionAdapter?.reInit(this)
// Padding if lock button visible
toolbarAction?.updateLockPaddingLeft()
}
@@ -960,6 +1125,26 @@ class GroupActivity : DatabaseLockActivity(),
super.onPause()
finishNodeAction()
+ searchView?.setOnQueryTextListener(null)
+ }
+
+ private fun addSearchQueryInSearchView(searchQuery: String) {
+ searchView?.setOnQueryTextListener(null)
+ searchView?.setQuery(searchQuery, false)
+ searchView?.setOnQueryTextListener(mOnSearchQueryTextListener)
+ }
+
+ private fun prepareDatabaseNavMenu() {
+ // hide or show nav menu
+ databaseNavView?.apply {
+ // depending on current mode
+ val modeCondition = mSpecialMode == SpecialMode.DEFAULT
+ menu.findItem(R.id.menu_app_settings)?.isVisible = modeCondition
+ menu.findItem(R.id.menu_merge_from)?.isVisible = mMergeDataAllowed && modeCondition
+ menu.findItem(R.id.menu_save_copy_to)?.isVisible = modeCondition
+ menu.findItem(R.id.menu_about)?.isVisible = modeCondition
+ menu.findItem(R.id.menu_contribute)?.isVisible = modeCondition
+ }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -974,125 +1159,127 @@ class GroupActivity : DatabaseLockActivity(),
if (!mMergeDataAllowed) {
menu.findItem(R.id.menu_merge_database)?.isVisible = false
}
- if (mSpecialMode == SpecialMode.DEFAULT) {
- MenuUtil.defaultMenuInflater(inflater, menu)
- } else {
+ if (mSpecialMode != SpecialMode.DEFAULT) {
menu.findItem(R.id.menu_merge_database)?.isVisible = false
menu.findItem(R.id.menu_reload_database)?.isVisible = false
}
-
// Menu for recycle bin
if (mRecyclingBinEnabled && mRecyclingBinIsCurrentGroup) {
inflater.inflate(R.menu.recycle_bin, menu)
}
- // Get the SearchView and set the searchable configuration
- val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager?
+ prepareDatabaseNavMenu()
+ // Get the SearchView and set the searchable configuration
menu.findItem(R.id.menu_search)?.let {
- val searchView = it.actionView as SearchView?
+ mLockSearchListeners = true
+ mSearchMenuItem = it
+ it.setOnActionExpandListener(mOnSearchActionExpandListener)
+ searchView = it.actionView as SearchView?
searchView?.apply {
+ val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager?
(searchManager?.getSearchableInfo(
ComponentName(this@GroupActivity, GroupActivity::class.java)
))?.let { searchableInfo ->
setSearchableInfo(searchableInfo)
}
- setIconifiedByDefault(false) // Do not iconify the widget; expand it by default
- suggestionsAdapter = mSearchSuggestionAdapter
- setOnSuggestionListener(mOnSuggestionListener)
+ val searchState = mSearchState
+ // already open
+ if (searchState != null) {
+ it.expandActionView()
+ addSearchQueryInSearchView(searchState.searchParameters.searchQuery)
+ searchFiltersView?.searchParameters = searchState.searchParameters
+ }
}
- // Expand the search view if defined in settings
- if (mRequestStartupSearch
- && PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)
- ) {
- // To request search only one time
- mRequestStartupSearch = false
- it.expandActionView()
+ if (it.isActionViewExpanded) {
+ toolbarBreadcrumb?.visibility = View.GONE
+ searchFiltersView?.visibility = View.VISIBLE
+ } else {
+ searchFiltersView?.visibility = View.GONE
+ toolbarBreadcrumb?.visibility = View.VISIBLE
}
+ mLockSearchListeners = false
}
super.onCreateOptionsMenu(menu)
// Launch education screen
Handler(Looper.getMainLooper()).post {
- performedNextEducation(
- GroupActivityEducation(this),
- menu
- )
+ performedNextEducation(menu)
}
return true
}
- private fun performedNextEducation(
- groupActivityEducation: GroupActivityEducation,
- menu: Menu
- ) {
+ private fun performedNextEducation(menu: Menu) {
// If no node, show education to add new one
val addNodeButtonEducationPerformed = actionNodeMode == null
&& addNodeButtonView?.addButtonView != null
&& addNodeButtonView!!.isEnable
- && groupActivityEducation.checkAndPerformedAddNodeButtonEducation(
+ && mGroupActivityEducation.checkAndPerformedAddNodeButtonEducation(
addNodeButtonView?.addButtonView!!,
{
addNodeButtonView?.openButtonIfClose()
},
{
- performedNextEducation(groupActivityEducation, menu)
+ performedNextEducation(menu)
}
)
if (!addNodeButtonEducationPerformed) {
val searchMenuEducationPerformed = toolbar != null
&& toolbar!!.findViewById(R.id.menu_search) != null
- && groupActivityEducation.checkAndPerformedSearchMenuEducation(
+ && mGroupActivityEducation.checkAndPerformedSearchMenuEducation(
toolbar!!.findViewById(R.id.menu_search),
{
menu.findItem(R.id.menu_search).expandActionView()
},
{
- performedNextEducation(groupActivityEducation, menu)
+ performedNextEducation(menu)
})
if (!searchMenuEducationPerformed) {
val sortMenuEducationPerformed = toolbar != null
&& toolbar!!.findViewById(R.id.menu_sort) != null
- && groupActivityEducation.checkAndPerformedSortMenuEducation(
+ && mGroupActivityEducation.checkAndPerformedSortMenuEducation(
toolbar!!.findViewById(R.id.menu_sort),
{
onOptionsItemSelected(menu.findItem(R.id.menu_sort))
},
{
- performedNextEducation(groupActivityEducation, menu)
+ performedNextEducation(menu)
})
if (!sortMenuEducationPerformed) {
// lockMenuEducationPerformed
val lockButtonView = findViewById(R.id.lock_button)
lockButtonView != null
- && groupActivityEducation.checkAndPerformedLockMenuEducation(
+ && mGroupActivityEducation.checkAndPerformedLockMenuEducation(
lockButtonView,
{
lockAndExit()
},
{
- performedNextEducation(groupActivityEducation, menu)
+ performedNextEducation(menu)
})
}
}
}
}
+ override fun hideHomeButtonIfModeIsNotDefault(): Boolean {
+ return false
+ }
+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
- // TODO change database
+ drawerLayout?.openDrawer(GravityCompat.START)
return true
}
R.id.menu_search -> {
- //onSearchRequested()
return true
}
R.id.menu_save_database -> {
@@ -1109,7 +1296,7 @@ class GroupActivity : DatabaseLockActivity(),
}
R.id.menu_empty_recycle_bin -> {
if (mRecyclingBinEnabled && mRecyclingBinIsCurrentGroup) {
- mCurrentGroup?.getChildren()?.let { listChildren ->
+ mMainGroup?.getChildren()?.let { listChildren ->
// Automatically delete all elements
deleteNodes(listChildren, true)
finishNodeAction()
@@ -1118,8 +1305,6 @@ class GroupActivity : DatabaseLockActivity(),
return true
}
else -> {
- // Check the time lock before launching settings
- MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true)
return super.onOptionsItemSelected(item)
}
}
@@ -1148,23 +1333,9 @@ class GroupActivity : DatabaseLockActivity(),
}
}
- private fun removeSearch() {
- intent.removeExtra(AUTO_SEARCH_KEY)
- if (Intent.ACTION_SEARCH == intent.action) {
- intent.action = Intent.ACTION_DEFAULT
- intent.removeExtra(SearchManager.QUERY)
- }
- }
-
private fun reloadCurrentGroup() {
- // Remove search in intent
removeSearch()
- // Reload real group
- try {
- mGroupViewModel.loadGroup(mDatabase, mCurrentGroupState)
- } catch (e: Exception) {
- Log.e(TAG, "Unable to rebuild the group", e)
- }
+ loadGroup()
}
override fun onBackPressed() {
@@ -1183,7 +1354,7 @@ class GroupActivity : DatabaseLockActivity(),
}
else -> {
// Load the previous group
- mGroupViewModel.loadGroup(mDatabase, mPreviousGroupsIds.removeLast())
+ loadMainGroup(mPreviousGroupsIds.removeLast())
}
}
}
@@ -1202,6 +1373,35 @@ class GroupActivity : DatabaseLockActivity(),
}
}
+ data class SearchState(
+ var searchParameters: SearchParameters,
+ var firstVisibleItem: Int?
+ ) : Parcelable {
+
+ private constructor(parcel: Parcel) : this(
+ parcel.readParcelable
+ (SearchParameters::class.java.classLoader) ?: SearchParameters(),
+ parcel.readInt()
+ )
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeParcelable(searchParameters, flags)
+ parcel.writeInt(firstVisibleItem ?: 0)
+ }
+
+ override fun describeContents() = 0
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): SearchState {
+ return SearchState(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+
data class GroupState(
var groupId: NodeId<*>?,
var firstVisibleItem: Int?
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/MainCredentialActivity.kt
similarity index 72%
rename from app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt
rename to app/src/main/java/com/kunzisoft/keepass/activities/MainCredentialActivity.kt
index 79277962c..cb17b3df6 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/MainCredentialActivity.kt
@@ -26,14 +26,14 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
-import android.text.Editable
-import android.text.TextWatcher
import android.util.Log
-import android.view.*
-import android.view.KeyEvent.KEYCODE_ENTER
-import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
-import android.view.inputmethod.InputMethodManager
-import android.widget.*
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.CompoundButton
+import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
@@ -44,10 +44,11 @@ import androidx.fragment.app.commit
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
-import com.kunzisoft.keepass.activities.helpers.*
+import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
+import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
+import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
-import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
@@ -55,11 +56,9 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation
-import com.kunzisoft.keepass.model.MainCredential
-import com.kunzisoft.keepass.model.RegisterInfo
-import com.kunzisoft.keepass.model.SearchInfo
+import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
-import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
+import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.CIPHER_DATABASE_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
@@ -68,23 +67,20 @@ import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
-import com.kunzisoft.keepass.view.KeyFileSelectionView
+import com.kunzisoft.keepass.view.MainCredentialView
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
import java.io.FileNotFoundException
-class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
+class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
// Views
private var toolbar: Toolbar? = null
private var filenameView: TextView? = null
- private var passwordView: EditText? = null
- private var keyFileSelectionView: KeyFileSelectionView? = null
+ private var mainCredentialView: MainCredentialView? = null
private var confirmButtonView: Button? = null
- private var checkboxPasswordView: CompoundButton? = null
- private var checkboxKeyFileView: CompoundButton? = null
private var infoContainerView: ViewGroup? = null
private lateinit var coordinatorLayout: CoordinatorLayout
private var advancedUnlockFragment: AdvancedUnlockFragment? = null
@@ -92,9 +88,10 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
private val mDatabaseFileViewModel: DatabaseFileViewModel by viewModels()
private val mAdvancedUnlockViewModel: AdvancedUnlockViewModel by viewModels()
+ private val mPasswordActivityEducation = PasswordActivityEducation(this)
+
private var mDefaultDatabase: Boolean = false
private var mDatabaseFileUri: Uri? = null
- private var mDatabaseKeyFileUri: Uri? = null
private var mRememberKeyFile: Boolean = false
private var mExternalFileHelper: ExternalFileHelper? = null
@@ -110,7 +107,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_password)
+ setContentView(R.layout.activity_main_credential)
toolbar = findViewById(R.id.toolbar)
toolbar?.title = getString(R.string.app_name)
@@ -118,12 +115,9 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
- confirmButtonView = findViewById(R.id.activity_password_open_button)
filenameView = findViewById(R.id.filename)
- passwordView = findViewById(R.id.password)
- keyFileSelectionView = findViewById(R.id.keyfile_selection)
- checkboxPasswordView = findViewById(R.id.password_checkbox)
- checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
+ mainCredentialView = findViewById(R.id.activity_password_credentials)
+ confirmButtonView = findViewById(R.id.activity_password_open_button)
infoContainerView = findViewById(R.id.activity_password_info_container)
coordinatorLayout = findViewById(R.id.activity_password_coordinator_layout)
@@ -134,41 +128,19 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
- mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
+ mExternalFileHelper = ExternalFileHelper(this@MainCredentialActivity)
mExternalFileHelper?.buildOpenDocument { uri ->
if (uri != null) {
- mDatabaseKeyFileUri = uri
- populateKeyFileTextView(uri)
+ mainCredentialView?.populateKeyFileTextView(uri)
}
}
- keyFileSelectionView?.setOpenDocumentClickListener(mExternalFileHelper)
-
- passwordView?.setOnEditorActionListener(onEditorActionListener)
- passwordView?.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
-
- override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
-
- override fun afterTextChanged(editable: Editable) {
- if (editable.toString().isNotEmpty() && checkboxPasswordView?.isChecked != true)
- checkboxPasswordView?.isChecked = true
- }
- })
- passwordView?.setOnKeyListener { _, _, keyEvent ->
- var handled = false
- if (keyEvent.action == KeyEvent.ACTION_DOWN
- && keyEvent?.keyCode == KEYCODE_ENTER) {
- verifyCheckboxesAndLoadDatabase()
- handled = true
- }
- handled
+ mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)
+ mainCredentialView?.onValidateListener = {
+ loadDatabase()
}
// If is a view intent
getUriFromIntent(intent)
- if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
- mDatabaseKeyFileUri = UriUtil.parse(savedInstanceState.getString(KEY_KEYFILE))
- }
// Init Biometric elements
advancedUnlockFragment = supportFragmentManager
@@ -183,10 +155,11 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
// Listen password checkbox to init advanced unlock and confirmation button
- checkboxPasswordView?.setOnCheckedChangeListener { _, _ ->
- mAdvancedUnlockViewModel.checkUnlockAvailability()
- enableOrNotTheConfirmationButton()
- }
+ mainCredentialView?.onPasswordChecked =
+ CompoundButton.OnCheckedChangeListener { _, _ ->
+ mAdvancedUnlockViewModel.checkUnlockAvailability()
+ enableConfirmationButton()
+ }
// Observe if default database
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
@@ -211,12 +184,13 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
invalidateOptionsMenu()
// Post init uri with KeyFile only if needed
+ val databaseKeyFileUri = mainCredentialView?.getMainCredential()?.keyFileUri
val keyFileUri =
if (mRememberKeyFile
- && (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) {
+ && (databaseKeyFileUri == null || databaseKeyFileUri.toString().isEmpty())) {
databaseFile?.keyFileUri
} else {
- mDatabaseKeyFileUri
+ databaseKeyFileUri
}
// Define title
@@ -229,10 +203,10 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
override fun onResume() {
super.onResume()
- mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@PasswordActivity)
+ mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@MainCredentialActivity)
// Back to previous keyboard is setting activated
- if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@PasswordActivity)) {
+ if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@MainCredentialActivity)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
@@ -271,7 +245,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
if (result.isSuccess) {
launchGroupActivityIfLoaded(database)
} else {
- passwordView?.requestFocusFromTouch()
+ mainCredentialView?.requestPasswordFocus()
var resultError = ""
val resultException = result.exception
@@ -288,7 +262,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
var databaseUri: Uri? = null
var mainCredential = MainCredential()
var readOnly = true
- var cipherEntity: CipherDatabaseEntity? = null
+ var cipherEncryptDatabase: CipherEncryptDatabase? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
@@ -296,8 +270,8 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
resultData.getParcelable(MAIN_CREDENTIAL_KEY)
?: mainCredential
readOnly = resultData.getBoolean(READ_ONLY_KEY)
- cipherEntity =
- resultData.getParcelable(CIPHER_ENTITY_KEY)
+ cipherEncryptDatabase =
+ resultData.getParcelable(CIPHER_DATABASE_KEY)
}
databaseUri?.let { databaseFileUri ->
@@ -305,7 +279,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
databaseFileUri,
mainCredential,
readOnly,
- cipherEntity,
+ cipherEncryptDatabase,
true
)
}
@@ -341,11 +315,16 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
if (action != null
&& action == VIEW_INTENT) {
mDatabaseFileUri = intent.data
- mDatabaseKeyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
+ mainCredentialView?.populateKeyFileTextView(UriUtil.getUriFromIntent(intent, KEY_KEYFILE))
} else {
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
- mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE)
+ intent?.getParcelableExtra(KEY_KEYFILE)?.let {
+ mainCredentialView?.populateKeyFileTextView(it)
+ }
}
+ try {
+ intent?.removeExtra(KEY_KEYFILE)
+ } catch (e: Exception) {}
mDatabaseFileUri?.let {
mDatabaseFileViewModel.checkIfIsDefaultDatabase(it)
}
@@ -380,51 +359,68 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
finish()
}
- override fun retrieveCredentialForEncryption(): String {
- return passwordView?.text?.toString() ?: ""
+ override fun retrieveCredentialForEncryption(): ByteArray {
+ return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
+ ?: byteArrayOf()
}
override fun conditionToStoreCredential(): Boolean {
- return checkboxPasswordView?.isChecked == true
+ return mainCredentialView?.conditionToStoreCredential() == true
}
- override fun onCredentialEncrypted(databaseUri: Uri,
- encryptedCredential: String,
- ivSpec: String) {
+ override fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
// Load the database if password is registered with biometric
- verifyCheckboxesAndLoadDatabase(
- CipherDatabaseEntity(
- databaseUri.toString(),
- encryptedCredential,
- ivSpec)
+ loadDatabase(mDatabaseFileUri,
+ mainCredentialView?.getMainCredential(),
+ cipherEncryptDatabase
)
}
- override fun onCredentialDecrypted(databaseUri: Uri,
- decryptedCredential: String) {
- // Load the database if password is retrieve from biometric
- // Retrieve from biometric
- verifyKeyFileCheckboxAndLoadDatabase(decryptedCredential)
+ private val credentialStorageListener = object: MainCredentialView.CredentialStorageListener {
+ override fun passwordToStore(password: String?): ByteArray? {
+ return password?.toByteArray()
+ }
+
+ override fun keyfileToStore(keyfile: Uri?): ByteArray? {
+ // TODO create byte array to store keyfile
+ return null
+ }
+
+ override fun hardwareKeyToStore(): ByteArray? {
+ // TODO create byte array to store hardware key
+ return null
+ }
}
- private val onEditorActionListener = object : TextView.OnEditorActionListener {
- override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
- if (actionId == IME_ACTION_DONE) {
- verifyCheckboxesAndLoadDatabase()
- return true
+ override fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
+ // Load the database if password is retrieve from biometric
+ // Retrieve from biometric
+ val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential()
+ when (cipherDecryptDatabase.credentialStorage) {
+ CredentialStorage.PASSWORD -> {
+ mainCredential.masterPassword = String(cipherDecryptDatabase.decryptedValue)
+ }
+ CredentialStorage.KEY_FILE -> {
+ // TODO advanced unlock key file
+ }
+ CredentialStorage.HARDWARE_KEY -> {
+ // TODO advanced unlock hardware key
}
- return false
}
+ loadDatabase(mDatabaseFileUri,
+ mainCredential,
+ null
+ )
}
private fun onDatabaseFileLoaded(databaseFileUri: Uri?, keyFileUri: Uri?) {
// Define Key File text
if (mRememberKeyFile) {
- populateKeyFileTextView(keyFileUri)
+ mainCredentialView?.populateKeyFileTextView(keyFileUri)
}
// Define listener for validate button
- confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() }
+ confirmButtonView?.setOnClickListener { loadDatabase() }
// If Activity is launch with a password and want to open directly
val intent = intent
@@ -433,66 +429,33 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
intent.removeExtra(KEY_PASSWORD)
val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false)
if (password != null) {
- populatePasswordTextView(password)
+ mainCredentialView?.populatePasswordTextView(password)
}
if (launchImmediately) {
- verifyCheckboxesAndLoadDatabase(password, keyFileUri)
+ loadDatabase()
} else {
// Init Biometric elements
mAdvancedUnlockViewModel.databaseFileLoaded(databaseFileUri)
}
- enableOrNotTheConfirmationButton()
+ enableConfirmationButton()
- // Auto select the password field and open keyboard
- passwordView?.postDelayed({
- passwordView?.requestFocusFromTouch()
- val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager?
- inputMethodManager?.showSoftInput(passwordView, InputMethodManager.SHOW_IMPLICIT)
- }, 100)
+ mainCredentialView?.focusPasswordFieldAndOpenKeyboard()
}
- private fun enableOrNotTheConfirmationButton() {
+ private fun enableConfirmationButton() {
// Enable or not the open button if setting is checked
- if (!PreferencesUtil.emptyPasswordAllowed(this@PasswordActivity)) {
- checkboxPasswordView?.let {
- confirmButtonView?.isEnabled = (checkboxPasswordView?.isChecked == true
- || checkboxKeyFileView?.isChecked == true)
- }
+ if (!PreferencesUtil.emptyPasswordAllowed(this@MainCredentialActivity)) {
+ confirmButtonView?.isEnabled = mainCredentialView?.isFill() ?: false
} else {
confirmButtonView?.isEnabled = true
}
}
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
- populatePasswordTextView(null)
+ mainCredentialView?.populatePasswordTextView(null)
if (clearKeyFile) {
- mDatabaseKeyFileUri = null
- populateKeyFileTextView(null)
- }
- }
-
- private fun populatePasswordTextView(text: String?) {
- if (text == null || text.isEmpty()) {
- passwordView?.setText("")
- if (checkboxPasswordView?.isChecked == true)
- checkboxPasswordView?.isChecked = false
- } else {
- passwordView?.setText(text)
- if (checkboxPasswordView?.isChecked != true)
- checkboxPasswordView?.isChecked = true
- }
- }
-
- private fun populateKeyFileTextView(uri: Uri?) {
- if (uri == null || uri.toString().isEmpty()) {
- keyFileSelectionView?.uri = null
- if (checkboxKeyFileView?.isChecked == true)
- checkboxKeyFileView?.isChecked = false
- } else {
- keyFileSelectionView?.uri = uri
- if (checkboxKeyFileView?.isChecked != true)
- checkboxKeyFileView?.isChecked = true
+ mainCredentialView?.populateKeyFileTextView(null)
}
}
@@ -504,41 +467,20 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
override fun onSaveInstanceState(outState: Bundle) {
- mDatabaseKeyFileUri?.let {
- outState.putString(KEY_KEYFILE, it.toString())
- }
outState.putBoolean(KEY_READ_ONLY, mReadOnly)
super.onSaveInstanceState(outState)
}
- private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
- val password: String? = passwordView?.text?.toString()
- val keyFile: Uri? = keyFileSelectionView?.uri
- verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity)
- }
-
- private fun verifyCheckboxesAndLoadDatabase(password: String?,
- keyFile: Uri?,
- cipherDatabaseEntity: CipherDatabaseEntity? = null) {
- val keyPassword = if (checkboxPasswordView?.isChecked != true) null else password
- verifyKeyFileCheckbox(keyFile)
- loadDatabase(mDatabaseFileUri, keyPassword, mDatabaseKeyFileUri, cipherDatabaseEntity)
- }
-
- private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
- val keyFile: Uri? = keyFileSelectionView?.uri
- verifyKeyFileCheckbox(keyFile)
- loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
- }
-
- private fun verifyKeyFileCheckbox(keyFile: Uri?) {
- mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
+ private fun loadDatabase() {
+ loadDatabase(mDatabaseFileUri,
+ mainCredentialView?.getMainCredential(),
+ null
+ )
}
private fun loadDatabase(databaseFileUri: Uri?,
- password: String?,
- keyFileUri: Uri?,
- cipherDatabaseEntity: CipherDatabaseEntity? = null) {
+ mainCredential: MainCredential?,
+ cipherEncryptDatabase: CipherEncryptDatabase?) {
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
clearCredentialsViews()
@@ -556,11 +498,12 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
databaseFileUri?.let { databaseUri ->
// Show the progress dialog and load the database
showProgressDialogAndLoadDatabase(
- databaseUri,
- MainCredential(password, keyFileUri),
- mReadOnly,
- cipherDatabaseEntity,
- false)
+ databaseUri,
+ mainCredential ?: MainCredential(),
+ mReadOnly,
+ cipherEncryptDatabase,
+ false
+ )
}
}
}
@@ -568,14 +511,14 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
private fun showProgressDialogAndLoadDatabase(databaseUri: Uri,
mainCredential: MainCredential,
readOnly: Boolean,
- cipherDatabaseEntity: CipherDatabaseEntity?,
+ cipherEncryptDatabase: CipherEncryptDatabase?,
fixDuplicateUUID: Boolean) {
loadDatabase(
- databaseUri,
- mainCredential,
- readOnly,
- cipherDatabaseEntity,
- fixDuplicateUUID
+ databaseUri,
+ mainCredential,
+ readOnly,
+ cipherEncryptDatabase,
+ fixDuplicateUUID
)
}
@@ -612,26 +555,27 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
- Handler(Looper.getMainLooper()).post { performedNextEducation(PasswordActivityEducation(this), menu) }
+ Handler(Looper.getMainLooper()).post {
+ performedNextEducation(menu)
+ }
}
}
- private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
- menu: Menu) {
+ private fun performedNextEducation(menu: Menu) {
val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null
- && passwordActivityEducation.checkAndPerformedUnlockEducation(
+ && mPasswordActivityEducation.checkAndPerformedUnlockEducation(
educationToolbar,
{
- performedNextEducation(passwordActivityEducation, menu)
+ performedNextEducation(menu)
},
{
- performedNextEducation(passwordActivityEducation, menu)
+ performedNextEducation(menu)
})
if (!unlockEducationPerformed) {
val readOnlyEducationPerformed =
educationToolbar?.findViewById(R.id.menu_open_file_read_mode_key) != null
- && passwordActivityEducation.checkAndPerformedReadOnlyEducation(
+ && mPasswordActivityEducation.checkAndPerformedReadOnlyEducation(
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
{
try {
@@ -639,19 +583,19 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
} catch (e: Exception) {
Log.e(TAG, "Unable to find read mode menu")
}
- performedNextEducation(passwordActivityEducation, menu)
+ performedNextEducation(menu)
},
{
- performedNextEducation(passwordActivityEducation, menu)
+ performedNextEducation(menu)
})
- advancedUnlockFragment?.performEducation(passwordActivityEducation,
+ advancedUnlockFragment?.performEducation(mPasswordActivityEducation,
readOnlyEducationPerformed,
{
- performedNextEducation(passwordActivityEducation, menu)
+ performedNextEducation(menu)
},
{
- performedNextEducation(passwordActivityEducation, menu)
+ performedNextEducation(menu)
})
}
}
@@ -682,7 +626,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
companion object {
- private val TAG = PasswordActivity::class.java.name
+ private val TAG = MainCredentialActivity::class.java.name
private const val UNLOCK_FRAGMENT_TAG = "UNLOCK_FRAGMENT_TAG"
@@ -696,7 +640,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
intentBuildLauncher: (Intent) -> Unit) {
- val intent = Intent(activity, PasswordActivity::class.java)
+ val intent = Intent(activity, MainCredentialActivity::class.java)
intent.putExtra(KEY_FILENAME, databaseFile)
if (keyFile != null)
intent.putExtra(KEY_KEYFILE, keyFile)
@@ -832,30 +776,30 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
try {
EntrySelectionHelper.doSpecialAction(activity.intent,
{
- PasswordActivity.launch(activity,
+ MainCredentialActivity.launch(activity,
databaseUri, keyFile)
},
{ searchInfo -> // Search Action
- PasswordActivity.launchForSearchResult(activity,
+ MainCredentialActivity.launchForSearchResult(activity,
databaseUri, keyFile,
searchInfo)
onLaunchActivitySpecialMode()
},
{ searchInfo -> // Save Action
- PasswordActivity.launchForSaveResult(activity,
+ MainCredentialActivity.launchForSaveResult(activity,
databaseUri, keyFile,
searchInfo)
onLaunchActivitySpecialMode()
},
{ searchInfo -> // Keyboard Selection Action
- PasswordActivity.launchForKeyboardResult(activity,
+ MainCredentialActivity.launchForKeyboardResult(activity,
databaseUri, keyFile,
searchInfo)
onLaunchActivitySpecialMode()
},
{ searchInfo, autofillComponent -> // Autofill Selection Action
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- PasswordActivity.launchForAutofillResult(activity,
+ MainCredentialActivity.launchForAutofillResult(activity,
databaseUri, keyFile,
autofillActivityResultLauncher,
autofillComponent,
@@ -866,7 +810,7 @@ class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderL
}
},
{ registerInfo -> // Registration Action
- PasswordActivity.launchForRegistration(activity,
+ MainCredentialActivity.launchForRegistration(activity,
databaseUri, keyFile,
registerInfo)
onLaunchActivitySpecialMode()
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupDialogFragment.kt
index 8c1cb5899..c5c952842 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupDialogFragment.kt
@@ -53,6 +53,10 @@ class GroupDialogFragment : DatabaseDialogFragment() {
private lateinit var expirationView: DateTimeFieldView
private lateinit var creationView: TextView
private lateinit var modificationView: TextView
+ private lateinit var searchableLabelView: TextView
+ private lateinit var searchableView: TextView
+ private lateinit var autoTypeLabelView: TextView
+ private lateinit var autoTypeView: TextView
private lateinit var uuidContainerView: ViewGroup
private lateinit var uuidReferenceView: TextView
@@ -62,6 +66,25 @@ class GroupDialogFragment : DatabaseDialogFragment() {
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
}
mPopulateIconMethod?.invoke(iconView, mGroupInfo.icon)
+
+ if (database?.allowCustomSearchableGroup() == true) {
+ searchableLabelView.visibility = View.VISIBLE
+ searchableView.visibility = View.VISIBLE
+ } else {
+ searchableLabelView.visibility = View.GONE
+ searchableView.visibility = View.GONE
+ }
+
+ // TODO Auto-Type
+ /*
+ if (database?.allowAutoType() == true) {
+ autoTypeLabelView.visibility = View.VISIBLE
+ autoTypeView.visibility = View.VISIBLE
+ } else {
+ autoTypeLabelView.visibility = View.GONE
+ autoTypeView.visibility = View.GONE
+ }
+ */
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@@ -75,6 +98,10 @@ class GroupDialogFragment : DatabaseDialogFragment() {
expirationView = root.findViewById(R.id.group_expiration)
creationView = root.findViewById(R.id.group_created)
modificationView = root.findViewById(R.id.group_modified)
+ searchableLabelView = root.findViewById(R.id.group_searchable_label)
+ searchableView = root.findViewById(R.id.group_searchable)
+ autoTypeLabelView = root.findViewById(R.id.group_auto_type_label)
+ autoTypeView = root.findViewById(R.id.group_auto_type)
uuidContainerView = root.findViewById(R.id.group_UUID_container)
uuidReferenceView = root.findViewById(R.id.group_UUID_reference)
@@ -123,6 +150,9 @@ class GroupDialogFragment : DatabaseDialogFragment() {
expirationView.dateTime = mGroupInfo.expiryTime
creationView.text = mGroupInfo.creationTime.getDateTimeString(resources)
modificationView.text = mGroupInfo.lastModificationTime.getDateTimeString(resources)
+ searchableView.text = stringFromInheritableBoolean(mGroupInfo.searchable)
+ autoTypeView.text = stringFromInheritableBoolean(mGroupInfo.enableAutoType,
+ mGroupInfo.defaultAutoTypeSequence)
val uuid = UuidUtil.toHexString(mGroupInfo.id)
if (uuid == null || uuid.isEmpty()) {
uuidContainerView.visibility = View.GONE
@@ -143,6 +173,15 @@ class GroupDialogFragment : DatabaseDialogFragment() {
return super.onCreateDialog(savedInstanceState)
}
+ private fun stringFromInheritableBoolean(enable: Boolean?, value: String? = null): String {
+ val valueString = if (value != null && value.isNotEmpty()) " [$value]" else ""
+ return when {
+ enable == null -> getString(R.string.inherited) + valueString
+ enable -> getString(R.string.enable) + valueString
+ else -> getString(R.string.disable)
+ }
+ }
+
override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelable(KEY_GROUP_INFO, mGroupInfo)
super.onSaveInstanceState(outState)
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt
index 29de1d2c8..97483bbcd 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt
@@ -23,9 +23,8 @@ import android.app.Dialog
import android.graphics.Color
import android.os.Bundle
import android.view.View
-import android.widget.Button
-import android.widget.ImageView
-import android.widget.TextView
+import android.view.ViewGroup
+import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels
import com.google.android.material.textfield.TextInputLayout
@@ -37,6 +36,7 @@ import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.view.DateTimeEditFieldView
+import com.kunzisoft.keepass.view.InheritedCompletionView
import com.kunzisoft.keepass.view.TagsCompletionView
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
import com.tokenautocomplete.FilteredArrayAdapter
@@ -58,6 +58,12 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
private lateinit var notesTextLayoutView: TextInputLayout
private lateinit var notesTextView: TextView
private lateinit var expirationView: DateTimeEditFieldView
+ private lateinit var searchableContainerView: TextInputLayout
+ private lateinit var searchableView: InheritedCompletionView
+ private lateinit var autoTypeContainerView: ViewGroup
+ private lateinit var autoTypeInheritedView: InheritedCompletionView
+ private lateinit var autoTypeSequenceView: TextView
+ private lateinit var tagsContainerView: TextInputLayout
private lateinit var tagsCompletionView: TagsCompletionView
private var tagsAdapter: FilteredArrayAdapter? = null
@@ -118,11 +124,24 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
}
mPopulateIconMethod?.invoke(iconButtonView, mGroupInfo.icon)
+ searchableContainerView.visibility = if (database?.allowCustomSearchableGroup() == true) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+
+ if (database?.allowAutoType() == true) {
+ autoTypeContainerView.visibility = View.VISIBLE
+ } else {
+ autoTypeContainerView.visibility = View.GONE
+ }
+
tagsAdapter = TagsProposalAdapter(requireContext(), database?.tagPool)
tagsCompletionView.apply {
threshold = 1
setAdapter(tagsAdapter)
}
+ tagsContainerView.visibility = if (database?.allowTags() == true) View.VISIBLE else View.GONE
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
@@ -134,6 +153,12 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
notesTextLayoutView = root.findViewById(R.id.group_edit_note_container)
notesTextView = root.findViewById(R.id.group_edit_note)
expirationView = root.findViewById(R.id.group_edit_expiration)
+ searchableContainerView = root.findViewById(R.id.group_edit_searchable_container)
+ searchableView = root.findViewById(R.id.group_edit_searchable)
+ autoTypeContainerView = root.findViewById(R.id.group_edit_auto_type_container)
+ autoTypeInheritedView = root.findViewById(R.id.group_edit_auto_type_inherited)
+ autoTypeSequenceView = root.findViewById(R.id.group_edit_auto_type_sequence)
+ tagsContainerView = root.findViewById(R.id.group_tags_label)
tagsCompletionView = root.findViewById(R.id.group_tags_completion_view)
// Retrieve the textColor to tint the icon
@@ -211,6 +236,11 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
expirationView.activation = groupInfo.expires
expirationView.dateTime = groupInfo.expiryTime
+ // Set searchable
+ searchableView.setValue(groupInfo.searchable)
+ // Set auto-type
+ autoTypeInheritedView.setValue(groupInfo.enableAutoType)
+ autoTypeSequenceView.text = groupInfo.defaultAutoTypeSequence
// Set Tags
groupInfo.tags.let { tags ->
tagsCompletionView.setText("")
@@ -229,6 +259,9 @@ class GroupEditDialogFragment : DatabaseDialogFragment() {
}
mGroupInfo.expires = expirationView.activation
mGroupInfo.expiryTime = expirationView.dateTime
+ mGroupInfo.searchable = searchableView.getValue()
+ mGroupInfo.enableAutoType = autoTypeInheritedView.getValue()
+ mGroupInfo.defaultAutoTypeSequence = autoTypeSequenceView.text.toString()
mGroupInfo.tags = tagsCompletionView.getTags()
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/MainCredentialDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/MainCredentialDialogFragment.kt
new file mode 100644
index 000000000..d45c3ea97
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/MainCredentialDialogFragment.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2022 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.activities.dialogs
+
+import android.app.Dialog
+import android.content.Context
+import android.net.Uri
+import android.os.Bundle
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
+import com.kunzisoft.keepass.model.MainCredential
+import com.kunzisoft.keepass.utils.UriUtil
+import com.kunzisoft.keepass.view.MainCredentialView
+
+class MainCredentialDialogFragment : DatabaseDialogFragment() {
+
+ private var mainCredentialView: MainCredentialView? = null
+
+ private var mListener: AskMainCredentialDialogListener? = null
+
+ private var mExternalFileHelper: ExternalFileHelper? = null
+
+ interface AskMainCredentialDialogListener {
+ 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 {
+ activity?.let { activity ->
+
+ var databaseUri: Uri? = null
+ arguments?.apply {
+ if (containsKey(KEY_ASK_CREDENTIAL_URI))
+ databaseUri = getParcelable(KEY_ASK_CREDENTIAL_URI)
+ }
+
+ val builder = AlertDialog.Builder(activity)
+
+ val root = activity.layoutInflater.inflate(R.layout.fragment_main_credential, null)
+ mainCredentialView = root.findViewById(R.id.main_credential_view)
+ databaseUri?.let {
+ root.findViewById(R.id.title_database)?.text =
+ UriUtil.getFileData(requireContext(), it)?.name
+ }
+ builder.setView(root)
+ // Add action buttons
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ mListener?.onAskMainCredentialDialogPositiveClick(
+ databaseUri,
+ retrieveMainCredential()
+ )
+ }
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ mListener?.onAskMainCredentialDialogNegativeClick(
+ databaseUri,
+ retrieveMainCredential()
+ )
+ }
+
+
+ mExternalFileHelper = ExternalFileHelper(this)
+ mExternalFileHelper?.buildOpenDocument { uri ->
+ if (uri != null) {
+ mainCredentialView?.populateKeyFileTextView(uri)
+ }
+ }
+ mainCredentialView?.setOpenKeyfileClickListener(mExternalFileHelper)
+
+ return builder.create()
+ }
+
+ return super.onCreateDialog(savedInstanceState)
+ }
+
+ private fun retrieveMainCredential(): MainCredential {
+ return mainCredentialView?.getMainCredential() ?: MainCredential()
+ }
+
+ companion object {
+
+ private const val KEY_ASK_CREDENTIAL_URI = "KEY_ASK_CREDENTIAL_URI"
+ const val TAG_ASK_MAIN_CREDENTIAL = "TAG_ASK_MAIN_CREDENTIAL"
+
+ fun getInstance(uri: Uri?): MainCredentialDialogFragment {
+ val fragment = MainCredentialDialogFragment()
+ val args = Bundle()
+ args.putParcelable(KEY_ASK_CREDENTIAL_URI, uri)
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetMainCredentialDialogFragment.kt
similarity index 94%
rename from app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt
rename to app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetMainCredentialDialogFragment.kt
index b976f1f54..b8b78c0f4 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/AssignMasterKeyDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetMainCredentialDialogFragment.kt
@@ -39,7 +39,7 @@ import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.KeyFileSelectionView
-class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
+class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
private var mMasterPassword: String? = null
private var mKeyFile: Uri? = null
@@ -56,7 +56,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
private var keyFileCheckBox: CompoundButton? = null
private var keyFileSelectionView: KeyFileSelectionView? = null
- private var mListener: AssignPasswordDialogListener? = null
+ private var mListener: AssignMainCredentialDialogListener? = null
private var mExternalFileHelper: ExternalFileHelper? = null
@@ -74,7 +74,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
}
}
- interface AssignPasswordDialogListener {
+ interface AssignMainCredentialDialogListener {
fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential)
fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential)
}
@@ -82,10 +82,10 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
override fun onAttach(activity: Context) {
super.onAttach(activity)
try {
- mListener = activity as AssignPasswordDialogListener
+ mListener = activity as AssignMainCredentialDialogListener
} catch (e: ClassCastException) {
throw ClassCastException(activity.toString()
- + " must implement " + AssignPasswordDialogListener::class.java.name)
+ + " must implement " + AssignMainCredentialDialogListener::class.java.name)
}
}
@@ -112,7 +112,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
- rootView = inflater.inflate(R.layout.fragment_set_password, null)
+ rootView = inflater.inflate(R.layout.fragment_set_main_credential, null)
builder.setView(rootView)
// Add action buttons
.setPositiveButton(android.R.string.ok) { _, _ -> }
@@ -254,7 +254,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
.setPositiveButton(android.R.string.ok) { _, _ ->
if (!verifyKeyFile()) {
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
- this@AssignMasterKeyDialogFragment.dismiss()
+ this@SetMainCredentialDialogFragment.dismiss()
}
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
@@ -269,7 +269,7 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
builder.setMessage(R.string.warning_no_encryption_key)
.setPositiveButton(android.R.string.ok) { _, _ ->
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
- this@AssignMasterKeyDialogFragment.dismiss()
+ this@SetMainCredentialDialogFragment.dismiss()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
mNoKeyConfirmationDialog = builder.create()
@@ -301,8 +301,8 @@ class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
private const val ALLOW_NO_MASTER_KEY_ARG = "ALLOW_NO_MASTER_KEY_ARG"
- fun getInstance(allowNoMasterKey: Boolean): AssignMasterKeyDialogFragment {
- val fragment = AssignMasterKeyDialogFragment()
+ fun getInstance(allowNoMasterKey: Boolean): SetMainCredentialDialogFragment {
+ val fragment = SetMainCredentialDialogFragment()
val args = Bundle()
args.putBoolean(ALLOW_NO_MASTER_KEY_ARG, allowNoMasterKey)
fragment.arguments = args
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryEditFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryEditFragment.kt
index 46b040868..08fb5e20d 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryEditFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryEditFragment.kt
@@ -29,6 +29,7 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
+import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.ReplaceFileDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
@@ -55,6 +56,7 @@ class EntryEditFragment: DatabaseFragment() {
private lateinit var attachmentsContainerView: ViewGroup
private lateinit var attachmentsListView: RecyclerView
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
+ private lateinit var tagsContainerView: TextInputLayout
private lateinit var tagsCompletionView: TagsCompletionView
private var tagsAdapter: FilteredArrayAdapter? = null
@@ -89,6 +91,7 @@ class EntryEditFragment: DatabaseFragment() {
templateView = view.findViewById(R.id.template_view)
attachmentsContainerView = view.findViewById(R.id.entry_attachments_container)
attachmentsListView = view.findViewById(R.id.entry_attachments_list)
+ tagsContainerView = view.findViewById(R.id.entry_tags_label)
tagsCompletionView = view.findViewById(R.id.entry_tags_completion_view)
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
@@ -157,11 +160,11 @@ class EntryEditFragment: DatabaseFragment() {
templateView.setIcon(iconImage)
}
- mEntryEditViewModel.onBackgroundColorSelected.observe(this) { color ->
+ mEntryEditViewModel.onBackgroundColorSelected.observe(viewLifecycleOwner) { color ->
templateView.setBackgroundColor(color)
}
- mEntryEditViewModel.onForegroundColorSelected.observe(this) { color ->
+ mEntryEditViewModel.onForegroundColorSelected.observe(viewLifecycleOwner) { color ->
templateView.setForegroundColor(color)
}
@@ -287,6 +290,7 @@ class EntryEditFragment: DatabaseFragment() {
threshold = 1
setAdapter(tagsAdapter)
}
+ tagsContainerView.visibility = if (database?.allowTags() == true) View.VISIBLE else View.GONE
}
private fun assignEntryInfo(entryInfo: EntryInfo?) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt
index 0e9e9ca2e..cf4b49287 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt
@@ -41,6 +41,8 @@ class EntryFragment: DatabaseFragment() {
private lateinit var attachmentsListView: RecyclerView
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
+ private lateinit var customDataView: TextView
+
private lateinit var uuidContainerView: View
private lateinit var uuidReferenceView: TextView
@@ -83,6 +85,9 @@ class EntryFragment: DatabaseFragment() {
creationDateView = view.findViewById(R.id.entry_created)
modificationDateView = view.findViewById(R.id.entry_modified)
+ // TODO Custom data
+ // customDataView = view.findViewById(R.id.entry_custom_data)
+
uuidContainerView = view.findViewById(R.id.entry_UUID_container)
uuidContainerView.apply {
visibility = if (PreferencesUtil.showUUID(context)) View.VISIBLE else View.GONE
@@ -154,11 +159,14 @@ class EntryFragment: DatabaseFragment() {
assignAttachments(entryInfo?.attachments ?: listOf())
// Assign dates
- assignCreationDate(entryInfo?.creationTime)
- assignModificationDate(entryInfo?.lastModificationTime)
+ creationDateView.text = entryInfo?.creationTime?.getDateTimeString(resources)
+ modificationDateView.text = entryInfo?.lastModificationTime?.getDateTimeString(resources)
+
+ // TODO Custom data
+ // customDataView.text = entryInfo?.customData?.toString()
// Assign special data
- assignUUID(entryInfo?.id)
+ uuidReferenceView.text = UuidUtil.toHexString(entryInfo?.id)
}
private fun showClipboardDialog() {
@@ -189,18 +197,6 @@ class EntryFragment: DatabaseFragment() {
templateView.reload()
}
- private fun assignCreationDate(date: DateInstant?) {
- creationDateView.text = date?.getDateTimeString(resources)
- }
-
- private fun assignModificationDate(date: DateInstant?) {
- modificationDateView.text = date?.getDateTimeString(resources)
- }
-
- private fun assignUUID(uuid: UUID?) {
- uuidReferenceView.text = UuidUtil.toHexString(uuid)
- }
-
/* -------------
* Attachments
* -------------
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/GroupFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/GroupFragment.kt
index f7d857838..8179f3381 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/GroupFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/GroupFragment.kt
@@ -152,7 +152,8 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
mAdapter = NodesAdapter(context, database).apply {
setOnNodeClickListener(object : NodesAdapter.NodeClickCallback {
override fun onNodeClick(database: Database, node: Node) {
- if (nodeActionSelectionMode) {
+ if (mCurrentGroup?.isVirtual == false
+ && nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
listActionNodes.remove(node)
@@ -169,7 +170,8 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
}
override fun onNodeLongClick(database: Database, node: Node): Boolean {
- if (nodeActionPasteMode == PasteMode.UNDEFINED) {
+ if (mCurrentGroup?.isVirtual == false
+ && nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
listActionNodes.add(node)
@@ -257,9 +259,9 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
private fun rebuildList() {
try {
// Add elements to the list
- mCurrentGroup?.let { mainGroup ->
+ mCurrentGroup?.let { currentGroup ->
// Thrown an exception when sort cannot be performed
- mAdapter?.rebuildList(mainGroup)
+ mAdapter?.rebuildList(currentGroup)
}
} catch (e:Exception) {
Log.e(TAG, "Unable to rebuild the list", e)
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseActivity.kt
index 5f62542de..c1d2e72b9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseActivity.kt
@@ -4,9 +4,9 @@ import android.net.Uri
import android.os.Bundle
import androidx.activity.viewModels
import com.kunzisoft.keepass.activities.stylish.StylishActivity
-import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
@@ -59,9 +59,9 @@ abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
fun loadDatabase(databaseUri: Uri,
mainCredential: MainCredential,
readOnly: Boolean,
- cipherEntity: CipherDatabaseEntity?,
+ cipherEncryptDatabase: CipherEncryptDatabase?,
fixDuplicateUuid: Boolean) {
- mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEntity, fixDuplicateUuid)
+ mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEncryptDatabase, fixDuplicateUuid)
}
protected fun closeDatabase() {
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt
index 6b5e79d64..4b75c1998 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt
@@ -88,8 +88,8 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseSave(save)
}
- mDatabaseViewModel.mergeDatabase.observe(this) { fixDuplicateUuid ->
- mDatabaseTaskProvider?.startDatabaseMerge(fixDuplicateUuid)
+ mDatabaseViewModel.mergeDatabase.observe(this) {
+ mDatabaseTaskProvider?.startDatabaseMerge()
}
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
@@ -263,8 +263,16 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
mDatabaseTaskProvider?.startDatabaseSave(true)
}
+ fun saveDatabaseTo(uri: Uri) {
+ mDatabaseTaskProvider?.startDatabaseSave(true, uri)
+ }
+
fun mergeDatabase() {
- mDatabaseTaskProvider?.startDatabaseMerge(false)
+ mDatabaseTaskProvider?.startDatabaseMerge()
+ }
+
+ fun mergeDatabaseFrom(uri: Uri, mainCredential: MainCredential) {
+ mDatabaseTaskProvider?.startDatabaseMerge(uri, mainCredential)
}
fun reloadDatabase() {
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseModeActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseModeActivity.kt
index ae7959ba1..573bc7070 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseModeActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseModeActivity.kt
@@ -1,6 +1,8 @@
package com.kunzisoft.keepass.activities.legacy
import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
import android.view.View
import android.widget.Toast
import com.kunzisoft.keepass.R
@@ -11,6 +13,7 @@ import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.SpecialModeView
+
/**
* Activity to manage database special mode (ie: selection mode)
*/
@@ -63,8 +66,7 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
EntrySelectionHelper.removeModesFromIntent(intent)
EntrySelectionHelper.removeInfoFromIntent(intent)
if (mSpecialMode != SpecialMode.DEFAULT) {
- // To move the app in background
- moveTaskToBack(true)
+ backToTheMainAppAndFinish()
}
}
}
@@ -77,8 +79,7 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
EntrySelectionHelper.removeModesFromIntent(intent)
EntrySelectionHelper.removeInfoFromIntent(intent)
if (mSpecialMode != SpecialMode.DEFAULT) {
- // To move the app in background
- moveTaskToBack(true)
+ backToTheMainAppAndFinish()
}
}
}
@@ -88,11 +89,19 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
// To get the app caller, only for IntentSender
super.onBackPressed()
} else {
- // To move the app in background
- moveTaskToBack(true)
+ backToTheMainAppAndFinish()
}
}
+ private fun backToTheMainAppAndFinish() {
+ // To move the app in background and return to the main app
+ moveTaskToBack(true)
+ // To remove this instance in the OS app selector
+ Handler(Looper.getMainLooper()).postDelayed({
+ finish()
+ }, 500)
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -160,12 +169,17 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
}
// To hide home button from the regular toolbar in special mode
- if (mSpecialMode != SpecialMode.DEFAULT) {
+ if (mSpecialMode != SpecialMode.DEFAULT
+ && hideHomeButtonIfModeIsNotDefault()) {
supportActionBar?.setDisplayHomeAsUpEnabled(false)
supportActionBar?.setDisplayShowHomeEnabled(false)
}
}
+ open fun hideHomeButtonIfModeIsNotDefault(): Boolean {
+ return true
+ }
+
private fun blockAutofill(searchInfo: SearchInfo?) {
val webDomain = searchInfo?.webDomain
val applicationId = searchInfo?.applicationId
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/stylish/Stylish.kt b/app/src/main/java/com/kunzisoft/keepass/activities/stylish/Stylish.kt
index 9972dc227..dd8a98920 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/stylish/Stylish.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/stylish/Stylish.kt
@@ -69,8 +69,10 @@ object Stylish {
context.getString(R.string.list_style_name_night) -> context.getString(R.string.list_style_name_light)
context.getString(R.string.list_style_name_black) -> context.getString(R.string.list_style_name_white)
context.getString(R.string.list_style_name_dark) -> context.getString(R.string.list_style_name_clear)
+ context.getString(R.string.list_style_name_simple_night) -> context.getString(R.string.list_style_name_simple)
context.getString(R.string.list_style_name_blue_night) -> context.getString(R.string.list_style_name_blue)
context.getString(R.string.list_style_name_red_night) -> context.getString(R.string.list_style_name_red)
+ context.getString(R.string.list_style_name_reply_night) -> context.getString(R.string.list_style_name_reply)
context.getString(R.string.list_style_name_purple_dark) -> context.getString(R.string.list_style_name_purple)
else -> styleString
}
@@ -81,8 +83,10 @@ object Stylish {
context.getString(R.string.list_style_name_light) -> context.getString(R.string.list_style_name_night)
context.getString(R.string.list_style_name_white) -> context.getString(R.string.list_style_name_black)
context.getString(R.string.list_style_name_clear) -> context.getString(R.string.list_style_name_dark)
+ context.getString(R.string.list_style_name_simple) -> context.getString(R.string.list_style_name_simple_night)
context.getString(R.string.list_style_name_blue) -> context.getString(R.string.list_style_name_blue_night)
context.getString(R.string.list_style_name_red) -> context.getString(R.string.list_style_name_red_night)
+ context.getString(R.string.list_style_name_reply) -> context.getString(R.string.list_style_name_reply_night)
context.getString(R.string.list_style_name_purple) -> context.getString(R.string.list_style_name_purple_dark)
else -> styleString
}
@@ -113,10 +117,14 @@ object Stylish {
context.getString(R.string.list_style_name_black) -> R.style.KeepassDXStyle_Black
context.getString(R.string.list_style_name_clear) -> R.style.KeepassDXStyle_Clear
context.getString(R.string.list_style_name_dark) -> R.style.KeepassDXStyle_Dark
+ context.getString(R.string.list_style_name_simple) -> R.style.KeepassDXStyle_Simple
+ context.getString(R.string.list_style_name_simple_night) -> R.style.KeepassDXStyle_Simple_Night
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
context.getString(R.string.list_style_name_blue_night) -> R.style.KeepassDXStyle_Blue_Night
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red
context.getString(R.string.list_style_name_red_night) -> R.style.KeepassDXStyle_Red_Night
+ context.getString(R.string.list_style_name_reply) -> R.style.KeepassDXStyle_Reply
+ context.getString(R.string.list_style_name_reply_night) -> R.style.KeepassDXStyle_Reply_Night
context.getString(R.string.list_style_name_purple) -> R.style.KeepassDXStyle_Purple
context.getString(R.string.list_style_name_purple_dark) -> R.style.KeepassDXStyle_Purple_Dark
else -> R.style.KeepassDXStyle_Light
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/stylish/StylishFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/stylish/StylishFragment.kt
index ddb69d1c6..cf279544a 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/stylish/StylishFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/stylish/StylishFragment.kt
@@ -23,11 +23,13 @@ import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StyleRes
import androidx.appcompat.view.ContextThemeWrapper
+import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
abstract class StylishFragment : Fragment() {
@@ -47,27 +49,41 @@ abstract class StylishFragment : Fragment() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = requireActivity().window
val defaultColor = Color.BLACK
-
+ val windowInset = WindowInsetsControllerCompat(window, window.decorView)
try {
val taStatusBarColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.statusBarColor))
window.statusBarColor = taStatusBarColor?.getColor(0, defaultColor) ?: defaultColor
taStatusBarColor?.recycle()
- } catch (e: Exception) {}
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to retrieve theme : status bar color", e)
+ }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
val taWindowStatusLight = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.windowLightStatusBar))
- if (taWindowStatusLight?.getBoolean(0, false) == true) {
- @Suppress("DEPRECATION")
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
- }
+ windowInset.isAppearanceLightStatusBars = taWindowStatusLight
+ ?.getBoolean(0, false) == true
taWindowStatusLight?.recycle()
- } catch (e: Exception) {}
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to retrieve theme : window light status bar", e)
+ }
}
try {
val taNavigationBarColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.navigationBarColor))
window.navigationBarColor = taNavigationBarColor?.getColor(0, defaultColor) ?: defaultColor
taNavigationBarColor?.recycle()
- } catch (e: Exception) {}
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to retrieve theme : navigation bar color", e)
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ try {
+ val taWindowLightNavigationBar = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.windowLightNavigationBar))
+ windowInset.isAppearanceLightNavigationBars = taWindowLightNavigationBar
+ ?.getBoolean(0, false) == true
+ taWindowLightNavigationBar?.recycle()
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to retrieve theme : navigation light navigation bar", e)
+ }
+ }
}
return super.onCreateView(inflater, container, savedInstanceState)
}
@@ -76,4 +92,8 @@ abstract class StylishFragment : Fragment() {
contextThemed = null
super.onDetach()
}
+
+ companion object {
+ private val TAG = StylishFragment::class.java.simpleName
+ }
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt
index bc304c8ee..c5fe46ada 100644
--- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt
@@ -29,7 +29,6 @@ import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.ColorInt
-import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback
@@ -87,8 +86,6 @@ class NodesAdapter (private val context: Context,
private var mNodeClickCallback: NodeClickCallback? = null
private var mClipboardHelper = ClipboardHelper(context)
- @ColorInt
- private val mContentSelectionColor: Int
@ColorInt
private val mTextColorPrimary: Int
@ColorInt
@@ -98,7 +95,7 @@ class NodesAdapter (private val context: Context,
@ColorInt
private val mColorAccentLight: Int
@ColorInt
- private val mTextColorSelected: Int
+ private val mColorOnAccentColor: Int
/**
* Determine if the adapter contains or not any element
@@ -115,8 +112,6 @@ class NodesAdapter (private val context: Context,
this.mNodeSortedListCallback = NodeSortedListCallback()
this.mNodeSortedList = SortedList(Node::class.java, mNodeSortedListCallback)
- // Color of content selection
- this.mContentSelectionColor = ContextCompat.getColor(context, R.color.white)
// Retrieve the color to tint the icon
val taTextColorPrimary = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary))
this.mTextColorPrimary = taTextColorPrimary.getColor(0, Color.BLACK)
@@ -130,13 +125,13 @@ class NodesAdapter (private val context: Context,
this.mTextColorSecondary = taTextColorSecondary.getColor(0, Color.BLACK)
taTextColorSecondary.recycle()
// To get background color for selection
- val taSelectionColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccentLight))
- this.mColorAccentLight = taSelectionColor.getColor(0, Color.GRAY)
- taSelectionColor.recycle()
+ val taColorAccentLight = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccentLight))
+ this.mColorAccentLight = taColorAccentLight.getColor(0, Color.GRAY)
+ taColorAccentLight.recycle()
// To get text color for selection
- val taSelectionTextColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorOnAccentColor))
- this.mTextColorSelected = taSelectionTextColor.getColor(0, Color.WHITE)
- taSelectionTextColor.recycle()
+ val taColorOnAccentColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorOnAccentColor))
+ this.mColorOnAccentColor = taColorOnAccentColor.getColor(0, Color.WHITE)
+ taColorOnAccentColor.recycle()
}
private fun assignPreferences() {
@@ -352,23 +347,6 @@ class NodesAdapter (private val context: Context,
isSelected = mActionNodesList.contains(subNode)
}
- // Assign image
- val iconColor = if (holder.container.isSelected)
- mContentSelectionColor
- else when (subNode.type) {
- Type.GROUP -> mTextColorPrimary
- Type.ENTRY -> mTextColor
- }
- holder.imageIdentifier?.setColorFilter(iconColor)
- holder.icon.apply {
- database.iconDrawableFactory.assignDatabaseIcon(this, subNode.icon, iconColor)
- // Relative size of the icon
- layoutParams?.apply {
- height = (mIconDefaultDimension * mPrefSizeMultiplier).toInt()
- width = (mIconDefaultDimension * mPrefSizeMultiplier).toInt()
- }
- }
-
// Assign text
holder.text.apply {
text = subNode.title
@@ -396,6 +374,14 @@ class NodesAdapter (private val context: Context,
holder.path?.visibility = View.GONE
}
+ // Assign icon colors
+ var iconColor = if (holder.container.isSelected)
+ mColorOnAccentColor
+ else when (subNode.type) {
+ Type.GROUP -> mTextColorPrimary
+ Type.ENTRY -> mTextColor
+ }
+
// Specific elements for entry
if (subNode.type == Type.ENTRY) {
val entry = subNode as Entry
@@ -457,13 +443,7 @@ class NodesAdapter (private val context: Context,
holder.otpProgress?.setIndicatorColor(foregroundColor)
holder.attachmentIcon?.setColorFilter(foregroundColor)
holder.meta.setTextColor(foregroundColor)
- holder.icon.apply {
- database.iconDrawableFactory.assignDatabaseIcon(
- this,
- subNode.icon,
- foregroundColor
- )
- }
+ iconColor = foregroundColor
} else {
holder.text.setTextColor(mTextColor)
holder.subText?.setTextColor(mTextColorSecondary)
@@ -473,12 +453,12 @@ class NodesAdapter (private val context: Context,
holder.meta.setTextColor(mTextColor)
}
} else {
- holder.text.setTextColor(mTextColorSelected)
- holder.subText?.setTextColor(mTextColorSelected)
- holder.otpToken?.setTextColor(mTextColorSelected)
- holder.otpProgress?.setIndicatorColor(mTextColorSelected)
- holder.attachmentIcon?.setColorFilter(mTextColorSelected)
- holder.meta.setTextColor(mTextColorSelected)
+ holder.text.setTextColor(mColorOnAccentColor)
+ holder.subText?.setTextColor(mColorOnAccentColor)
+ holder.otpToken?.setTextColor(mColorOnAccentColor)
+ holder.otpProgress?.setIndicatorColor(mColorOnAccentColor)
+ holder.attachmentIcon?.setColorFilter(mColorOnAccentColor)
+ holder.meta.setTextColor(mColorOnAccentColor)
}
database.stopManageEntry(entry)
@@ -499,6 +479,17 @@ class NodesAdapter (private val context: Context,
}
}
+ // Assign image
+ holder.imageIdentifier?.setColorFilter(iconColor)
+ holder.icon.apply {
+ database.iconDrawableFactory.assignDatabaseIcon(this, subNode.icon, iconColor)
+ // Relative size of the icon
+ layoutParams?.apply {
+ height = (mIconDefaultDimension * mPrefSizeMultiplier).toInt()
+ width = (mIconDefaultDimension * mPrefSizeMultiplier).toInt()
+ }
+ }
+
// Assign click
holder.container.setOnClickListener {
mNodeClickCallback?.onNodeClick(database, subNode)
diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/SearchEntryCursorAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/SearchEntryCursorAdapter.kt
deleted file mode 100644
index 06d576f21..000000000
--- a/app/src/main/java/com/kunzisoft/keepass/adapters/SearchEntryCursorAdapter.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright 2019 Jeremy Jamet / Kunzisoft.
- *
- * This file is part of KeePassDX.
- *
- * KeePassDX is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KeePassDX is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KeePassDX. If not, see .
- *
- */
-package com.kunzisoft.keepass.adapters
-
-import android.content.Context
-import android.database.Cursor
-import android.database.MatrixCursor
-import android.graphics.Color
-import android.provider.BaseColumns
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.cursoradapter.widget.CursorAdapter
-import com.kunzisoft.keepass.R
-import com.kunzisoft.keepass.database.element.Database
-import com.kunzisoft.keepass.database.element.Entry
-import com.kunzisoft.keepass.database.element.Group
-import com.kunzisoft.keepass.database.element.node.NodeId
-import com.kunzisoft.keepass.database.element.node.NodeIdUUID
-import com.kunzisoft.keepass.database.search.SearchHelper
-import com.kunzisoft.keepass.settings.PreferencesUtil
-import com.kunzisoft.keepass.view.strikeOut
-import java.util.*
-
-class SearchEntryCursorAdapter(private val context: Context,
- private val database: Database)
- : CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
-
- private val cursorInflater: LayoutInflater? = context.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
- private var mDisplayUsername: Boolean = false
- private var mOmitBackup: Boolean = true
- private val iconColor: Int
-
- init {
- // Get the icon color
- val taTextColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
- this.iconColor = taTextColor.getColor(0, Color.WHITE)
- taTextColor.recycle()
-
- reInit(context)
- }
-
- fun reInit(context: Context) {
- this.mDisplayUsername = PreferencesUtil.showUsernamesListEntries(context)
- this.mOmitBackup = PreferencesUtil.omitBackup(context)
- }
-
- override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
-
- val view = cursorInflater!!.inflate(R.layout.item_search_entry, parent, false)
- val viewHolder = ViewHolder()
- viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon)
- viewHolder.textViewTitle = view.findViewById(R.id.entry_text)
- viewHolder.textViewSubTitle = view.findViewById(R.id.entry_subtext)
- viewHolder.textViewPath = view.findViewById(R.id.entry_path)
- view.tag = viewHolder
-
- return view
- }
-
- override fun bindView(view: View, context: Context, cursor: Cursor?) {
- getEntryFrom(cursor)?.let { currentEntry ->
- val viewHolder = view.tag as ViewHolder
-
- // Assign image
- viewHolder.imageViewIcon?.let { iconView ->
- database.iconDrawableFactory.assignDatabaseIcon(iconView, currentEntry.icon, iconColor)
- }
-
- // Assign title
- viewHolder.textViewTitle?.apply {
- text = currentEntry.getVisualTitle()
- strikeOut(currentEntry.isCurrentlyExpires)
- }
-
- // Assign subtitle
- viewHolder.textViewSubTitle?.apply {
- val entryUsername = currentEntry.username
- text = if (mDisplayUsername && entryUsername.isNotEmpty()) {
- String.format("(%s)", entryUsername)
- } else {
- ""
- }
- visibility = if (text.isEmpty()) View.GONE else View.VISIBLE
- strikeOut(currentEntry.isCurrentlyExpires)
- }
-
- viewHolder.textViewPath?.apply {
- text = currentEntry.getPathString()
- }
- }
- }
-
- private fun getEntryFrom(cursor: Cursor?): Entry? {
- val entryCursor = cursor as? EntryCursor?
- entryCursor?.getNodeId()?.let {
- return database.getEntryById(it)
- }
- return null
- }
-
- override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
- return searchEntries(context, constraint.toString())
- }
-
- private fun searchEntries(context: Context, query: String): Cursor {
- val cursor = EntryCursor()
- val searchGroup = database.createVirtualGroupFromSearch(query,
- mOmitBackup,
- SearchHelper.MAX_SEARCH_ENTRY)
- if (searchGroup != null) {
- // Search in hide entries but not meta-stream
- for (entry in searchGroup.getFilteredChildEntries(Group.ChildFilter.getDefaults(context))) {
- database.startManageEntry(entry)
- cursor.addEntry(entry)
- database.stopManageEntry(entry)
- }
- }
- return cursor
- }
-
- fun getEntryFromPosition(position: Int): Entry? {
- var pwEntry: Entry? = null
-
- val cursor = this.cursor
- if (cursor.moveToFirst() && cursor.move(position)) {
- pwEntry = getEntryFrom(cursor)
- }
- return pwEntry
- }
-
- private class ViewHolder {
- var imageViewIcon: ImageView? = null
- var textViewTitle: TextView? = null
- var textViewSubTitle: TextView? = null
- var textViewPath: TextView? = null
- }
-
- private class EntryCursor : MatrixCursor(arrayOf(
- ID,
- COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
- COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS
- )) {
-
- private var entryId: Long = 0
-
- fun addEntry(entry: Entry) {
- addRow(arrayOf(
- entryId,
- entry.nodeId.id.mostSignificantBits,
- entry.nodeId.id.leastSignificantBits
- ))
- entryId++
- }
-
- fun getNodeId(): NodeId {
- return NodeIdUUID(
- UUID(getLong(getColumnIndex(COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
- getLong(getColumnIndex(COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)))
- )
- }
-
- companion object {
- const val ID = BaseColumns._ID
- const val COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS = "UUID_most_significant_bits"
- const val COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS = "UUID_least_significant_bits"
- }
- }
-}
diff --git a/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt
index 4ede01649..d39f8c6bc 100644
--- a/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/app/database/CipherDatabaseAction.kt
@@ -22,7 +22,9 @@ package com.kunzisoft.keepass.app.database
import android.content.*
import android.net.Uri
import android.os.IBinder
+import android.util.Base64
import android.util.Log
+import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.SingletonHolderParameter
@@ -125,15 +127,40 @@ class CipherDatabaseAction(context: Context) {
}
fun getCipherDatabase(databaseUri: Uri,
- cipherDatabaseResultListener: (CipherDatabaseEntity?) -> Unit) {
+ cipherDatabaseResultListener: (CipherEncryptDatabase?) -> Unit) {
if (useTempDao) {
serviceActionTask {
- cipherDatabaseResultListener.invoke(mBinder?.getCipherDatabase(databaseUri))
+ mBinder?.getCipherDatabase(databaseUri)?.let { cipherDatabaseEntity ->
+ val cipherDatabase = CipherEncryptDatabase().apply {
+ this.databaseUri = Uri.parse(cipherDatabaseEntity.databaseUri)
+ this.encryptedValue = Base64.decode(
+ cipherDatabaseEntity.encryptedValue,
+ Base64.NO_WRAP
+ )
+ this.specParameters = Base64.decode(
+ cipherDatabaseEntity.specParameters,
+ Base64.NO_WRAP
+ )
+ }
+ cipherDatabaseResultListener.invoke(cipherDatabase)
+ }
}
} else {
IOActionTask(
{
- cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())
+ cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())?.let { cipherDatabaseEntity ->
+ CipherEncryptDatabase().apply {
+ this.databaseUri = Uri.parse(cipherDatabaseEntity.databaseUri)
+ this.encryptedValue = Base64.decode(
+ cipherDatabaseEntity.encryptedValue,
+ Base64.NO_WRAP
+ )
+ this.specParameters = Base64.decode(
+ cipherDatabaseEntity.specParameters,
+ Base64.NO_WRAP
+ )
+ }
+ }
},
{
cipherDatabaseResultListener.invoke(it)
@@ -149,18 +176,27 @@ class CipherDatabaseAction(context: Context) {
}
}
- fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity,
+ fun addOrUpdateCipherDatabase(cipherEncryptDatabase: CipherEncryptDatabase,
cipherDatabaseResultListener: (() -> Unit)? = null) {
- if (useTempDao) {
- // The only case to create service (not needed to get an info)
- serviceActionTask(true) {
- mBinder?.addOrUpdateCipherDatabase(cipherDatabaseEntity)
- cipherDatabaseResultListener?.invoke()
- }
- } else {
- IOActionTask(
+ cipherEncryptDatabase.databaseUri?.let { databaseUri ->
+
+ val cipherDatabaseEntity = CipherDatabaseEntity(
+ databaseUri.toString(),
+ Base64.encodeToString(cipherEncryptDatabase.encryptedValue, Base64.NO_WRAP),
+ Base64.encodeToString(cipherEncryptDatabase.specParameters, Base64.NO_WRAP),
+ )
+
+ if (useTempDao) {
+ // The only case to create service (not needed to get an info)
+ serviceActionTask(true) {
+ mBinder?.addOrUpdateCipherDatabase(cipherDatabaseEntity)
+ cipherDatabaseResultListener?.invoke()
+ }
+ } else {
+ IOActionTask(
{
- val cipherDatabaseRetrieve = cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
+ val cipherDatabaseRetrieve =
+ cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
// Update values if element not yet in the database
if (cipherDatabaseRetrieve == null) {
cipherDatabaseDao.add(cipherDatabaseEntity)
@@ -171,7 +207,8 @@ class CipherDatabaseAction(context: Context) {
{
cipherDatabaseResultListener?.invoke()
}
- ).execute()
+ ).execute()
+ }
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt
index c933ca3da..bc8b93599 100644
--- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockFragment.kt
@@ -40,6 +40,9 @@ import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.database.exception.IODatabaseException
import com.kunzisoft.keepass.education.PasswordActivityEducation
+import com.kunzisoft.keepass.model.CipherDecryptDatabase
+import com.kunzisoft.keepass.model.CipherEncryptDatabase
+import com.kunzisoft.keepass.model.CredentialStorage
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
@@ -60,6 +63,9 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
var databaseFileUri: Uri? = null
private set
+ // TODO Retrieve credential storage from app database
+ var credentialDatabaseStorage: CredentialStorage = CredentialStorage.DEFAULT
+
/**
* Manage setting to auto open biometric prompt
*/
@@ -477,6 +483,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
} ?: checkUnlockAvailability()
}
+ @RequiresApi(Build.VERSION_CODES.M)
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
lifecycleScope.launch(Dispatchers.Main) {
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
@@ -528,16 +535,29 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
}
- override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
+ override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {
databaseFileUri?.let { databaseUri ->
- mBuilderListener?.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec)
+ mBuilderListener?.onCredentialEncrypted(
+ CipherEncryptDatabase().apply {
+ this.databaseUri = databaseUri
+ this.credentialStorage = credentialDatabaseStorage
+ this.encryptedValue = encryptedValue
+ this.specParameters = ivSpec
+ }
+ )
}
}
- override fun handleDecryptedResult(decryptedValue: String) {
+ override fun handleDecryptedResult(decryptedValue: ByteArray) {
// Load database directly with password retrieve
- databaseFileUri?.let {
- mBuilderListener?.onCredentialDecrypted(it, decryptedValue)
+ databaseFileUri?.let { databaseUri ->
+ mBuilderListener?.onCredentialDecrypted(
+ CipherDecryptDatabase().apply {
+ this.databaseUri = databaseUri
+ this.credentialStorage = credentialDatabaseStorage
+ this.decryptedValue = decryptedValue
+ }
+ )
}
}
@@ -551,6 +571,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
}
+ @RequiresApi(Build.VERSION_CODES.M)
override fun onGenericException(e: Exception) {
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
setAdvancedUnlockedMessageView(errorMessage)
@@ -580,6 +601,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
}
+ @RequiresApi(Build.VERSION_CODES.M)
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
lifecycleScope.launch(Dispatchers.Main) {
mAdvancedUnlockInfoView?.message = text
@@ -617,10 +639,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
interface BuilderListener {
- fun retrieveCredentialForEncryption(): String
+ fun retrieveCredentialForEncryption(): ByteArray
fun conditionToStoreCredential(): Boolean
- fun onCredentialEncrypted(databaseUri: Uri, encryptedCredential: String, ivSpec: String)
- fun onCredentialDecrypted(databaseUri: Uri, decryptedCredential: String)
+ fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase)
+ fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase)
}
override fun onPause() {
diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt
index a771c39de..0439da147 100644
--- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockManager.kt
@@ -27,7 +27,6 @@ import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.security.keystore.KeyProperties
-import android.util.Base64
import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
@@ -214,18 +213,15 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
- fun encryptData(value: String) {
+ fun encryptData(value: ByteArray) {
if (!isKeyManagerInitialized) {
return
}
try {
- val encrypted = cipher?.doFinal(value.toByteArray())
- val encryptedBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP)
-
+ val encrypted = cipher?.doFinal(value) ?: byteArrayOf()
// passes updated iv spec on to callback so this can be stored for decryption
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
- val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
- advancedUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
+ advancedUnlockCallback?.handleEncryptedResult(encrypted, spec.iv)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to encrypt data", e)
@@ -233,12 +229,12 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
- fun initDecryptData(ivSpecValue: String,
+ fun initDecryptData(ivSpecValue: ByteArray,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
initDecryptData(ivSpecValue, actionIfCypherInit, true)
}
- private fun initDecryptData(ivSpecValue: String,
+ private fun initDecryptData(ivSpecValue: ByteArray,
actionIfCypherInit: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit,
firstLaunch: Boolean = true) {
if (!isKeyManagerInitialized) {
@@ -246,9 +242,7 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
try {
// important to restore spec here that was used for decryption
- val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP)
- val spec = IvParameterSpec(iv)
-
+ val spec = IvParameterSpec(ivSpecValue)
getSecretKey()?.let { secretKey ->
cipher?.let { cipher ->
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
@@ -284,15 +278,14 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
}
}
- fun decryptData(encryptedValue: String) {
+ fun decryptData(encryptedValue: ByteArray) {
if (!isKeyManagerInitialized) {
return
}
try {
// actual decryption here
- val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
- cipher?.doFinal(encrypted)?.let { decrypted ->
- advancedUnlockCallback?.handleDecryptedResult(String(decrypted))
+ cipher?.doFinal(encryptedValue)?.let { decrypted ->
+ advancedUnlockCallback?.handleDecryptedResult(decrypted)
}
} catch (badPaddingException: BadPaddingException) {
Log.e(TAG, "Unable to decrypt data", badPaddingException)
@@ -367,8 +360,8 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
fun onAuthenticationSucceeded()
fun onAuthenticationFailed()
fun onAuthenticationError(errorCode: Int, errString: CharSequence)
- fun handleEncryptedResult(encryptedValue: String, ivSpec: String)
- fun handleDecryptedResult(decryptedValue: String)
+ fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray)
+ fun handleDecryptedResult(decryptedValue: ByteArray)
}
companion object {
@@ -469,9 +462,9 @@ class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity)
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {}
- override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
+ override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {}
- override fun handleDecryptedResult(decryptedValue: String) {}
+ override fun handleDecryptedResult(decryptedValue: ByteArray) {}
override fun onUnrecoverableKeyException(e: Exception) {
advancedCallback.onUnrecoverableKeyException(e)
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignMainCredentialInDatabaseRunnable.kt
similarity index 98%
rename from app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt
rename to app/src/main/java/com/kunzisoft/keepass/database/action/AssignMainCredentialInDatabaseRunnable.kt
index a49096425..f405faeb6 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignMainCredentialInDatabaseRunnable.kt
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.utils.UriUtil
-open class AssignPasswordInDatabaseRunnable (
+open class AssignMainCredentialInDatabaseRunnable (
context: Context,
database: Database,
protected val mDatabaseUri: Uri,
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt
index d1f792c8e..e0c11577c 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt
@@ -35,7 +35,7 @@ class CreateDatabaseRunnable(context: Context,
private val templateGroupName: String?,
mainCredential: MainCredential,
private val createDatabaseResult: ((Result) -> Unit)?)
- : AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, mainCredential) {
+ : AssignMainCredentialInDatabaseRunnable(context, mDatabase, databaseUri, mainCredential) {
override fun onStartRun() {
try {
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/DatabaseTaskProvider.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/DatabaseTaskProvider.kt
index c98a44eeb..18468a863 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/DatabaseTaskProvider.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/DatabaseTaskProvider.kt
@@ -33,7 +33,6 @@ import androidx.lifecycle.lifecycleScope
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
-import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
@@ -43,6 +42,7 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
+import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
@@ -84,7 +84,6 @@ import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
import kotlinx.coroutines.launch
import java.util.*
-import kotlin.collections.ArrayList
/**
* Utility class to connect an activity or a service to the DatabaseTaskNotificationService,
@@ -344,21 +343,23 @@ class DatabaseTaskProvider {
fun startDatabaseLoad(databaseUri: Uri,
mainCredential: MainCredential,
readOnly: Boolean,
- cipherEntity: CipherDatabaseEntity?,
+ cipherEncryptDatabase: CipherEncryptDatabase?,
fixDuplicateUuid: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
putBoolean(DatabaseTaskNotificationService.READ_ONLY_KEY, readOnly)
- putParcelable(DatabaseTaskNotificationService.CIPHER_ENTITY_KEY, cipherEntity)
+ putParcelable(DatabaseTaskNotificationService.CIPHER_DATABASE_KEY, cipherEncryptDatabase)
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
}
, ACTION_DATABASE_LOAD_TASK)
}
- fun startDatabaseMerge(fixDuplicateUuid: Boolean) {
+ fun startDatabaseMerge(fromDatabaseUri: Uri? = null,
+ mainCredential: MainCredential? = null) {
start(Bundle().apply {
- putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
+ putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, fromDatabaseUri)
+ putParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY, mainCredential)
}
, ACTION_DATABASE_MERGE_TASK)
}
@@ -693,9 +694,10 @@ class DatabaseTaskProvider {
/**
* Save Database without parameter
*/
- fun startDatabaseSave(save: Boolean) {
+ fun startDatabaseSave(save: Boolean, saveToUri: Uri? = null) {
start(Bundle().apply {
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
+ putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, saveToUri)
}
, ACTION_DATABASE_SAVE)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt
index ef1c2be70..2329ff7f7 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt
@@ -22,12 +22,11 @@ package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
-import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData
-import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
+import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -39,7 +38,7 @@ class LoadDatabaseRunnable(private val context: Context,
private val mUri: Uri,
private val mMainCredential: MainCredential,
private val mReadonly: Boolean,
- private val mCipherEntity: CipherDatabaseEntity?,
+ private val mCipherEncryptDatabase: CipherEncryptDatabase?,
private val mFixDuplicateUUID: Boolean,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mLoadDatabaseResult: ((Result) -> Unit)?)
@@ -76,9 +75,9 @@ class LoadDatabaseRunnable(private val context: Context,
}
// Register the biometric
- mCipherEntity?.let { cipherDatabaseEntity ->
+ mCipherEncryptDatabase?.let { cipherDatabase ->
CipherDatabaseAction.getInstance(context)
- .addOrUpdateCipherDatabase(cipherDatabaseEntity) // return value not called
+ .addOrUpdateCipherDatabase(cipherDatabase) // return value not called
}
// Register the current time to init the lock timer
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/MergeDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/MergeDatabaseRunnable.kt
index 1a6a09c95..a0ee19616 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/MergeDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/MergeDatabaseRunnable.kt
@@ -20,17 +20,19 @@
package com.kunzisoft.keepass.database.action
import android.content.Context
+import android.net.Uri
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData
-import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
+import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
-import com.kunzisoft.keepass.utils.UriUtil
class MergeDatabaseRunnable(private val context: Context,
private val mDatabase: Database,
+ private val mDatabaseToMergeUri: Uri?,
+ private val mDatabaseToMergeMainCredential: MainCredential?,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mLoadDatabaseResult: ((Result) -> Unit)?)
: ActionRunnable() {
@@ -41,11 +43,14 @@ class MergeDatabaseRunnable(private val context: Context,
override fun onActionRun() {
try {
- mDatabase.mergeData(context.contentResolver,
- { memoryWanted ->
- BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
- },
- progressTaskUpdater)
+ mDatabase.mergeData(mDatabaseToMergeUri,
+ mDatabaseToMergeMainCredential,
+ context.contentResolver,
+ { memoryWanted ->
+ BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
+ },
+ progressTaskUpdater
+ )
} catch (e: LoadDatabaseException) {
setError(e)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt
index 29f71c742..3b88d51ed 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt
@@ -20,13 +20,15 @@
package com.kunzisoft.keepass.database.action
import android.content.Context
+import android.net.Uri
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.tasks.ActionRunnable
open class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database,
- private var saveDatabase: Boolean)
+ private var saveDatabase: Boolean,
+ private var databaseCopyUri: Uri? = null)
: ActionRunnable() {
var mAfterSaveDatabase: ((Result) -> Unit)? = null
@@ -37,7 +39,7 @@ open class SaveDatabaseRunnable(protected var context: Context,
database.checkVersion()
if (saveDatabase && result.isSuccess) {
try {
- database.saveData(context.contentResolver)
+ database.saveData(databaseCopyUri, context.contentResolver)
} catch (e: DatabaseException) {
setError(e)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/CustomData.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/CustomData.kt
index 4264e682f..ff47530f9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/CustomData.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/CustomData.kt
@@ -1,3 +1,22 @@
+/*
+ * Copyright 2020 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
package com.kunzisoft.keepass.database.element
import android.os.Parcel
@@ -17,7 +36,10 @@ class CustomData : Parcelable {
}
constructor(parcel: Parcel) {
- ParcelableUtil.readStringParcelableMap(parcel, CustomDataItem::class.java)
+ mCustomDataItems.clear()
+ mCustomDataItems.putAll(ParcelableUtil
+ .readStringParcelableMap(parcel, CustomDataItem::class.java)
+ )
}
fun get(key: String): CustomDataItem? {
@@ -46,6 +68,10 @@ class CustomData : Parcelable {
}
}
+ override fun toString(): String {
+ return mCustomDataItems.toString()
+ }
+
override fun writeToParcel(parcel: Parcel, flags: Int) {
ParcelableUtil.writeStringParcelableMap(parcel, flags, mCustomDataItems)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/CustomDataItem.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/CustomDataItem.kt
index b59ad0607..9d67b3d4b 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/CustomDataItem.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/CustomDataItem.kt
@@ -21,6 +21,10 @@ class CustomDataItem : Parcelable {
this.lastModificationTime = lastModificationTime
}
+ override fun toString(): String {
+ return value
+ }
+
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(key)
parcel.writeString(value)
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
index c28438ea6..004906cdf 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt
@@ -62,7 +62,6 @@ import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import java.io.*
import java.util.*
-import kotlin.collections.ArrayList
class Database {
@@ -275,6 +274,9 @@ class Database {
}
}
+ val defaultFileExtension: String
+ get() = mDatabaseKDB?.defaultFileExtension ?: mDatabaseKDBX?.defaultFileExtension ?: ".bin"
+
val type: Class<*>?
get() = mDatabaseKDB?.javaClass ?: mDatabaseKDBX?.javaClass
@@ -496,7 +498,7 @@ class Database {
* Determine if a configurable templates group is available or not for this version of database
* @return true if a configurable templates group available
*/
- val allowConfigurableTemplatesGroup: Boolean
+ val allowTemplatesGroup: Boolean
get() = mDatabaseKDBX != null
// Maybe another templates method with KDBX5
@@ -635,9 +637,13 @@ class Database {
}
DatabaseInputKDB(databaseKDB)
.openDatabase(databaseInputStream,
- mainCredential.masterPassword,
- keyFileInputStream,
- progressTaskUpdater)
+ progressTaskUpdater
+ ) {
+ databaseKDB.retrieveMasterKey(
+ mainCredential.masterPassword,
+ keyFileInputStream
+ )
+ }
databaseKDB
},
{ databaseInputStream ->
@@ -648,9 +654,12 @@ class Database {
DatabaseInputKDBX(databaseKDBX).apply {
setMethodToCheckIfRAMIsSufficient(isRAMSufficient)
openDatabase(databaseInputStream,
- mainCredential.masterPassword,
- keyFileInputStream,
- progressTaskUpdater)
+ progressTaskUpdater) {
+ databaseKDBX.retrieveMasterKey(
+ mainCredential.masterPassword,
+ keyFileInputStream,
+ )
+ }
}
databaseKDBX
}
@@ -672,7 +681,9 @@ class Database {
}
@Throws(LoadDatabaseException::class)
- fun mergeData(contentResolver: ContentResolver,
+ fun mergeData(databaseToMergeUri: Uri?,
+ databaseToMergeMainCredential: MainCredential?,
+ contentResolver: ContentResolver,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
progressTaskUpdater: ProgressTaskUpdater?) {
@@ -682,30 +693,52 @@ class Database {
// New database instance to get new changes
val databaseToMerge = Database()
- databaseToMerge.fileUri = this.fileUri
+ databaseToMerge.fileUri = databaseToMergeUri ?: this.fileUri
+ // Pass KeyFile Uri as InputStreams
+ var keyFileInputStream: InputStream? = null
try {
- databaseToMerge.fileUri?.let { databaseUri ->
-
- val databaseKDB = DatabaseKDB()
- val databaseKDBX = DatabaseKDBX()
+ val databaseUri = databaseToMerge.fileUri
+ if (databaseUri != null) {
+ if (databaseToMergeMainCredential != null) {
+ // Get keyFile inputStream
+ databaseToMergeMainCredential.keyFileUri?.let { keyFile ->
+ keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyFile)
+ }
+ }
databaseToMerge.readDatabaseStream(contentResolver, databaseUri,
{ databaseInputStream ->
- DatabaseInputKDB(databaseKDB)
- .openDatabase(databaseInputStream,
- masterKey,
- progressTaskUpdater)
- databaseKDB
+ val databaseToMergeKDB = DatabaseKDB()
+ DatabaseInputKDB(databaseToMergeKDB)
+ .openDatabase(databaseInputStream, progressTaskUpdater) {
+ if (databaseToMergeMainCredential != null) {
+ databaseToMergeKDB.retrieveMasterKey(
+ databaseToMergeMainCredential.masterPassword,
+ keyFileInputStream,
+ )
+ } else {
+ databaseToMergeKDB.masterKey = masterKey
+ }
+ }
+ databaseToMergeKDB
},
{ databaseInputStream ->
- DatabaseInputKDBX(databaseKDBX).apply {
+ val databaseToMergeKDBX = DatabaseKDBX()
+ DatabaseInputKDBX(databaseToMergeKDBX).apply {
setMethodToCheckIfRAMIsSufficient(isRAMSufficient)
- openDatabase(databaseInputStream,
- masterKey,
- progressTaskUpdater)
+ openDatabase(databaseInputStream, progressTaskUpdater) {
+ if (databaseToMergeMainCredential != null) {
+ databaseToMergeKDBX.retrieveMasterKey(
+ databaseToMergeMainCredential.masterPassword,
+ keyFileInputStream,
+ )
+ } else {
+ databaseToMergeKDBX.masterKey = masterKey
+ }
+ }
}
- databaseKDBX
+ databaseToMergeKDBX
}
)
@@ -715,13 +748,19 @@ class Database {
}
databaseToMerge.mDatabaseKDB?.let { databaseKDBToMerge ->
databaseMerger.merge(databaseKDBToMerge)
+ if (databaseToMergeUri != null) {
+ this.dataModifiedSinceLastLoading = true
+ }
}
databaseToMerge.mDatabaseKDBX?.let { databaseKDBXToMerge ->
databaseMerger.merge(databaseKDBXToMerge)
+ if (databaseToMergeUri != null) {
+ this.dataModifiedSinceLastLoading = true
+ }
}
}
- } ?: run {
- throw IODatabaseException("Database URI is null, database cannot be reloaded")
+ } else {
+ throw IODatabaseException("Database URI is null, database cannot be merged")
}
} catch (e: FileNotFoundException) {
throw FileNotFoundDatabaseException("Unable to load the keyfile")
@@ -730,6 +769,7 @@ class Database {
} catch (e: Exception) {
throw LoadDatabaseException(e)
} finally {
+ keyFileInputStream?.close()
databaseToMerge.clearAndClose()
}
}
@@ -741,7 +781,8 @@ class Database {
// Retrieve the stream from the old database URI
try {
- fileUri?.let { oldDatabaseUri ->
+ val oldDatabaseUri = fileUri
+ if (oldDatabaseUri != null) {
readDatabaseStream(contentResolver, oldDatabaseUri,
{ databaseInputStream ->
val databaseKDB = DatabaseKDB()
@@ -749,9 +790,9 @@ class Database {
databaseKDB.binaryCache = it.binaryCache
}
DatabaseInputKDB(databaseKDB)
- .openDatabase(databaseInputStream,
- masterKey,
- progressTaskUpdater)
+ .openDatabase(databaseInputStream, progressTaskUpdater) {
+ databaseKDB.masterKey = masterKey
+ }
databaseKDB
},
{ databaseInputStream ->
@@ -761,14 +802,14 @@ class Database {
}
DatabaseInputKDBX(databaseKDBX).apply {
setMethodToCheckIfRAMIsSufficient(isRAMSufficient)
- openDatabase(databaseInputStream,
- masterKey,
- progressTaskUpdater)
+ openDatabase(databaseInputStream, progressTaskUpdater) {
+ databaseKDBX.masterKey = masterKey
+ }
}
databaseKDBX
}
)
- } ?: run {
+ } else {
throw IODatabaseException("Database URI is null, database cannot be reloaded")
}
} catch (e: FileNotFoundException) {
@@ -782,29 +823,39 @@ class Database {
}
}
- fun isGroupSearchable(group: Group, omitBackup: Boolean): Boolean {
- return mDatabaseKDB?.isGroupSearchable(group.groupKDB, omitBackup) ?:
- mDatabaseKDBX?.isGroupSearchable(group.groupKDBX, omitBackup) ?:
- false
+ fun groupIsInRecycleBin(group: Group): Boolean {
+ val groupKDB = group.groupKDB
+ val groupKDBX = group.groupKDBX
+ if (groupKDB != null) {
+ return mDatabaseKDB?.isInRecycleBin(groupKDB) ?: false
+ } else if (groupKDBX != null) {
+ return mDatabaseKDBX?.isInRecycleBin(groupKDBX) ?: false
+ }
+ return false
}
- fun createVirtualGroupFromSearch(searchQuery: String,
- omitBackup: Boolean,
+ fun groupIsInTemplates(group: Group): Boolean {
+ val groupKDBX = group.groupKDBX
+ if (groupKDBX != null) {
+ return mDatabaseKDBX?.getTemplatesGroup() == groupKDBX
+ }
+ return false
+ }
+
+ fun createVirtualGroupFromSearch(searchParameters: SearchParameters,
+ fromGroup: NodeId<*>? = null,
max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
- SearchParameters().apply {
- this.searchQuery = searchQuery
- }, omitBackup, max)
+ searchParameters, fromGroup, max)
}
fun createVirtualGroupFromSearchInfo(searchInfoString: String,
- omitBackup: Boolean,
max: Int = Integer.MAX_VALUE): Group? {
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
SearchParameters().apply {
searchQuery = searchInfoString
searchInTitles = true
- searchInUserNames = false
+ searchInUsernames = false
searchInPasswords = false
searchInUrls = true
searchInNotes = true
@@ -812,8 +863,11 @@ class Database {
searchInOther = true
searchInUUIDs = false
searchInTags = false
+ searchInCurrentGroup = false
+ searchInSearchableGroup = true
+ searchInRecycleBin = false
searchInTemplates = false
- }, omitBackup, max)
+ }, null, max)
}
val tagPool: Tags
@@ -855,10 +909,61 @@ class Database {
}
@Throws(DatabaseOutputException::class)
- fun saveData(contentResolver: ContentResolver) {
+ fun saveData(databaseCopyUri: Uri?, contentResolver: ContentResolver) {
try {
- this.fileUri?.let {
- saveData(contentResolver, it)
+ val saveUri = databaseCopyUri ?: this.fileUri
+ if (saveUri != null) {
+ if (saveUri.scheme == "file") {
+ saveUri.path?.let { filename ->
+ val tempFile = File("$filename.tmp")
+
+ var fileOutputStream: FileOutputStream? = null
+ try {
+ fileOutputStream = FileOutputStream(tempFile)
+ val pmo = mDatabaseKDB?.let { DatabaseOutputKDB(it, fileOutputStream) }
+ ?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, fileOutputStream) }
+ pmo?.output()
+ } catch (e: Exception) {
+ throw IOException(e)
+ } finally {
+ fileOutputStream?.close()
+ }
+
+ // Force data to disk before continuing
+ try {
+ fileOutputStream?.fd?.sync()
+ } catch (e: SyncFailedException) {
+ // Ignore if fsync fails. We tried.
+ }
+
+ if (!tempFile.renameTo(File(filename))) {
+ throw IOException()
+ }
+ }
+ } else {
+ var outputStream: OutputStream? = null
+ try {
+ outputStream = contentResolver.openOutputStream(saveUri, "rwt")
+ outputStream?.let { definedOutputStream ->
+ val databaseOutput =
+ mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
+ ?: mDatabaseKDBX?.let {
+ DatabaseOutputKDBX(
+ it,
+ definedOutputStream
+ )
+ }
+ databaseOutput?.output()
+ }
+ } catch (e: Exception) {
+ throw IOException(e)
+ } finally {
+ outputStream?.close()
+ }
+ }
+ if (databaseCopyUri == null) {
+ this.dataModifiedSinceLastLoading = false
+ }
}
} catch (e: Exception) {
Log.e(TAG, "Unable to save database", e)
@@ -866,55 +971,6 @@ class Database {
}
}
- @Throws(IOException::class, DatabaseOutputException::class)
- private fun saveData(contentResolver: ContentResolver, uri: Uri) {
-
- if (uri.scheme == "file") {
- uri.path?.let { filename ->
- val tempFile = File("$filename.tmp")
-
- var fileOutputStream: FileOutputStream? = null
- try {
- fileOutputStream = FileOutputStream(tempFile)
- val pmo = mDatabaseKDB?.let { DatabaseOutputKDB(it, fileOutputStream) }
- ?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, fileOutputStream) }
- pmo?.output()
- } catch (e: Exception) {
- throw IOException(e)
- } finally {
- fileOutputStream?.close()
- }
-
- // Force data to disk before continuing
- try {
- fileOutputStream?.fd?.sync()
- } catch (e: SyncFailedException) {
- // Ignore if fsync fails. We tried.
- }
-
- if (!tempFile.renameTo(File(filename))) {
- throw IOException()
- }
- }
- } else {
- var outputStream: OutputStream? = null
- try {
- outputStream = contentResolver.openOutputStream(uri, "rwt")
- outputStream?.let { definedOutputStream ->
- val databaseOutput = mDatabaseKDB?.let { DatabaseOutputKDB(it, definedOutputStream) }
- ?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, definedOutputStream) }
- databaseOutput?.output()
- }
- } catch (e: Exception) {
- throw IOException(e)
- } finally {
- outputStream?.close()
- }
- }
- this.fileUri = uri
- this.dataModifiedSinceLastLoading = false
- }
-
fun clearIndexesAndBinaries(filesDirectory: File? = null) {
this.mDatabaseKDB?.clearIndexes()
this.mDatabaseKDBX?.clearIndexes()
@@ -1245,6 +1301,18 @@ class Database {
return mDatabaseKDBX != null
}
+ fun allowCustomSearchableGroup(): Boolean {
+ return mDatabaseKDBX != null
+ }
+
+ fun allowAutoType(): Boolean {
+ return mDatabaseKDBX != null
+ }
+
+ fun allowTags(): Boolean {
+ return mDatabaseKDBX != null
+ }
+
/**
* Remove oldest history for each entry if more than max items or max memory
*/
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt
index 72f2182ee..d7c6d9211 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt
@@ -26,6 +26,7 @@ import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
+import com.kunzisoft.keepass.database.element.entry.AutoType
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
@@ -276,6 +277,18 @@ class Entry : Node, EntryVersionedInterface {
}
}
+ var customData: CustomData
+ get() = entryKDBX?.customData ?: CustomData()
+ set(value) {
+ entryKDBX?.customData = value
+ }
+
+ var autoType: AutoType
+ get() = entryKDBX?.autoType ?: AutoType()
+ set(value) {
+ entryKDBX?.autoType = value
+ }
+
private fun isTan(): Boolean {
return title == PMS_TAN_ENTRY && username.isNotEmpty()
}
@@ -460,6 +473,8 @@ class Entry : Node, EntryVersionedInterface {
entryInfo.tags = tags
entryInfo.backgroundColor = backgroundColor
entryInfo.foregroundColor = foregroundColor
+ entryInfo.customData = customData
+ entryInfo.autoType = autoType
entryInfo.customFields = getExtraFields().toMutableList()
// Add otpElement to generate token
entryInfo.otpModel = getOtpElement()?.otpModel
@@ -497,6 +512,8 @@ class Entry : Node, EntryVersionedInterface {
tags = newEntryInfo.tags
backgroundColor = newEntryInfo.backgroundColor
foregroundColor = newEntryInfo.foregroundColor
+ customData = newEntryInfo.customData
+ autoType = newEntryInfo.autoType
addExtraFields(newEntryInfo.customFields)
database?.attachmentPool?.let { binaryPool ->
newEntryInfo.attachments.forEach { attachment ->
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Group.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Group.kt
index c1d001462..3f4e94155 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/Group.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Group.kt
@@ -262,6 +262,12 @@ class Group : Node, GroupVersionedInterface {
}
}
+ var customData: CustomData
+ get() = groupKDBX?.customData ?: CustomData()
+ set(value) {
+ groupKDBX?.customData = value
+ }
+
override fun getChildGroups(): List {
return groupKDB?.getChildGroups()?.map {
Group(it)
@@ -434,13 +440,36 @@ class Group : Node, GroupVersionedInterface {
groupKDBX?.nodeId = id
}
- fun setEnableAutoType(enableAutoType: Boolean?) {
- groupKDBX?.enableAutoType = enableAutoType
+ var searchable: Boolean?
+ get() = groupKDBX?.enableSearching
+ set(value) {
+ groupKDBX?.enableSearching = value
+ }
+
+ fun isSearchable(): Boolean {
+ val searchableGroup = searchable
+ if (searchableGroup == null) {
+ val parenGroup = parent
+ if (parenGroup == null)
+ return true
+ else
+ return parenGroup.isSearchable()
+ } else {
+ return searchableGroup
+ }
}
- fun setEnableSearching(enableSearching: Boolean?) {
- groupKDBX?.enableSearching = enableSearching
- }
+ var enableAutoType: Boolean?
+ get() = groupKDBX?.enableAutoType
+ set(value) {
+ groupKDBX?.enableAutoType = value
+ }
+
+ var defaultAutoTypeSequence: String
+ get() = groupKDBX?.defaultAutoTypeSequence ?: ""
+ set(value) {
+ groupKDBX?.defaultAutoTypeSequence = value
+ }
fun setExpanded(expanded: Boolean) {
groupKDBX?.isExpanded = expanded
@@ -462,7 +491,11 @@ class Group : Node, GroupVersionedInterface {
groupInfo.expires = expires
groupInfo.expiryTime = expiryTime
groupInfo.notes = notes
+ groupInfo.searchable = searchable
+ groupInfo.enableAutoType = enableAutoType
+ groupInfo.defaultAutoTypeSequence = defaultAutoTypeSequence
groupInfo.tags = tags
+ groupInfo.customData = customData
return groupInfo
}
@@ -475,7 +508,11 @@ class Group : Node, GroupVersionedInterface {
expires = groupInfo.expires
expiryTime = groupInfo.expiryTime
notes = groupInfo.notes
+ searchable = groupInfo.searchable
+ enableAutoType = groupInfo.enableAutoType
+ defaultAutoTypeSequence = groupInfo.defaultAutoTypeSequence
tags = groupInfo.tags
+ customData = groupInfo.customData
}
override fun equals(other: Any?): Boolean {
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Tags.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Tags.kt
index f9bc4f1f8..608a2f34b 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/Tags.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Tags.kt
@@ -53,6 +53,10 @@ class Tags: Parcelable {
return mTags.isEmpty()
}
+ fun isNotEmpty(): Boolean {
+ return !isEmpty()
+ }
+
fun size(): Int {
return mTags.size
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt
index c5cf1ce59..f10901014 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt
@@ -64,6 +64,9 @@ class DatabaseKDB : DatabaseVersioned() {
override val version: String
get() = "V1"
+ override val defaultFileExtension: String
+ get() = ".kdb"
+
init {
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
rootGroup = createGroup().apply {
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt
index 3a1fa3c8b..adc05d675 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt
@@ -201,6 +201,9 @@ class DatabaseKDBX : DatabaseVersioned {
return "V2 - KDBX$kdbxStringVersion"
}
+ override val defaultFileExtension: String
+ get() = ".kdbx"
+
private open class NodeOperationHandler : NodeHandler() {
var containsCustomData = false
override fun operate(node: T): Boolean {
@@ -224,7 +227,7 @@ class DatabaseKDBX : DatabaseVersioned {
private inner class GroupOperationHandler: NodeOperationHandler() {
var containsTags = false
override fun operate(node: GroupKDBX): Boolean {
- if (!node.tags.isEmpty())
+ if (node.tags.isNotEmpty())
containsTags = true
return super.operate(node)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt
index 260a828a2..3ffae91b2 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt
@@ -62,6 +62,7 @@ abstract class DatabaseVersioned<
protected set
abstract val version: String
+ abstract val defaultFileExtension: String
/**
* To manage binaries in faster way
@@ -325,14 +326,6 @@ abstract class DatabaseVersioned<
abstract fun isInRecycleBin(group: Group): Boolean
- fun isGroupSearchable(group: Group?, omitBackup: Boolean): Boolean {
- if (group == null)
- return false
- if (omitBackup && isInRecycleBin(group))
- return false
- return true
- }
-
fun clearIconsCache() {
iconsManager.doForEachCustomIcon { _, binary ->
try {
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDBX.kt
index 172091432..14dca1de5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDBX.kt
@@ -41,9 +41,9 @@ class GroupKDBX : GroupVersioned, NodeKDBXInte
override var customData = CustomData()
var notes = ""
var isExpanded = true
- var defaultAutoTypeSequence = ""
- var enableAutoType: Boolean? = null
var enableSearching: Boolean? = null
+ var enableAutoType: Boolean? = null
+ var defaultAutoTypeSequence: String = ""
var lastTopVisibleEntry: UUID = DatabaseVersioned.UUID_ZERO
override var tags = Tags()
override var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
@@ -69,11 +69,11 @@ class GroupKDBX : GroupVersioned, NodeKDBXInte
customData = parcel.readParcelable(CustomData::class.java.classLoader) ?: CustomData()
notes = parcel.readString() ?: notes
isExpanded = parcel.readByte().toInt() != 0
- defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
- val isAutoTypeEnabled = parcel.readInt()
- enableAutoType = if (isAutoTypeEnabled == -1) null else isAutoTypeEnabled == 1
val isSearchingEnabled = parcel.readInt()
enableSearching = if (isSearchingEnabled == -1) null else isSearchingEnabled == 1
+ val isAutoTypeEnabled = parcel.readInt()
+ enableAutoType = if (isAutoTypeEnabled == -1) null else isAutoTypeEnabled == 1
+ defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
lastTopVisibleEntry = parcel.readSerializable() as UUID
tags = parcel.readParcelable(Tags::class.java.classLoader) ?: tags
previousParentGroup = parcel.readParcelable(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
@@ -94,9 +94,9 @@ class GroupKDBX : GroupVersioned, NodeKDBXInte
dest.writeParcelable(customData, flags)
dest.writeString(notes)
dest.writeByte((if (isExpanded) 1 else 0).toByte())
- dest.writeString(defaultAutoTypeSequence)
- dest.writeInt(if (enableAutoType == null) -1 else if (enableAutoType!!) 1 else 0)
dest.writeInt(if (enableSearching == null) -1 else if (enableSearching!!) 1 else 0)
+ dest.writeInt(if (enableAutoType == null) -1 else if (enableAutoType!!) 1 else 0)
+ dest.writeString(defaultAutoTypeSequence)
dest.writeSerializable(lastTopVisibleEntry)
dest.writeParcelable(tags, flags)
dest.writeParcelable(ParcelUuid(previousParentGroup), flags)
@@ -111,9 +111,9 @@ class GroupKDBX : GroupVersioned, NodeKDBXInte
customData = CustomData(source.customData)
notes = source.notes
isExpanded = source.isExpanded
- defaultAutoTypeSequence = source.defaultAutoTypeSequence
- enableAutoType = source.enableAutoType
enableSearching = source.enableSearching
+ enableAutoType = source.enableAutoType
+ defaultAutoTypeSequence = source.defaultAutoTypeSequence
lastTopVisibleEntry = source.lastTopVisibleEntry
tags = source.tags
previousParentGroup = source.previousParentGroup
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/template/TemplateField.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/template/TemplateField.kt
index 19a2c23b7..07665a8b4 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/element/template/TemplateField.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/element/template/TemplateField.kt
@@ -103,7 +103,7 @@ object TemplateField {
LABEL_SSID.equals(name, true) -> context.getString(R.string.ssid)
LABEL_TYPE.equals(name, true) -> context.getString(R.string.type)
LABEL_CRYPTOCURRENCY.equals(name, true) -> context.getString(R.string.cryptocurrency)
- LABEL_TOKEN.equals(name, true) -> context.getString(R.string.token)
+ LABEL_TOKEN.equals(name, false) -> context.getString(R.string.token)
LABEL_PUBLIC_KEY.equals(name, true) -> context.getString(R.string.public_key)
LABEL_PRIVATE_KEY.equals(name, true) -> context.getString(R.string.private_key)
LABEL_SEED.equals(name, true) -> context.getString(R.string.seed)
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt
index 7ff7e1294..321876af4 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt
@@ -43,15 +43,8 @@ abstract class DatabaseInput> (protected var m
@Throws(LoadDatabaseException::class)
abstract fun openDatabase(databaseInputStream: InputStream,
- password: String?,
- keyfileInputStream: InputStream?,
- progressTaskUpdater: ProgressTaskUpdater?): D
-
-
- @Throws(LoadDatabaseException::class)
- abstract fun openDatabase(databaseInputStream: InputStream,
- masterKey: ByteArray,
- progressTaskUpdater: ProgressTaskUpdater?): D
+ progressTaskUpdater: ProgressTaskUpdater?,
+ assignMasterKey: (() -> Unit)): D
protected fun startKeyTimer(progressTaskUpdater: ProgressTaskUpdater?) {
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt
index 7a019bbca..8d9373ecd 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt
@@ -39,7 +39,6 @@ import java.security.MessageDigest
import java.util.*
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
-import kotlin.collections.HashMap
/**
@@ -50,27 +49,8 @@ class DatabaseInputKDB(database: DatabaseKDB)
@Throws(LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream,
- password: String?,
- keyfileInputStream: InputStream?,
- progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDB {
- return openDatabase(databaseInputStream, progressTaskUpdater) {
- mDatabase.retrieveMasterKey(password, keyfileInputStream)
- }
- }
-
- @Throws(LoadDatabaseException::class)
- override fun openDatabase(databaseInputStream: InputStream,
- masterKey: ByteArray,
- progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDB {
- return openDatabase(databaseInputStream, progressTaskUpdater) {
- mDatabase.masterKey = masterKey
- }
- }
-
- @Throws(LoadDatabaseException::class)
- private fun openDatabase(databaseInputStream: InputStream,
- progressTaskUpdater: ProgressTaskUpdater?,
- assignMasterKey: (() -> Unit)? = null): DatabaseKDB {
+ progressTaskUpdater: ProgressTaskUpdater?,
+ assignMasterKey: (() -> Unit)): DatabaseKDB {
try {
startKeyTimer(progressTaskUpdater)
@@ -96,7 +76,7 @@ class DatabaseInputKDB(database: DatabaseKDB)
throw VersionDatabaseException()
}
- assignMasterKey?.invoke()
+ assignMasterKey.invoke()
// Select algorithm
when {
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt
index 35d3a7661..ed6e748c7 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt
@@ -101,27 +101,8 @@ class DatabaseInputKDBX(database: DatabaseKDBX)
@Throws(LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream,
- password: String?,
- keyfileInputStream: InputStream?,
- progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDBX {
- return openDatabase(databaseInputStream, progressTaskUpdater) {
- mDatabase.retrieveMasterKey(password, keyfileInputStream)
- }
- }
-
- @Throws(LoadDatabaseException::class)
- override fun openDatabase(databaseInputStream: InputStream,
- masterKey: ByteArray,
- progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDBX {
- return openDatabase(databaseInputStream, progressTaskUpdater) {
- mDatabase.masterKey = masterKey
- }
- }
-
- @Throws(LoadDatabaseException::class)
- private fun openDatabase(databaseInputStream: InputStream,
- progressTaskUpdater: ProgressTaskUpdater?,
- assignMasterKey: (() -> Unit)? = null): DatabaseKDBX {
+ progressTaskUpdater: ProgressTaskUpdater?,
+ assignMasterKey: (() -> Unit)): DatabaseKDBX {
try {
startKeyTimer(progressTaskUpdater)
@@ -133,7 +114,7 @@ class DatabaseInputKDBX(database: DatabaseKDBX)
hashOfHeader = headerAndHash.hash
val pbHeader = headerAndHash.header
- assignMasterKey?.invoke()
+ assignMasterKey.invoke()
mDatabase.makeFinalKey(header.masterSeed)
stopKeyTimer()
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt
index 6677e6fb8..7228efeb5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt
@@ -663,7 +663,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeTags(tags: Tags) {
- if (!tags.isEmpty()) {
+ if (tags.isNotEmpty()) {
writeString(DatabaseKDBXXML.ElemTags, tags.toString())
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt b/app/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt
index 1b2310d81..25c3847ce 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/merge/DatabaseKDBXMerger.kt
@@ -30,6 +30,7 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeId
+import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.utils.readAllBytes
import java.io.IOException
@@ -43,7 +44,6 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
* Merge a KDB database in a KDBX database, by default all data are copied from the KDB
*/
fun merge(databaseToMerge: DatabaseKDB) {
- // TODO Test KDB merge
val rootGroup = database.rootGroup
val rootGroupId = rootGroup?.nodeId
val rootGroupToMerge = databaseToMerge.rootGroup
@@ -53,6 +53,11 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
throw IOException("Database is not open")
}
+ // Replace the UUID of the KDB root group to init seed
+ databaseToMerge.removeGroupIndex(rootGroupToMerge)
+ rootGroupToMerge.nodeId = NodeIdInt(0)
+ databaseToMerge.addGroupIndex(rootGroupToMerge)
+
// Merge children
rootGroupToMerge.doForEachChild(
object : NodeHandler() {
@@ -87,31 +92,57 @@ class DatabaseKDBXMerger(private var database: DatabaseKDBX) {
val entry = database.getEntryById(entryId)
databaseToMerge.getEntryById(entryId)?.let { srcEntryToMerge ->
- // Retrieve parent in current database
- var parentEntryToMerge: GroupKDBX? = null
- srcEntryToMerge.parent?.nodeId?.let {
- val parentGroupIdToMerge = getNodeIdUUIDFrom(seed, it)
- parentEntryToMerge = database.getGroupById(parentGroupIdToMerge)
- }
- val entryToMerge = EntryKDBX().apply {
- this.nodeId = srcEntryToMerge.nodeId
- this.icon = srcEntryToMerge.icon
- this.creationTime = DateInstant(srcEntryToMerge.creationTime)
- this.lastModificationTime = DateInstant(srcEntryToMerge.lastModificationTime)
- this.lastAccessTime = DateInstant(srcEntryToMerge.lastAccessTime)
- this.expiryTime = DateInstant(srcEntryToMerge.expiryTime)
- this.expires = srcEntryToMerge.expires
- this.title = srcEntryToMerge.title
- this.username = srcEntryToMerge.username
- this.password = srcEntryToMerge.password
- this.url = srcEntryToMerge.url
- this.notes = srcEntryToMerge.notes
- // TODO attachment
- }
- if (entry != null) {
- entry.updateWith(entryToMerge, false)
- } else if (parentEntryToMerge != null) {
- database.addEntryTo(entryToMerge, parentEntryToMerge)
+ // Do not merge meta stream elements
+ if (!srcEntryToMerge.isMetaStream()) {
+ // Retrieve parent in current database
+ var parentEntryToMerge: GroupKDBX? = null
+ srcEntryToMerge.parent?.nodeId?.let {
+ val parentGroupIdToMerge = getNodeIdUUIDFrom(seed, it)
+ parentEntryToMerge = database.getGroupById(parentGroupIdToMerge)
+ }
+ // Copy attachment
+ var newAttachment: Attachment? = null
+ srcEntryToMerge.getAttachment(databaseToMerge.attachmentPool)?.let { attachment ->
+ val binarySize = attachment.binaryData.getSize()
+ val binaryData = database.buildNewBinaryAttachment(
+ isRAMSufficient.invoke(binarySize),
+ attachment.binaryData.isCompressed,
+ attachment.binaryData.isProtected
+ )
+ attachment.binaryData.getInputDataStream(databaseToMerge.binaryCache)
+ .use { inputStream ->
+ binaryData.getOutputDataStream(database.binaryCache)
+ .use { outputStream ->
+ inputStream.readAllBytes { buffer ->
+ outputStream.write(buffer)
+ }
+ }
+ }
+ newAttachment = Attachment(attachment.name, binaryData)
+ }
+ // Create new entry format
+ val entryToMerge = EntryKDBX().apply {
+ this.nodeId = srcEntryToMerge.nodeId
+ this.icon = srcEntryToMerge.icon
+ this.creationTime = DateInstant(srcEntryToMerge.creationTime)
+ this.lastModificationTime = DateInstant(srcEntryToMerge.lastModificationTime)
+ this.lastAccessTime = DateInstant(srcEntryToMerge.lastAccessTime)
+ this.expiryTime = DateInstant(srcEntryToMerge.expiryTime)
+ this.expires = srcEntryToMerge.expires
+ this.title = srcEntryToMerge.title
+ this.username = srcEntryToMerge.username
+ this.password = srcEntryToMerge.password
+ this.url = srcEntryToMerge.url
+ this.notes = srcEntryToMerge.notes
+ newAttachment?.let {
+ this.putAttachment(it, database.attachmentPool)
+ }
+ }
+ if (entry != null) {
+ entry.updateWith(entryToMerge, false)
+ } else if (parentEntryToMerge != null) {
+ database.addEntryTo(entryToMerge, parentEntryToMerge)
+ }
}
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt
index 594f077e4..55b5d58b2 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt
@@ -24,6 +24,7 @@ import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
+import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_FIELD
@@ -37,7 +38,7 @@ class SearchHelper {
fun createVirtualGroupWithSearchResult(database: Database,
searchParameters: SearchParameters,
- omitBackup: Boolean,
+ fromGroup: NodeId<*>? = null,
max: Int): Group? {
val searchGroup = database.createGroup(virtual = true)
@@ -45,7 +46,15 @@ class SearchHelper {
// Search all entries
incrementEntry = 0
- database.rootGroup?.doForEachChild(
+
+ val allowCustomSearchable = database.allowCustomSearchableGroup()
+ val startGroup = if (searchParameters.searchInCurrentGroup && fromGroup != null) {
+ database.getGroupById(fromGroup) ?: database.rootGroup
+ } else {
+ database.rootGroup
+ }
+ if (groupConditions(database, startGroup, searchParameters, allowCustomSearchable, max)) {
+ startGroup?.doForEachChild(
object : NodeHandler() {
override fun operate(node: Entry): Boolean {
if (incrementEntry >= max)
@@ -62,19 +71,43 @@ class SearchHelper {
},
object : NodeHandler() {
override fun operate(node: Group): Boolean {
- return when {
- incrementEntry >= max -> false
- database.isGroupSearchable(node, omitBackup) -> true
- else -> false
- }
+ return groupConditions(database,
+ node,
+ searchParameters,
+ allowCustomSearchable,
+ max
+ )
}
},
- false)
+ false
+ )
+ }
searchGroup?.refreshNumberOfChildEntries()
return searchGroup
}
+ private fun groupConditions(database: Database,
+ group: Group?,
+ searchParameters: SearchParameters,
+ allowCustomSearchable: Boolean,
+ max: Int): Boolean {
+ return if (group == null)
+ false
+ else if (incrementEntry >= max)
+ false
+ else if (database.groupIsInRecycleBin(group))
+ searchParameters.searchInRecycleBin
+ else if (database.groupIsInTemplates(group))
+ searchParameters.searchInTemplates
+ else if (!allowCustomSearchable)
+ true
+ else if (searchParameters.searchInSearchableGroup)
+ group.isSearchable()
+ else
+ true
+ }
+
private fun entryContainsString(database: Database,
entry: Entry,
searchParameters: SearchParameters): Boolean {
@@ -88,7 +121,18 @@ class SearchHelper {
}
companion object {
- const val MAX_SEARCH_ENTRY = 10
+ const val MAX_SEARCH_ENTRY = 1000
+
+ /**
+ * Method to show the number of search results with max results
+ */
+ fun showNumberOfSearchResults(number: Int): String {
+ return if (number >= MAX_SEARCH_ENTRY) {
+ (MAX_SEARCH_ENTRY-1).toString() + "+"
+ } else {
+ number.toString()
+ }
+ }
/**
* Utility method to perform actions if item is found or not after an auto search in [database]
@@ -110,7 +154,6 @@ class SearchHelper {
// If search provide results
database.createVirtualGroupFromSearchInfo(
searchInfo.toString(),
- PreferencesUtil.omitBackup(context),
MAX_SEARCH_ENTRY
)?.let { searchGroup ->
if (searchGroup.numberOfChildEntries > 0) {
@@ -132,16 +175,23 @@ class SearchHelper {
fun searchInEntry(entry: Entry,
searchParameters: SearchParameters): Boolean {
val searchQuery = searchParameters.searchQuery
- // Entry don't contains string if the search string is empty
+
+ // Not found if the search string is empty
if (searchQuery.isEmpty())
return false
+ // Exclude entry expired
+ if (searchParameters.excludeExpired) {
+ if (entry.isCurrentlyExpires)
+ return false
+ }
+
// Search all strings in the KDBX entry
if (searchParameters.searchInTitles) {
if (checkSearchQuery(entry.title, searchParameters))
return true
}
- if (searchParameters.searchInUserNames) {
+ if (searchParameters.searchInUsernames) {
if (checkSearchQuery(entry.username, searchParameters))
return true
}
@@ -158,8 +208,8 @@ class SearchHelper {
return true
}
if (searchParameters.searchInUUIDs) {
- val hexString = UuidUtil.toHexString(entry.nodeId.id)
- if (hexString != null && hexString.contains(searchQuery, true))
+ val hexString = UuidUtil.toHexString(entry.nodeId.id) ?: ""
+ if (checkSearchQuery(hexString, searchParameters))
return true
}
if (searchParameters.searchInOther) {
@@ -171,21 +221,31 @@ class SearchHelper {
}
}
}
+ if (searchParameters.searchInTags) {
+ if (checkSearchQuery(entry.tags.toString(), searchParameters))
+ return true
+ }
return false
}
private fun checkSearchQuery(stringToCheck: String, searchParameters: SearchParameters): Boolean {
/*
// TODO Search settings
- var regularExpression = false
- var ignoreCase = true
var removeAccents = true <- Too much time, to study
- var excludeExpired = false
- var searchOnlyInCurrentGroup = false
*/
- return stringToCheck.isNotEmpty()
- && stringToCheck.contains(
- searchParameters.searchQuery, true)
+ if (stringToCheck.isEmpty())
+ return false
+ return if (searchParameters.isRegex) {
+ val regex = if (searchParameters.caseSensitive) {
+ searchParameters.searchQuery.toRegex(RegexOption.DOT_MATCHES_ALL)
+ } else {
+ searchParameters.searchQuery
+ .toRegex(setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE))
+ }
+ regex.matches(stringToCheck)
+ } else {
+ stringToCheck.contains(searchParameters.searchQuery, !searchParameters.caseSensitive)
+ }
}
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchParameters.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchParameters.kt
index 7d08a0be5..34201a607 100644
--- a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchParameters.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchParameters.kt
@@ -19,21 +19,82 @@
*/
package com.kunzisoft.keepass.database.search
+import android.os.Parcel
+import android.os.Parcelable
+
/**
* Parameters for searching strings in the database.
*/
-class SearchParameters {
+class SearchParameters() : Parcelable{
var searchQuery: String = ""
+ var caseSensitive = false
+ var isRegex = false
var searchInTitles = true
- var searchInUserNames = true
+ var searchInUsernames = true
var searchInPasswords = false
var searchInUrls = true
+ var excludeExpired = false
var searchInNotes = true
var searchInOTP = false
var searchInOther = true
var searchInUUIDs = false
- var searchInTags = true
+ var searchInTags = false
+ var searchInCurrentGroup = false
+ var searchInSearchableGroup = true
+ var searchInRecycleBin = false
var searchInTemplates = false
+
+ constructor(parcel: Parcel) : this() {
+ searchQuery = parcel.readString() ?: searchQuery
+ caseSensitive = parcel.readByte() != 0.toByte()
+ searchInTitles = parcel.readByte() != 0.toByte()
+ searchInUsernames = parcel.readByte() != 0.toByte()
+ searchInPasswords = parcel.readByte() != 0.toByte()
+ searchInUrls = parcel.readByte() != 0.toByte()
+ excludeExpired = parcel.readByte() != 0.toByte()
+ searchInNotes = parcel.readByte() != 0.toByte()
+ searchInOTP = parcel.readByte() != 0.toByte()
+ searchInOther = parcel.readByte() != 0.toByte()
+ searchInUUIDs = parcel.readByte() != 0.toByte()
+ searchInTags = parcel.readByte() != 0.toByte()
+ searchInCurrentGroup = parcel.readByte() != 0.toByte()
+ searchInSearchableGroup = parcel.readByte() != 0.toByte()
+ searchInRecycleBin = parcel.readByte() != 0.toByte()
+ searchInTemplates = parcel.readByte() != 0.toByte()
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeString(searchQuery)
+ parcel.writeByte(if (caseSensitive) 1 else 0)
+ parcel.writeByte(if (searchInTitles) 1 else 0)
+ parcel.writeByte(if (searchInUsernames) 1 else 0)
+ parcel.writeByte(if (searchInPasswords) 1 else 0)
+ parcel.writeByte(if (searchInUrls) 1 else 0)
+ parcel.writeByte(if (excludeExpired) 1 else 0)
+ parcel.writeByte(if (searchInNotes) 1 else 0)
+ parcel.writeByte(if (searchInOTP) 1 else 0)
+ parcel.writeByte(if (searchInOther) 1 else 0)
+ parcel.writeByte(if (searchInUUIDs) 1 else 0)
+ parcel.writeByte(if (searchInTags) 1 else 0)
+ parcel.writeByte(if (searchInCurrentGroup) 1 else 0)
+ parcel.writeByte(if (searchInSearchableGroup) 1 else 0)
+ parcel.writeByte(if (searchInRecycleBin) 1 else 0)
+ parcel.writeByte(if (searchInTemplates) 1 else 0)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): SearchParameters {
+ return SearchParameters(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/education/Education.kt b/app/src/main/java/com/kunzisoft/keepass/education/Education.kt
index 8b7d52e71..e40ebbf58 100644
--- a/app/src/main/java/com/kunzisoft/keepass/education/Education.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/education/Education.kt
@@ -31,23 +31,45 @@ import com.kunzisoft.keepass.R
open class Education(val activity: Activity) {
+ private var mOneEducationHintOpen = false
/**
* Utility method to save preference after an education action
*/
protected fun checkAndPerformedEducation(isEducationAlreadyPerformed: Boolean,
- tapTarget: TapTarget,
- listener: TapTargetView.Listener,
- saveEducationStringId: Int): Boolean {
+ tapTarget: TapTarget,
+ listener: TapTargetView.Listener,
+ saveEducationStringId: Int): Boolean {
var doEducation = false
- if (isEducationScreensEnabled()) {
- if (isEducationAlreadyPerformed) {
- try {
- TapTargetView.showFor(activity, tapTarget, listener)
- saveEducationPreference(activity, saveEducationStringId)
- doEducation = true
- } catch (e: Exception) {
- Log.w(Education::class.java.name, "Can't performed education " + e.message)
- }
+ if (isEducationScreensEnabled()
+ && !mOneEducationHintOpen
+ && !isEducationAlreadyPerformed) {
+ try {
+ TapTargetView.showFor(activity, tapTarget, object : TapTargetView.Listener() {
+ override fun onTargetClick(view: TapTargetView) {
+ mOneEducationHintOpen = false
+ saveEducationPreference(activity, saveEducationStringId)
+ super.onTargetClick(view)
+ listener.onTargetClick(view)
+ }
+
+ override fun onOuterCircleClick(view: TapTargetView?) {
+ mOneEducationHintOpen = false
+ saveEducationPreference(activity, saveEducationStringId)
+ super.onOuterCircleClick(view)
+ listener.onOuterCircleClick(view)
+ view?.dismiss(false)
+ }
+
+ override fun onTargetCancel(view: TapTargetView?) {
+ mOneEducationHintOpen = false
+ saveEducationPreference(activity, saveEducationStringId)
+ super.onTargetCancel(view)
+ }
+ })
+ mOneEducationHintOpen = true
+ doEducation = true
+ } catch (e: Exception) {
+ Log.w(Education::class.java.name, "Can't performed education " + e.message)
}
}
return doEducation
@@ -117,6 +139,29 @@ open class Education(val activity: Activity) {
R.string.education_add_attachment_key,
R.string.education_setup_OTP_key)
+ fun putPropertiesInEducationPreferences(context: Context,
+ editor: SharedPreferences.Editor,
+ name: String,
+ value: String) {
+ when (name) {
+ context.getString(R.string.education_create_db_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_select_db_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_unlock_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_read_only_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_biometric_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_search_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_new_node_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_sort_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_lock_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_copy_username_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_entry_edit_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_password_generator_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_entry_new_field_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_add_attachment_key) -> editor.putBoolean(name, value.toBoolean())
+ context.getString(R.string.education_setup_OTP_key) -> editor.putBoolean(name, value.toBoolean())
+ }
+ }
+
/**
* Get preferences bundle for education
*/
diff --git a/app/src/main/java/com/kunzisoft/keepass/education/EntryActivityEducation.kt b/app/src/main/java/com/kunzisoft/keepass/education/EntryActivityEducation.kt
index 5f45d85f7..e4792855c 100644
--- a/app/src/main/java/com/kunzisoft/keepass/education/EntryActivityEducation.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/education/EntryActivityEducation.kt
@@ -32,8 +32,7 @@ class EntryActivityEducation(activity: Activity)
fun checkAndPerformedEntryCopyEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(
- !isEducationCopyUsernamePerformed(activity),
+ return checkAndPerformedEducation(isEducationCopyUsernamePerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_field_copy_title),
activity.getString(R.string.education_field_copy_summary))
@@ -64,8 +63,7 @@ class EntryActivityEducation(activity: Activity)
fun checkAndPerformedEntryEditEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(
- !isEducationEntryEditPerformed(activity),
+ return checkAndPerformedEducation(isEducationEntryEditPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_entry_edit_title),
activity.getString(R.string.education_entry_edit_summary))
diff --git a/app/src/main/java/com/kunzisoft/keepass/education/EntryEditActivityEducation.kt b/app/src/main/java/com/kunzisoft/keepass/education/EntryEditActivityEducation.kt
index b4aa83ba5..39092833c 100644
--- a/app/src/main/java/com/kunzisoft/keepass/education/EntryEditActivityEducation.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/education/EntryEditActivityEducation.kt
@@ -35,7 +35,7 @@ class EntryEditActivityEducation(activity: Activity)
fun checkAndPerformedGeneratePasswordEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationPasswordGeneratorPerformed(activity),
+ return checkAndPerformedEducation(isEducationPasswordGeneratorPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_generate_password_title),
activity.getString(R.string.education_generate_password_summary))
@@ -66,7 +66,7 @@ class EntryEditActivityEducation(activity: Activity)
fun checkAndPerformedEntryNewFieldEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationEntryNewFieldPerformed(activity),
+ return checkAndPerformedEducation(isEducationEntryNewFieldPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_entry_new_field_title),
activity.getString(R.string.education_entry_new_field_summary))
@@ -97,7 +97,7 @@ class EntryEditActivityEducation(activity: Activity)
fun checkAndPerformedAttachmentEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationAddAttachmentPerformed(activity),
+ return checkAndPerformedEducation(isEducationAddAttachmentPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_add_attachment_title),
activity.getString(R.string.education_add_attachment_summary))
@@ -128,7 +128,7 @@ class EntryEditActivityEducation(activity: Activity)
fun checkAndPerformedSetUpOTPEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationSetupOTPPerformed(activity),
+ return checkAndPerformedEducation(isEducationSetupOTPPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_setup_OTP_title),
activity.getString(R.string.education_setup_OTP_summary))
diff --git a/app/src/main/java/com/kunzisoft/keepass/education/FileDatabaseSelectActivityEducation.kt b/app/src/main/java/com/kunzisoft/keepass/education/FileDatabaseSelectActivityEducation.kt
index 7a58b943c..86ea81200 100644
--- a/app/src/main/java/com/kunzisoft/keepass/education/FileDatabaseSelectActivityEducation.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/education/FileDatabaseSelectActivityEducation.kt
@@ -39,7 +39,7 @@ class FileDatabaseSelectActivityEducation(activity: Activity)
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
// Try to open the creation base education
- return checkAndPerformedEducation(!isEducationCreateDatabasePerformed(activity),
+ return checkAndPerformedEducation(isEducationCreateDatabasePerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_create_database_title),
activity.getString(R.string.education_create_database_summary))
@@ -71,7 +71,7 @@ class FileDatabaseSelectActivityEducation(activity: Activity)
fun checkAndPerformedSelectDatabaseEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationSelectDatabasePerformed(activity),
+ return checkAndPerformedEducation(isEducationSelectDatabasePerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_select_database_title),
activity.getString(R.string.education_select_database_summary))
diff --git a/app/src/main/java/com/kunzisoft/keepass/education/GroupActivityEducation.kt b/app/src/main/java/com/kunzisoft/keepass/education/GroupActivityEducation.kt
index 2293bd47c..23e67b948 100644
--- a/app/src/main/java/com/kunzisoft/keepass/education/GroupActivityEducation.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/education/GroupActivityEducation.kt
@@ -31,7 +31,7 @@ class GroupActivityEducation(activity: Activity)
fun checkAndPerformedAddNodeButtonEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationNewNodePerformed(activity),
+ return checkAndPerformedEducation(isEducationNewNodePerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_new_node_title),
activity.getString(R.string.education_new_node_summary))
@@ -58,7 +58,7 @@ class GroupActivityEducation(activity: Activity)
fun checkAndPerformedSearchMenuEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationSearchPerformed(activity),
+ return checkAndPerformedEducation(isEducationSearchPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_search_title),
activity.getString(R.string.education_search_summary))
@@ -85,7 +85,7 @@ class GroupActivityEducation(activity: Activity)
fun checkAndPerformedSortMenuEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationSortPerformed(activity),
+ return checkAndPerformedEducation(isEducationSortPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_sort_title),
activity.getString(R.string.education_sort_summary))
@@ -112,7 +112,7 @@ class GroupActivityEducation(activity: Activity)
fun checkAndPerformedLockMenuEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationLockPerformed(activity),
+ return checkAndPerformedEducation(isEducationLockPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_lock_title),
activity.getString(R.string.education_lock_summary))
diff --git a/app/src/main/java/com/kunzisoft/keepass/education/PasswordActivityEducation.kt b/app/src/main/java/com/kunzisoft/keepass/education/PasswordActivityEducation.kt
index c3f58180d..dc4afd1d9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/education/PasswordActivityEducation.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/education/PasswordActivityEducation.kt
@@ -32,7 +32,7 @@ class PasswordActivityEducation(activity: Activity)
fun checkAndPerformedUnlockEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationUnlockPerformed(activity),
+ return checkAndPerformedEducation(isEducationUnlockPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_unlock_title),
activity.getString(R.string.education_unlock_summary))
@@ -60,7 +60,7 @@ class PasswordActivityEducation(activity: Activity)
fun checkAndPerformedReadOnlyEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationReadOnlyPerformed(activity),
+ return checkAndPerformedEducation(isEducationReadOnlyPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_read_only_title),
activity.getString(R.string.education_read_only_summary))
@@ -87,7 +87,7 @@ class PasswordActivityEducation(activity: Activity)
fun checkAndPerformedBiometricEducation(educationView: View,
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
- return checkAndPerformedEducation(!isEducationBiometricPerformed(activity),
+ return checkAndPerformedEducation(isEducationBiometricPerformed(activity),
TapTarget.forView(educationView,
activity.getString(R.string.education_advanced_unlock_title),
activity.getString(R.string.education_advanced_unlock_summary))
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/CipherDecryptDatabase.kt b/app/src/main/java/com/kunzisoft/keepass/model/CipherDecryptDatabase.kt
new file mode 100644
index 000000000..0b3503468
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/model/CipherDecryptDatabase.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2022 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.model
+
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import com.kunzisoft.keepass.utils.readEnum
+import com.kunzisoft.keepass.utils.writeEnum
+
+class CipherDecryptDatabase(): Parcelable {
+
+ var databaseUri: Uri? = null
+ var credentialStorage: CredentialStorage = CredentialStorage.DEFAULT
+ var decryptedValue: ByteArray = byteArrayOf()
+
+ constructor(parcel: Parcel): this() {
+ databaseUri = parcel.readParcelable(Uri::class.java.classLoader)
+ credentialStorage = parcel.readEnum() ?: credentialStorage
+ decryptedValue = ByteArray(parcel.readInt())
+ parcel.readByteArray(decryptedValue)
+ }
+
+ fun replaceContent(copy: CipherDecryptDatabase) {
+ this.decryptedValue = copy.decryptedValue
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeParcelable(databaseUri, flags)
+ parcel.writeEnum(credentialStorage)
+ parcel.writeInt(decryptedValue.size)
+ parcel.writeByteArray(decryptedValue)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): CipherDecryptDatabase {
+ return CipherDecryptDatabase(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as CipherDecryptDatabase
+
+ if (databaseUri != other.databaseUri) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return databaseUri.hashCode()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/CipherEncryptDatabase.kt b/app/src/main/java/com/kunzisoft/keepass/model/CipherEncryptDatabase.kt
new file mode 100644
index 000000000..f3ed3dade
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/model/CipherEncryptDatabase.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2022 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.model
+
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import com.kunzisoft.keepass.utils.readEnum
+import com.kunzisoft.keepass.utils.writeEnum
+
+class CipherEncryptDatabase(): Parcelable {
+
+ var databaseUri: Uri? = null
+ var credentialStorage: CredentialStorage = CredentialStorage.DEFAULT
+ var encryptedValue: ByteArray = byteArrayOf()
+ var specParameters: ByteArray = byteArrayOf()
+
+ constructor(parcel: Parcel): this() {
+ databaseUri = parcel.readParcelable(Uri::class.java.classLoader)
+ credentialStorage = parcel.readEnum() ?: credentialStorage
+ encryptedValue = ByteArray(parcel.readInt())
+ parcel.readByteArray(encryptedValue)
+ specParameters = ByteArray(parcel.readInt())
+ parcel.readByteArray(specParameters)
+ }
+
+ fun replaceContent(copy: CipherEncryptDatabase) {
+ this.encryptedValue = copy.encryptedValue
+ this.specParameters = copy.specParameters
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeParcelable(databaseUri, flags)
+ parcel.writeEnum(credentialStorage)
+ parcel.writeInt(encryptedValue.size)
+ parcel.writeByteArray(encryptedValue)
+ parcel.writeInt(specParameters.size)
+ parcel.writeByteArray(specParameters)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): CipherEncryptDatabase {
+ return CipherEncryptDatabase(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as CipherEncryptDatabase
+
+ if (databaseUri != other.databaseUri) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return databaseUri.hashCode()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/CredentialStorage.kt b/app/src/main/java/com/kunzisoft/keepass/model/CredentialStorage.kt
new file mode 100644
index 000000000..b82d583b0
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/model/CredentialStorage.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.model
+
+enum class CredentialStorage {
+ PASSWORD, KEY_FILE, HARDWARE_KEY;
+
+ companion object {
+ fun getFromOrdinal(ordinal: Int): CredentialStorage {
+ return when (ordinal) {
+ 0 -> PASSWORD
+ 1 -> KEY_FILE
+ 2 -> HARDWARE_KEY
+ else -> DEFAULT
+ }
+ }
+
+ val DEFAULT: CredentialStorage
+ get() = PASSWORD
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt
index 12d1b4b0a..bab761f92 100644
--- a/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt
@@ -23,6 +23,7 @@ import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.*
+import com.kunzisoft.keepass.database.element.entry.AutoType
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.otp.OtpElement
@@ -42,6 +43,7 @@ class EntryInfo : NodeInfo {
var foregroundColor: Int? = null
var customFields: MutableList = mutableListOf()
var attachments: MutableList = mutableListOf()
+ var autoType: AutoType = AutoType()
var otpModel: OtpModel? = null
var isTemplate: Boolean = false
@@ -60,6 +62,7 @@ class EntryInfo : NodeInfo {
foregroundColor = if (readFgColor == -1) null else readFgColor
parcel.readList(customFields, Field::class.java.classLoader)
parcel.readList(attachments, Attachment::class.java.classLoader)
+ autoType = parcel.readParcelable(AutoType::class.java.classLoader) ?: autoType
otpModel = parcel.readParcelable(OtpModel::class.java.classLoader) ?: otpModel
isTemplate = parcel.readByte().toInt() != 0
}
@@ -80,6 +83,7 @@ class EntryInfo : NodeInfo {
parcel.writeInt(foregroundColor ?: -1)
parcel.writeList(customFields)
parcel.writeList(attachments)
+ parcel.writeParcelable(autoType, flags)
parcel.writeParcelable(otpModel, flags)
parcel.writeByte((if (isTemplate) 1 else 0).toByte())
}
@@ -105,6 +109,7 @@ class EntryInfo : NodeInfo {
return customFields.lastOrNull { it.name == label }?.protectedValue?.toString() ?: ""
}
+ // Return true if modified
private fun addUniqueField(field: Field, number: Int = 0) {
var sameName = false
var sameValue = false
@@ -126,7 +131,19 @@ class EntryInfo : NodeInfo {
(customFields as ArrayList).add(Field(field.name + suffix, field.protectedValue))
}
- fun saveSearchInfo(database: Database?, searchInfo: SearchInfo) {
+ private fun containsDomainOrApplicationId(search: String): Boolean {
+ if (url.contains(search))
+ return true
+ return customFields.find {
+ it.protectedValue.stringValue.contains(search)
+ } != null
+ }
+
+ /**
+ * Add searchInfo to current EntryInfo, return true if new data, false if no modification
+ */
+ fun saveSearchInfo(database: Database?, searchInfo: SearchInfo): Boolean {
+ var modification = false
searchInfo.otpString?.let { otpString ->
// Replace the OTP field
OtpEntryFields.parseOTPUri(otpString)?.let { otpElement ->
@@ -141,31 +158,45 @@ class EntryInfo : NodeInfo {
mutableCustomFields.remove(otpField)
}
mutableCustomFields.add(otpField)
+ modification = true
}
} ?: searchInfo.webDomain?.let { webDomain ->
// If unable to save web domain in custom field or URL not populated, save in URL
val scheme = searchInfo.webScheme
- val webScheme = if (scheme.isNullOrEmpty()) "http" else scheme
+ val webScheme = if (scheme.isNullOrEmpty()) "https" else scheme
val webDomainToStore = "$webScheme://$webDomain"
- if (database?.allowEntryCustomFields() != true || url.isEmpty()) {
- url = webDomainToStore
- } else if (url != webDomainToStore) {
- // Save web domain in custom field
- addUniqueField(Field(WEB_DOMAIN_FIELD_NAME,
- ProtectedString(false, webDomainToStore)),
+ if (!containsDomainOrApplicationId(webDomain)) {
+ if (database?.allowEntryCustomFields() != true || url.isEmpty()) {
+ url = webDomainToStore
+ } else {
+ // Save web domain in custom field
+ addUniqueField(
+ Field(
+ WEB_DOMAIN_FIELD_NAME,
+ ProtectedString(false, webDomainToStore)
+ ),
1 // Start to one because URL is a standard field name
- )
+ )
+ }
+ modification = true
}
} ?: run {
// Save application id in custom field
if (database?.allowEntryCustomFields() == true) {
searchInfo.applicationId?.let { applicationId ->
- addUniqueField(Field(APPLICATION_ID_FIELD_NAME,
- ProtectedString(false, applicationId))
- )
+ if (!containsDomainOrApplicationId(applicationId)) {
+ addUniqueField(
+ Field(
+ APPLICATION_ID_FIELD_NAME,
+ ProtectedString(false, applicationId)
+ )
+ )
+ modification = true
+ }
}
}
}
+ return modification
}
fun saveRegisterInfo(database: Database?, registerInfo: RegisterInfo) {
@@ -209,6 +240,7 @@ class EntryInfo : NodeInfo {
if (foregroundColor != other.foregroundColor) return false
if (customFields != other.customFields) return false
if (attachments != other.attachments) return false
+ if (autoType != other.autoType) return false
if (otpModel != other.otpModel) return false
if (isTemplate != other.isTemplate) return false
@@ -227,6 +259,7 @@ class EntryInfo : NodeInfo {
result = 31 * result + foregroundColor.hashCode()
result = 31 * result + customFields.hashCode()
result = 31 * result + attachments.hashCode()
+ result = 31 * result + autoType.hashCode()
result = 31 * result + (otpModel?.hashCode() ?: 0)
result = 31 * result + isTemplate.hashCode()
return result
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/GroupInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/GroupInfo.kt
index c7e146d29..fcd5df0d5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/model/GroupInfo.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/model/GroupInfo.kt
@@ -12,6 +12,9 @@ class GroupInfo : NodeInfo {
var id: UUID? = null
var notes: String? = null
+ var searchable: Boolean? = null
+ var enableAutoType: Boolean? = null
+ var defaultAutoTypeSequence: String = ""
var tags: Tags = Tags()
init {
@@ -23,6 +26,11 @@ class GroupInfo : NodeInfo {
constructor(parcel: Parcel): super(parcel) {
id = parcel.readParcelable(ParcelUuid::class.java.classLoader)?.uuid ?: id
notes = parcel.readString()
+ val isSearchingEnabled = parcel.readInt()
+ searchable = if (isSearchingEnabled == -1) null else isSearchingEnabled == 1
+ val isAutoTypeEnabled = parcel.readInt()
+ enableAutoType = if (isAutoTypeEnabled == -1) null else isAutoTypeEnabled == 1
+ defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
tags = parcel.readParcelable(Tags::class.java.classLoader) ?: tags
}
@@ -31,6 +39,9 @@ class GroupInfo : NodeInfo {
val uuid = if (id != null) ParcelUuid(id) else null
parcel.writeParcelable(uuid, flags)
parcel.writeString(notes)
+ parcel.writeInt(if (searchable == null) -1 else if (searchable!!) 1 else 0)
+ parcel.writeInt(if (enableAutoType == null) -1 else if (enableAutoType!!) 1 else 0)
+ parcel.writeString(defaultAutoTypeSequence)
parcel.writeParcelable(tags, flags)
}
@@ -41,6 +52,9 @@ class GroupInfo : NodeInfo {
if (id != other.id) return false
if (notes != other.notes) return false
+ if (searchable != other.searchable) return false
+ if (enableAutoType != other.enableAutoType) return false
+ if (defaultAutoTypeSequence != other.defaultAutoTypeSequence) return false
if (tags != other.tags) return false
return true
@@ -50,6 +64,9 @@ class GroupInfo : NodeInfo {
var result = super.hashCode()
result = 31 * result + (id?.hashCode() ?: 0)
result = 31 * result + (notes?.hashCode() ?: 0)
+ result = 31 * result + searchable.hashCode()
+ result = 31 * result + enableAutoType.hashCode()
+ result = 31 * result + defaultAutoTypeSequence.hashCode()
result = 31 * result + tags.hashCode()
return result
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/model/NodeInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/NodeInfo.kt
index 95de2b790..5becba7b5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/model/NodeInfo.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/model/NodeInfo.kt
@@ -2,6 +2,7 @@ package com.kunzisoft.keepass.model
import android.os.Parcel
import android.os.Parcelable
+import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.utils.UuidUtil
@@ -15,6 +16,7 @@ open class NodeInfo() : Parcelable {
var lastModificationTime: DateInstant = DateInstant()
var expires: Boolean = false
var expiryTime: DateInstant = DateInstant.IN_ONE_MONTH_DATE_TIME
+ var customData: CustomData = CustomData()
constructor(parcel: Parcel) : this() {
title = parcel.readString() ?: title
@@ -23,6 +25,7 @@ open class NodeInfo() : Parcelable {
lastModificationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: lastModificationTime
expires = parcel.readInt() != 0
expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
+ customData = parcel.readParcelable(CustomData::class.java.classLoader) ?: customData
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
@@ -32,6 +35,7 @@ open class NodeInfo() : Parcelable {
parcel.writeParcelable(lastModificationTime, flags)
parcel.writeInt(if (expires) 1 else 0)
parcel.writeParcelable(expiryTime, flags)
+ parcel.writeParcelable(customData, flags)
}
override fun describeContents(): Int {
@@ -48,6 +52,7 @@ open class NodeInfo() : Parcelable {
if (lastModificationTime != other.lastModificationTime) return false
if (expires != other.expires) return false
if (expiryTime != other.expiryTime) return false
+ if (customData != other.customData) return false
return true
}
@@ -59,6 +64,7 @@ open class NodeInfo() : Parcelable {
result = 31 * result + lastModificationTime.hashCode()
result = 31 * result + expires.hashCode()
result = 31 * result + expiryTime.hashCode()
+ result = 31 * result + customData.hashCode()
return result
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
index 81e41cc27..fb04544a7 100644
--- a/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/DatabaseTaskNotificationService.kt
@@ -22,12 +22,14 @@ package com.kunzisoft.keepass.services
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
-import android.os.*
+import android.os.Binder
+import android.os.Build
+import android.os.Bundle
+import android.os.IBinder
import android.util.Log
import androidx.media.app.NotificationCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.GroupActivity
-import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.action.*
import com.kunzisoft.keepass.database.action.history.DeleteEntryHistoryDatabaseRunnable
import com.kunzisoft.keepass.database.action.history.RestoreEntryHistoryDatabaseRunnable
@@ -39,16 +41,19 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
+import com.kunzisoft.keepass.model.CipherEncryptDatabase
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.timeout.TimeoutHelper
-import com.kunzisoft.keepass.utils.*
+import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
+import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
+import com.kunzisoft.keepass.utils.LOCK_ACTION
+import com.kunzisoft.keepass.utils.closeDatabase
import com.kunzisoft.keepass.viewmodels.FileDatabaseInfo
import kotlinx.coroutines.*
import java.util.*
-import kotlin.collections.ArrayList
open class DatabaseTaskNotificationService : LockNotificationService(), ProgressTaskUpdater {
@@ -226,7 +231,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
val actionRunnable: ActionRunnable? = when (intentAction) {
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent, database)
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent, database)
- ACTION_DATABASE_MERGE_TASK -> buildDatabaseMergeActionTask(database)
+ ACTION_DATABASE_MERGE_TASK -> buildDatabaseMergeActionTask(intent, database)
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(database)
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent, database)
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent, database)
@@ -469,7 +474,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
intent?.removeExtra(DATABASE_URI_KEY)
intent?.removeExtra(MAIN_CREDENTIAL_KEY)
intent?.removeExtra(READ_ONLY_KEY)
- intent?.removeExtra(CIPHER_ENTITY_KEY)
+ intent?.removeExtra(CIPHER_DATABASE_KEY)
intent?.removeExtra(FIX_DUPLICATE_UUID_KEY)
intent?.removeExtra(GROUP_KEY)
intent?.removeExtra(ENTRY_KEY)
@@ -570,13 +575,13 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
if (intent.hasExtra(DATABASE_URI_KEY)
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
&& intent.hasExtra(READ_ONLY_KEY)
- && intent.hasExtra(CIPHER_ENTITY_KEY)
+ && intent.hasExtra(CIPHER_DATABASE_KEY)
&& intent.hasExtra(FIX_DUPLICATE_UUID_KEY)
) {
val databaseUri: Uri? = intent.getParcelableExtra(DATABASE_URI_KEY)
val mainCredential: MainCredential = intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
val readOnly: Boolean = intent.getBooleanExtra(READ_ONLY_KEY, true)
- val cipherEntity: CipherDatabaseEntity? = intent.getParcelableExtra(CIPHER_ENTITY_KEY)
+ val cipherEncryptDatabase: CipherEncryptDatabase? = intent.getParcelableExtra(CIPHER_DATABASE_KEY)
if (databaseUri == null)
return null
@@ -589,7 +594,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
databaseUri,
mainCredential,
readOnly,
- cipherEntity,
+ cipherEncryptDatabase,
intent.getBooleanExtra(FIX_DUPLICATE_UUID_KEY, false),
this
) { result ->
@@ -598,7 +603,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
putParcelable(DATABASE_URI_KEY, databaseUri)
putParcelable(MAIN_CREDENTIAL_KEY, mainCredential)
putBoolean(READ_ONLY_KEY, readOnly)
- putParcelable(CIPHER_ENTITY_KEY, cipherEntity)
+ putParcelable(CIPHER_DATABASE_KEY, cipherEncryptDatabase)
}
}
} else {
@@ -606,10 +611,21 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}
}
- private fun buildDatabaseMergeActionTask(database: Database): ActionRunnable {
+ private fun buildDatabaseMergeActionTask(intent: Intent, database: Database): ActionRunnable {
+ var databaseToMergeUri: Uri? = null
+ var databaseToMergeMainCredential: MainCredential? = null
+ if (intent.hasExtra(DATABASE_URI_KEY)) {
+ databaseToMergeUri = intent.getParcelableExtra(DATABASE_URI_KEY)
+ }
+ if (intent.hasExtra(MAIN_CREDENTIAL_KEY)) {
+ databaseToMergeMainCredential = intent.getParcelableExtra(MAIN_CREDENTIAL_KEY)
+ }
+
return MergeDatabaseRunnable(
this,
database,
+ databaseToMergeUri,
+ databaseToMergeMainCredential,
this
) { result ->
// No need to add each info to reload database
@@ -633,7 +649,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
) {
val databaseUri: Uri = intent.getParcelableExtra(DATABASE_URI_KEY) ?: return null
- AssignPasswordInDatabaseRunnable(this,
+ AssignMainCredentialInDatabaseRunnable(this,
database,
databaseUri,
intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
@@ -911,9 +927,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
*/
private fun buildDatabaseSave(intent: Intent, database: Database): ActionRunnable? {
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
+
+ var databaseCopyUri: Uri? = null
+ if (intent.hasExtra(DATABASE_URI_KEY)) {
+ databaseCopyUri = intent.getParcelableExtra(DATABASE_URI_KEY)
+ }
+
SaveDatabaseRunnable(this,
database,
- !database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
+ !database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
+ databaseCopyUri)
} else {
null
}
@@ -963,7 +986,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
const val DATABASE_URI_KEY = "DATABASE_URI_KEY"
const val MAIN_CREDENTIAL_KEY = "MAIN_CREDENTIAL_KEY"
const val READ_ONLY_KEY = "READ_ONLY_KEY"
- const val CIPHER_ENTITY_KEY = "CIPHER_ENTITY_KEY"
+ const val CIPHER_DATABASE_KEY = "CIPHER_DATABASE_KEY"
const val FIX_DUPLICATE_UUID_KEY = "FIX_DUPLICATE_UUID_KEY"
const val GROUP_KEY = "GROUP_KEY"
const val ENTRY_KEY = "ENTRY_KEY"
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt
index 35dab43ae..1e9fa538a 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt
@@ -41,12 +41,12 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() {
}
}
- override fun onDisplayPreferenceDialog(preference: Preference?) {
+ override fun onDisplayPreferenceDialog(preference: Preference) {
var otherDialogFragment = false
var dialogFragment: DialogFragment? = null
- when (preference?.key) {
+ when (preference.key) {
getString(R.string.autofill_application_id_blocklist_key) -> {
dialogFragment = AutofillBlocklistAppIdPreferenceDialogFragmentCompat.newInstance(preference.key)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsFragment.kt
index c1af92d98..9616fd7a7 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/MagikeyboardSettingsFragment.kt
@@ -34,13 +34,13 @@ class MagikeyboardSettingsFragment : PreferenceFragmentCompat() {
setPreferencesFromResource(R.xml.preferences_keyboard, rootKey)
}
- override fun onDisplayPreferenceDialog(preference: Preference?) {
+ override fun onDisplayPreferenceDialog(preference: Preference) {
var otherDialogFragment = false
var dialogFragment: DialogFragment? = null
// Main Preferences
- when (preference?.key) {
+ when (preference.key) {
getString(R.string.keyboard_entry_timeout_key) -> {
dialogFragment = DurationDialogFragmentCompat.newInstance(preference.key)
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt
index 588410864..1885202e1 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt
@@ -365,7 +365,9 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
) { _, _ ->
validate?.invoke()
deleteKeysAlertDialog?.setOnDismissListener(null)
- AdvancedUnlockManager.deleteAllEntryKeysInKeystoreForBiometric(activity)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ AdvancedUnlockManager.deleteAllEntryKeysInKeystoreForBiometric(activity)
+ }
}
.setNegativeButton(resources.getString(android.R.string.cancel)
) { _, _ ->}
@@ -437,9 +439,9 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
}
- override fun onPreferenceTreeClick(preference: Preference?): Boolean {
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
// To reload group when appearance settings are modified
- when (preference?.key) {
+ when (preference.key) {
getString(R.string.setting_style_key),
getString(R.string.setting_style_brightness_key),
getString(R.string.setting_icon_pack_choose_key),
@@ -459,13 +461,13 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
return super.onPreferenceTreeClick(preference)
}
- override fun onDisplayPreferenceDialog(preference: Preference?) {
+ override fun onDisplayPreferenceDialog(preference: Preference) {
var otherDialogFragment = false
var dialogFragment: DialogFragment? = null
// Main Preferences
- when (preference?.key) {
+ when (preference.key) {
getString(R.string.app_timeout_key),
getString(R.string.clipboard_timeout_key),
getString(R.string.temp_advanced_unlock_timeout_key) -> {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
index c55a727f3..3c2c7c0d8 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt
@@ -30,7 +30,7 @@ import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreference
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.R
-import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
+import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
@@ -43,7 +43,6 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.settings.preference.*
import com.kunzisoft.keepass.settings.preferencedialogfragment.*
import com.kunzisoft.keepass.tasks.ActionRunnable
-import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetrieval {
@@ -155,18 +154,22 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
// Database name
dbNamePref = findPreference(getString(R.string.database_name_key))
- if (database.allowName) {
- dbNamePref?.summary = database.name
- } else {
- dbGeneralPrefCategory?.removePreference(dbNamePref)
+ dbNamePref?.let { namePreference ->
+ if (database.allowName) {
+ namePreference.summary = database.name
+ } else {
+ dbGeneralPrefCategory?.removePreference(namePreference)
+ }
}
// Database description
dbDescriptionPref = findPreference(getString(R.string.database_description_key))
- if (database.allowDescription) {
- dbDescriptionPref?.summary = database.description
- } else {
- dbGeneralPrefCategory?.removePreference(dbDescriptionPref)
+ dbDescriptionPref?.let { descriptionPreference ->
+ if (database.allowDescription) {
+ dbDescriptionPref?.summary = database.description
+ } else {
+ dbGeneralPrefCategory?.removePreference(descriptionPreference)
+ }
}
// Database default username
@@ -238,7 +241,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
// Templates
val templatesGroupPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_templates_key))
templatesGroupPref = findPreference(getString(R.string.templates_group_uuid_key))
- if (database.allowConfigurableTemplatesGroup) {
+ if (database.allowTemplatesGroup) {
val templatesEnablePref: SwitchPreference? = findPreference(getString(R.string.templates_group_enable_key))
templatesEnablePref?.apply {
isChecked = database.isTemplatesEnabled
@@ -334,7 +337,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
findPreference(getString(R.string.settings_database_change_credentials_key))?.apply {
isEnabled = if (!mDatabaseReadOnly) {
onPreferenceClickListener = Preference.OnPreferenceClickListener {
- AssignMasterKeyDialogFragment.getInstance(database.allowNoMasterKey)
+ SetMainCredentialDialogFragment.getInstance(database.allowNoMasterKey)
.show(parentFragmentManager, "passwordDialog")
false
}
@@ -355,7 +358,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
}
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = super.onCreateView(inflater, container, savedInstanceState)
try {
@@ -565,13 +568,13 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
}
}
- override fun onDisplayPreferenceDialog(preference: Preference?) {
+ override fun onDisplayPreferenceDialog(preference: Preference) {
var otherDialogFragment = false
var dialogFragment: DialogFragment? = null
// Main Preferences
- when (preference?.key) {
+ when (preference.key) {
getString(R.string.database_name_key) -> {
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.key)
}
@@ -669,27 +672,29 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
}
R.id.menu_merge_database -> {
mergeDatabase()
- return true
+ true
}
R.id.menu_reload_database -> {
reloadDatabase()
- return true
+ true
}
-
- else -> {
+ R.id.menu_app_settings -> {
// Check the time lock before launching settings
// TODO activity menu
(activity as SettingsActivity?)?.let {
- MenuUtil.onDefaultMenuOptionsItemSelected(it, item, true)
+ SettingsActivity.launch(it, true)
}
+ true
+ }
+ else -> {
super.onOptionsItemSelected(item)
}
}
}
- override fun onPreferenceTreeClick(preference: Preference?): Boolean {
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
// To reload group when database settings are modified
- when (preference?.key) {
+ when (preference.key) {
getString(R.string.database_name_key),
getString(R.string.database_description_key),
getString(R.string.database_default_username_key),
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt
index 822101ee3..0089d8ab9 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt
@@ -92,12 +92,6 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.remember_keyfile_locations_default))
}
- fun omitBackup(context: Context): Boolean {
- val prefs = PreferenceManager.getDefaultSharedPreferences(context)
- return prefs.getBoolean(context.getString(R.string.omit_backup_search_key),
- context.resources.getBoolean(R.bool.omit_backup_search_default))
- }
-
fun automaticallyFocusSearch(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.auto_focus_search_key),
@@ -608,7 +602,6 @@ object PreferencesUtil {
context.getString(R.string.enable_read_only_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_auto_save_database_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_keep_screen_on_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.omit_backup_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.auto_focus_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.subdomain_search_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.app_timeout_key) -> editor.putString(name, value.toLong().toString())
@@ -678,23 +671,7 @@ object PreferencesUtil {
putPropertiesInPreferences(properties,
Education.getEducationSharedPreferences(context)) { editor, name, value ->
- when (name) {
- context.getString(R.string.education_create_db_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_select_db_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_unlock_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_read_only_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_biometric_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_search_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_new_node_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_sort_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_lock_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_copy_username_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_entry_edit_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_password_generator_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_entry_new_field_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_add_attachment_key) -> editor.putBoolean(name, value.toBoolean())
- context.getString(R.string.education_setup_OTP_key) -> editor.putBoolean(name, value.toBoolean())
- }
+ Education.putPropertiesInEducationPreferences(context, editor, name, value)
}
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt
index 04f19014c..eb68fd2b5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt
@@ -32,7 +32,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.kunzisoft.keepass.R
-import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
+import com.kunzisoft.keepass.activities.dialogs.SetMainCredentialDialogFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.database.element.Database
@@ -46,7 +46,7 @@ import java.util.*
open class SettingsActivity
: DatabaseLockActivity(),
MainPreferenceFragment.Callback,
- AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
+ SetMainCredentialDialogFragment.AssignMainCredentialDialogListener {
private var backupManager: BackupManager? = null
private var mExternalFileHelper: ExternalFileHelper? = null
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/DurationDialogPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/DurationDialogPreference.kt
index 4d1f203d8..46f7a4e92 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/DurationDialogPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/DurationDialogPreference.kt
@@ -61,9 +61,9 @@ class DurationDialogPreference @JvmOverloads constructor(context: Context,
}
}
- override fun onGetDefaultValue(a: TypedArray?, index: Int): Any {
+ override fun onGetDefaultValue(a: TypedArray, index: Int): Any {
return try {
- a?.getString(index)?.toLongOrNull() ?: mDuration
+ a.getString(index)?.toLongOrNull() ?: mDuration
} catch (e: Exception) {
mDuration
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt
index 42e1bbbbd..367f73489 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt
@@ -35,7 +35,7 @@ open class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
return R.layout.pref_dialog_input_numbers
}
- override fun setSummary(summary: CharSequence) {
+ override fun setSummary(summary: CharSequence?) {
if (summary == UNKNOWN_VALUE_STRING) {
isEnabled = false
super.setSummary("")
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt
index 168efe2aa..ff07d973d 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt
@@ -30,7 +30,7 @@ class InputKdfSizePreference @JvmOverloads constructor(context: Context,
defStyleRes: Int = defStyleAttr)
: InputKdfNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
- override fun setSummary(summary: CharSequence) {
+ override fun setSummary(summary: CharSequence?) {
if (summary == UNKNOWN_VALUE_STRING) {
super.setSummary(summary)
} else {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt
index e0fe8da97..116eaedbe 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt
@@ -34,7 +34,7 @@ open class InputNumberPreference @JvmOverloads constructor(context: Context,
return R.layout.pref_dialog_input_numbers
}
- override fun setSummary(summary: CharSequence) {
+ override fun setSummary(summary: CharSequence?) {
if (summary == INFINITE_VALUE_STRING) {
super.setSummary("∞")
} else {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt
index 90b1e42d1..39dee92e6 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt
@@ -30,7 +30,7 @@ open class InputSizePreference @JvmOverloads constructor(context: Context,
defStyleRes: Int = defStyleAttr)
: InputNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
- override fun setSummary(summary: CharSequence) {
+ override fun setSummary(summary: CharSequence?) {
var summaryString = summary
try {
val memorySize = summary.toString().toLong()
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseTemplatesGroupPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseTemplatesGroupPreferenceDialogFragmentCompat.kt
index fa4200fea..4349e92d3 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseTemplatesGroupPreferenceDialogFragmentCompat.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseTemplatesGroupPreferenceDialogFragmentCompat.kt
@@ -64,7 +64,7 @@ class DatabaseTemplatesGroupPreferenceDialogFragmentCompat
super.onDialogClosed(database, positiveResult)
if (positiveResult) {
database?.let {
- if (database.allowConfigurableTemplatesGroup) {
+ if (database.allowTemplatesGroup) {
val oldGroup = database.templatesGroup
val newGroup = mGroupTemplates
database.setTemplatesGroup(newGroup)
diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt
index 73013862e..c7233968d 100644
--- a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt
@@ -32,18 +32,11 @@ import com.kunzisoft.keepass.settings.SettingsActivity
object MenuUtil {
- fun contributionMenuInflater(inflater: MenuInflater, menu: Menu) {
- if (!(BuildConfig.FULL_VERSION && BuildConfig.CLOSED_STORE))
- inflater.inflate(R.menu.contribution, menu)
- }
-
fun defaultMenuInflater(inflater: MenuInflater, menu: Menu) {
- contributionMenuInflater(inflater, menu)
- inflater.inflate(R.menu.default_menu, menu)
- }
-
- fun onContributionItemSelected(context: Context) {
- UriUtil.gotoUrl(context, R.string.contribution_url)
+ inflater.inflate(R.menu.settings, menu)
+ inflater.inflate(R.menu.about, menu)
+ if (!(BuildConfig.FULL_VERSION && BuildConfig.CLOSED_STORE))
+ menu.findItem(R.id.menu_contribute)?.isVisible = false
}
/*
@@ -54,7 +47,7 @@ object MenuUtil {
timeoutEnable: Boolean = false) {
when (item.itemId) {
R.id.menu_contribute -> {
- onContributionItemSelected(activity)
+ UriUtil.gotoUrl(activity, R.string.contribution_url)
}
R.id.menu_app_settings -> {
// To avoid flickering when launch settings in a LockingActivity
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/InheritedCompletionView.kt b/app/src/main/java/com/kunzisoft/keepass/view/InheritedCompletionView.kt
new file mode 100644
index 000000000..cde0638cf
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/view/InheritedCompletionView.kt
@@ -0,0 +1,63 @@
+package com.kunzisoft.keepass.view
+
+import android.content.Context
+import android.text.InputType
+import android.util.AttributeSet
+import android.widget.ArrayAdapter
+import androidx.appcompat.widget.AppCompatAutoCompleteTextView
+import com.kunzisoft.keepass.R
+
+class InheritedCompletionView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null
+) : AppCompatAutoCompleteTextView(context, attrs) {
+
+ val adapter = ArrayAdapter(
+ context,
+ android.R.layout.simple_list_item_1,
+ InheritedStatus.listOfStrings(context))
+
+ init {
+ setAdapter(adapter)
+ inputType = InputType.TYPE_NULL
+ adapter.filter.filter(null)
+ }
+
+ fun getValue(): Boolean? {
+ return InheritedStatus.getStatusFromString(context, text.toString()).value
+ }
+
+ fun setValue(inherited: Boolean?) {
+ setText(context.getString(InheritedStatus.getStatusFromValue(inherited).stringId))
+ adapter.filter.filter(null)
+ }
+
+ private enum class InheritedStatus(val stringId: Int, val value: Boolean?) {
+ INHERITED(R.string.inherited, null),
+ ENABLE(R.string.enable, true),
+ DISABLE(R.string.disable, false);
+
+ companion object {
+ fun listOfStrings(context: Context): List {
+ return listOf(
+ context.getString(INHERITED.stringId),
+ context.getString(ENABLE.stringId),
+ context.getString(DISABLE.stringId)
+ )
+ }
+
+ fun getStatusFromValue(value: Boolean?): InheritedStatus {
+ values().find { it.value == value }?.let {
+ return it
+ }
+ return INHERITED
+ }
+
+ fun getStatusFromString(context: Context, text: String): InheritedStatus {
+ values().find { context.getString(it.stringId) == text }?.let {
+ return it
+ }
+ return INHERITED
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/MainCredentialView.kt b/app/src/main/java/com/kunzisoft/keepass/view/MainCredentialView.kt
new file mode 100644
index 000000000..41f701fd9
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/view/MainCredentialView.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2022 Jeremy Jamet / Kunzisoft.
+ *
+ * This file is part of KeePassDX.
+ *
+ * KeePassDX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KeePassDX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KeePassDX. If not, see .
+ *
+ */
+package com.kunzisoft.keepass.view
+
+import android.content.Context
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.AttributeSet
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.CompoundButton
+import android.widget.EditText
+import android.widget.FrameLayout
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
+import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
+import com.kunzisoft.keepass.model.CredentialStorage
+import com.kunzisoft.keepass.model.MainCredential
+
+class MainCredentialView @JvmOverloads constructor(context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0)
+ : FrameLayout(context, attrs, defStyle) {
+
+ private var passwordView: EditText
+ private var keyFileSelectionView: KeyFileSelectionView
+ private var checkboxPasswordView: CompoundButton
+ private var checkboxKeyFileView: CompoundButton
+
+ var onPasswordChecked: (CompoundButton.OnCheckedChangeListener)? = null
+ var onValidateListener: (() -> Unit)? = null
+
+ private var mCredentialStorage: CredentialStorage = CredentialStorage.PASSWORD
+
+ init {
+ val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
+ inflater?.inflate(R.layout.view_main_credentials, this)
+
+ passwordView = findViewById(R.id.password)
+ keyFileSelectionView = findViewById(R.id.keyfile_selection)
+ checkboxPasswordView = findViewById(R.id.password_checkbox)
+ checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
+
+ val onEditorActionListener = object : TextView.OnEditorActionListener {
+ override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ onValidateListener?.invoke()
+ return true
+ }
+ return false
+ }
+ }
+
+ passwordView.setOnEditorActionListener(onEditorActionListener)
+ passwordView.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+
+ override fun afterTextChanged(editable: Editable) {
+ if (editable.toString().isNotEmpty() && !checkboxPasswordView.isChecked)
+ checkboxPasswordView.isChecked = true
+ }
+ })
+ passwordView.setOnKeyListener { _, _, keyEvent ->
+ var handled = false
+ if (keyEvent.action == KeyEvent.ACTION_DOWN
+ && keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER
+ ) {
+ onValidateListener?.invoke()
+ handled = true
+ }
+ handled
+ }
+
+ checkboxPasswordView.setOnCheckedChangeListener { view, checked ->
+ onPasswordChecked?.onCheckedChanged(view, checked)
+ }
+ }
+
+ fun setOpenKeyfileClickListener(externalFileHelper: ExternalFileHelper?) {
+ keyFileSelectionView.setOpenDocumentClickListener(externalFileHelper)
+ }
+
+ fun populatePasswordTextView(text: String?) {
+ if (text == null || text.isEmpty()) {
+ passwordView.setText("")
+ if (checkboxPasswordView.isChecked)
+ checkboxPasswordView.isChecked = false
+ } else {
+ passwordView.setText(text)
+ if (checkboxPasswordView.isChecked)
+ checkboxPasswordView.isChecked = true
+ }
+ }
+
+ fun populateKeyFileTextView(uri: Uri?) {
+ if (uri == null || uri.toString().isEmpty()) {
+ keyFileSelectionView.uri = null
+ if (checkboxKeyFileView.isChecked)
+ checkboxKeyFileView.isChecked = false
+ } else {
+ keyFileSelectionView.uri = uri
+ if (!checkboxKeyFileView.isChecked)
+ checkboxKeyFileView.isChecked = true
+ }
+ }
+
+ fun isFill(): Boolean {
+ return checkboxPasswordView.isChecked || checkboxKeyFileView.isChecked
+ }
+
+ fun getMainCredential(): MainCredential {
+ return MainCredential().apply {
+ this.masterPassword = if (checkboxPasswordView.isChecked)
+ passwordView.text?.toString() else null
+ this.keyFileUri = if (checkboxKeyFileView.isChecked)
+ keyFileSelectionView.uri else null
+ }
+ }
+
+ fun changeConditionToStoreCredential(credentialStorage: CredentialStorage) {
+ this.mCredentialStorage = credentialStorage
+ }
+
+ fun conditionToStoreCredential(): Boolean {
+ // TODO HARDWARE_KEY
+ return when (mCredentialStorage) {
+ CredentialStorage.PASSWORD -> checkboxPasswordView.isChecked
+ CredentialStorage.KEY_FILE -> checkboxPasswordView.isChecked
+ CredentialStorage.HARDWARE_KEY -> false
+ }
+ }
+
+ /**
+ * Return content of the store credential view allowed,
+ * String? for password
+ *
+ */
+ fun retrieveCredentialForStorage(listener: CredentialStorageListener): ByteArray? {
+ return when (mCredentialStorage) {
+ CredentialStorage.PASSWORD -> listener.passwordToStore(passwordView.text?.toString())
+ CredentialStorage.KEY_FILE -> listener.keyfileToStore(keyFileSelectionView.uri)
+ CredentialStorage.HARDWARE_KEY -> listener.hardwareKeyToStore()
+ }
+ }
+
+ interface CredentialStorageListener {
+ fun passwordToStore(password: String?): ByteArray?
+ fun keyfileToStore(keyfile: Uri?): ByteArray?
+ fun hardwareKeyToStore(): ByteArray?
+ }
+
+ fun requestPasswordFocus() {
+ passwordView.requestFocusFromTouch()
+ }
+
+ // Auto select the password field and open keyboard
+ fun focusPasswordFieldAndOpenKeyboard() {
+ passwordView.postDelayed({
+ passwordView.requestFocusFromTouch()
+ val inputMethodManager = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as? InputMethodManager?
+ inputMethodManager?.showSoftInput(passwordView, InputMethodManager.SHOW_IMPLICIT)
+ }, 100)
+ }
+
+ override fun onSaveInstanceState(): Parcelable {
+ val superState = super.onSaveInstanceState()
+ val saveState = SavedState(superState)
+ saveState.mCredentialStorage = this.mCredentialStorage
+ return saveState
+ }
+
+ override fun onRestoreInstanceState(state: Parcelable?) {
+ if (state !is SavedState) {
+ super.onRestoreInstanceState(state)
+ return
+ }
+ super.onRestoreInstanceState(state.superState)
+ this.mCredentialStorage = state.mCredentialStorage ?: CredentialStorage.DEFAULT
+ }
+
+ internal class SavedState : BaseSavedState {
+ var mCredentialStorage: CredentialStorage? = null
+
+ constructor(superState: Parcelable?) : super(superState) {}
+
+ private constructor(parcel: Parcel) : super(parcel) {
+ mCredentialStorage = CredentialStorage.getFromOrdinal(parcel.readInt())
+ }
+
+ override fun writeToParcel(out: Parcel, flags: Int) {
+ super.writeToParcel(out, flags)
+ out.writeInt(mCredentialStorage?.ordinal ?: 0)
+ }
+
+ companion object CREATOR : Parcelable.Creator {
+ override fun createFromParcel(parcel: Parcel): SavedState {
+ return SavedState(parcel)
+ }
+
+ override fun newArray(size: Int): Array {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/NavigationDatabaseView.kt b/app/src/main/java/com/kunzisoft/keepass/view/NavigationDatabaseView.kt
new file mode 100644
index 000000000..63475e7e5
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/view/NavigationDatabaseView.kt
@@ -0,0 +1,73 @@
+package com.kunzisoft.keepass.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.graphics.BlendModeColorFilterCompat
+import androidx.core.graphics.BlendModeCompat
+import androidx.core.view.isVisible
+import com.google.android.material.navigation.NavigationView
+import com.kunzisoft.keepass.R
+
+class NavigationDatabaseView @JvmOverloads constructor(context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0)
+ : NavigationView(context, attrs, defStyle) {
+
+ private var databaseNavContainerView: View? = null
+ private var databaseNavIconView: ImageView? = null
+ private var databaseNavModifiedView: ImageView? = null
+ private var databaseNavColorView: ImageView? = null
+ private var databaseNavNameView: TextView? = null
+ private var databaseNavPathView: TextView? = null
+ private var databaseNavVersionView: TextView? = null
+
+ init {
+ inflateHeaderView(R.layout.nav_header_database)
+ databaseNavIconView = databaseNavContainerView?.findViewById(R.id.nav_database_icon)
+ databaseNavModifiedView = databaseNavContainerView?.findViewById(R.id.nav_database_modified)
+ databaseNavColorView = databaseNavContainerView?.findViewById(R.id.nav_database_color)
+ databaseNavNameView = databaseNavContainerView?.findViewById(R.id.nav_database_name)
+ databaseNavPathView = databaseNavContainerView?.findViewById(R.id.nav_database_path)
+ databaseNavVersionView = databaseNavContainerView?.findViewById(R.id.nav_database_version)
+ }
+
+ override fun inflateHeaderView(res: Int): View {
+ val headerView = super.inflateHeaderView(res)
+ databaseNavContainerView = headerView
+ return headerView
+ }
+
+ fun setDatabaseName(name: String) {
+ databaseNavNameView?.text = name
+ }
+
+ fun setDatabasePath(path: String?) {
+ if (path != null) {
+ databaseNavPathView?.text = path
+ databaseNavPathView?.visibility = View.VISIBLE
+ } else {
+ databaseNavPathView?.visibility = View.GONE
+ }
+ }
+
+ fun setDatabaseVersion(version: String) {
+ databaseNavVersionView?.text = version
+ }
+
+ fun setDatabaseModifiedSinceLastLoading(modified: Boolean) {
+ databaseNavModifiedView?.isVisible = modified
+ }
+
+ fun setDatabaseColor(color: Int?) {
+ if (color != null) {
+ databaseNavColorView?.drawable?.colorFilter = BlendModeColorFilterCompat
+ .createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_IN)
+ databaseNavColorView?.visibility = View.VISIBLE
+ } else {
+ databaseNavColorView?.visibility = View.GONE
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt b/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt
new file mode 100644
index 000000000..271971d35
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt
@@ -0,0 +1,252 @@
+package com.kunzisoft.keepass.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CompoundButton
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.view.isVisible
+import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.database.search.SearchHelper
+import com.kunzisoft.keepass.database.search.SearchParameters
+
+class SearchFiltersView @JvmOverloads constructor(context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0)
+ : LinearLayout(context, attrs, defStyle) {
+
+ private var searchContainer: ViewGroup
+ private var searchAdvanceFiltersContainer: ViewGroup? = null
+ private var searchExpandButton: ImageView
+ private var searchNumbers: TextView
+ private var searchCurrentGroup: CompoundButton
+ private var searchCaseSensitive: CompoundButton
+ private var searchRegex: CompoundButton
+ private var searchTitle: CompoundButton
+ private var searchUsername: CompoundButton
+ private var searchPassword: CompoundButton
+ private var searchURL: CompoundButton
+ private var searchExpires: CompoundButton
+ private var searchNotes: CompoundButton
+ private var searchOther: CompoundButton
+ private var searchUUID: CompoundButton
+ private var searchTag: CompoundButton
+ private var searchGroupSearchable: CompoundButton
+ private var searchRecycleBin: CompoundButton
+ private var searchTemplate: CompoundButton
+
+ var searchParameters = SearchParameters()
+ get() {
+ return field.apply {
+ this.searchInCurrentGroup = searchCurrentGroup.isChecked
+ this.caseSensitive = searchCaseSensitive.isChecked
+ this.isRegex = searchRegex.isChecked
+ this.searchInTitles = searchTitle.isChecked
+ this.searchInUsernames = searchUsername.isChecked
+ this.searchInPasswords = searchPassword.isChecked
+ this.searchInUrls = searchURL.isChecked
+ this.excludeExpired = !(searchExpires.isChecked)
+ this.searchInNotes = searchNotes.isChecked
+ this.searchInOther = searchOther.isChecked
+ this.searchInUUIDs = searchUUID.isChecked
+ this.searchInTags = searchTag.isChecked
+ this.searchInRecycleBin = searchRecycleBin.isChecked
+ this.searchInTemplates = searchTemplate.isChecked
+ }
+ }
+ set(value) {
+ field = value
+ val tempListener = mOnParametersChangeListener
+ mOnParametersChangeListener = null
+ searchCurrentGroup.isChecked = value.searchInCurrentGroup
+ searchCaseSensitive.isChecked = value.caseSensitive
+ searchRegex.isChecked = value.isRegex
+ searchTitle.isChecked = value.searchInTitles
+ searchUsername.isChecked = value.searchInUsernames
+ searchPassword.isChecked = value.searchInPasswords
+ searchURL.isChecked = value.searchInUrls
+ searchExpires.isChecked = !value.excludeExpired
+ searchNotes.isChecked = value.searchInNotes
+ searchOther.isChecked = value.searchInOther
+ searchUUID.isChecked = value.searchInUUIDs
+ searchTag.isChecked = value.searchInTags
+ searchGroupSearchable.isChecked = value.searchInRecycleBin
+ searchRecycleBin.isChecked = value.searchInRecycleBin
+ searchTemplate.isChecked = value.searchInTemplates
+ mOnParametersChangeListener = tempListener
+ }
+
+ var onParametersChangeListener: ((searchParameters: SearchParameters) -> Unit)? = null
+ private var mOnParametersChangeListener: ((searchParameters: SearchParameters) -> Unit)? = {
+ // To recalculate height
+ if (searchAdvanceFiltersContainer?.visibility == View.VISIBLE) {
+ searchAdvanceFiltersContainer?.expand(
+ false,
+ searchAdvanceFiltersContainer?.getFullHeight()
+ )
+ }
+ onParametersChangeListener?.invoke(searchParameters)
+ }
+
+ init {
+ val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
+ inflater?.inflate(R.layout.view_search_filters, this)
+
+ searchContainer = findViewById(R.id.search_container)
+ searchAdvanceFiltersContainer = findViewById(R.id.search_advance_filters)
+ searchExpandButton = findViewById(R.id.search_expand)
+ searchNumbers = findViewById(R.id.search_numbers)
+ searchCurrentGroup = findViewById(R.id.search_chip_current_group)
+ searchCaseSensitive = findViewById(R.id.search_chip_case_sensitive)
+ searchRegex = findViewById(R.id.search_chip_regex)
+ searchTitle = findViewById(R.id.search_chip_title)
+ searchUsername = findViewById(R.id.search_chip_username)
+ searchPassword = findViewById(R.id.search_chip_password)
+ searchURL = findViewById(R.id.search_chip_url)
+ searchExpires = findViewById(R.id.search_chip_expires)
+ searchNotes = findViewById(R.id.search_chip_note)
+ searchUUID = findViewById(R.id.search_chip_uuid)
+ searchOther = findViewById(R.id.search_chip_other)
+ searchTag = findViewById(R.id.search_chip_tag)
+ searchGroupSearchable = findViewById(R.id.search_chip_group_searchable)
+ searchRecycleBin = findViewById(R.id.search_chip_recycle_bin)
+ searchTemplate = findViewById(R.id.search_chip_template)
+
+ // Expand menu with button
+ searchExpandButton.setOnClickListener {
+ val isVisible = searchAdvanceFiltersContainer?.visibility == View.VISIBLE
+ if (isVisible)
+ closeAdvancedFilters()
+ else
+ openAdvancedFilters()
+ }
+
+ searchCurrentGroup.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInCurrentGroup = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchCaseSensitive.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.caseSensitive = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchRegex.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.isRegex = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchTitle.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInTitles = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchUsername.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInUsernames = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchPassword.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInPasswords = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchURL.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInUrls = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchExpires.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.excludeExpired = !isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchNotes.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInNotes = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchUUID.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInUUIDs = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchOther.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInOther = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchTag.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInTags = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchGroupSearchable.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInSearchableGroup = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchRecycleBin.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInRecycleBin = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ searchTemplate.setOnCheckedChangeListener { _, isChecked ->
+ searchParameters.searchInTemplates = isChecked
+ mOnParametersChangeListener?.invoke(searchParameters)
+ }
+ }
+
+ fun setNumbers(numbers: Int) {
+ searchNumbers.text = SearchHelper.showNumberOfSearchResults(numbers)
+ }
+
+ fun setCurrentGroupText(text: String) {
+ val maxChars = 12
+ searchCurrentGroup.text = when {
+ text.isEmpty() -> context.getString(R.string.current_group)
+ text.length > maxChars -> text.substring(0, maxChars) + "…"
+ else -> text
+ }
+ }
+
+ fun availableOther(available: Boolean) {
+ searchOther.isVisible = available
+ }
+
+ fun availableTags(available: Boolean) {
+ searchTag.isVisible = available
+ }
+
+ fun enableTags(enable: Boolean) {
+ searchTag.isEnabled = enable
+ }
+
+ fun availableSearchableGroup(available: Boolean) {
+ searchGroupSearchable.isVisible = available
+ }
+
+ fun availableTemplates(available: Boolean) {
+ searchTemplate.isVisible = available
+ }
+
+ fun enableTemplates(enable: Boolean) {
+ searchTemplate.isEnabled = enable
+ }
+
+ fun closeAdvancedFilters() {
+ searchAdvanceFiltersContainer?.collapse()
+ }
+
+ private fun openAdvancedFilters() {
+ searchAdvanceFiltersContainer?.expand(true,
+ searchAdvanceFiltersContainer?.getFullHeight()
+ )
+ }
+
+ override fun setVisibility(visibility: Int) {
+ when (visibility) {
+ View.VISIBLE -> {
+ searchAdvanceFiltersContainer?.visibility = View.GONE
+ searchContainer.showByFading()
+ }
+ else -> {
+ searchContainer.hideByFading()
+ if (searchAdvanceFiltersContainer?.visibility == View.VISIBLE) {
+ searchAdvanceFiltersContainer?.visibility = View.INVISIBLE
+ searchAdvanceFiltersContainer?.collapse()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
index 119ec2732..44182c9ee 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
@@ -51,6 +51,8 @@ import androidx.appcompat.widget.ActionMenuView
import androidx.core.graphics.drawable.DrawableCompat
import android.graphics.drawable.Drawable
+import android.view.ViewGroup
+import android.widget.LinearLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
@@ -113,8 +115,7 @@ fun View.collapse(animate: Boolean = true,
onCollapseFinished: (() -> Unit)? = null) {
val recordViewHeight = layoutParams.height
val slideAnimator = ValueAnimator.ofInt(height, 0)
- if (animate)
- slideAnimator.duration = 300L
+ slideAnimator.duration = if (animate) 300L else 0L
slideAnimator.addUpdateListener { animation ->
layoutParams.height = animation.animatedValue as Int
requestLayout()
@@ -143,8 +144,7 @@ fun View.expand(animate: Boolean = true,
layoutParams.height = 0
val slideAnimator = ValueAnimator
.ofInt(0, viewHeight)
- if (animate)
- slideAnimator.duration = 300L
+ slideAnimator.duration = if (animate) 300L else 0L
var alreadyVisible = false
slideAnimator.addUpdateListener { animation ->
layoutParams.height = animation.animatedValue as Int
@@ -168,12 +168,38 @@ fun View.expand(animate: Boolean = true,
}.start()
}
+/***
+ * This function returns the actual height the layout.
+ * The getHeight() function returns the current height which might be zero if
+ * the layout's visibility is GONE
+ */
+fun ViewGroup.getFullHeight(): Int {
+ measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ val initialVisibility = visibility
+ visibility = LinearLayout.VISIBLE
+ val desiredWidth = View.MeasureSpec.makeMeasureSpec(
+ width,
+ View.MeasureSpec.AT_MOST
+ )
+ measure(desiredWidth, View.MeasureSpec.UNSPECIFIED)
+ val totalHeight = measuredHeight
+ visibility = initialVisibility
+ return totalHeight
+}
+
fun View.hideByFading() {
alpha = 1f
animate()
.alpha(0f)
.setDuration(140)
- .setListener(null)
+ .setListener(object: Animator.AnimatorListener {
+ override fun onAnimationStart(p0: Animator?) {}
+ override fun onAnimationEnd(p0: Animator?) {
+ isVisible = false
+ }
+ override fun onAnimationCancel(p0: Animator?) {}
+ override fun onAnimationRepeat(p0: Animator?) {}
+ })
}
fun View.showByFading() {
diff --git a/app/src/main/java/com/kunzisoft/keepass/viewmodels/GroupViewModel.kt b/app/src/main/java/com/kunzisoft/keepass/viewmodels/GroupViewModel.kt
index f47c6d1ec..a442929a4 100644
--- a/app/src/main/java/com/kunzisoft/keepass/viewmodels/GroupViewModel.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/viewmodels/GroupViewModel.kt
@@ -22,26 +22,30 @@ package com.kunzisoft.keepass.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.app.database.IOActionTask
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
-import com.kunzisoft.keepass.database.element.node.Node
+import com.kunzisoft.keepass.database.element.node.NodeId
+import com.kunzisoft.keepass.database.search.SearchHelper
+import com.kunzisoft.keepass.database.search.SearchParameters
class GroupViewModel: ViewModel() {
+ val mainGroup : LiveData get() = _mainGroup
+ private val _mainGroup = MutableLiveData()
+
val group : LiveData get() = _group
private val _group = MutableLiveData()
val firstPositionVisible : LiveData get() = _firstPositionVisible
private val _firstPositionVisible = MutableLiveData()
- fun loadGroup(database: Database?,
- groupState: GroupActivity.GroupState?) {
+ fun loadMainGroup(database: Database?,
+ groupId: NodeId<*>?,
+ showFromPosition: Int?) {
IOActionTask(
{
- val groupId = groupState?.groupId
if (groupId != null) {
database?.getGroupById(groupId)
} else {
@@ -50,44 +54,46 @@ class GroupViewModel: ViewModel() {
},
{ group ->
if (group != null) {
- _group.value = SuperGroup(group,
+ _mainGroup.value = SuperGroup(group,
database?.recycleBin == group,
- groupState?.firstVisibleItem)
+ showFromPosition)
+ _group.value = _mainGroup.value
}
}
).execute()
}
- fun loadGroup(database: Database?,
- group: Group,
- showFromPosition: Int?) {
- _group.value = SuperGroup(group,
- database?.recycleBin == group,
- showFromPosition)
+ fun loadSearchGroup(database: Database?,
+ searchParameters: SearchParameters,
+ fromGroup: NodeId<*>?,
+ showFromPosition: Int?) {
+ IOActionTask(
+ {
+ database?.createVirtualGroupFromSearch(
+ searchParameters,
+ fromGroup,
+ SearchHelper.MAX_SEARCH_ENTRY
+ )
+ },
+ { group ->
+ if (group != null) {
+ _group.value = SuperGroup(group,
+ database?.recycleBin == group,
+ showFromPosition,
+ searchParameters)
+ }
+ }
+ ).execute()
}
fun assignPosition(position: Int) {
_firstPositionVisible.value = position
}
- fun loadGroupFromSearch(database: Database?,
- searchQuery: String,
- omitBackup: Boolean) {
- IOActionTask(
- {
- database?.createVirtualGroupFromSearch(searchQuery, omitBackup)
- },
- { group ->
- if (group != null) {
- _group.value = SuperGroup(group,
- database?.recycleBin == group,
- 0)
- }
- }
- ).execute()
- }
-
- data class SuperGroup(val group: Group, val isRecycleBin: Boolean, var showFromPosition: Int?)
+ data class SuperGroup(val group: Group,
+ val isRecycleBin: Boolean,
+ var showFromPosition: Int?,
+ var searchParameters: SearchParameters = SearchParameters())
companion object {
private val TAG = GroupViewModel::class.java.name
diff --git a/app/src/main/res/color/background_button_color_secondary.xml b/app/src/main/res/color/background_button_color_secondary.xml
index 6824d6795..0a69948d9 100644
--- a/app/src/main/res/color/background_button_color_secondary.xml
+++ b/app/src/main/res/color/background_button_color_secondary.xml
@@ -2,5 +2,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/color/background_color_chip.xml b/app/src/main/res/color/background_color_chip.xml
new file mode 100644
index 000000000..18061d697
--- /dev/null
+++ b/app/src/main/res/color/background_color_chip.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/list_primary_color.xml b/app/src/main/res/color/list_primary_color.xml
index d5cc012a7..06694e43a 100644
--- a/app/src/main/res/color/list_primary_color.xml
+++ b/app/src/main/res/color/list_primary_color.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/color/text_color_button.xml b/app/src/main/res/color/text_color_button.xml
index 9203a4a5d..13c44bba4 100644
--- a/app/src/main/res/color/text_color_button.xml
+++ b/app/src/main/res/color/text_color_button.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/background_button.xml b/app/src/main/res/drawable/background_button.xml
index addbbc56d..a61486df1 100644
--- a/app/src/main/res/drawable/background_button.xml
+++ b/app/src/main/res/drawable/background_button.xml
@@ -9,7 +9,7 @@
android:right="8dp"
android:top="12dp"
android:bottom="12dp"/>
-
+
-
diff --git a/app/src/main/res/drawable/background_button_small.xml b/app/src/main/res/drawable/background_button_small.xml
index fbcba852e..3516e7b18 100644
--- a/app/src/main/res/drawable/background_button_small.xml
+++ b/app/src/main/res/drawable/background_button_small.xml
@@ -11,7 +11,7 @@
android:right="14dp"
android:top="4dp"
android:bottom="8dp"/>
-
+
diff --git a/app/src/main/res/drawable/background_image_button.xml b/app/src/main/res/drawable/background_image_button.xml
index b7a501a66..794856688 100644
--- a/app/src/main/res/drawable/background_image_button.xml
+++ b/app/src/main/res/drawable/background_image_button.xml
@@ -3,7 +3,7 @@
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_check_white_24dp.xml b/app/src/main/res/drawable/ic_check_white_24dp.xml
index 17aca2af1..7ef4361ff 100644
--- a/app/src/main/res/drawable/ic_check_white_24dp.xml
+++ b/app/src/main/res/drawable/ic_check_white_24dp.xml
@@ -1,5 +1,9 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_current_folder_white_24dp.xml b/app/src/main/res/drawable/ic_current_folder_white_24dp.xml
new file mode 100644
index 000000000..a155c1081
--- /dev/null
+++ b/app/src/main/res/drawable/ic_current_folder_white_24dp.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_database_white_36dp.xml b/app/src/main/res/drawable/ic_database_white_36dp.xml
new file mode 100644
index 000000000..3d0e57c7c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_database_white_36dp.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_merge_from_white_24dp.xml b/app/src/main/res/drawable/ic_merge_from_white_24dp.xml
new file mode 100644
index 000000000..b70af1987
--- /dev/null
+++ b/app/src/main/res/drawable/ic_merge_from_white_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_modified_white_12dp.xml b/app/src/main/res/drawable/ic_modified_white_12dp.xml
new file mode 100644
index 000000000..eba1bd509
--- /dev/null
+++ b/app/src/main/res/drawable/ic_modified_white_12dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_regex_white_24dp.xml b/app/src/main/res/drawable/ic_regex_white_24dp.xml
new file mode 100644
index 000000000..ffe6c14a0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_regex_white_24dp.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_save_copy_to_white_24dp.xml b/app/src/main/res/drawable/ic_save_copy_to_white_24dp.xml
new file mode 100644
index 000000000..32e5fc593
--- /dev/null
+++ b/app/src/main/res/drawable/ic_save_copy_to_white_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_templates_white_24dp.xml b/app/src/main/res/drawable/ic_templates_white_24dp.xml
new file mode 100644
index 000000000..01a04086d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_templates_white_24dp.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/key_background_normal.xml b/app/src/main/res/drawable/key_background_normal.xml
index c6cdf17b6..b93917116 100644
--- a/app/src/main/res/drawable/key_background_normal.xml
+++ b/app/src/main/res/drawable/key_background_normal.xml
@@ -25,6 +25,6 @@
android:right="0dp"
android:top="12dp"
android:bottom="12dp"/>
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/key_background_pressed.xml b/app/src/main/res/drawable/key_background_pressed.xml
index 18d70899e..39bb882f6 100644
--- a/app/src/main/res/drawable/key_background_pressed.xml
+++ b/app/src/main/res/drawable/key_background_pressed.xml
@@ -25,6 +25,6 @@
android:right="0dp"
android:top="12dp"
android:bottom="12dp"/>
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_entry.xml b/app/src/main/res/layout/activity_entry.xml
index 7c264f82d..ae3bcd13d 100644
--- a/app/src/main/res/layout/activity_entry.xml
+++ b/app/src/main/res/layout/activity_entry.xml
@@ -104,7 +104,7 @@
android:visibility="gone"
android:background="?attr/colorAccent"
android:padding="12dp"
- android:textColor="?attr/textColorInverse"
+ android:textColor="?attr/colorOnAccentColor"
android:text="@string/entry_history"/>
.
-->
-
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
-
+ android:layout_height="match_parent">
-
-
+ android:theme="?attr/toolbarSpecialAppearance" />
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_below="@+id/toolbar"
+ android:layout_above="@+id/toolbar_action">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ android:orientation="vertical"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ android:layout_below="@+id/toolbar">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ android:layout_alignParentBottom="true"/>
-
+
-
-
-
-
-
-
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ app:itemTextColor="?android:attr/textColor"
+ app:subheaderColor="?attr/colorAccent"
+ android:fitsSystemWindows="true" />
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_password.xml b/app/src/main/res/layout/activity_main_credential.xml
similarity index 61%
rename from app/src/main/res/layout/activity_password.xml
rename to app/src/main/res/layout/activity_main_credential.xml
index e562b9381..975c66c5e 100644
--- a/app/src/main/res/layout/activity_password.xml
+++ b/app/src/main/res/layout/activity_main_credential.xml
@@ -107,102 +107,21 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="wrap_content"
+ android:background="?android:attr/windowBackground" />
+
diff --git a/app/src/main/res/layout/activity_search_results.xml b/app/src/main/res/layout/activity_search_results.xml
deleted file mode 100644
index 0526f5d2b..000000000
--- a/app/src/main/res/layout/activity_search_results.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_color_picker.xml b/app/src/main/res/layout/fragment_color_picker.xml
index 01a98be2a..091c092ec 100644
--- a/app/src/main/res/layout/fragment_color_picker.xml
+++ b/app/src/main/res/layout/fragment_color_picker.xml
@@ -43,7 +43,7 @@
android:layout_margin="20dp"
android:text="@string/enable"
android:background="@drawable/background_button_small"
- android:textColor="@color/white"
+ android:textColor="?attr/colorOnAccentColor"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:minHeight="48dp"/>
diff --git a/app/src/main/res/layout/fragment_entry.xml b/app/src/main/res/layout/fragment_entry.xml
index b72f4b88c..813f11970 100644
--- a/app/src/main/res/layout/fragment_entry.xml
+++ b/app/src/main/res/layout/fragment_entry.xml
@@ -109,6 +109,32 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_set_password.xml b/app/src/main/res/layout/fragment_set_main_credential.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_set_password.xml
rename to app/src/main/res/layout/fragment_set_main_credential.xml
diff --git a/app/src/main/res/layout/item_file_info.xml b/app/src/main/res/layout/item_file_info.xml
index 1acc01ddf..bd1b66b38 100644
--- a/app/src/main/res/layout/item_file_info.xml
+++ b/app/src/main/res/layout/item_file_info.xml
@@ -71,8 +71,8 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/file_information_button"
android:button="@drawable/checkbox_star"
- android:buttonTint="?android:attr/textColorHintInverse"
- app:buttonTint="?android:attr/textColorHintInverse"
+ android:buttonTint="?android:attr/textColorSecondaryInverse"
+ app:buttonTint="?android:attr/textColorSecondaryInverse"
android:gravity="center"
android:contentDescription="@string/default_checkbox" />
@@ -189,7 +189,7 @@
android:paddingTop="8dp"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginEnd="@dimen/default_margin"
- android:textColor="?android:attr/textColorHintInverse"
+ android:textColor="?android:attr/textColorSecondaryInverse"
app:layout_constraintBottom_toBottomOf="@+id/file_modification_container"
app:layout_constraintEnd_toEndOf="parent"
tools:text="8 bytes" />
@@ -202,7 +202,7 @@
android:src="@drawable/ic_content_delete_white_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
- android:tint="?android:attr/textColorHintInverse"
+ android:tint="?android:attr/textColorSecondaryInverse"
style="@style/KeepassDXStyle.ImageButton.Simple.Secondary"/>
diff --git a/app/src/main/res/layout/item_search_entry.xml b/app/src/main/res/layout/item_search_entry.xml
deleted file mode 100644
index 6918589d2..000000000
--- a/app/src/main/res/layout/item_search_entry.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/keyboard_container.xml b/app/src/main/res/layout/keyboard_container.xml
index 573fbd89a..7d7330c03 100644
--- a/app/src/main/res/layout/keyboard_container.xml
+++ b/app/src/main/res/layout/keyboard_container.xml
@@ -23,13 +23,13 @@
android:layout_alignParentBottom="true"
android:paddingTop="4dp"
android:paddingBottom="8dp"
- android:background="@color/grey_blue_dark"
+ android:background="@color/grey_blue_deep"
android:orientation="vertical">
.
-->
diff --git a/app/src/main/res/layout/nav_header_database.xml b/app/src/main/res/layout/nav_header_database.xml
new file mode 100644
index 000000000..cfaa045a4
--- /dev/null
+++ b/app/src/main/res/layout/nav_header_database.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/toolbar_default.xml b/app/src/main/res/layout/toolbar_default.xml
index 043546dc8..d3bd36aa1 100644
--- a/app/src/main/res/layout/toolbar_default.xml
+++ b/app/src/main/res/layout/toolbar_default.xml
@@ -20,7 +20,6 @@
-
@@ -66,7 +67,7 @@
android:layout_marginBottom="-12dp"
android:descendantFocusability="blocksDescendants"
android:visibility="gone">
-
@@ -88,15 +90,16 @@
+ app:useCompatPadding="true" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_button_lock.xml b/app/src/main/res/layout/view_button_lock.xml
index fb2e02aa1..8607d6097 100644
--- a/app/src/main/res/layout/view_button_lock.xml
+++ b/app/src/main/res/layout/view_button_lock.xml
@@ -9,8 +9,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
+ android:layout_gravity="center"
android:layout_margin="8dp"
android:contentDescription="@string/lock"
+ android:visibility="gone"
android:src="@drawable/ic_lock_white_padding_24dp"
xmlns:app="http://schemas.android.com/apk/res-auto" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_edit_date_time.xml b/app/src/main/res/layout/view_edit_date_time.xml
index 3b944dce7..bcc218e9a 100644
--- a/app/src/main/res/layout/view_edit_date_time.xml
+++ b/app/src/main/res/layout/view_edit_date_time.xml
@@ -20,6 +20,10 @@
android:focusable="false"
android:cursorVisible="false"
android:focusableInTouchMode="false"
+ android:paddingLeft="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:paddingRight="16dp"
style="@style/KeepassDXStyle.TextAppearance.TextNode"
tools:text="2020-03-04 05:00" />
diff --git a/app/src/main/res/layout/view_main_credentials.xml b/app/src/main/res/layout/view_main_credentials.xml
new file mode 100644
index 000000000..254b53e01
--- /dev/null
+++ b/app/src/main/res/layout/view_main_credentials.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_search_filters.xml b/app/src/main/res/layout/view_search_filters.xml
new file mode 100644
index 000000000..e5a91003b
--- /dev/null
+++ b/app/src/main/res/layout/view_search_filters.xml
@@ -0,0 +1,213 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/default_menu.xml b/app/src/main/res/menu/about.xml
similarity index 60%
rename from app/src/main/res/menu/default_menu.xml
rename to app/src/main/res/menu/about.xml
index 6e2628794..319474231 100644
--- a/app/src/main/res/menu/default_menu.xml
+++ b/app/src/main/res/menu/about.xml
@@ -19,16 +19,18 @@
-->
diff --git a/app/src/main/res/menu/database_extra.xml b/app/src/main/res/menu/database_extra.xml
new file mode 100644
index 000000000..fecd0e923
--- /dev/null
+++ b/app/src/main/res/menu/database_extra.xml
@@ -0,0 +1,42 @@
+
+
+
diff --git a/app/src/main/res/menu/open_file.xml b/app/src/main/res/menu/open_file.xml
index 11f3ee674..695b37dae 100644
--- a/app/src/main/res/menu/open_file.xml
+++ b/app/src/main/res/menu/open_file.xml
@@ -22,7 +22,7 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/contribution.xml b/app/src/main/res/menu/settings.xml
similarity index 70%
rename from app/src/main/res/menu/contribution.xml
rename to app/src/main/res/menu/settings.xml
index 535de0089..c37ad7367 100644
--- a/app/src/main/res/menu/contribution.xml
+++ b/app/src/main/res/menu/settings.xml
@@ -1,7 +1,7 @@
\ No newline at end of file
+
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index f5c907c10..f439a3e6b 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -20,10 +20,10 @@
الصفحة الرئيسة
قبول
إضافة مجموعة
- التعمية
- خوارزمية التعمية
+ التشفير
+ خوارزمية التشفير
التطبيق
- أقواس
+ الأقواس
تمديد ASCII
السماح
مُسِحت الحافظة
@@ -86,7 +86,6 @@
التَّوازِي
جار حفظ قاعدة البيانات …
المساحة
- البحث
الفرز
تصاعدي ↓
العنوان
@@ -95,7 +94,6 @@
تاريخ آخر تعديل
خاصّ
البحث
- نتائج البحث
تسطير
حروف كبيرة
تحذير
@@ -106,13 +104,13 @@
إضافة حقول مخصصة
نسخ حقل
تأمين قاعدة البيانات
- التغذية الراجعة
+ ردود الفعل
التنفيذ لمُدير كلمات المرور «كي باس» على نظام أندرويد
- إضافة مدخلة
- تحرير مدخلة
+ إضافة إدخال
+ تحرير الإدخال
وظيفة اشتقاق المفتاح
- انتهت المهلة
- مدة الانتظار قبل إقفال قاعدة البيانات
+ المهلة
+ مدة الخمول قبل قفل قاعدة البيانات
المحرر الذي يمتلك صلاحتي ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT ضروري لانشاء, وفتح وحفض قواعد البيانات.
بعض الأجهزة لا تسمح للتطبيقات باستعمال الحافظة.
مهلة الحافظة
@@ -159,7 +157,6 @@
قابل للتعديل
فتح قاعدة بيانات موجودة
انشاء قاعدة بيانات
- لا تبحثفي مدخلات النسخ الاحتياطي
قيد العمل…
KeePassDX يحتاج صلاحية الكتابة من اجل تعديل قاعدة البيانات.
خوارزمية تشفير جميع البيانات.
@@ -302,7 +299,6 @@
للمتابعة هل تريد حل المشكلة بتوليد UUID للعناصر المكررة ؟
تحتوي قاعدة البيانات على UUID مكرر.
البحث السريع
- أزِل مجموعتي \"سلة المحذوفات\" و\" النسخ الاحتياطي\" من نتائج البحث
احذف السجل
استعادة السجل
أفرغ سلة المحذوفات
@@ -316,7 +312,7 @@
نوع كلمة المرور لمرة واحدة
عين اعتماد واحد على الأقل.
ساهم
- تواصل معنا
+ الإتصال بنا
البصمة
يجب ألا تغير محتوى ملف المفتاح، في أحسن الحالات يجب أن يحتوي بيانات مولدة عشوائيا.
من غير المستحسن اضافة ملف مفتاح فارغ.
@@ -430,7 +426,7 @@
بيانات الملف موجودة سلفًا.
حدث خطأ أثناء إزالة بيانات الملف.
حدث خطأ أثناء تنفيذ إجراء على قاعدة البيانات.
- معلومات السر لمرة واحدة
+ معلومات كلمة المرور لمرة واحدة
العضوية
الاسم
البريد الإلكتروني
@@ -489,4 +485,5 @@
هل تريد حذف جميع العقد نهائيًا من سلة المهملات؟
احفظ معلومات البحث
اسأل لحفظ البيانات
+ لون قاعدة البيانات
\ No newline at end of file
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 41c42248c..8bfde1140 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -102,8 +102,6 @@
Mai
Cap resultat de cerca
Instal·leu un navegador web per a obri aquesta URL
- No cerquis entrades a còpia de seguretat ni paperera
- Omet els grups \'Còpia de seguretat\' i paperera dels resultats de cerca
Creant nova base de dades…
Treballant…
Elimina
@@ -112,11 +110,9 @@
Més passades d\'encriptació donen protecció addicional contra atacs de força bruta, però poden alentir molt la càrrega i el desat de la base de dades.
Guardant la base de dades…
Espai
- Cerca
Ordre natural
Especial
Cerca
- Resultats de la cerca
Subratllat
Versió de la base de dades no suportada.
Majúscules
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 0a58b1bfe..349129cd8 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -109,8 +109,6 @@
Nikdy
Žádné výsledky hledání
Pro otevření tohoto URL nainstalujte webový prohlížeč.
- Neprohledávat položky v záloze
- Vynechat skupiny „Záloha“ a \"Koš\" z výsledků vyhledávání
Vytváření nové databáze…
Pracuji…
Ochrana
@@ -121,11 +119,9 @@
Vyšší počet šifrovacích průchodů zvýší odolnost proti útoku zkoušením všech možných hesel, ale může výrazně zpomalit načítání a ukládání.
Ukládání databáze…
Mezera
- Hledat
Přirozené řazení
Speciální
Hledat
- Výsledky hledání
Podtržítko
Nepodporovaná verze databáze.
Velká písmena
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 2ebc6bddf..764a60622 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -108,8 +108,6 @@
Aldrig
Ingen søgeresultater
Installer en web-browser til at åbne URL.
- Gennemsøg ikke backup poster
- Udelader \"Sikkerhedskopiering\" og \"Papirkurv\" - grupper fra søgeresultater
Opretter ny database…
Arbejder…
Beskyttelse
@@ -120,11 +118,9 @@
Yderligere krypteringsrunder giver højere beskyttelse mod brute-force angreb, men kan virkelig forsinke læsnings- og skrivehastigheden.
Gemmer database…
Mellemrum
- Søg
Naturlig rækkefølge
Speciel
Søg
- Søgeresultater
Understregning
Database-versionen er ikke understøttet.
Store bogstaver
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 269573361..9a53dddc3 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -118,8 +118,6 @@
Nie
Keine Suchergebnisse
Bitte einen Webbrowser installieren, um diese URL zu öffnen.
- Papierkorb und Backup nicht durchsuchen
- Die Gruppen „Backup“ und „Papierkorb“ werden bei der Suche nicht berücksichtigt
Schnellsuche
Beim Öffnen einer Datenbank eine Suche veranlassen
Neue Datenbank anlegen …
@@ -133,11 +131,9 @@
Zusätzliche Schlüsseltransformationen bieten einen besseren Schutz gegen Wörterbuch- oder Brute-Force-Angriffe. Allerdings dauert dann auch das Laden und Speichern der Datenbank entsprechend länger.
Datenbank wird gespeichert …
Leerzeichen
- Suchen
Natürliche Sortierung
Spezialsymbole
Suchen
- Suchergebnisse
Unterstriche
Datenbankversion wird nicht unterstützt.
Großbuchstaben
@@ -238,10 +234,10 @@
Einträge durchsuchen
Titel, Benutzernamen oder Inhalte anderer Feldern eingeben, um die Passwörter wiederzufinden.
Eintrag bearbeiten
- Bearbeiten Sie Ihren Eintrag mit persönlichen Feldern. Persönliche Felder können mit Querverweisen aus anderen Einträgen ergänzt werden.
+ Bearbeiten Sie Ihren Eintrag mit eigenen Feldern. Eigene Felder können mit Querverweisen aus anderen Einträgen ergänzt werden.
Ein starkes Passwort erstellen
Generieren Sie ein starkes Passwort, um es mit Ihrem Eintrag zu verknüpfen, definieren Sie es einfach nach den Kriterien des Formulars und denken Sie an die Passwortsicherheit.
- Benutzerdefinierte Felder hinzufügen
+ Eigene Felder hinzufügen
Tragen Sie ein zusätzliches Feld ein, fügen Sie einen Wert hinzu und schützen Sie es optional.
Die Datenbank entsperren
Ein Feld kopieren
@@ -435,8 +431,10 @@
- Wald
- Göttlich
- Klassisch
+ - Einfach
- Mond
- Sonne
+ - Antwort
- Kunzite
Datei Schreibrechte gewähren, um Datenbankänderungen zu speichern
@@ -532,7 +530,7 @@
Rücktaste
Eintrag auswählen
Zurück zur vorherigen Tastatur
- Benutzerdefinierte Felder
+ Eigene Felder
Alle Verschlüsselungsschlüssel, die mit der modernen Entsperrerkennung zusammenhängen, löschen\?
Geräteanmeldedaten entsperren
Geräteanmeldedaten
@@ -563,7 +561,7 @@
Datei zum Exportieren von App-Eigenschaften erstellen
App-Eigenschaften exportieren
Datei zum Importieren von App-Eigenschaften auswählen
- Sie können hier keine Gruppe verschieben.
+ Hierher kann keine Gruppe verschoben werden.
Ausfüllvorschläge
Dieses Wort ist reserviert und kann nicht verwendet werden.
Benutzerdefiniert
@@ -621,4 +619,20 @@
Hintergrundfarbe des Eintrags
Daten zusammenführen
Erneutes Laden der Datenbank wird die lokal durchgeführten Datenänderungen löschen.
+ Schlagwörter
+ Farbe des Eintrags
+ Bildschirm eingeschaltet lassen
+ Der Hash der Datei ist nicht garantiert, da Android die Daten im laufenden Betrieb ändern kann. Ändern Sie die Dateierweiterung in .bin, um die Integrität zu gewährleisten.
+ Den Bildschirm beim Ansehen des Eintrags eingeschaltet lassen
+ Vorder- und Hintergrundfarbe in einem Eintrag anzeigen
+ Auto-Type-Sequenz
+ Regulärer Ausdruck
+ Durchsuchbar
+ Vererbt
+ Eigene Daten
+ Suchfilter
+ Aktuelle Gruppe
+ Groß-/Kleinschreibung beachten
+ Zusammenführen von …
+ Kopie speichern unter …
\ No newline at end of file
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 0f513aeb2..625e2d0fc 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -111,8 +111,6 @@
Ποτέ
Δεν βρέθηκαν αποτελέσματα αναζήτησης
Εγκαταστήστε ένα πρόγραμμα περιήγησης για να ανοίξετε αυτήν τη διεύθυνση URL.
- Να μην γίνει αναζήτηση μέσα από τις καταχωρήσεις αντιγραφών ασφαλείας
- Παράληψη ομάδας \"Αντίγραφο Ασφαλείας\" και \"Κάδος Ανακύκλωσης\" από τα αποτελέσματα αναζήτησης
Δημιουργία νέας βάσης δεδομένων…
Επεξεργασία…
Προστασία
@@ -123,11 +121,9 @@
Επιπλέον κύκλοι κρυπτογράφησης παρέχουν πρόσθετη προστασία ενάντια σε επιθέσεις brute force, αλλά μπορεί να επιβραδύνει πολύ την φόρτωση και την αποθήκευση.
Αποθήκευση βάσης δεδομένων…
Κενό
- Αναζήτηση
Κανονική ταξινόμηση
Ειδικοί
Αναζήτηση
- Αποτελέσματα αναζήτησης
Υπογράμμιση
Μη υποστηριζόμενη έκδοση βάσης δεδομένων.
Κεφαλαία
@@ -476,7 +472,7 @@
Εμφανίζει το UUID που είναι συνδεδεμένο σε μια καταχώρηση ή σε μια ομάδα
Εμφάνιση UUID
Δεν επιτρέπεται η αποθήκευση δεδομένων για μια βάση δεδομένων που ανοίγει ως μόνο για ανάγνωση.
- Ζητήστε να αποθηκεύσετε δεδομένα όταν επικυρώνεται μια φόρμα
+ Ζητήστε αποθήκευση δεδομένων όταν ολοκληρωθεί η συμπλήρωση μιας φόρμας
Ζητήστε να αποθηκεύσετε δεδομένα
Προσπαθήστε να αποθηκεύσετε πληροφορίες αναζήτησης κατά την επιλογή χειροκίνητης καταχώρισης
Αποθήκευση πληροφοριών αναζήτησης
@@ -616,4 +612,17 @@
Διατηρήστε την οθόνη ενεργή
Χρώματα καταχώρησης
Εμφανίζει τα χρώματα του προσκηνίου και του φόντου σε μια καταχώρηση
+ Αναζητήσιμο
+ Διάκριση πεζών-κεφαλαίων
+ Συγχώνευση από …
+ Κληρονόμησε
+ Αναζήτηση φίλτρων
+ Κεφαλίδα πλοήγησης
+ Κλείσιμο συρταριού πλοήγησης
+ Άνοιγμα συρταριού πλοήγησης
+ Προσαρμοσμένα δεδομένα
+ Αλληλουχία Αυτόματης-Πληκτρολόγησης
+ Τρέχουσα ομάδα
+ Κοινή έκφραση
+ Αποθήκευση αντιγράφου στο …
\ No newline at end of file
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index b470d8700..d08b35e18 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -5,13 +5,11 @@
Versio %1$s
Averto
Majuskloj
- Serĉrezultoj
Serĉi
Uzantnomo
Titolo
Naturala ordo
Ordigi
- Serĉi
Ne ĉesigu la aplikaĵon…
Datumbaza konservado…
Paralelado
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 868dbbe6e..30f52ef37 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -104,8 +104,6 @@
Nunca
Sin resultado de búsqueda
Instale un navegador web para abrir esta URL.
- No buscar en las entradas de respaldo
- Omite los grupos «Respaldo» y «Papelera de reciclaje» de los resultados de búsqueda
Creando nueva base de datos…
Trabajando…
Quitar
@@ -114,7 +112,6 @@
Un alto número de pasadas de cifrado proporciona protección adicional contra ataques de fuerza bruta, pero puede ralentizar mucho el cargado y el guardado.
Guardando base de datos…
Espacio
- Buscar
Orden natural
Especial
Búsqueda
@@ -164,7 +161,6 @@
Creación
Modificación
Acceso
- Resultados de búsqueda
Atención
No se pudieron encontrar los datos de entrada.
Evite los caracteres de la contraseña fuera del formato de codificación de texto en el archivo de la base de datos (los caracteres no reconocidos se convierten a la misma letra).
@@ -268,7 +264,7 @@
No se pudo cargar la base de datos.
No se pudo cargar la clave. Intente disminuir el uso de memoria de KDF.
Enseña nombres de usuario
- Enseña nombres de usuador en las listras de entradas
+ Muestra los nombres de usuario en las listas de entrada
Copiar
Mover
Pegar
@@ -327,10 +323,10 @@
Añadir campo
Eliminar el campo
UUID
- No puede mover una entrada aquí.
+ No se puede mover una entrada aquí.
No puede copiar una entrada aquí.
Mostrar el número de entradas
- Mostrar el número de entradas en un grupo
+ Muestra el número de entradas de un grupo
Fondo
Actualizar
Cerrar campos
@@ -400,7 +396,7 @@
La base de datos contiene UUIDs duplicados.
Restaurar historial
Vaciar papelera de reciclaje
- Guardar base de datos
+ Guardar datos
Creando base de datos…
%1$s con la misma UUID %2$s ya existe.
Esta etiqueta ya existe.
@@ -462,7 +458,7 @@
No se pudo inicializar el indicador de desbloqueo avanzado.
Error de desbloqueo avanzado: %1$s
No se pudo reconocer la impresión de desbloqueo avanzado
- No se pudo leer la llave de desbloqueo avanzada. Por favor, bórrela y repita el procedimiento de reconocimiento de desbloqueo.
+ No se puede leer la clave de desbloqueo avanzada. Por favor, bórrela y repita el procedimiento de reconocimiento del desbloqueo.
Extraer la credencial de la base de datos con datos de desbloqueo avanzado
Abrir la base de datos con reconocimiento de desbloqueo avanzado
Advertencia: Aún debes recordar tu contraseña maestra si usas el reconocimiento de desbloqueo avanzado.
@@ -537,9 +533,9 @@
KiB
B
Sugerencias en línea
- Sobrescribir las modificaciones externas guardando la base de datos o recargándola con los últimos cambios.
+ Fusionar los datos, sobrescribir las modificaciones externas guardando la base de datos o recargarla con los últimos cambios.
La información contenida en su archivo de base de datos ha sido modificada fuera de la aplicación.
- Recargar la base de datos
+ Recargar datos
El tipo de OTP existente no es reconocido por este formulario, su validación ya no puede generar correctamente el token.
¡Cancelado!
Los datos del archivo ya existen.
@@ -610,4 +606,25 @@
Color de la base de datos
Color de primer plano de la entrada
Color de fondo de la entrada
+ Etiquetas
+ La recarga de la base de datos borrará los datos modificados localmente.
+ El hash del archivo no está garantizado porque Android puede cambiar sus datos sobre la marcha. Cambia la extensión del archivo a .bin para una correcta integridad.
+ Mantener la pantalla encendida
+ Mantenga la pantalla encendida cuando vea la entrada
+ Muestra los colores de primer y segundo plano en una entrada
+ Colores de entrada
+ Fusionar datos
+ Cajón de navegación cerrado
+ Consultable
+ Expresión regular
+ Cabecera de navegación
+ Cajón de navegación abierto
+ Sensible a las mayúsculas y minúsculas
+ Secuencia de autotipado
+ Datos personalizados
+ Grupo actual
+ Filtros de búsqueda
+ Fusionar desde …
+ Guardar una copia en …
+ Heredado
\ No newline at end of file
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index e501ae3fe..1c740c6a1 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -111,8 +111,6 @@
Inoiz ez
Emaitzarik gabeko bilaketa
URL kudatzeko euskarririk ez.
- Ez bilatu segurtasun kopiaren sarreretan
- Kendu segurtasun kopien taldea bilaketen emaitzetatik (.kdb fitxategie dagokie bakarrik)
Datubase berria sortzen…
Lanean…
Babesa
@@ -122,11 +120,9 @@
Enkriptatzeko ronda gehiago indar gordineko atakeen kontrako babes gehiago ematen dute, baina kargatzea eta gordetzea moteldu dezakete modu nabarmenean.
Datubasea gordetzen…
Lekua
- Bilatu
DB-a antolatzeko ordena
Berezia
Sarreraren Izena / Deskribapena
- Bilaketaren emaitzak
Azpimarratu
Euskarririk gabeko datubase bertsioa.
Maiuskulak
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index a4bb3195a..8e01e02c7 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -17,7 +17,6 @@
بزرگ
نسخه پایگاه داده پشتیبانی نمی شود.
زیر خط بزنید
- نتایج جستجو
جستجو
ویژه
دسترسی
@@ -30,7 +29,6 @@
گروه های قبل
اول کمترین
مرتب سازی
- جستجو
فاصله
برنامه را نکشید
اجرای فرمان…
@@ -64,8 +62,6 @@
جستجوی زیردریایی
درخواست جستجو در هنگام باز کردن یک پایگاه داده
جستجوی سریع
- Omits \"پشتیبان گیری\" و \"سطل بازیافت\" گروه از نتایج جستجو
- از طریق ورودی های پشتیبان جستجو نکنید
ایجاد پایگاه داده جدید
باز کردن پایگاه داده موجود
برای باز کردن این URL یک مرورگر وب نصب کنید.
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index 6dd0373f1..edfa408bd 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -111,8 +111,6 @@
Ei koskaan
Ei hakutuloksia
Asenna web-selain avataksesi URL:n.
- Älä etsi varmuuskopioista eikä roskakorista
- Poista \'Varmuuskopiot\' ja roskakori hakutuloksista
Luodaan uutta tietokantaa…
Työskennellään…
Suojaus
@@ -122,11 +120,9 @@
Lisätty kierrosten määrä parantaa suojausta raa\'alla voimalla tehdyiltä murtoyrityksiltä, mutta voi todella hidastaa lataamista ja tallentamista.
Tallennetaan tietokantaa…
Tila
- Etsi
Luonnollinen järjestys
Erityistä
Tietueen otsikko/kuvaus
- Hakutulokset
Alleviivattu
Ei-tuettu salasanatietokannan versio.
Isot kirjaimet
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 76b1f5872..5c01efbec 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -119,8 +119,6 @@
Aucun résultat
Installer un navigateur Web pour ouvrir cette URL.
Ouvrir une base de données existante
- Ne pas rechercher dans les entrées sauvegardées
- Omet les groupes \"Sauvegarde\" et \"Corbeille\" des résultats de recherche
Création d’une nouvelle base de données…
Traitement en cours…
Protection
@@ -138,7 +136,6 @@
Degré de parallélisme (nombre de fils d’exécution) utilisé par la fonction de dérivation de clé.
Enregistrement de la base de données…
Espace
- Rechercher
Trier
Ascendant ↓
Groupes avant
@@ -151,7 +148,6 @@
Accèsen
Spéciaux
Rechercher
- Résultats de recherche
Souligné
Version de la base de données non prise en charge.
Majuscules
@@ -270,8 +266,10 @@
- Forêt
- Divin
- Classique
+ - Simple
- Lune
- Soleil
+ - Réponse
- Kunzite
Collection d’icônes
@@ -490,7 +488,7 @@
Affiche l’UUID lié à une entrée ou un groupe
Afficher l’UUID
L’enregistrement des données n’est pas autorisé pour une base de données ouverte en lecture seule.
- Demande à enregistrer des données quand un formulaire est validé
+ Demande de sauvegarde des données à la fin du remplissage d\'un formulaire
Demander à enregistrer des données
Essayer d’enregistrer les informations de recherche lors de la sélection manuelle d’une entrée
Enregistrer les infos de recherche
@@ -623,4 +621,18 @@
Affiche les couleurs de premier plan et d\'arrière-plan dans une entrée
Le rechargement de la base de données supprimera les données modifiées localement.
Couleurs d\'entrée
+ Groupe actuel
+ En-tête de navigation
+ Tiroir de navigation ouvert
+ Expression régulière
+ Tiroir de navigation fermé
+ Filtres de recherche
+ Sauvegarder une copie vers …
+ Fusionner depuis …
+ Wi-Fi
+ Recherchable
+ Hérité
+ Séquence Auto-Type
+ Données personnalisées
+ Sensible à la casse
\ No newline at end of file
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index ecc1e652b..1ef25b899 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -108,9 +108,9 @@
Datoteka ključa je prazna.
Duljina
Prikaži korisnička imena
- Prikaži korisnička imena u popisima unosa
+ Prikazuje korisnička imena u popisima unosa
Prikaži broj unosa
- Prikaži broj unosa u grupi
+ Prikazuje broj unosa u grupi
Stvaranje baze podataka …
Učitavanje baze podataka …
Sakrij lozinke
@@ -129,7 +129,7 @@
Izbriši
Sakrij lozinku
Zaključaj bazu podataka
- Spremi bazu podataka
+ Spremi podatke
Otvori
Traži
Prikaži lozinku
@@ -144,7 +144,6 @@
Instalirajte web preglednik da bi ste otvorili ovaj URL.
Otvori postojeću bazu podataka
Stvori novu bazu podataka
- Ne pretražuj kopije unosa
Stvaranje nove baze podataka …
Zaštita
Baza podataka sadrži duplicirane UUID-ove.
@@ -156,7 +155,6 @@
Spremanje baze podataka …
Izvršavanje naredbe …
Razmak
- Pretraga
Sortiraj po
Počevši s najnižim ↓
Grupe prije
@@ -167,7 +165,6 @@
Vremenu kreiranja
Zadnja promjena
Pretraga
- Rezultati pretrage
Nepodržana verzija baze podataka.
Upozorenje
Nastaviti bez zaštitite otključavanja lozinkom\?
@@ -242,7 +239,7 @@
Barem jedan skup podataka za prijavu mora biti postavljen.
Svaki niz mora imati ime polja.
Nije moguće aktivirati uslugu automatskog ispunjavanja.
- Unos se ne može ovdje premijestiti.
+ Unos se ne može ovdje premjestiti.
Unos se ne može ovdje kopirati.
Grupa se ne može ovjde kopirati.
Nije moguće stvoriti bazu podataka s ovom lozinkom i datotekom ključa.
@@ -259,7 +256,6 @@
Premjesti
Otkaži
Zaštićeno od pisanja
- Izostavi grupe \"Backup\" i \"Recycle bin\" iz rezultata pretraživanja
Rad u tijeku …
Zaštićeno od pisanja
Ovisno o upravitelju datoteka, KeePassDX možda neće moći pisati u tvoje spremište.
@@ -503,7 +499,7 @@
Otvori prozor naprednog otključavanja za spremanje podataka za prijavu
Otvori prozor naprednog otključavanja za otključavanje baze podataka
Nije moguće prepoznati digitanlni otisak za napredno otključavanje
- Nije moguće pročitati ključ naprednog otključavanja. Izbriši ga i ponovi postupak prepoznavanja.
+ Nije moguće pročitati ključ naprednog otključavanja. Izbriši ga i ponovi postupak prepoznavanja otključavanja.
Tipka Enter
Tipka Backspace
Odaberi unos
@@ -526,9 +522,9 @@
Prijedlozi za automatsko popunjavanje su dodani.
Pokušaj prikazivanja prijedloga za automatsko popunjavanje izravno s kompatibilne tipkovnice
Pristup datoteci koju je opozvao upravljač datoteka. Zatvori bazu podataka i ponovo je otvori s njezinog mjesta.
- Prepiši vanjske promjene spremanjem baze podataka ili je ponovo učitaj s najnovijim promjenama.
+ Sjedini podatke, prepiši vanjske promjene spremanjem baze podataka ili je ponovo učitaj s najnovijim promjenama.
Podaci u datoteci tvoje baze podataka izmijenjeni su izvan programa.
- Ponovo učitaj bazu podataka
+ Ponovo učitaj podatke
Ovaj obrazac ne prepoznaje postojeću vrstu jednokratne lozinke. Provjera valjanosti možda više neće pravilno generirati token.
GiB
MiB
@@ -600,4 +596,15 @@
Ime ikone
Nisi dopustio/la programu da koristi točan alarm. Kao rezultat toga, značajke koje zahtijevaju tajmer neće biti obavljene s točnim vremenom.
Dozvola
+ Oznake
+ Sjedini podatke
+ Ponovnim učitavanjem baze podataka izbrisat će se lokalno izmijenjeni podaci.
+ Hash-šifra datoteke nije zajamčena jer Android svoje podatke može promijeniti za vrijeme rada. Promijeni datotečni nastavak u .bin radi ispravnog integriteta.
+ Ostavi ekran uključen
+ Ostavi ekran uključen tijekom gledanja unosa
+ Boje unosa
+ Prikazuje prednje boje i boje pozadine u unosu
+ Prednja boja unosa
+ Boja baze podataka
+ Pozadinska boja unosa
\ No newline at end of file
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index c0ed36ef0..bd7d6f668 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -111,8 +111,6 @@
Soha
Nincs találat
Telepítsen egy webböngészőt az URL megnyitásához.
- Ne keressen a biztonsági mentésekben
- A „Biztonsági mentés” csoport kihagyása a keresésből (csak a .kdb fájlokra érvényes)
Új adatbázis létrehozása…
Feldolgozás…
Védelem
@@ -124,11 +122,9 @@
A további titkosítási körök magasabb védelmet biztosítanak a nyers erőt használó támadások ellen, ugyanakkor jelentősen lassíthatják az adatbázis betöltését vagy mentését.
Adatbázis mentése…
Szóköz
- Keresés
Természetes rendezés
Speciális
Keresés
- Találatok
Aláhúzás
Nem támogatott adatbázis-verzió.
Nagybetűs
diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml
index a9e3b15d4..ca073d97b 100644
--- a/app/src/main/res/values-id/strings.xml
+++ b/app/src/main/res/values-id/strings.xml
@@ -12,7 +12,7 @@
Tampilkan Kata Sandi
Cari
Buka
- Simpan Basisdata
+ Simpan Database
Basisdata Terkunci
Sembunyikan Kata Sandi
Batal
@@ -41,7 +41,7 @@
Ukuran daftar item
Tampilkan jumlah entri dalam sebuah grup
Tampilkan jumlah entri
- Tampilkan nama pengguna dalam daftar entri
+ Tampilkan nama pengguna di daftar entri
Tampilkan nama pengguna
Panjangnya
File Kunci kosong.
@@ -177,8 +177,6 @@
Kontak
Minta pencarian saat membuka database
Pencarian cepat
- Menghilangkan grup \"Cadangan\" dan \"Tempat sampah\" dari hasil penelusuran
- Jangan mencari melalui entri cadangan
Buat basisdata baru
Buka basisdata yang sudah ada
Pasang browser web untuk membuka URL ini.
@@ -222,13 +220,11 @@
Hapus data ini\?
Peringatan
Versi database tidak di dukung.
- Hasil pencarian
Cari
Akses
Modifikasi
Nama pengguna
Judul
- Cari
Jangan tutup aplikasi…
Menjalankan perintah…
Menyimpan basisdata…
@@ -300,7 +296,7 @@
MiB
KiB
B
- Menampilkan UUID yang ditautkan ke entri
+ Tampilkan UUID yang ditautkan ke entri atau grup
Hindari karakter kata sandi di luar format pengkodean teks dalam file database (karakter yang tidak dikenal dikonversi ke huruf yang sama).
Huruf besar
Penciptaan
@@ -315,7 +311,7 @@
Mode simpan
Mode pencarian
Hapus pembuka kunci lanjutan
- Muat ulang database
+ Muat ulang data
URI database tidak dapat diambil.
Nama bidang sudah ada.
Menyimpan item baru tidak diperbolehkan dalam database read-only
@@ -401,7 +397,7 @@
Buka basisdata dengan pengenalan pembuka kunci tingkat lanjut
Peringatan: Anda tetap harus mengingat kata sandi induk Anda jika Anda menggunakan pengenalan pembukaan kunci tingkat lanjut.
Akses ke berkas dicabut oleh pengelola berkas, tutup basisdata dan buka kembali dari lokasi tempatnya.
- Timpa modifikasi eksternal dengan cara menyimpan basisdata atau muat balik dengan perubahan terakhir.
+ Gabungkan data, timpa perubahan dengan cara menyimpan database atau muat ulang dengan perubahan terbaru.
Informasi yang tersimpan di berkas basisdata Anda sudah diubah di luar aplikasi.
Lanjut tanpa kunci enkripsi\?
Lanjut tanpa proteksi buka kunci dengan kata sandi\?
@@ -500,7 +496,7 @@
Peringatan: Papan klip dipakai oleh semua aplikasi. Jika data sensitif disalin, perangkat lunak lain mungkin dapat mendapatkannya.
Kunci basis data Anda dengan cepat, Anda bisa mengatur aplikasi untuk mengunci sendiri setelah beberapa saat dan ketika layar berubah mati.
Pilih bagaimana entri dan grup diurut.
- Anda belum mengizinkan aplikasi untuk menggunakan alarm yang tepat. Sebagai hasilnya, fitur yang membutuhkan pewaktu tidak akan dilakukan dengan waktu yang tepat.
+ Anda belum mengizinkan aplikasi untuk menggunakan alarm. Sebagai hasilnya, fitur yang membutuhkan timer tidak bisa dilakukan tepat waktu.
Izin
Rekomendasi pembaruan
Rekomendasi mengubah kunci utama (hari)
@@ -534,4 +530,12 @@
Dengan membeli versi pro,
Warna basis data
Gabung data
+ Memuat ulang database akan menghapus data yang diubah secara lokal.
+ Hash file tidak terjamin karena Android dapat mengubah datanya saat itu juga. Ubah ekstensi file menjadi .bin agar integritas bisa dihasilkan secara tepat.
+ Warna latar depan entri
+ Warna latar belakang entri
+ Wi-Fi
+ Label
+ Buka penguncian lanjutan untuk menyimpan kredensial
+ Hapus permanen node yang dipilih\?
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 9ce63ff1b..1a9e0f263 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -113,8 +113,6 @@
Mai
Nessun risultato di ricerca
Installa un browser web per aprire questo URL.
- Non cercare nelle voci di backup
- Ometti i gruppi «Backup» e «Cestino» dai risultati di ricerca
Creazione di un nuovo database…
In corso…
Protezione
@@ -126,11 +124,9 @@
Livelli di cifratura aggiuntivi forniscono una maggiore protezione contro attacchi di tipo forza bruta, ma può rallentare il caricamento e il salvataggio.
Salvataggio del database…
Spazio
- Cerca
Ordine naturale
Speciali
Cerca
- Risultati della ricerca
Trattino basso
Versione del database non supportata.
Maiuscole
@@ -333,8 +329,8 @@
Lunghezza della password
Aggiungi un campo
Rimuovi un campo
- Non è possibile spostare una voce qui.
- Non è possibile copiare una voce qui.
+ Non puoi spostare una voce qui.
+ Non puoi copiare una voce qui.
Mostra il numero di voci
Mostra il numero di voci in un gruppo
Aggiorna
@@ -365,7 +361,7 @@
Impostazioni di sicurezza
Il database contiene Identificativi Univoci Universali (UUID) duplicati.
Non è possibile salvare il database.
- Salva database
+ Salva dati
Svuota il cestino
Esecuzione del comando…
Vuoi eliminare definitivamente i nodi selezionati\?
@@ -517,7 +513,7 @@
Contenuto
Non è possibile inizializzare lo sblocco avanzato.
Non è possibile riconoscere lo sblocco avanzato
- Non è possibile leggere la chiave di sblocco avanzato. Eliminala e ripeti la prodecura dello sblocco.
+ Non è possibile leggere la chiave di sblocco avanzato. Eliminala e ripeti la procedura di riconoscimento dello sblocco.
Estrai le credenziali del database con i dati dallo sblocco avanzato
Attenzione: dovrai sempre ricordare la password principale anche se usi lo sblocco avanzato.
Riconoscimento con sblocco avanzato
@@ -534,9 +530,9 @@
Mostra i suggerimenti di riempimento campi in una tastiera compatibile
Suggerimenti in linea
L\'accesso al file è stato revocato dal file manager, chiudi il database e riaprilo dalla sua posizione originale.
- Sovrascrivi le modifiche esterne salvando il database o ricaricalo con gli ultimi cambiamenti.
+ Unisci i dati, sovrascrivi le modifiche esterne salvando il database o ricaricalo con le ultime modifiche.
I dati nel tuo database sono stati modificati al di fuori di questa app.
- Ricarica database
+ Ricarica dati
Il tipo di OTP esistente non è riconosciuto da questo modulo, la sua convalida potrebbe non generare più correttamente il token.
Annullato!
GiB
@@ -564,7 +560,7 @@
Seleziona un file da cui importare le proprietà dell\'app
Importa le proprietà dell\'app
Questa parola è riservata e non può essere usata.
- Non è possibile spostare un gruppo qui.
+ Non puoi spostare un gruppo qui.
Modelli
Gruppo di modelli
Usa modelli dinamici per riempire i campi di una voce
@@ -611,4 +607,24 @@
Colore database
Non hai consentito all\'app di utilizzare un allarme preciso. Di conseguenza, le funzionalità che richiedono un timer non verranno eseguite con un tempo preciso.
Autorizzazione
+ Etichette
+ Unisci dati
+ Ricaricare il database eliminerà i dati modificati in locale.
+ L\'hash del file non è garantito perché Android può cambiare i suoi dati al volo. Cambia l\'estensione del file in .bin per una corretta integrità.
+ Tieni lo schermo acceso
+ Tieni lo schermo acceso mentre si vede la voce
+ Colori della voce
+ Mostra colori in evidenza e in secondo piano in una voce
+ Dati personalizzati
+ Gruppo attuale
+ Apertura del cassetto di navigazione
+ Chiusura del cassetto di navigazione
+ Ricercabile
+ Intestazione di navigazione
+ Ereditato
+ Filtri di ricerca
+ Espressione regolare
+ Unisci da …
+ Salva una copia in …
+ Distinzione tra maiuscole e minuscole
\ No newline at end of file
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index f1b2a045d..291fda3db 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -109,8 +109,6 @@
אף פעם
אין תוצאות חיפוש
אין מטפל לכתובת url זו.
- אל תחפש ערכי גיבוי
- הורד את קבוצת \"גיבוי\" מתוצאות חיפוש (תואם רק לקבצי kdb)
צור מסד נתונים חדש…
עובד…
הגנה
@@ -120,10 +118,8 @@
מספר סיבובי הצפנה גבוה יותר מספר הגה טובה יותר נגד התקפות, אבל יכול להעלות בהרבה את זמן הטעינה והשמירה.
שומר מסד נתונים…
גודל
- חיפוש
סדר מיון מסד נתונים
מיוחד
- תוצאות חיפוש
קו תחתון
גרסת מסד נתונים לא נתמכת.
רישית
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index e489a6bf0..46e26b444 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -196,8 +196,6 @@
この URL を開くにはウェブブラウザをインストールしてください。
既存のデータベースを開く
新しいデータベースを作成
- バックアップ エントリーを検索しない
- 検索結果から [バックアップ] と [ゴミ箱] のグループを省きます
クイック検索
データベースを開いたとき、検索を促します
サブドメイン検索
@@ -232,7 +230,6 @@
コマンドを実行しています…
アプリを終了しないでください…
スペース
- 検索
フィルタ
並べ替え
昇順 ↓
@@ -246,7 +243,6 @@
最終アクセス
特殊文字
検索
- 検索結果
アンダースコア
対応していないバージョンのデータベースです。
大文字
@@ -480,8 +476,10 @@
- Forest
- Divine
- Classic
+ - Simple
- Moon
- Sun
+ - Reply
- Kunzite
アイコンパック
@@ -601,4 +599,18 @@
外部アイコン
テンプレート
アイコン名
+ タグ
+ マージする
+ Android は実行中にデータを変更しうるため、ファイルのハッシュ値は保証されません。整合性を正しく保つにはファイルの拡張子を.binに変更してください。
+ 権限
+ 画面の表示を保つ
+ エントリーを開いているときは画面をオンのままにします
+ データベース カラー
+ エントリーのフォアグラウンド カラー
+ エントリーのバックグラウンド カラー
+ Wi-Fi
+ データベースを再度読み込むとローカルで変更されたデータが削除されます。
+ アプリに正確なアラームの使用を許可していません。その結果、タイマーを必要とする機能が正確な時間で行われなくなります。
+ エントリー カラー
+ フォアグラウンド・バックグラウンド カラーをエントリー内に表示します
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 3f56cd507..506162433 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -131,7 +131,6 @@
이 URL을 열기 위해 웹 브라우저를 설치하십시오.
가지고 있는 데이터베이스 열기
새 데이터베이스 생성
- 백업 항목 검색하지 않기
새 데이터베이스 생성 중…
작업 중…
보호
@@ -144,7 +143,6 @@
바이너리 바이트 단위의 메모리 용량이 키 파생 기능에 사용됩니다.
데이터베이스 저장 중…
공간
- 검색
정렬
오름차순 ↓
파일 열기
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index f1fefc4ba..526422c40 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -68,8 +68,6 @@
Eiti į URL
Niekada
Apsauga
- Paieška
- Paieškos rezultatai
Versija %1$s
Įspėjimas
Atidaryti esamą duomenų bazę
@@ -87,7 +85,6 @@
Programėlės neveiklumas
Nepavyko išvalyti iškarpinės
Teksto dydis grupės sąraše
- Neiškoti atkūrimo įrašuose
KeePassDX negali apdoroti šio URI.
Įrašo duomenys nerasti.
Rakto failas yra tuščias.
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index f3100b69e..65ffa0446 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -108,8 +108,6 @@
Nekad
Nav meklēšanas rezultātu
Neizdevās atvērt saiti.
- Nemeklēt kopijās un atkritnē
- Izlaist kopijas un atkritni no meklēšanas rezultātiem
Izveido jaunu datu bāzi…
Darbojas…
Aizsardzība
@@ -119,11 +117,9 @@
Augstākā līmeņa šifrēšana sniedz lielāku aizsardzību, bet palēnina darbības ar datu bāzēm.
Datu bāzes saglabāšana…
Atstarpe
- Meklēt
Izejas
Speciālie
Ieraksta nosaukums/apraksts
- Meklēšanas rezultāti
Pasvītrojums
Neatbalstīta datu bāzes versija.
Lielie burti
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index d7a51b58c..81da1f6c5 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -227,7 +227,6 @@
Autofill service could not be activated.
ക്ലിപ്പ്ബോർഡ് ടൈംഔട്ട്
ആപ്പ് ടൈംഔട്ട്
- തിരയൽ ഫലങ്ങൾ
ഹിസ്റ്ററി വീണ്ടെടുക്കുക
കാലഹരണപ്പെടുന്നു
വൈബ്രേറ്ററി കീപ്രസ്സ്
@@ -254,7 +253,6 @@
സ്ക്രീൻ ലോക്ക്
എൻട്രി തിരുത്തുക
ഫയലിന്റെ പേര്
- തിരയുക
എൻട്രി ചേർക്കുക
ലോക്കുചെയ്യുന്നതിന് \'തിരികെ\' അമർത്തുക
ക്ലിപ്പ്ബോർഡ് തകരാർ
@@ -307,8 +305,6 @@
ഡാറ്റാബേസിൽ തനിപ്പകർപ്പ് UUID-കൾ അടങ്ങിയിരിക്കുന്നു.
നിങ്ങളുടെ ഫയൽ മാനേജരെ ആശ്രയിച്ച്, നിങ്ങളുടെ സ്റ്റോറേജിൽ എഴുതാൻ KeePassDX-നെ അനുവദിച്ചേക്കില്ല.
ഡാറ്റാബേസ് തുറക്കുമ്പോൾ തിരയൽ അഭ്യർത്ഥിക്കുക
- തിരയൽ ഫലങ്ങളിൽ നിന്ന് \"ബാക്കപ്പ്\", \"റീസൈക്കിൾ ബിൻ\" ഗ്രൂപ്പുകൾ ഒഴിവാക്കുക
- ബാക്കപ്പ് എൻട്രികളിലൂടെ തിരയരുത്
പരിഷ്ക്കരിക്കാവുന്ന
റൈറ്റ്-പരിരക്ഷിതമാണ്
ടോക്കണിൽ %1$d മുതൽ %2$d അക്കങ്ങൾ വരെ ഉണ്ടായിരിക്കണം.
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 951b23ce5..adffe80d1 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -130,8 +130,6 @@
Ingen søkeresultater
Kan ikke håndtere denne nettadressen.
Velg en eksisterende database
- Ikke søk gjennom sikkerhetskopioppføringer
- Utelat \"Sikkerhetskopi\"-gruppen fra søkeresultater (har kunn innvirkning på .kdb-filer)
Oppretter ny database…
Arbeider…
Beskyttelse
@@ -149,7 +147,6 @@
Graden av parallellitet (dvs. antallet tråder) brukt av nøkkelutledingsfunksjonen.
Lagrer database…
Mellomrom
- Søk
Sorter
Laveste først ↓
Grupper før
@@ -162,7 +159,6 @@
Sist åpnet
Spesialtegn
Søk
- Søkeresultater
Understrek
Ustøttet databaseversjon.
Store bokstaver
@@ -600,4 +596,16 @@
Anbefal endring av hovednøkkel (dager)
Nøkkellageret er ikke igangsatt på riktig vis.
Bytt tilbake til forrige tastatur automatisk etter kjøring av «Automatisk tastehandling»
+ Etiketter
+ Flett data
+ Sjekksummen for filen er ikke garantert, siden Android kan endre dens data. Endre filutvidelsen til .bin for å beskytte den.
+ Behold skjermen påslått
+ (når oppføringen er oppe)
+ Oppføringsfarger
+ Forgrunnsfarge for oppføringer
+ Databasefarge
+ Bakgrunnsfarge for oppføringer
+ Wi-Fi
+ Viser forgrunn- og bakgrunsfarger for en oppføring
+ Gjeninnlasting av databasen vil slette lokalt endret data.
\ No newline at end of file
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index b608161b8..3adeaadd7 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -34,7 +34,7 @@
Bestandsbeheer dat de Intent-actie ACTION_CREATE_DOCUMENT en ACTION_OPEN_DOCUMENT accepteert, is nodig om databasebestanden aan te maken, te openen en op te slaan.
Klembord gewist
Time-out van het klembord
- Tijd van opslag op het klembord (indien ondersteund op dit apparaat)
+ Duur van opslag op het klembord (indien ondersteund op dit apparaat)
Selecteer om %1$s naar klembord te kopiëren
Databasesleutel ophalen…
Database
@@ -104,8 +104,6 @@
Nooit
Geen zoekresultaten
Installeer een webbrowser om deze URL te openen.
- Back-upitems niet doorzoeken
- Hiermee worden groepen \"Back-up\" en \"Prullenbak\" uit de zoekresultaten weggelaten
Nieuwe database aanmaken…
Bezig met verwerken…
Verwijderen
@@ -114,7 +112,6 @@
Een hoger aantal encryptie-cycli geeft bijkomende bescherming tegen brute-force aanvallen, maar kan het laden en opslaan sterk vertragen.
Database opslaan…
Ruimte
- Zoeken
Natuurlijke volgorde
Speciaal
Zoeken
@@ -149,7 +146,7 @@
Ongeldig algoritme.
Het sleutelbestand is leeg.
Gebruikersnamen tonen
- Gebruikersnamen tonen in lijsten
+ Toont gebruikersnamen in itemlijsten
Kopie van %1$s
Formulierinvulling
Kopiëren
@@ -178,7 +175,6 @@
Gecreëerd op
Aangepast om
Geopend om
- Zoekresultaten
Waarschuwing
Vermijd wachtwoordtekens buiten het tekstcoderingsformaat in het databasebestand (niet-herkende tekens worden geconverteerd naar dezelfde letter).
Doorgaan zonder beveiliging voor wachtwoordontgrendeling\?
@@ -255,7 +251,7 @@
Bewerk het item met aangepaste velden. Referenties kunnen worden toegevoegd tussen velden van verschillende items.
Genereer een sterk wachtwoord
Genereer een sterk wachtwoord om aan het item te koppelen, definieer het eenvoudig volgens de criteria van het formulier en vergeet het veilige wachtwoord niet.
- Voeg aangepaste velden toe
+ Aangepaste velden toevoegen
Registreer een extra veld, voeg een waarde toe en bescherm dit desgewenst.
Ontgrendel je database
Database alleen-lezen
@@ -288,8 +284,8 @@
Bijdragen
App-thema
Thema gebruikt in de app
- Icon pack
- Gebruikt Icon Pack
+ Pictogrammenpakket
+ Gebruikt pictogrammenpakket
Build %1$s
Magikeyboard
Magikeyboard (KeePassDX)
@@ -299,7 +295,7 @@
Time-out om de toetsenbordinvoer te wissen
Kennisgevingsinfo
Toon een melding wanneer een item beschikbaar is
- Invoer
+ Item
%1$s beschikbaar op Magikeyboard
%1$s
Wissen bij het sluiten
@@ -338,7 +334,7 @@
Je kan hier geen item plaatsen.
Je kan hier geen item kopiëren.
Aantal items tonen
- Toon het aantal items in een groep
+ Toont het aantal items in een groep
Achtergrond
Update
Velden sluiten
@@ -374,7 +370,7 @@
De database bevat dubbele UUID\'s.
Probleem oplossen door nieuwe UUID\'s te genereren voor de duplicaten\?
Database geopend
- Kopieer invoervelden met behulp van het klembord van dit apparaat
+ Kopieer velden met behulp van het klembord van dit apparaat
Geavanceerde ontgrendeling gebruiken om een database gemakkelijker te openen
Gegevenscompressie
Gegevenscompressie verkleint de omvang van de database
@@ -410,8 +406,8 @@
Automatisch zoekresultaten voorstellen vanuit het webdomein of de toepassings-ID
Automatisch zoeken
Prullenbak
- Geef de vergrendelknop weer in de gebruikersinterface
- Vergrendelknop weergeven
+ Toont de vergrendelknop in de gebruikersinterface
+ Vergrendelknop tonen
Instellingen voor automatisch aanvullen
De sleutelopslag is niet correct geïnitialiseerd.
Geselecteerde knooppunten definitief verwijderen\?
@@ -475,12 +471,12 @@
Gegevens
Niet-gekoppelde gegevens verwijderen
Verwijdert bijlagen die in de database staan, maar niet aan een item zijn gekoppeld
- Geeft de UUID weer die is gekoppeld aan een item of een groep
+ Toont de UUID die is gekoppeld aan een item of een groep
UUID tonen
Het opslaan van gegevens is niet toegestaan voor een database die is geopend als alleen-lezen.
Vraag om gegevens op te slaan wanneer een formulier is gevalideerd
Vragen om gegevens op te slaan
- Probeer zoekinformatie op te slaan bij u een handmatige invoerselectie
+ Probeer zoekinformatie op te slaan bij het maken van een handmatige selectie
Zoekinformatie opslaan
Sluit de database na een selectie voor automatisch aanvullen
Database sluiten
@@ -536,7 +532,7 @@
Probeer suggesties voor automatisch aanvullen rechtstreeks vanaf een compatibel toetsenbord weer te geven
Inline suggesties
Toegang tot het bestand is ingetrokken door de bestandsbeheerder, sluit de database en open deze opnieuw vanaf de locatie.
- Voeg de gegevens samen, overschrijf de externe aanpassingen door de database op te slaan of laad de database met de jongste wijzigingen opnieuw.
+ Voeg de gegevens samen, overschrijf de externe wijzigingen door de database op te slaan of herlaad deze met de laatste wijzigingen.
De informatie in het databasebestand is buiten de app gewijzigd.
Gegevens opnieuw laden
Kan de lijst niet correct opnieuw opbouwen.
@@ -567,15 +563,15 @@
Identiteitskaart
Pincode
Kaarthouder
- Geeft OTP-tokens weer in de lijst met items
- OTP-token weergeven
- Toon optie om de gebruiker database-invoer te laten selecteren
+ Toont OTP-tokens in de lijst met items
+ OTP-token tonen
+ Toon optie om de gebruiker een database-item te laten selecteren
Handmatige selectie
Sjablonen
Sjabloongroep
Gebruik dynamische sjablonen om de velden van een item in te vullen
Sjabloongebruik
- Selecteer invoer…
+ Selecteer item…
Extern pictogram
Versie
Sjabloon
@@ -608,7 +604,14 @@
Toestemming
Kleur van de database
Door de database opnieuw te laden, worden de lokaal gewijzigde gegevens gewist.
- Voorgrondkleur invoer
+ Voorgrondkleur item
Gegevens samenvoegen
- Achtergrondkleur invoer
+ Achtergrondkleur item
+ Labels
+ Het controlegetal van het bestand is niet gegarandeerd omdat Android zijn gegevens direct kan wijzigen. Wijzig de bestandsextensie in .bin voor de juiste integriteit.
+ Houd het scherm aan
+ Houd het scherm aan bij het bekijken van een item
+ Itemkleuren
+ Toont voorgrond- en achtergrondkleuren in een item
+ Wi-Fi
\ No newline at end of file
diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml
index 71da3ef73..d028066f1 100644
--- a/app/src/main/res/values-nn/strings.xml
+++ b/app/src/main/res/values-nn/strings.xml
@@ -25,7 +25,6 @@
Legg til gruppe
Encryption algorithm
Programtidsavbrot
-
App
Programinnstillingar
Parentesar
@@ -101,8 +100,6 @@
Aldri
Ingen søkjeresultat
Ingen behandlar for denne adressa.
- Søk ikkje i kopipostane eller søppelbøtta
- Søkjeresultatet inneheld ikkje oppføringar frå \'Backup\' eller søppelbøtta
Lager ny database …
Arbeider …
Ta vekk
@@ -111,7 +108,6 @@
Fleire krypteringsomgangar gjev tilleggsvern mot rå makt-åtak, men kan òg gjera lasting og lagring mykje tregare.
Lagrar databasen …
Mellomrom
- Søk
DB-sortering
Spesial
Oppføringa sin tittel/skildring
diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml
index 4aa8bd8ce..47e590a98 100644
--- a/app/src/main/res/values-pa/strings.xml
+++ b/app/src/main/res/values-pa/strings.xml
@@ -39,7 +39,6 @@
ਵੱਡੇ ਅੱਖਰ (ਅੰਗਰੇਜ਼ੀ)
ਗ਼ੈਰ-ਸਹਾਇਕ ਡਾਟਾਬੇਸ ਵਰਜ਼ਨ ਹੈ।
ਹੇਠਾਂ ਲਾਈਨ
- ਖੋਜ ਨਤੀਜੇ
ਖੋਜ
ਖਾਸ
ਪਹੁੰਚ
@@ -50,7 +49,6 @@
ਸਭ ਤੋਂ ਘੱਟ ਪਹਿਲਾਂ ↓
ਲੜੀਬੱਧ
ਫਿਲਟਰ
- ਖੋਜੋ
ਥਾਂ
…ਡਾਟਾਬੇਸ ਸੰਭਾਲਿਆ ਜਾ ਰਿਹਾ ਹੈ
ਮੈਮੋਰੀ ਵਰਤੋਂ
@@ -72,7 +70,6 @@
ਅਧੀਨ-ਡੋਮੇਨ ਖੋਜ
ਜਦੋਂ ਡਾਟਾਬੇਸ ਖੋਲ੍ਹਣਾ ਹੋਵੇ ਤਾਂ ਖੋਜ ਦੀ ਮੰਗ ਕਰੋ
ਫ਼ੌਰੀ ਖੋਜ
- ਬੈਕਐਪ ਐਂਟਰੀਆਂ ਰਾਹੀਂ ਨਾ ਖੋਜੋ
ਨਵਾਂ ਡਾਟਾਬੇਸ ਬਣਾਓ
ਮੌਜੂਦਾ ਡਾਟਾਬੇਸ ਨੂੰ ਖੋਲ੍ਹੋ
ਇਹ URL ਖੋਲ੍ਹਣ ਲਈ ਵੈੱਬ ਬਰਾਊਜ਼ਰ ਇੰਸਟਾਲ ਕਰੋ।
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 8b76a87a6..fc7c35e5f 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -100,8 +100,6 @@
Nigdy
Brak wyników wyszukiwania
Zainstaluj przeglądarkę internetową, aby otworzyć ten adres URL.
- Nie wyszukuj wpisów kopii zapasowej
- Pomija grupy \"Kopia zapasowa\" i \"Kosz\" z wyników wyszukiwania
Tworzenie nowej bazy danych…
Pracuję…
Usuń
@@ -110,7 +108,6 @@
Dodatkowe rundy szyfrowania zapewniają lepszą ochronę przed atakami typu brute force, ale mogą znacznie spowolnić ładowanie i zapisywanie.
Zapisywanie bazy danych…
Spacja
- Szukaj
Porządek naturalny
Znaki specjalne
Szukaj
@@ -146,7 +143,7 @@
Błędny algorytm.
Plik klucza jest pusty.
Pokaż nazwy użytkowników
- Pokaż nazwy użytkowników na listach wpisów
+ Wyświetla nazwy użytkowników na listach wpisów
Skopiowano %1$s
Wypełnianie formularzy
Kopiuj
@@ -170,7 +167,6 @@
Utwórz
Modyfikacja
Dostęp
- Wyniki wyszukiwania
Ostrzeżenie
Unikaj znaków hasła spoza formatu kodowania tekstu w pliku bazy danych (nierozpoznane znaki są konwertowane na tę samą literę).
Kosz na dole
@@ -326,12 +322,12 @@
Usuń pole
Nie możesz tutaj skopiować wpisu.
Pokaż liczbę wpisów
- Pokaż liczbę wpisów w grupie
+ Wyświetla liczbę wpisów w grupie
Tryb wyboru
Dodaj węzeł
Powtórz przełączanie widoczności hasła
UUID
- W to miejsce nie można przenieść wpisu.
+ Nie możesz tutaj przenieść wpisu.
Pole hasła
Pole pliku klucza
Tło
@@ -356,7 +352,7 @@
Cyfry
Algorytm
OTP
- Tutaj nie można skopiować grupy.
+ Nie możesz tutaj skopiować grupy.
Tworzenie bazy danych…
Ustawienia zabezpieczeń
Ustawienia klucza głównego
@@ -476,8 +472,8 @@
Wyświetla identyfikator UUID powiązany z wpisem lub grupą
Pokaż UUID
Zapisywanie danych nie jest dozwolone dla bazy danych otwartej jako tylko do odczytu.
- Poproś o zapisanie danych po zatwierdzeniu formularza
- Poproś o zapisanie danych
+ Pytaj o zapisanie danych po zakończeniu wypełniania formularza
+ Pytaj o zapisanie danych
Spróbuj zapisać informacje wyszukiwania podczas dokonywania ręcznego wyboru pozycji
Zapisz informacje wyszukiwania
Zamknij bazę danych po wybraniu autouzupełniania
@@ -506,7 +502,7 @@
Uwierzytelnienie urządzenia
Wpisz hasło, a następnie kliknij ten przycisk.
Nie można rozpoznać zaawansowanego wydruku odblokowującego
- Nie można odczytać zaawansowanego klucza odblokowuj. Usuń go i powtórz procedurę rozpoznawania odblokowania.
+ Nie można odczytać zaawansowanego klucza odblokowującego. Usuń go i powtórz procedurę rozpoznawania odblokowania.
Wyodrębnij poświadczenia bazy danych z zaawansowanymi danymi odblokowującymi
Ostrzeżenie: Jeśli używasz zaawansowanego rozpoznawania odblokowania, nadal musisz zapamiętać hasło główne.
Zaawansowane rozpoznawanie odblokowania
@@ -531,7 +527,7 @@
Otwórz monit odblokowania zaawansowanego, aby odblokować bazę danych
Otwórz monit odblokowania zaawansowanego, aby odblokować bazę danych
Dostęp do pliku odwołany przez menedżera plików, zamknij bazę danych i otwórz ją ponownie z jej lokalizacji.
- Scal dane, zastąp modyfikacje zewnętrzne poprzez zapisanie bazy danych lub ponownie załaduj bazę danych z najnowszymi zmianam.
+ Scal dane, zastąp modyfikacje zewnętrzne poprzez zapisanie bazy danych lub ponownie załaduj ją z najnowszymi zmianam.
Informacje zawarte w pliku bazy danych zostały zmodyfikowane poza aplikacją.
Ponownie załaduj dane
GiB
@@ -560,7 +556,7 @@
Wybierz plik, aby zaimportować właściwości aplikacji
Importuj właściwości aplikacji
Wystąpił błąd podczas wykonywania akcji w bazie danych.
- Nie możesz przenieść tutaj grupy.
+ Nie możesz tutaj przenieść grupy.
To słowo jest zastrzeżone i nie może być używane.
Imię i nazwisko
Standardowe
@@ -610,4 +606,24 @@
Kolor bazy danych
Ponowne załadowanie bazy danych spowoduje usunięcie lokalnie zmodyfikowanych danych.
Scal dane
+ Kolory wpisu
+ Hash pliku nie jest gwarantowany, ponieważ system Android może zmieniać swoje dane w locie. Zmień rozszerzenie pliku na .bin, aby uzyskać prawidłową integralność.
+ Nie wyłączaj ekranu podczas oglądania wpisu
+ Znaczniki
+ Nie wyłączaj ekranu
+ Wyświetla kolory pierwszego planu i tła we wpisie
+ Nagłówek nawigacyjny
+ Szuflada nawigacyjna otwarta
+ Szuflada nawigacyjna zamknięta
+ Wielkość liter
+ Wyrażenie regularne
+ Dziedziczone
+ Sekwencja automatycznego wpisywania
+ Obecna grupa
+ Dane niestandardowe
+ Scal z…
+ Filtry wyszukiwania
+ Automatyczne wpisywanie
+ Przeszukiwalne
+ Zapisz kopię w…
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 10f6824a7..b1cbbe1a9 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -76,7 +76,7 @@
Comprimento
Senha
Senha
- Não foi possível ler credenciais.
+ Não foi possível ler as credenciais.
Não foi possível reconhecer o formato do banco de dados.
Comprimento
Tamanho da lista de grupos
@@ -102,8 +102,6 @@
Nunca
Sem resultados na busca
Instale um navegador para abrir esta URL.
- Não buscar por entradas no backup ou na lixeira
- Omite os grupos \"Backup\" e \"Lixeira\" dos resultados da busca
Criando novo banco de dados…
Trabalhando…
Remover
@@ -112,7 +110,6 @@
Rodadas adicionais de criptografia adicionam mais proteção contra ataques de força bruta, mas podem tornar o processo de carregar e salvar mais lentos.
Salvando banco de dados…
Espaço
- Busca
Ordenação natural
Especial
Buscar
@@ -171,7 +168,6 @@
Data de criação
Última modificação
Último acesso
- Resultados da busca
Aviso
Evite caracteres fora do formato de codificação do arquivo do banco (todos os caracteres não reconhecidos são convertidos para a mesma letra).
Continuar sem proteção de desbloqueio por senha\?
@@ -286,7 +282,7 @@
Não foi possível carregar seu banco de dados.
Não pôde carregar a chave. Tente diminuir o \"Uso de Memória\" do KDF.
Mostrar nomes de usuário
- Mostrar nomes de usuário em listas de entrada
+ Exibe nomes de usuário em listas de entrada
Área de transferência
Build %1$s
Magikeyboard
@@ -329,10 +325,10 @@
Adicionar campo
Excluir campo
UUID
- Você não pode mover uma entrada para cá.
- Você não pode copiar uma entrada daqui.
+ Você não pode mover uma entrada aqui.
+ Você não pode copiar uma entrada aqui.
Mostrar número de entradas
- Mostrar o número de entradas dentro de um grupo
+ Exibe o número de entradas em um grupo
Nó filho
Caixa de seleção da senha
Caixa de seleção do arquivo-chave
@@ -522,12 +518,12 @@
Incapaz de inicializar o prompt de desbloqueio avançado.
Erro de desbloqueio avançado: %1$s
Não foi possível reconhecer a impressão de desbloqueio avançado
- Não é possível ler a chave de desbloqueio avançado. Exclua-o e repita o procedimento de reconhecimento de desbloqueio.
+ Não é possível ler a chave de desbloqueio avançada. Por favor, apague-a e repita o procedimento de reconhecimento de desbloqueio.
Extraia credencial de banco de dados com dados de desbloqueio avançado
Banco de dados aberto com reconhecimento avançado de desbloqueio
Aviso: você ainda precisa lembrar sua senha mestra se usar o reconhecimento de desbloqueio avançado.
Reconhecimento de desbloqueio avançado
- Abra o prompt de desbloqueio avançado para armazenar credenciais
+ Abra o prompt de desbloqueio avançado para armazenar as credenciais
Abra o prompt de desbloqueio avançado para desbloquear o banco de dados
Atualização de segurança biométrica necessária.
Nenhuma credencial biométrica ou de dispositivo está registrada.
@@ -608,4 +604,12 @@
Cor de plano de fundo da entrada
Recarregar o banco de dados excluirá os dados modificados localmente.
Mesclar dados
+ Etiquetas
+ Mantenha a tela ligada
+ Mantenha a tela ligada enquanto estiver vendo a entrada
+ Cores de entrada
+ Exibe as cores de primeiro plano e de fundo em uma entrada
+ Você não pode mover um grupo aqui.
+ Wi-Fi
+ O hash do arquivo não é garantido porque o Android pode alterar seus dados em tempo real. Altere a extensão do arquivo para .bin para obter a integridade correta.
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 80f25c909..bbeac9952 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -112,8 +112,6 @@
Nunca
A pesquisa não obteve resultados
Instale um navegador para abrir esta URL.
- Não procurar por entradas na cópia de segurança e lixo
- Omite os grupos \"Cópia de segurança\" e \"Caixote da reciclagem\" dos resultados da busca
A criar nova base de dados…
Em funcionamento…
Proteção
@@ -125,11 +123,9 @@
As rodadas adicionais de encriptação fornecem mais proteção contra ataques de força bruta, mas podem tornar o processo de carregar e guardar mais lentos.
A guardar a base de dados…
Espaço
- Pesquisar
Ordenação natural
Especiais
Pesquisar
- Resultados da pesquisa
Sublinhado
Versão da base de dados não suportada.
Maiúsculas
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 127e6e370..07cc07b23 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -107,7 +107,6 @@
Maiúsculas
Versão da base de dados não suportada.
Sublinhado
- Resultados da pesquisa
Pesquisar
Especiais
Último acesso
@@ -120,7 +119,6 @@
Grupos primeiro
Mais baixo primeiro ↓
Ordenar
- Pesquisar
Espaço
A guardar a base de dados…
Grau de paralelismo (ou seja, número de threads) usado pela função de derivação de chave.
@@ -254,8 +252,6 @@
Mostrar ficheiros recentes
Solicitar uma pesquisa quando abrir a base de dados
Pesquisa rápida
- Omite os grupos \"Cópia de segurança\" e \"Caixote da reciclagem\" dos resultados da busca
- Não procurar por entradas na cópia de segurança e lixo
Sobre
Mascarar palavras-passe (***) por predefinição
Ocultar palavras-passe
@@ -281,7 +277,7 @@
Eliminar permanentemente os nós selecionados\?
A executar o comando…
Esvaziar caixote da reciclagem
- Guardar base de dados
+ Guardar dados
Não foi possível guardar a base de dados.
Ligue a sua palavra-passe às suas credenciais biométricas ou do dispositivo para desbloquear rapidamente a sua base de dados.
Configurações do teclado do dispositivo
@@ -351,7 +347,7 @@
Atualizar
Plano de fundo
Um gestor de ficheiros que aceita a ação intencional ACTION_CREATE_DOCUMENT e ACTION_OPEN_DOCUMENT é necessário para criar, abrir e gravar ficheiros da base de dados.
- Mostrar o número de entradas dentro de um grupo
+ Mostra o número de entradas dentro de um grupo
Mostrar número de entradas
Não se pode copiar uma entrada aqui.
Não se pode mover uma entrada para aqui.
@@ -400,7 +396,7 @@
Compilação %1$s
A criar a chave da base de dados…
Área de transferência
- Mostrar nomes de utilizador na lista entradas
+ Mostra nomes de utilizador nas listas de entradas
Mostrar nomes de utilizador
Não foi possível carregar a chave. Tente diminuir o \"Uso de memória\" do KDF.
Não foi possível abrir a sua base de dados.
@@ -466,14 +462,14 @@
É necessária uma atualização de segurança biométrica.
Não está registada nenhuma credencial biométrica ou de dispositivo.
Acesso ao ficheiro revogado pelo gestor de ficheiros. Feche a base de dados e reabra-a a partir da sua localização.
- Substitua as alterações externas, guardando a base de dados ou recarregando-a com as últimas alterações.
+ Unir os dados, substituir as alterações externas guardado a base de dados ou recarregá-los com as últimas alterações.
A informação contida no seu ficheiro da base de dados foi alterada fora da aplicação.
Eliminar permanentemente todos os nós do caixote da reciclagem\?
Modo de registo
Modo de guardar
Modo de pesquisa
Eliminar chave de desbloqueio avançada
- Recarregar base de dados
+ Recarregar dados
Não foi possível reconstruir adequadamente a lista.
Não foi possível recuperar o URI da base de dados.
O nome do campo já existe.
@@ -580,5 +576,18 @@
Informação de palavra-passe de uso único
Nome do ícone
Permissão
- Não permitiu que a app usasse um alarme exato. Como resultado, as funcionalidades que requerem um temporizador não serão feitas com um tempo exato.
+ Não permitiu que a aplicação usasse um alarme exato. Como resultado, as funcionalidades que requerem um temporizador não serão feitas com um tempo exato.
+ O hash do ficheiro não é garantido porque o Android pode alterar os seus dados em tempo real. Altere a extensão do ficheiro para .bin para obter a integridade correta.
+ Mostra as cores de fundo e de primeiro plano numa entrada
+ Etiquetas
+ Wi-Fi
+ Unir dados
+ Recarregar a base de dados irá eliminar os dados alterados localmente.
+ Manter o ecrã ligado
+ Manter o ecrã ligado ao ver uma entrada
+ Cores da entrada
+ Não se pode mover um grupo para aqui.
+ Cor da base de dados
+ Cor do primeiro plano da página inicial
+ Cor do fundo da página inicial
\ No newline at end of file
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index d91d5ecc6..075333673 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -208,7 +208,6 @@
Executarea comenzii …
Nu omori aplicația …
Spatiu
- Cauta
Sorteaza
Prima cea mai mică ↓
Grupuri înainte
@@ -221,7 +220,6 @@
Acces
Special
Căutare
- Rezultatele căutării
Subliniere
Versiunea bazei de date neacceptată.
Cu majuscule
@@ -407,8 +405,6 @@
Pachet de pictograme folosit în aplicație
Ascundeți intrările expirate
Înscrierile expirate vor fi ascunse
- Nu căutați prin intrări de rezervă
- Omite grupurile „Backup” și „Recycle bin” din rezultatele căutării
Căutare rapidă
Solicitați o căutare atunci când deschideți o bază de date
Salvați locația bazelor de date
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 3fbfdf81c..8711c725e 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -112,8 +112,6 @@
Никогда
Совпадения не найдены
Установите браузер, чтобы открыть этот URL.
- Не искать в резервных копиях
- Не искать в группах \"Резервирование\" и \"Корзина\"
Создание новой базы…
Обработка…
Защита
@@ -125,11 +123,9 @@
Дополнительные раунды шифрования – выше стойкость базы к подбору пароля, но медленнее открытие и сохранение.
Сохранение базы…
П р о б е л
- Поиск
Естественный порядок
$пеци@льные
Поиск
- Результаты поиска
_Подчёркивание_
Неподдерживаемая версия базы.
ЗАГЛАВНЫЕ
@@ -483,7 +479,7 @@
Показывать UUID, связанный с записью или группой
Показывать UUID
Сохранение данных невозможно для базы, открытой только для чтения.
- Запрашивать сохранение данных после проверки формы
+ Запрашивать сохранение данных после завершения заполнения формы
Запрос сохранения данных
Закрывать базу после выбора автозаполнения
Закрыть базу
@@ -610,4 +606,23 @@
Цвет текста записи
Объединить данные
Перезагрузка базы удалит локально изменённые данные.
+ Метки
+ Хеш файла не гарантируется, поскольку Android может изменять свои данные на лету. Измените расширение файла на .bin для сохранения целостности.
+ Держать экран включённым
+ Не отключать экран при просмотре записи
+ Цвета записей
+ Показывать цвета текста и фона записи
+ Открыть панель навигации
+ Заголовок навигации
+ Искомое
+ Наследуемое
+ С учётом регистра
+ Сохранить копию в…
+ Объединить с…
+ Фильтры поиска
+ Регулярное выражение
+ Закрыть панель навигации
+ Последовательность автонабора
+ Текущая группа
+ Пользовательские данные
\ No newline at end of file
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 7edb79f9e..374bf96ee 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -101,8 +101,6 @@
Nikdy
Žiadne výsledky hľadania
Žiaden manažér pre url.
- Neprehľadávať položky
- Vynechať skupinu \'Backup\' a Recycle Bin z výsledkov hľadania
Vytváram novú databázu…
Pracujem…
Odstrániť
@@ -111,7 +109,6 @@
Vyššie opakovania šifrovania dávajú vyššiu ochranu proti útokom hrubou silou, ale môžu spomaliť načítavanie a ukladanie.
Ukladám databázu…
Miesto
- Hľadať
DB zoradenie poradia
Špeciálne
Záznam názov/popis
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 37a822a4c..401164e70 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -110,8 +110,6 @@
Aldrig
Inget sökresultat
Installera en webbläsare för att öppna denna URL.
- Sök inte efter backup-poster
- Utelämnar poster i grupperna \"Backup\" och \"Papperskorg\"
Skapar ny databas…
Arbetar…
Skydd
@@ -121,11 +119,9 @@
Högre antal krypteringsrundor ger ytterligare skydd mot bruteforce-attacker, men kan göra det betydligt långsammare att ladda och spara.
Sparar databas…
Mellanslag
- Sök
Naturlig ordning
Specialtecken
Postens titel/beskrivning
- Sökresultat
Understreck
Databasversionen stöds ej.
Versaler
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index b314ef83d..fb14a88b7 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -96,7 +96,7 @@
Anahtar dosya boş.
Uzunluk
Kullanıcı adlarını göster
- Girdi listelerinde kullanıcı adlarını göster
+ Girdi listelerinde kullanıcı adlarını görüntüler
Liste ögelerinin boyutu
Öge listesindeki metin boyutu
Veri tabanı yükleniyor…
@@ -130,8 +130,6 @@
Bu URL\'u açmak için bir web tarayıcısı yükleyin.
Mevcut veri tabanını aç
Yeni veri tabanı oluştur
- Yedek girdilerde arama
- Arama sonuçlarından \"Yedekleme\" ve \"Geri dönüşüm kutusu\" gruplarını atlar
Yeni veri tabanı oluşturuluyor…
Çalışıyor…
Koruma
@@ -149,7 +147,6 @@
Anahtar türev fonksiyonu tarafından kullanılan paralellik derecesi (yani iplik sayısı).
Veri tabanı kaydediliyor…
Boşluk
- Ara
Sırala
Önce en düşük ↓
Grup önce
@@ -162,7 +159,6 @@
Erişim
Özel
Ara
- Arama Sonuçları
Eksi
Altı çizili
Desteklenmeyen veri tabanı sürümü.
@@ -320,7 +316,7 @@
Bir girdiyi buraya taşıyamazsınız.
Bir girdiyi buraya kopyalayamazsınız.
Girdi sayısını göster
- Bir gruptaki girdilerin sayısını göster
+ Bir gruptaki girdilerin sayısını görüntüler
Kilitlemek için \'Geri\'ye basın
Geri dönüşüm kutusu
Girdi seçimi
@@ -470,7 +466,7 @@
Bir girdiye veya gruba bağlı UUID\'yi görüntüler
UUID\'yi göster
Salt okunur olarak açılan bir veri tabanı için veri kaydına izin verilmiyor.
- Bir form doğrulandığında verileri kaydetmek için sor
+ Form doldurma işlemi tamamlandığında verileri kaydetmek için sor
Verileri kaydetmek için sor
Elle girdi seçimi yaparken arama bilgilerini kaydetmeyi dene
Arama bilgilerini kaydet
@@ -526,7 +522,7 @@
Doğrudan uyumlu bir klavyeden otomatik doldurma önerileri görüntülemeye çalış
Satır içi öneriler
Dosyaya erişim dosya yöneticisi tarafından iptal edildi, veri tabanını kapatın ve bulunduğu yerden yeniden açın.
- Verileri birleştirin, veri tabanını kaydederek harici değişikliklerin üzerine yazın veya en son değişikliklerle veri tabanını yeniden yükleyin.
+ Verileri birleştirin, veri tabanını kaydederek harici değişikliklerin üzerine yazın veya en son değişikliklerle yeniden yükleyin.
Veri tabanı dosyanızda bulunan bilgiler, uygulamanın dışında değiştirildi.
Verileri yeniden yükle
GiB
@@ -605,4 +601,23 @@
Girdi arka plan rengi
Verileri birleştir
Veri tabanını yeniden yüklemek, yerel olarak değiştirilen verileri silecektir.
+ Etiketler
+ Android, verilerini anında değiştirebildiğinden dosyanın sağlama toplamı garanti edilmez. Doğru bütünlük için dosya uzantısını .bin olarak değiştirin.
+ Ekranı açık tut
+ Girdi renkleri
+ Bir girdide ön plan ve arka plan renklerini görüntüler
+ Girdiyi izlerken ekranı açık tutun
+ Gezinme başlığı
+ Gezinme çekmecesi açık
+ Gezinme çekmecesi kapalı
+ Aranabilir
+ Devralınan
+ Otomatik yazma sırası
+ Şuradan birleştir…
+ Düzenli ifade
+ Arama filtreleri
+ Büyük/küçük harf duyarlı
+ Geçerli grup
+ Özel veri
+ Bir kopyasını şuraya kaydet…
\ No newline at end of file
diff --git a/app/src/main/res/values-tt/strings.xml b/app/src/main/res/values-tt/strings.xml
index c1438ab36..57d224e61 100644
--- a/app/src/main/res/values-tt/strings.xml
+++ b/app/src/main/res/values-tt/strings.xml
@@ -13,7 +13,6 @@
Кулланучы исеме
Исем
Иң түбән беренче ↓
- Эзләү
Тиз эзләү
Беркайчан да
Серсүзне күрсәтү
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 548267ad0..72ff83020 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -102,8 +102,6 @@
Ніколи
Нічого не знайдено
Щоб відкрити цю URL-адресу, встановіть переглядач Інтернету.
- Не шукати у записах резервних копій
- Не шукати у групах «Резервна копія» та «Кошик»
Створення нової бази даних…
Працює…
Вилучити
@@ -112,7 +110,6 @@
Додаткова кількість циклів шифрування забезпечує кращий захист від грубих атак, але може дещо уповільнити завантаження та збереження.
Збереження бази даних…
Пробіл
- Пошук
Звичайний порядок
Спеціальний
Введіть заголовок/опис
@@ -199,7 +196,7 @@
Створення бази даних…
Показувати кількість записів у групі
Показувати кількість записів
- Показувати імена користувачів у переліках записів
+ Показувати імена користувачів у списках записів
Показувати імена користувачів
Файл ключа порожній.
%1$s з таким самим UUID %2$s вже існує.
@@ -215,7 +212,7 @@
Неможливо створити базу даних із цим паролем та файлом ключа.
Не вдалося створити файл бази даних.
Ви не можете копіювати групу сюди.
- Ви не можете копіювати записи сюди.
+ Ви не можете копіювати запис сюди.
Ви не можете перемістити запис сюди.
Не вдалось ввімкнути службу автозаповнення.
Ця мітка вже існує.
@@ -421,7 +418,6 @@
Надайте дозвіл змінювати файл для збереження змін у базі даних
У паролі, уникайте символів поза форматом кодування тексту у файлі бази даних (нерозпізнані символи будуть перетворені на ту ж літеру).
Попередження
- Результати пошуку
Доступ
Востаннє змінено
Дата створення
@@ -476,7 +472,7 @@
Показ пов\'язаного з записом чи групою UUID
Показувати UUID
Збереження даних заборонено для бази даних, відкритої лише для читання.
- Запитувати зберігати дані після перевірки форми
+ Запитувати зберігати дані після заповнення форми
Запит збереження даних
Намагатися зберегти подробиці пошуку під час вибору запису власноруч
Збереження відомостей пошуку
@@ -531,7 +527,7 @@
Спробувати показ пропозицій автозаповнення безпосередньо з сумісної клавіатури
Вбудовані пропозиції
Доступ до файлу скасовано менеджером файлів, закрийте базу даних і знову відкрийте її з її розташування.
- Об\'єднати дані, перезаписати зовнішні зміни, зберігши базу даних або перезавантажити базу даних з найновішими змінами.
+ Об\'єднати дані, перезаписати зовнішні зміни, зберігши базу даних або перезавантажити її з найновішими змінами.
Відомості, що містяться у файлі бази даних, змінено за межами застосунку.
Перезавантажити дані
ГіБ
@@ -610,4 +606,23 @@
Колір тексту запису
Об\'єднати дані
Перезавантаження бази даних видалить локально змінені дані.
+ Не вимикати екран
+ Не вимикати екран під час перегляду запису
+ Кольори записів
+ Показує кольори шрифту й тла запису
+ Мітки
+ Хеш файлу не гарантується, оскільки Android може змінювати свої дані на льоту. Змініть розширення файлу на .bin для збереження цілісності.
+ Панель навігації
+ Відкрити панель навігації
+ Закрити панель навігації
+ Шукане
+ Успадковане
+ Поточна група
+ З урахуванням регістру
+ Регулярний вираз
+ Об\'єднати з …
+ Користувацькі дані
+ Фільтри пошуку
+ Послідовність автовведення
+ Зберегти копію в …
\ No newline at end of file
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
index bf17bcbd9..c7be9d679 100644
--- a/app/src/main/res/values-v21/styles.xml
+++ b/app/src/main/res/values-v21/styles.xml
@@ -48,7 +48,11 @@
-
+
@@ -58,7 +62,7 @@
-
diff --git a/app/src/main/res/values-v21/styles_divine.xml b/app/src/main/res/values-v21/styles_divine.xml
index 632408253..450665471 100644
--- a/app/src/main/res/values-v21/styles_divine.xml
+++ b/app/src/main/res/values-v21/styles_divine.xml
@@ -22,14 +22,14 @@
- @style/KeepassDXStyle.Preference.v21
- @color/grey_dark
- @color/grey_dark
- - @style/KeepassDXStyle.Clear.DateTime.Dialog
- - @style/KeepassDXStyle.Clear.DateTime.Dialog
+ - @style/KeepassDXStyle.White.DateTime.Dialog
+ - @style/KeepassDXStyle.White.DateTime.Dialog
diff --git a/app/src/main/res/values-v21/styles_reply.xml b/app/src/main/res/values-v21/styles_reply.xml
new file mode 100644
index 000000000..2bccd5769
--- /dev/null
+++ b/app/src/main/res/values-v21/styles_reply.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values-v21/styles_simple.xml b/app/src/main/res/values-v21/styles_simple.xml
new file mode 100644
index 000000000..d5a0f4ea5
--- /dev/null
+++ b/app/src/main/res/values-v21/styles_simple.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values-v23/fingerprint.xml b/app/src/main/res/values-v23/fingerprint.xml
index 9ca38763b..aef4ccdf0 100644
--- a/app/src/main/res/values-v23/fingerprint.xml
+++ b/app/src/main/res/values-v23/fingerprint.xml
@@ -42,9 +42,7 @@
M0 0 L160 0 L160 40 L0 40 Z
M0 120 L160 120 L160 160 L0 160 Z
- #deffffff
- #ffffffff
-
- #ff607d8b
+ ?attr/colorOnAccentColor
+ ?attr/colorOnAccentColor
diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml
index a45d29099..4aac1f9e7 100644
--- a/app/src/main/res/values-v23/styles.xml
+++ b/app/src/main/res/values-v23/styles.xml
@@ -21,4 +21,11 @@
+
+
+
diff --git a/app/src/main/res/values-v23/styles_simple.xml b/app/src/main/res/values-v23/styles_simple.xml
new file mode 100644
index 000000000..8bd4e1137
--- /dev/null
+++ b/app/src/main/res/values-v23/styles_simple.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/app/src/main/res/values-v27/styles_classic.xml b/app/src/main/res/values-v27/styles_classic.xml
new file mode 100644
index 000000000..003e545c2
--- /dev/null
+++ b/app/src/main/res/values-v27/styles_classic.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/app/src/main/res/values-v27/styles_divine.xml b/app/src/main/res/values-v27/styles_divine.xml
new file mode 100644
index 000000000..251346395
--- /dev/null
+++ b/app/src/main/res/values-v27/styles_divine.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/app/src/main/res/values-v27/styles_simple.xml b/app/src/main/res/values-v27/styles_simple.xml
new file mode 100644
index 000000000..bf756fc2b
--- /dev/null
+++ b/app/src/main/res/values-v27/styles_simple.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 46bbcecf0..20d6e02fe 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -109,7 +109,6 @@
更多的迭代次数能更好地抵抗暴力破解攻击,但也会增加读取和保存的时间。
正在保存数据库…
空格
- 搜索
自然排序
特殊符号
搜索
@@ -152,8 +151,6 @@
显示密码
只读
可修改
- 搜索时忽略备份条目
- 从搜索结果中忽略“备份”和“回收站”群组
保护
只读
根据您的文件管理器,KeePassDX 可能不允许在您的存储中写入数据。
@@ -171,7 +168,6 @@
新建时间
修改时间
访问时间
- 搜索结果
警告
版本 %1$s
历史
@@ -333,7 +329,7 @@
添加字段
删除字段
UUID
- 无法移动条目到此处。
+ 你不能将条目移到此处。
您不能在此处复制条目。
显示条目数量
显示群组中的条目数
@@ -476,7 +472,7 @@
显示与一个条目或群组相链接的 UUID
显示 UUID
以只读方式打开的数据库不允许保存数据。
- 表单经过验证时,询问是否保存数据
+ 填写完表单后,询问是否保存数据
询问是否保存数据
手动选择条目时,尝试保存搜索信息
保存搜索信息
@@ -610,4 +606,24 @@
条目背景色
合并数据
重新加载数据库将删除本地修改的数据。
+ 文件的哈希值不能保证,因为 Android 可以在运行中更改数据。将文件扩展名更改为 .bin 以确保完整性。
+ 保持屏幕亮起
+ 查看条目时保持屏幕亮起
+ 条目颜色
+ 显示条目的前景色和背景色
+ 标签
+ 可搜索
+ 自动输入序列
+ 自定义数据
+ 当前组
+ 正则表达式
+ 合并自…
+ 保存副本到…
+ 继承的
+ 导航标题
+ 导航抽屉关闭
+ 导航抽屉开启
+ 搜索过滤器
+ 区分大小写
+ 自动输入
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index f2a994c16..761897635 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -448,8 +448,6 @@
沒有這個連結的處理程式。
通知
卡號
- 從搜尋結果中省略\"備份\"和\"回收桶\"組
- 不要搜尋備份的項目
點擊以開啟高級解鎖提示來存儲憑證
點擊以使用高級解鎖
其他
@@ -495,9 +493,7 @@
保存模式
正在保存資料庫…
條目名稱/說明
- 搜尋
搜尋模式
- 搜尋結果
安全註記
安全
種子
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index b97eb3bc7..11c9ca46a 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -23,6 +23,10 @@
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 3f27c7b43..dc19f7eb3 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -41,11 +41,9 @@
#000001
#E0000000
- #3c474c
- #263238
-
- #90CAF9
- #5DB2F6
+ #E6F3FF
+ #90CAF9
+ #5DB2F6
#2196F3
#1E88E5
#1976D2
@@ -56,20 +54,27 @@
#2486AB
#2c7a96
- #a5d6a7
+ #a5d6a7
+ #66BB6A
#4caf50
#43a047
#388e3c
#2e7d32
+ #1B5E20
- #ef9a9a
+ #ef9a9a
+ #EF5350
#f44336
#e53935
#d32f2f
- #ffa726
+ #FFCC80
+ #ffa726
+ #FF9800
#fb8c00
- #ef6c00
+ #F57C00
+ #ef6c00
+ #E65100
#F2EDFA
#F7F3FD
@@ -82,4 +87,26 @@
#451D5D
#E0451D5D
#361748
+
+ #FCFCFF
+ #E0FCFCFF
+ #202124
+ #E0202124
+ #3E4247
+ #3c474c
+ #263238
+ #5E97F6
+ #1A73E8
+
+ #FFBA52
+ #F9AA33
+ #EDF0F2
+ #D1DBE0
+ #4A6572
+ #344955
+ #E0232F34
+ #232F34
+ #27343A
+ #414445
+
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index 0e146ac90..446636576 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -46,6 +46,7 @@
keepass
+ _copy
.kdbx
KeePassDX Database
keepassdx_%1$s.properties
@@ -69,8 +70,6 @@
true
enable_keep_screen_on_key
false
- omit_backup_search_key
- true
auto_focus_search_key
false
subdomain_search_key
@@ -325,26 +324,34 @@
KeepassDXStyle_Black
KeepassDXStyle_Clear
KeepassDXStyle_Dark
+ KeepassDXStyle_Simple
+ KeepassDXStyle_Simple_Night
KeepassDXStyle_Blue
KeepassDXStyle_Blue_Night
KeepassDXStyle_Red
KeepassDXStyle_Red_Night
+ KeepassDXStyle_Reply
+ KeepassDXStyle_Reply_Night
KeepassDXStyle_Purple
KeepassDXStyle_Purple_Dark
- @string/list_style_name_light
- @string/list_style_name_white
- @string/list_style_name_clear
+ - @string/list_style_name_simple
- @string/list_style_name_blue
- @string/list_style_name_red
+ - @string/list_style_name_reply
- @string/list_style_name_purple
- @string/list_style_name_night
- @string/list_style_name_black
- @string/list_style_name_dark
+ - @string/list_style_name_simple_night
- @string/list_style_name_blue_night
- @string/list_style_name_red_night
+ - @string/list_style_name_reply_night
- @string/list_style_name_purple_dark
KeepassDXStyle_Brightness_Light
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 536aff4d5..76e4370c8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -61,6 +61,9 @@
Database color
Entry foreground color
Entry background color
+ Navigation header
+ Navigation drawer open
+ Navigation drawer close
Validate
Discard changes?
Discard
@@ -92,9 +95,14 @@
Attachments
Keyfile
Modified
+ Searchable
+ Inherit
+ Auto-Type
+ Auto-Type sequence
Could not find entry data.
Password
Tags
+ Custom data
Save
Title
Set up one-time password
@@ -103,7 +111,11 @@
Period (seconds)
Counter
Digits
+ Search filters
Algorithm
+ Current group
+ Case sensitive
+ Regular expression
Debit / Credit Card
Holder
Number
@@ -234,6 +246,8 @@
Save data
Merge data
Reload data
+ Merge from …
+ Save a copy to …
Open
Search
Show password
@@ -251,8 +265,6 @@
Install a web browser to open this URL.
Open existing database
Create new database
- Don\'t search through backup entries
- Omits \"Backup\" and \"Recycle bin\" groups from search results
Quick search
Request a search when opening a database
Subdomain search
@@ -298,7 +310,6 @@
Executing the command…
Do not kill the app…
Space
- Search
Filter
Sort
Lowest first ↓
@@ -312,7 +323,6 @@
Access
Special
Search
- Search results
Underline
Unsupported database version.
Upper-case
@@ -507,7 +517,7 @@
Save search info
Try to save search information when making a manual entry selection
Ask to save data
- Ask to save data when a form is validated
+ Ask for saving data when filling of a form is completed
Application blocklist
Blocklist that prevents auto filling of apps
Web domain blocklist
@@ -626,8 +636,10 @@
- Forest
- Divine
- Classic
+ - Simple
- Moon
- Sun
+ - Reply
- Kunzite
Theme brightness
diff --git a/app/src/main/res/values/style_classic.xml b/app/src/main/res/values/style_classic.xml
index d13fe72a8..f2973c4ae 100644
--- a/app/src/main/res/values/style_classic.xml
+++ b/app/src/main/res/values/style_classic.xml
@@ -19,7 +19,8 @@
-->
-
+
+
@@ -89,10 +94,15 @@
+
@@ -155,11 +170,16 @@
+
@@ -69,7 +74,7 @@
@@ -129,7 +139,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/style_simple.xml b/app/src/main/res/values/style_simple.xml
new file mode 100644
index 000000000..5db9eaf2b
--- /dev/null
+++ b/app/src/main/res/values/style_simple.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/style_sun.xml b/app/src/main/res/values/style_sun.xml
index d48239d38..212d2ca60 100644
--- a/app/src/main/res/values/style_sun.xml
+++ b/app/src/main/res/values/style_sun.xml
@@ -24,10 +24,11 @@
- @color/red
- @color/red_dark
- @color/orange
- - @color/orange_light
+ - @color/orange_lighter
- @color/orange
- @color/red
- - @color/red_lighter
+ - @color/red_lightest
+ - @color/red_lightest
- @color/white_dark
- @color/white
- @style/KeepassDXStyle.Toolbar.Red
@@ -38,15 +39,19 @@
- @style/KeepassDXStyle.Red.Dialog
- @color/red_dark
- @color/white
+
+ - @color/white
+ - @color/red_lighter
+ - @color/red
@@ -84,10 +89,11 @@
- @color/red
- @color/red_dark
- @color/orange
- - @color/orange_light
+ - @color/orange_lighter
- @color/orange
- @color/red
- - @color/red_lighter
+ - @color/red_lightest
+ - @color/red_lightest
- @color/grey_dark
- @style/KeepassDXStyle.Toolbar.Red.Night
- @style/KeepassDXStyle.Toolbar.Home.Red.Night
@@ -97,15 +103,19 @@
- @style/KeepassDXStyle.Red.Night.Dialog
- @color/red_dark
- @color/white
+
+ - @color/white
+ - @color/red_lighter
+ - @color/red
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index cf3910235..179dda3b8 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -30,7 +30,6 @@
-
+
+
+
+
@@ -318,7 +342,7 @@
- 8dp
@@ -372,7 +396,7 @@
@@ -418,7 +442,12 @@
-
+
-
-
+
@@ -535,7 +566,7 @@