First refactoring pass

This commit is contained in:
J-Jamet
2021-07-30 18:11:15 +02:00
parent d4cd5b73bd
commit 840a2253e2
62 changed files with 2000 additions and 1329 deletions

View File

@@ -25,14 +25,13 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentSender import android.content.IntentSender
import android.os.Build import android.os.Build
import android.os.Bundle
import android.view.inputmethod.InlineSuggestionsRequest import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.selection.DatabaseActivity
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.EXTRA_INLINE_SUGGESTIONS_REQUEST import com.kunzisoft.keepass.autofill.AutofillHelper.EXTRA_INLINE_SUGGESTIONS_REQUEST
import com.kunzisoft.keepass.autofill.KeeAutofillService import com.kunzisoft.keepass.autofill.KeeAutofillService
@@ -44,14 +43,14 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.LOCK_ACTION import com.kunzisoft.keepass.utils.LOCK_ACTION
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : AppCompatActivity() { class AutofillLauncherActivity : DatabaseActivity() {
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
mDatabase = Database.getInstance()
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
// End activity if database not loaded
if (database?.loaded != true) {
finish()
}
// Retrieve selection mode // Retrieve selection mode
EntrySelectionHelper.retrieveSpecialModeFromIntent(intent).let { specialMode -> EntrySelectionHelper.retrieveSpecialModeFromIntent(intent).let { specialMode ->
when (specialMode) { when (specialMode) {
@@ -64,7 +63,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
} }
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain -> SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain searchInfo.webDomain = concreteWebDomain
mDatabase?.let { database -> database?.let { database ->
launchSelection(database, searchInfo) launchSelection(database, searchInfo)
} }
} }
@@ -75,7 +74,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
val searchInfo = SearchInfo(registerInfo?.searchInfo) val searchInfo = SearchInfo(registerInfo?.searchInfo)
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain -> SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain searchInfo.webDomain = concreteWebDomain
mDatabase?.let { database -> database?.let { database ->
launchRegistration(database, searchInfo, registerInfo) launchRegistration(database, searchInfo, registerInfo)
} }
} }
@@ -87,8 +86,6 @@ class AutofillLauncherActivity : AppCompatActivity() {
} }
} }
} }
super.onCreate(savedInstanceState)
} }
private fun launchSelection(database: Database, private fun launchSelection(database: Database,

View File

@@ -0,0 +1,11 @@
package com.kunzisoft.keepass.activities
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable
interface DatabaseRetrieval {
fun onDatabaseRetrieved(database: Database?)
fun onDatabaseActionFinished(database: Database,
actionTask: String,
result: ActionRunnable.Result)
}

View File

@@ -35,7 +35,6 @@ import android.widget.ProgressBar
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.EntryFragment import com.kunzisoft.keepass.activities.fragments.EntryFragment
@@ -45,6 +44,7 @@ import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation import com.kunzisoft.keepass.education.EntryActivityEducation
@@ -57,6 +57,7 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.* import com.kunzisoft.keepass.utils.*
@@ -79,6 +80,9 @@ class EntryActivity : LockingActivity() {
private val mEntryViewModel: EntryViewModel by viewModels() private val mEntryViewModel: EntryViewModel by viewModels()
private var mEntryId: NodeId<UUID>? = null
private var mHistoryPosition: Int = -1
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap() private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
@@ -108,23 +112,18 @@ class EntryActivity : LockingActivity() {
collapsingToolbarLayout?.title = " " collapsingToolbarLayout?.title = " "
toolbar?.title = " " toolbar?.title = " "
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
// Retrieve the textColor to tint the icon // Retrieve the textColor to tint the icon
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent)) val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
iconColor = taIconColor.getColor(0, Color.BLACK) iconColor = taIconColor.getColor(0, Color.BLACK)
taIconColor.recycle() taIconColor.recycle()
mReadOnly = mDatabase?.isReadOnly != false || mReadOnly
// Get Entry from UUID // Get Entry from UUID
try { try {
intent.getParcelableExtra<NodeId<UUID>?>(KEY_ENTRY)?.let { entryId -> intent.getParcelableExtra<NodeId<UUID>?>(KEY_ENTRY)?.let { entryId ->
mEntryId = entryId
intent.removeExtra(KEY_ENTRY) intent.removeExtra(KEY_ENTRY)
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1) mHistoryPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
intent.removeExtra(KEY_ENTRY_HISTORY_POSITION) intent.removeExtra(KEY_ENTRY_HISTORY_POSITION)
mEntryViewModel.loadEntry(entryId, historyPosition)
} }
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key") Log.e(TAG, "Unable to retrieve the entry key")
@@ -152,7 +151,7 @@ class EntryActivity : LockingActivity() {
// Assign title icon // Assign title icon
titleIconView?.let { iconView -> titleIconView?.let { iconView ->
mDatabase?.iconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, iconColor) mIconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, iconColor)
} }
// Assign title text // Assign title text
@@ -209,23 +208,36 @@ class EntryActivity : LockingActivity() {
historySelected.historyPosition, historySelected.historyPosition,
mReadOnly) mReadOnly)
} }
}
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result -> override fun onDatabaseRetrieved(database: Database?) {
when (actionTask) { super.onDatabaseRetrieved(database)
ACTION_DATABASE_RESTORE_ENTRY_HISTORY, // Focus view to reinitialize timeout
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> { coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, database)
// Close the current activity after an history action mEntryViewModel.setDatabase(database)
if (result.isSuccess) mEntryViewModel.loadEntry(mEntryId, mHistoryPosition)
finish() }
}
ACTION_DATABASE_RELOAD_TASK -> { override fun onDatabaseActionFinished(
// Close the current activity database: Database,
this.showActionErrorIfNeeded(result) actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
ACTION_DATABASE_RESTORE_ENTRY_HISTORY,
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> {
// Close the current activity after an history action
if (result.isSuccess)
finish() finish()
}
} }
coordinatorLayout?.showActionErrorIfNeeded(result) ACTION_DATABASE_RELOAD_TASK -> {
// Close the current activity
this.showActionErrorIfNeeded(result)
finish()
}
} }
coordinatorLayout?.showActionErrorIfNeeded(result)
} }
override fun onResume() { override fun onResume() {
@@ -356,25 +368,23 @@ class EntryActivity : LockingActivity() {
} }
R.id.menu_restore_entry_history -> { R.id.menu_restore_entry_history -> {
mEntryViewModel.getMainEntry()?.let { mainEntry -> mEntryViewModel.getMainEntry()?.let { mainEntry ->
mProgressDatabaseTaskProvider?.startDatabaseRestoreEntryHistory( restoreEntryHistory(
mainEntry, mainEntry,
mEntryViewModel.getEntryHistoryPosition(), mEntryViewModel.getEntryHistoryPosition())
!mReadOnly && mAutoSaveEnable)
} }
} }
R.id.menu_delete_entry_history -> { R.id.menu_delete_entry_history -> {
mEntryViewModel.getMainEntry()?.let { mainEntry -> mEntryViewModel.getMainEntry()?.let { mainEntry ->
mProgressDatabaseTaskProvider?.startDatabaseDeleteEntryHistory( deleteEntryHistory(
mainEntry, mainEntry,
mEntryViewModel.getEntryHistoryPosition(), mEntryViewModel.getEntryHistoryPosition())
!mReadOnly && mAutoSaveEnable)
} }
} }
R.id.menu_save_database -> { R.id.menu_save_database -> {
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly) saveDatabase()
} }
R.id.menu_reload_database -> { R.id.menu_reload_database -> {
mProgressDatabaseTaskProvider?.startDatabaseReload(false) reloadDatabase()
} }
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any) android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
} }

View File

@@ -66,6 +66,7 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
@@ -95,6 +96,9 @@ class EntryEditActivity : LockingActivity(),
private val mEntryEditViewModel: EntryEditViewModel by viewModels() private val mEntryEditViewModel: EntryEditViewModel by viewModels()
private var mAllowCustomFields = false
private var mAllowOTP = false
// To manage attachments // To manage attachments
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
@@ -122,9 +126,6 @@ class EntryEditActivity : LockingActivity(),
validateButton = findViewById(R.id.entry_edit_validate) validateButton = findViewById(R.id.entry_edit_validate)
loadingView = findViewById(R.id.loading) loadingView = findViewById(R.id.loading)
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
stopService(Intent(this, ClipboardEntryNotificationService::class.java)) stopService(Intent(this, ClipboardEntryNotificationService::class.java))
stopService(Intent(this, KeyboardEntryNotificationService::class.java)) stopService(Intent(this, KeyboardEntryNotificationService::class.java))
@@ -227,7 +228,7 @@ class EntryEditActivity : LockingActivity(),
templateSelectorSpinner?.apply { templateSelectorSpinner?.apply {
// Build template selector // Build template selector
if (templates.isNotEmpty()) { if (templates.isNotEmpty()) {
adapter = TemplatesSelectorAdapter(this@EntryEditActivity, mDatabase, templates) adapter = TemplatesSelectorAdapter(this@EntryEditActivity, mIconDrawableFactory, templates)
setSelection(templates.indexOf(defaultTemplate)) setSelection(templates.indexOf(defaultTemplate))
onItemSelectedListener = object : AdapterView.OnItemSelectedListener { onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
@@ -245,71 +246,74 @@ class EntryEditActivity : LockingActivity(),
mEntryEditViewModel.onEntrySaved.observe(this) { entrySave -> mEntryEditViewModel.onEntrySaved.observe(this) { entrySave ->
// Open a progress dialog and save entry // Open a progress dialog and save entry
entrySave.parent?.let { parent -> entrySave.parent?.let { parent ->
mProgressDatabaseTaskProvider?.startDatabaseCreateEntry( createEntry(entrySave.newEntry, parent)
entrySave.newEntry,
parent,
!mReadOnly && mAutoSaveEnable
)
} ?: run { } ?: run {
mProgressDatabaseTaskProvider?.startDatabaseUpdateEntry( updateEntry(entrySave.oldEntry, entrySave.newEntry)
entrySave.oldEntry,
entrySave.newEntry,
!mReadOnly && mAutoSaveEnable
)
} }
} }
}
// Create progress dialog override fun onDatabaseRetrieved(database: Database?) {
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result -> super.onDatabaseRetrieved(database)
when (actionTask) { // Focus view to reinitialize timeout
ACTION_DATABASE_CREATE_ENTRY_TASK, coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, database)
ACTION_DATABASE_UPDATE_ENTRY_TASK -> { mEntryEditViewModel.setDatabase(database)
try { mAllowCustomFields = database?.allowEntryCustomFields() == true
if (result.isSuccess) { mAllowOTP = database?.allowOTP == true
var newNodes: List<Node> = ArrayList() }
result.data?.getBundle(DatabaseTaskNotificationService.NEW_NODES_KEY)?.let { newNodesBundle ->
mDatabase?.let { database -> override fun onDatabaseActionFinished(
newNodes = DatabaseTaskNotificationService.getListNodesFromBundle(database, newNodesBundle) database: Database,
} actionTask: String,
} result: ActionRunnable.Result
if (newNodes.size == 1) { ) {
(newNodes[0] as? Entry?)?.let { entry -> super.onDatabaseActionFinished(database, actionTask, result)
EntrySelectionHelper.doSpecialAction(intent, when (actionTask) {
{ ACTION_DATABASE_CREATE_ENTRY_TASK,
// Finish naturally ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
finishForEntryResult(actionTask, entry) try {
}, if (result.isSuccess) {
{ var newNodes: List<Node> = ArrayList()
// Nothing when search retrieved result.data?.getBundle(DatabaseTaskNotificationService.NEW_NODES_KEY)?.let { newNodesBundle ->
}, newNodes = DatabaseTaskNotificationService.getListNodesFromBundle(database, newNodesBundle)
{ }
entryValidatedForSave(actionTask, entry) if (newNodes.size == 1) {
}, (newNodes[0] as? Entry?)?.let { entry ->
{ EntrySelectionHelper.doSpecialAction(intent,
entryValidatedForKeyboardSelection(actionTask, entry) {
}, // Finish naturally
{ _, _ -> finishForEntryResult(actionTask, entry)
entryValidatedForAutofillSelection(entry) },
}, {
{ // Nothing when search retrieved
entryValidatedForAutofillRegistration(actionTask, entry) },
} {
) entryValidatedForSave(actionTask, entry)
} },
{
entryValidatedForKeyboardSelection(database, actionTask, entry)
},
{ _, _ ->
entryValidatedForAutofillSelection(database, entry)
},
{
entryValidatedForAutofillRegistration(actionTask, entry)
}
)
} }
} }
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve entry after database action", e)
} }
} } catch (e: Exception) {
ACTION_DATABASE_RELOAD_TASK -> { Log.e(TAG, "Unable to retrieve entry after database action", e)
// Close the current activity
this.showActionErrorIfNeeded(result)
finish()
} }
} }
coordinatorLayout?.showActionErrorIfNeeded(result) ACTION_DATABASE_RELOAD_TASK -> {
// Close the current activity
this.showActionErrorIfNeeded(result)
finish()
}
} }
coordinatorLayout?.showActionErrorIfNeeded(result)
} }
private fun entryValidatedForSave(actionTask: String, entry: Entry) { private fun entryValidatedForSave(actionTask: String, entry: Entry) {
@@ -317,26 +321,22 @@ class EntryEditActivity : LockingActivity(),
finishForEntryResult(actionTask, entry) finishForEntryResult(actionTask, entry)
} }
private fun entryValidatedForKeyboardSelection(actionTask: String, entry: Entry) { private fun entryValidatedForKeyboardSelection(database: Database, actionTask: String, entry: Entry) {
// Populate Magikeyboard with entry // Populate Magikeyboard with entry
mDatabase?.let { database -> populateKeyboardAndMoveAppToBackground(this,
populateKeyboardAndMoveAppToBackground(this, entry.getEntryInfo(database),
entry.getEntryInfo(database), intent)
intent)
}
onValidateSpecialMode() onValidateSpecialMode()
// Don't keep activity history for entry edition // Don't keep activity history for entry edition
finishForEntryResult(actionTask, entry) finishForEntryResult(actionTask, entry)
} }
private fun entryValidatedForAutofillSelection(entry: Entry) { private fun entryValidatedForAutofillSelection(database: Database, entry: Entry) {
// Build Autofill response with the entry selected // Build Autofill response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mDatabase?.let { database -> AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity,
AutofillHelper.buildResponseAndSetResult(this@EntryEditActivity, database,
database, entry.getEntryInfo(database))
entry.getEntryInfo(database))
}
} }
onValidateSpecialMode() onValidateSpecialMode()
} }
@@ -466,10 +466,8 @@ class EntryEditActivity : LockingActivity(),
override fun onPrepareOptionsMenu(menu: Menu?): Boolean { override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
val allowCustomField = mDatabase?.allowEntryCustomFields() == true
menu?.findItem(R.id.menu_add_field)?.apply { menu?.findItem(R.id.menu_add_field)?.apply {
isEnabled = allowCustomField isEnabled = mAllowCustomFields
isVisible = isEnabled isVisible = isEnabled
} }
@@ -481,9 +479,8 @@ class EntryEditActivity : LockingActivity(),
} }
menu?.findItem(R.id.menu_add_otp)?.apply { menu?.findItem(R.id.menu_add_otp)?.apply {
val allowOTP = mDatabase?.allowOTP == true
// OTP not compatible below KitKat // OTP not compatible below KitKat
isEnabled = allowOTP isEnabled = mAllowOTP
&& !mEntryEditViewModel.entryIsTemplate() && !mEntryEditViewModel.entryIsTemplate()
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
isVisible = isEnabled isVisible = isEnabled
@@ -513,7 +510,7 @@ class EntryEditActivity : LockingActivity(),
if (!generatePasswordEductionPerformed) { if (!generatePasswordEductionPerformed) {
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field) val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
val addNewFieldEducationPerformed = mDatabase?.allowEntryCustomFields() == true val addNewFieldEducationPerformed = mAllowCustomFields
&& addNewFieldView != null && addNewFieldView != null
&& addNewFieldView.isVisible && addNewFieldView.isVisible
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation( && entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(

View File

@@ -27,6 +27,8 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.selection.DatabaseActivity
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.magikeyboard.MagikIME import com.kunzisoft.keepass.magikeyboard.MagikIME
@@ -39,14 +41,10 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
* Activity to search or select entry in database, * Activity to search or select entry in database,
* Commonly used with Magikeyboard * Commonly used with Magikeyboard
*/ */
class EntrySelectionLauncherActivity : AppCompatActivity() { class EntrySelectionLauncherActivity : DatabaseActivity() {
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
mDatabase = Database.getInstance()
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
var sharedWebDomain: String? = null var sharedWebDomain: String? = null
var otpString: String? = null var otpString: String? = null
@@ -72,20 +70,22 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
else -> {} else -> {}
} }
// Build domain search param // Build domain search param
val searchInfo = SearchInfo().apply { val searchInfo = SearchInfo().apply {
this.webDomain = sharedWebDomain this.webDomain = sharedWebDomain
this.otpString = otpString this.otpString = otpString
} }
// End activity if database not loaded
if (database?.loaded != true) {
finish()
}
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain -> SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain searchInfo.webDomain = concreteWebDomain
mDatabase?.let { database -> database?.let { database ->
launch(database, searchInfo) launch(database, searchInfo)
} }
} }
super.onCreate(savedInstanceState)
} }
private fun launch(database: Database, private fun launch(database: Database,

View File

@@ -50,7 +50,6 @@ import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillComponent import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential
@@ -61,6 +60,7 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.* import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel
@@ -85,15 +85,9 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mDatabase = Database.getInstance()
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext) mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
setContentView(R.layout.activity_file_selection) setContentView(R.layout.activity_file_selection)
@@ -195,36 +189,52 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
mAdapterDatabaseHistory?.setDefaultDatabase(it) mAdapterDatabaseHistory?.setDefaultDatabase(it)
} }
// Attach the dialog thread to this activity // Construct adapter with listeners
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply { if (PreferencesUtil.showRecentFiles(this@FileDatabaseSelectActivity)) {
onActionFinish = { actionTask, result -> databaseFilesViewModel.loadListOfDatabases()
when (actionTask) { } else {
ACTION_DATABASE_CREATE_TASK -> { mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
result.data?.getParcelable<Uri?>(DATABASE_URI_KEY)?.let { databaseUri -> mAdapterDatabaseHistory?.notifyDataSetChanged()
val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential() }
databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri) }
}
GroupActivity.launch(this@FileDatabaseSelectActivity, override fun onDatabaseRetrieved(database: Database?) {
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity)) super.onDatabaseRetrieved(database)
} if (database?.loaded == true) {
ACTION_DATABASE_LOAD_TASK -> { launchGroupActivity(database)
val database = mDatabase }
if (result.isSuccess }
&& database?.loaded == true) {
launchGroupActivity(database) override fun onDatabaseActionFinished(
} else { database: Database,
var resultError = "" actionTask: String,
val resultMessage = result.message result: ActionRunnable.Result
// Show error message ) {
if (resultMessage != null && resultMessage.isNotEmpty()) { super.onDatabaseActionFinished(database, actionTask, result)
resultError = "$resultError $resultMessage" when (actionTask) {
} ACTION_DATABASE_CREATE_TASK -> {
Log.e(TAG, resultError) result.data?.getParcelable<Uri?>(DATABASE_URI_KEY)?.let { databaseUri ->
Snackbar.make(coordinatorLayout, val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential()
resultError, databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri)
Snackbar.LENGTH_LONG).asError().show() }
} GroupActivity.launch(this@FileDatabaseSelectActivity,
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity))
}
ACTION_DATABASE_LOAD_TASK -> {
if (result.isSuccess
&& database.loaded) {
launchGroupActivity(database)
} else {
var resultError = ""
val resultMessage = result.message
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
} }
Log.e(TAG, resultError)
Snackbar.make(coordinatorLayout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
} }
} }
} }
@@ -300,29 +310,6 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
createDatabaseButtonView?.visibility = View.GONE createDatabaseButtonView?.visibility = View.GONE
} }
} }
val database = mDatabase
if (database?.loaded == true) {
launchGroupActivity(database)
} else {
// Construct adapter with listeners
if (PreferencesUtil.showRecentFiles(this)) {
databaseFilesViewModel.loadListOfDatabases()
} else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
// Register progress task
mProgressDatabaseTaskProvider?.registerProgressTask()
}
}
override fun onPause() {
// Unregister progress task
mProgressDatabaseTaskProvider?.unregisterProgressTask()
super.onPause()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@@ -334,15 +321,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
} }
override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) { override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) {
try { try {
mDatabaseFileUri?.let { databaseUri -> mDatabaseFileUri?.let { databaseUri ->
// Create the new database // Create the new database
mProgressDatabaseTaskProvider?.startDatabaseCreate( createDatabase(databaseUri, mainCredential)
databaseUri,
mainCredential
)
} }
} catch (e: Exception) { } catch (e: Exception) {
val error = getString(R.string.error_create_database_file) val error = getString(R.string.error_create_database_file)

View File

@@ -67,6 +67,8 @@ class IconPickerActivity : LockingActivity() {
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -84,11 +86,6 @@ class IconPickerActivity : LockingActivity() {
mExternalFileHelper = ExternalFileHelper(this) mExternalFileHelper = ExternalFileHelper(this)
uploadButton = findViewById(R.id.icon_picker_upload) uploadButton = findViewById(R.id.icon_picker_upload)
if (mDatabase?.allowCustomIcons == true) {
uploadButton.setOpenDocumentClickListener(mExternalFileHelper)
} else {
uploadButton.visibility = View.GONE
}
lockView = findViewById(R.id.lock_button) lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener { lockView?.setOnClickListener {
@@ -114,9 +111,6 @@ class IconPickerActivity : LockingActivity() {
mIconImage = savedInstanceState.getParcelable(EXTRA_ICON) ?: mIconImage mIconImage = savedInstanceState.getParcelable(EXTRA_ICON) ?: mIconImage
} }
// Focus view to reinitialize timeout
findViewById<ViewGroup>(R.id.icon_picker_container)?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
iconPickerViewModel.standardIconPicked.observe(this) { iconStandard -> iconPickerViewModel.standardIconPicked.observe(this) { iconStandard ->
mIconImage.standard = iconStandard mIconImage.standard = iconStandard
// Remove the custom icon if a standard one is selected // Remove the custom icon if a standard one is selected
@@ -150,6 +144,22 @@ class IconPickerActivity : LockingActivity() {
} }
} }
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
mDatabase = database
if (database?.allowCustomIcons == true) {
uploadButton.setOpenDocumentClickListener(mExternalFileHelper)
} else {
uploadButton.visibility = View.GONE
}
// Focus view to reinitialize timeout
findViewById<ViewGroup>(R.id.icon_picker_container)
?.resetAppTimeoutWhenViewFocusedOrChanged(this, database)
}
private fun updateIconsSelectedViews() { private fun updateIconsSelectedViews() {
if (mIconsSelected.isEmpty()) { if (mIconsSelected.isEmpty()) {
mCustomIconsSelectionMode = false mCustomIconsSelectionMode = false

View File

@@ -33,11 +33,15 @@ import com.igreenwood.loupe.Loupe
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import kotlin.math.max import kotlin.math.max
class ImageViewerActivity : LockingActivity() { class ImageViewerActivity : LockingActivity() {
private lateinit var imageView: ImageView
private lateinit var progressView: View
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -49,43 +53,8 @@ class ImageViewerActivity : LockingActivity() {
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
val imageContainerView: ViewGroup = findViewById(R.id.image_viewer_container) val imageContainerView: ViewGroup = findViewById(R.id.image_viewer_container)
val imageView: ImageView = findViewById(R.id.image_viewer_image) imageView = findViewById(R.id.image_viewer_image)
val progressView: View = findViewById(R.id.image_viewer_progress) progressView = findViewById(R.id.image_viewer_progress)
// Approximately, to not OOM and allow a zoom
val mImagePreviewMaxWidth = max(
resources.displayMetrics.widthPixels * 2,
resources.displayMetrics.heightPixels * 2
)
try {
progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
supportActionBar?.title = attachment.name
val size = attachment.binaryData.getSize()
supportActionBar?.subtitle = Formatter.formatFileSize(this, size)
mDatabase?.let { database ->
BinaryDatabaseManager.loadBitmap(
database,
attachment.binaryData,
mImagePreviewMaxWidth
) { bitmapLoaded ->
if (bitmapLoaded == null) {
finish()
} else {
progressView.visibility = View.GONE
imageView.setImageBitmap(bitmapLoaded)
}
}
}
} ?: finish()
} catch (e: Exception) {
Log.e(TAG, "Unable to view the binary", e)
finish()
}
Loupe.create(imageView, imageContainerView) { Loupe.create(imageView, imageContainerView) {
onViewTranslateListener = object : Loupe.OnViewTranslateListener { onViewTranslateListener = object : Loupe.OnViewTranslateListener {
@@ -110,6 +79,45 @@ class ImageViewerActivity : LockingActivity() {
} }
} }
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
try {
progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
supportActionBar?.title = attachment.name
val size = attachment.binaryData.getSize()
supportActionBar?.subtitle = Formatter.formatFileSize(this, size)
// Approximately, to not OOM and allow a zoom
val mImagePreviewMaxWidth = max(
resources.displayMetrics.widthPixels * 2,
resources.displayMetrics.heightPixels * 2
)
database?.let { database ->
BinaryDatabaseManager.loadBitmap(
database,
attachment.binaryData,
mImagePreviewMaxWidth
) { bitmapLoaded ->
if (bitmapLoaded == null) {
finish()
} else {
progressView.visibility = View.GONE
imageView.setImageBitmap(bitmapLoaded)
}
}
}
} ?: finish()
} catch (e: Exception) {
Log.e(TAG, "Unable to view the binary", e)
finish()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> finish() android.R.id.home -> finish()

View File

@@ -19,22 +19,20 @@
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.os.Bundle import com.kunzisoft.keepass.activities.selection.DatabaseActivity
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.search.SearchHelper
/** /**
* Activity to select entry in database and populate it in Magikeyboard * Activity to select entry in database and populate it in Magikeyboard
*/ */
class MagikeyboardLauncherActivity : AppCompatActivity() { class MagikeyboardLauncherActivity : DatabaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
val database = Database.getInstance() database?.let {
val readOnly = database.isReadOnly
val readOnly = database.isReadOnly SearchHelper.checkAutoSearchInfo(this,
SearchHelper.checkAutoSearchInfo(this,
database, database,
null, null,
{ {
@@ -49,8 +47,8 @@ class MagikeyboardLauncherActivity : AppCompatActivity() {
// Pass extra to get entry // Pass extra to get entry
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this) FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this)
} }
) )
}
finish() finish()
super.onCreate(savedInstanceState)
} }
} }

View File

@@ -49,7 +49,6 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.autofill.AutofillComponent import com.kunzisoft.keepass.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
@@ -63,6 +62,7 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.MAIN_CREDENTIAL_KEY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
@@ -87,8 +87,6 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
private val databaseFileViewModel: DatabaseFileViewModel by viewModels() private val databaseFileViewModel: DatabaseFileViewModel by viewModels()
private var mDatabase: Database? = null
private var mDefaultDatabase: Boolean = false private var mDefaultDatabase: Boolean = false
private var mDatabaseFileUri: Uri? = null private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null private var mDatabaseKeyFileUri: Uri? = null
@@ -109,15 +107,11 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
field = value field = value
} }
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var mAllowAutoOpenBiometricPrompt: Boolean = true private var mAllowAutoOpenBiometricPrompt: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mDatabase = Database.getInstance()
setContentView(R.layout.activity_password) setContentView(R.layout.activity_password)
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
@@ -208,74 +202,107 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri) onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri)
} }
}
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply { override fun onResume() {
onActionFinish = { actionTask, result -> super.onResume()
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck advanced unlock if error
advancedUnlockFragment?.initAdvancedUnlockMode()
if (result.isSuccess) { mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@PasswordActivity)
mDatabaseKeyFileUri = null
clearCredentialsViews(true)
mDatabase?.let { database ->
launchGroupActivity(database)
}
} else {
var resultError = ""
val resultException = result.exception
val resultMessage = result.message
if (resultException != null) { // Back to previous keyboard is setting activated
resultError = resultException.getLocalizedMessage(resources) if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@PasswordActivity)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
when (resultException) { // Don't allow auto open prompt if lock become when UI visible
is DuplicateUuidDatabaseException -> { mAllowAutoOpenBiometricPrompt = if (LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true)
// Relaunch loading if we need to fix UUID false
showLoadDatabaseDuplicateUuidMessage { else
mAllowAutoOpenBiometricPrompt
mDatabaseFileUri?.let { databaseFileUri ->
databaseFileViewModel.loadDatabaseFile(databaseFileUri)
}
var databaseUri: Uri? = null checkPermission()
var mainCredential: MainCredential = MainCredential() }
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
result.data?.let { resultData -> override fun onDatabaseRetrieved(database: Database?) {
databaseUri = resultData.getParcelable(DATABASE_URI_KEY) super.onDatabaseRetrieved(database)
mainCredential = resultData.getParcelable(MAIN_CREDENTIAL_KEY) ?: mainCredential if (database?.loaded == true) {
readOnly = resultData.getBoolean(READ_ONLY_KEY) // If the database isn't accessible make sure to clear the password field, if it
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY) // was saved in the instance state
} clearCredentialsViews()
launchGroupActivity(database)
}
}
databaseUri?.let { databaseFileUri -> override fun onDatabaseActionFinished(
showProgressDialogAndLoadDatabase( database: Database,
databaseFileUri, actionTask: String,
mainCredential, result: ActionRunnable.Result
readOnly, ) {
cipherEntity, super.onDatabaseActionFinished(database, actionTask, result)
true) when (actionTask) {
} ACTION_DATABASE_LOAD_TASK -> {
} // Recheck advanced unlock if error
advancedUnlockFragment?.initAdvancedUnlockMode()
if (result.isSuccess) {
mDatabaseKeyFileUri = null
clearCredentialsViews(true)
launchGroupActivity(database)
} else {
var resultError = ""
val resultException = result.exception
val resultMessage = result.message
if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources)
when (resultException) {
is DuplicateUuidDatabaseException -> {
// Relaunch loading if we need to fix UUID
showLoadDatabaseDuplicateUuidMessage {
var databaseUri: Uri? = null
var mainCredential: MainCredential = MainCredential()
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
mainCredential = resultData.getParcelable(MAIN_CREDENTIAL_KEY) ?: mainCredential
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
} }
is FileNotFoundDatabaseException -> {
// Remove this default database inaccessible databaseUri?.let { databaseFileUri ->
if (mDefaultDatabase) { showProgressDialogAndLoadDatabase(
databaseFileViewModel.removeDefaultDatabase() databaseFileUri,
} mainCredential,
readOnly,
cipherEntity,
true)
} }
} }
} }
is FileNotFoundDatabaseException -> {
// Show error message // Remove this default database inaccessible
if (resultMessage != null && resultMessage.isNotEmpty()) { if (mDefaultDatabase) {
resultError = "$resultError $resultMessage" databaseFileViewModel.removeDefaultDatabase()
}
} }
Log.e(TAG, resultError)
Snackbar.make(coordinatorLayout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
} }
} }
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError)
Snackbar.make(coordinatorLayout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
} }
} }
} }
@@ -359,41 +386,6 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
} }
} }
override fun onResume() {
super.onResume()
val database = mDatabase
if (database?.loaded == true) {
launchGroupActivity(database)
} else {
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
// If the database isn't accessible make sure to clear the password field, if it
// was saved in the instance state
if (mDatabase?.loaded == true) {
clearCredentialsViews()
}
mProgressDatabaseTaskProvider?.registerProgressTask()
// Back to previous keyboard is setting activated
if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
// Don't allow auto open prompt if lock become when UI visible
mAllowAutoOpenBiometricPrompt = if (LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == true)
false
else
mAllowAutoOpenBiometricPrompt
mDatabaseFileUri?.let { databaseFileUri ->
databaseFileViewModel.loadDatabaseFile(databaseFileUri)
}
checkPermission()
}
}
private fun onDatabaseFileLoaded(databaseFileUri: Uri?, keyFileUri: Uri?) { private fun onDatabaseFileLoaded(databaseFileUri: Uri?, keyFileUri: Uri?) {
// Define Key File text // Define Key File text
if (mRememberKeyFile) { if (mRememberKeyFile) {
@@ -417,8 +409,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
} else { } else {
// Init Biometric elements // Init Biometric elements
advancedUnlockFragment?.loadDatabase(databaseFileUri, advancedUnlockFragment?.loadDatabase(databaseFileUri,
mAllowAutoOpenBiometricPrompt mAllowAutoOpenBiometricPrompt)
&& mProgressDatabaseTaskProvider?.isBinded() != true) // TODO && mDatabaseTaskProvider?.isBinded() != true)
} }
enableOrNotTheConfirmationButton() enableOrNotTheConfirmationButton()
@@ -468,8 +460,6 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
} }
override fun onPause() { override fun onPause() {
mProgressDatabaseTaskProvider?.unregisterProgressTask()
// Reinit locking activity UI variable // Reinit locking activity UI variable
LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
mAllowAutoOpenBiometricPrompt = true mAllowAutoOpenBiometricPrompt = true
@@ -546,7 +536,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
readOnly: Boolean, readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?, cipherDatabaseEntity: CipherDatabaseEntity?,
fixDuplicateUUID: Boolean) { fixDuplicateUUID: Boolean) {
mProgressDatabaseTaskProvider?.startDatabaseLoad( loadDatabase(
databaseUri, databaseUri,
mainCredential, mainCredential,
readOnly, readOnly,
@@ -716,7 +706,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
when (resultCode) { when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK -> { LockingActivity.RESULT_EXIT_LOCK -> {
clearCredentialsViews() clearCredentialsViews()
mDatabase?.clearAndClose(this) // TODO Database
Database.getInstance().clearAndClose(this)
} }
Activity.RESULT_CANCELED -> { Activity.RESULT_CANCELED -> {
clearCredentialsViews() clearCredentialsViews()

View File

@@ -57,17 +57,17 @@ open class DeleteNodesDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val database = Database.getInstance()
arguments?.apply { arguments?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY) if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) { && containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(database, this) // TODO Database
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), this)
} }
} ?: savedInstanceState?.apply { } ?: savedInstanceState?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY) if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) { && containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(database, savedInstanceState) // TODO Database
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), savedInstanceState)
} }
} }
activity?.let { activity -> activity?.let { activity ->

View File

@@ -43,8 +43,6 @@ import org.joda.time.DateTime
class GroupEditDialogFragment : DialogFragment() { class GroupEditDialogFragment : DialogFragment() {
private var mDatabase: Database? = null
private var mEditGroupListener: EditGroupListener? = null private var mEditGroupListener: EditGroupListener? = null
private var mEditGroupDialogAction = EditGroupDialogAction.NONE private var mEditGroupDialogAction = EditGroupDialogAction.NONE
@@ -101,9 +99,6 @@ class GroupEditDialogFragment : DialogFragment() {
iconColor = ta.getColor(0, Color.WHITE) iconColor = ta.getColor(0, Color.WHITE)
ta.recycle() ta.recycle()
// Init elements
mDatabase = Database.getInstance()
if (savedInstanceState != null if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_ACTION_ID) && savedInstanceState.containsKey(KEY_ACTION_ID)
&& savedInstanceState.containsKey(KEY_GROUP_INFO)) { && savedInstanceState.containsKey(KEY_GROUP_INFO)) {
@@ -203,7 +198,8 @@ class GroupEditDialogFragment : DialogFragment() {
} }
private fun assignIconView() { private fun assignIconView() {
mDatabase?.iconDrawableFactory?.assignDatabaseIcon(iconButtonView, mGroupInfo.icon, iconColor) // TODO Database
Database.getInstance()?.iconDrawableFactory?.assignDatabaseIcon(iconButtonView, mGroupInfo.icon, iconColor)
} }
fun setIcon(icon: IconImage) { fun setIcon(icon: IconImage) {

View File

@@ -1,15 +1,34 @@
package com.kunzisoft.keepass.activities.fragments package com.kunzisoft.keepass.activities.fragments
import android.content.Context import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.activities.DatabaseRetrieval
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.activities.stylish.StylishFragment import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseFragment : StylishFragment() { abstract class DatabaseFragment : StylishFragment(), DatabaseRetrieval {
protected var mDatabase: Database? = null private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
override fun onAttach(context: Context) { override fun onViewCreated(view: View,
super.onAttach(context) savedInstanceState: Bundle?) {
mDatabase = Database.getInstance() super.onViewCreated(view, savedInstanceState)
mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), database)
onDatabaseRetrieved(database)
}
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
// Can be overridden by a subclass
} }
} }

View File

@@ -36,6 +36,7 @@ import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.icons.IconDrawableFactory import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
@@ -62,6 +63,8 @@ class EntryEditFragment: DatabaseFragment() {
private lateinit var attachmentsListView: RecyclerView private lateinit var attachmentsListView: RecyclerView
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
private var mAllowMultipleAttachments: Boolean = false
override fun onCreateView(inflater: LayoutInflater, override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
@@ -89,8 +92,6 @@ class EntryEditFragment: DatabaseFragment() {
attachmentsContainerView = view.findViewById(R.id.entry_attachments_container) attachmentsContainerView = view.findViewById(R.id.entry_attachments_container)
attachmentsListView = view.findViewById(R.id.entry_attachments_list) attachmentsListView = view.findViewById(R.id.entry_attachments_list)
view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), mDatabase)
templateView.apply { templateView.apply {
populateIconMethod = { imageView, icon -> populateIconMethod = { imageView, icon ->
drawFactory?.assignDatabaseIcon(imageView, icon, iconColor) drawFactory?.assignDatabaseIcon(imageView, icon, iconColor)
@@ -109,20 +110,6 @@ class EntryEditFragment: DatabaseFragment() {
} }
} }
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
attachmentsAdapter?.database = mDatabase
attachmentsAdapter?.onListSizeChangedListener = { previousSize, newSize ->
if (previousSize > 0 && newSize == 0) {
attachmentsContainerView.collapse(true)
} else if (previousSize == 0 && newSize == 1) {
attachmentsContainerView.expand(true)
}
}
attachmentsListView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
if (savedInstanceState != null) { if (savedInstanceState != null) {
val attachments: List<Attachment> = val attachments: List<Attachment> =
savedInstanceState.getParcelableArrayList(ATTACHMENTS_TAG) ?: listOf() savedInstanceState.getParcelableArrayList(ATTACHMENTS_TAG) ?: listOf()
@@ -206,10 +193,12 @@ class EntryEditFragment: DatabaseFragment() {
mEntryEditViewModel.onBuildNewAttachment.observe(viewLifecycleOwner) { mEntryEditViewModel.onBuildNewAttachment.observe(viewLifecycleOwner) {
val attachmentToUploadUri = it.attachmentToUploadUri val attachmentToUploadUri = it.attachmentToUploadUri
val fileName = it.fileName val fileName = it.fileName
mDatabase?.buildNewBinaryAttachment()?.let { binaryAttachment ->
// TODO Database
Database.getInstance()?.buildNewBinaryAttachment()?.let { binaryAttachment ->
val entryAttachment = Attachment(fileName, binaryAttachment) val entryAttachment = Attachment(fileName, binaryAttachment)
// Ask to replace the current attachment // Ask to replace the current attachment
if ((mDatabase?.allowMultipleAttachments == false if ((!mAllowMultipleAttachments
&& containsAttachment()) || && containsAttachment()) ||
containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD))) { containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD))) {
ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment) ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment)
@@ -249,16 +238,25 @@ class EntryEditFragment: DatabaseFragment() {
} }
} }
override fun onAttach(context: Context) { override fun onDatabaseRetrieved(database: Database?) {
super.onAttach(context) drawFactory = database?.iconDrawableFactory
drawFactory = mDatabase?.iconDrawableFactory mAllowMultipleAttachments = database?.allowMultipleAttachments == true
}
override fun onDetach() { attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
super.onDetach() attachmentsAdapter?.database = database
attachmentsAdapter?.onListSizeChangedListener = { previousSize, newSize ->
drawFactory = null if (previousSize > 0 && newSize == 0) {
attachmentsContainerView.collapse(true)
} else if (previousSize == 0 && newSize == 1) {
attachmentsContainerView.expand(true)
}
}
attachmentsListView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
} }
private fun assignEntryInfo(entryInfo: EntryInfo?) { private fun assignEntryInfo(entryInfo: EntryInfo?) {
@@ -307,7 +305,7 @@ class EntryEditFragment: DatabaseFragment() {
private fun putAttachment(attachment: EntryAttachmentState, private fun putAttachment(attachment: EntryAttachmentState,
onPreviewLoaded: (() -> Unit)? = null) { onPreviewLoaded: (() -> Unit)? = null) {
// When only one attachment is allowed // When only one attachment is allowed
if (mDatabase?.allowMultipleAttachments == false) { if (mAllowMultipleAttachments) {
clearAttachments() clearAttachments()
} }
attachmentsContainerView.visibility = View.VISIBLE attachmentsContainerView.visibility = View.VISIBLE

View File

@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
@@ -63,8 +64,6 @@ class EntryFragment: DatabaseFragment() {
context?.let { context -> context?.let { context ->
mClipboardHelper = ClipboardHelper(context) mClipboardHelper = ClipboardHelper(context)
attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
attachmentsAdapter?.database = mDatabase
} }
rootView = view rootView = view
@@ -75,14 +74,6 @@ class EntryFragment: DatabaseFragment() {
templateView = view.findViewById(R.id.entry_template) templateView = view.findViewById(R.id.entry_template)
loadTemplateSettings() loadTemplateSettings()
attachmentsContainerView = view.findViewById(R.id.entry_attachments_container)
attachmentsListView = view.findViewById(R.id.entry_attachments_list)
attachmentsListView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
creationDateView = view.findViewById(R.id.entry_created) creationDateView = view.findViewById(R.id.entry_created)
modificationDateView = view.findViewById(R.id.entry_modified) modificationDateView = view.findViewById(R.id.entry_modified)
@@ -112,6 +103,21 @@ class EntryFragment: DatabaseFragment() {
} }
} }
override fun onDatabaseRetrieved(database: Database?) {
context?.let { context ->
attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
attachmentsAdapter?.database = database
}
attachmentsContainerView = requireView().findViewById(R.id.entry_attachments_container)
attachmentsListView = requireView().findViewById(R.id.entry_attachments_list)
attachmentsListView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = attachmentsAdapter
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
}
private fun loadTemplateSettings() { private fun loadTemplateSettings() {
context?.let { context -> context?.let { context ->
templateView.setFirstTimeAskAllowCopyProtectedFields(PreferencesUtil.isFirstTimeAskAllowCopyProtectedFields(context)) templateView.setFirstTimeAskAllowCopyProtectedFields(PreferencesUtil.isFirstTimeAskAllowCopyProtectedFields(context))

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.activities.fragments
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
@@ -31,8 +32,8 @@ class IconCustomFragment : IconFragment<IconImageCustom>() {
return R.layout.fragment_icon_grid return R.layout.fragment_icon_grid
} }
override fun defineIconList() { override fun defineIconList(database: Database?) {
mDatabase?.doForEachCustomIcons { customIcon, _ -> database?.doForEachCustomIcons { customIcon, _ ->
iconPickerAdapter.addIcon(customIcon, false) iconPickerAdapter.addIcon(customIcon, false)
} }
} }

View File

@@ -29,6 +29,7 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.IconPickerAdapter import com.kunzisoft.keepass.adapters.IconPickerAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImageDraw import com.kunzisoft.keepass.database.element.icon.IconImageDraw
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -47,31 +48,7 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
abstract fun retrieveMainLayoutId(): Int abstract fun retrieveMainLayoutId(): Int
abstract fun defineIconList() abstract fun defineIconList(database: Database?)
override fun onAttach(context: Context) {
super.onAttach(context)
// Retrieve the textColor to tint the icon
val ta = contextThemed?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
val tintColor = ta?.getColor(0, Color.BLACK) ?: Color.BLACK
ta?.recycle()
iconPickerAdapter = IconPickerAdapter<T>(context, tintColor).apply {
iconDrawableFactory = mDatabase?.iconDrawableFactory
}
CoroutineScope(Dispatchers.IO).launch {
val populateList = launch {
iconPickerAdapter.clear()
defineIconList()
}
withContext(Dispatchers.Main) {
populateList.join()
iconPickerAdapter.notifyDataSetChanged()
}
}
}
override fun onCreateView(inflater: LayoutInflater, override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@@ -88,6 +65,28 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
iconPickerAdapter.iconPickerListener = this iconPickerAdapter.iconPickerListener = this
} }
override fun onDatabaseRetrieved(database: Database?) {
// Retrieve the textColor to tint the icon
val ta = contextThemed?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
val tintColor = ta?.getColor(0, Color.BLACK) ?: Color.BLACK
ta?.recycle()
iconPickerAdapter = IconPickerAdapter<T>(requireContext(), tintColor).apply {
iconDrawableFactory = database?.iconDrawableFactory
}
CoroutineScope(Dispatchers.IO).launch {
val populateList = launch {
iconPickerAdapter.clear()
defineIconList(database)
}
withContext(Dispatchers.Main) {
populateList.join()
iconPickerAdapter.notifyDataSetChanged()
}
}
}
fun onIconDeleteClicked() { fun onIconDeleteClicked() {
iconActionSelectionMode = false iconActionSelectionMode = false
} }

View File

@@ -10,6 +10,7 @@ import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.IconPickerPagerAdapter import com.kunzisoft.keepass.adapters.IconPickerPagerAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
class IconPickerFragment : DatabaseFragment() { class IconPickerFragment : DatabaseFragment() {
@@ -28,17 +29,7 @@ class IconPickerFragment : DatabaseFragment() {
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewPager = view.findViewById(R.id.icon_picker_pager) super.onViewCreated(view, savedInstanceState)
val tabLayout = view.findViewById<TabLayout>(R.id.icon_picker_tabs)
iconPickerPagerAdapter = IconPickerPagerAdapter(this,
if (mDatabase?.allowCustomIcons == true) 2 else 1)
viewPager.adapter = iconPickerPagerAdapter
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = when (position) {
1 -> getString(R.string.icon_section_custom)
else -> getString(R.string.icon_section_standard)
}
}.attach()
arguments?.apply { arguments?.apply {
if (containsKey(ICON_TAB_ARG)) { if (containsKey(ICON_TAB_ARG)) {
@@ -52,6 +43,20 @@ class IconPickerFragment : DatabaseFragment() {
} }
} }
override fun onDatabaseRetrieved(database: Database?) {
viewPager = requireView().findViewById(R.id.icon_picker_pager)
val tabLayout = requireView().findViewById<TabLayout>(R.id.icon_picker_tabs)
iconPickerPagerAdapter = IconPickerPagerAdapter(this,
if (database?.allowCustomIcons == true) 2 else 1)
viewPager.adapter = iconPickerPagerAdapter
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = when (position) {
1 -> getString(R.string.icon_section_custom)
else -> getString(R.string.icon_section_standard)
}
}.attach()
}
enum class IconTab { enum class IconTab {
STANDARD, CUSTOM STANDARD, CUSTOM
} }

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.activities.fragments package com.kunzisoft.keepass.activities.fragments
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
@@ -29,8 +30,8 @@ class IconStandardFragment : IconFragment<IconImageStandard>() {
return R.layout.fragment_icon_grid return R.layout.fragment_icon_grid
} }
override fun defineIconList() { override fun defineIconList(database: Database?) {
mDatabase?.doForEachStandardIcons { standardIcon -> database?.doForEachStandardIcons { standardIcon ->
iconPickerAdapter.addIcon(standardIcon, false) iconPickerAdapter.addIcon(standardIcon, false)
} }
} }

View File

@@ -25,6 +25,7 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.* import android.view.*
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
@@ -34,11 +35,13 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.adapters.NodeAdapter import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.viewmodels.GroupViewModel
import java.util.* import java.util.*
class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListener { class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListener {
@@ -47,10 +50,12 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
private var onScrollListener: OnScrollListener? = null private var onScrollListener: OnScrollListener? = null
private var mNodesRecyclerView: RecyclerView? = null private var mNodesRecyclerView: RecyclerView? = null
var mainGroup: Group? = null
private set
private var mAdapter: NodeAdapter? = null private var mAdapter: NodeAdapter? = null
private val mGroupViewModel: GroupViewModel by activityViewModels()
private var mCurrentGroup: Group? = null
var nodeActionSelectionMode = false var nodeActionSelectionMode = false
private set private set
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
@@ -64,6 +69,9 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
private var readOnly: Boolean = false private var readOnly: Boolean = false
private var specialMode: SpecialMode = SpecialMode.DEFAULT private var specialMode: SpecialMode = SpecialMode.DEFAULT
private var mRecycleBinEnable: Boolean = false
private var mRecycleBin: Group? = null
val isEmpty: Boolean val isEmpty: Boolean
get() = mAdapter == null || mAdapter?.itemCount?:0 <= 0 get() = mAdapter == null || mAdapter?.itemCount?:0 <= 0
@@ -99,56 +107,63 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
setHasOptionsMenu(true) setHasOptionsMenu(true)
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments) readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
}
arguments?.let { args -> override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Contains all the group in element super.onViewCreated(view, savedInstanceState)
if (args.containsKey(GROUP_KEY)) {
mainGroup = args.getParcelable(GROUP_KEY) mGroupViewModel.group.observe(viewLifecycleOwner) {
} mCurrentGroup = it.group
if (args.containsKey(IS_SEARCH)) { isASearchResult = it.group.isVirtual
isASearchResult = args.getBoolean(IS_SEARCH) rebuildList()
}
} }
}
override fun onDatabaseRetrieved(database: Database?) {
mRecycleBinEnable = database?.isRecycleBinEnabled == true
mRecycleBin = database?.recycleBin
contextThemed?.let { context -> contextThemed?.let { context ->
mDatabase?.let { database -> database?.let { database ->
mAdapter = NodeAdapter(context, database) mAdapter = NodeAdapter(context, database).apply {
} setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
mAdapter?.apply { override fun onNodeClick(database: Database, node: Node) {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback { if (nodeActionSelectionMode) {
override fun onNodeClick(node: Node) { if (listActionNodes.contains(node)) {
if (nodeActionSelectionMode) { // Remove selected item if already selected
if (listActionNodes.contains(node)) { listActionNodes.remove(node)
// Remove selected item if already selected } else {
listActionNodes.remove(node) // Add selected item if not already selected
listActionNodes.add(node)
}
nodeClickListener?.onNodeSelected(database, listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
} else { } else {
// Add selected item if not already selected nodeClickListener?.onNodeClick(database, node)
listActionNodes.add(node)
} }
nodeClickListener?.onNodeSelected(listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
} else {
nodeClickListener?.onNodeClick(node)
} }
}
override fun onNodeLongClick(node: Node): Boolean { override fun onNodeLongClick(database: Database, node: Node): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) { if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click // Select the first item after a long click
if (!listActionNodes.contains(node)) if (!listActionNodes.contains(node))
listActionNodes.add(node) listActionNodes.add(node)
nodeClickListener?.onNodeSelected(listActionNodes) nodeClickListener?.onNodeSelected(database, listActionNodes)
setActionNodes(listActionNodes) setActionNodes(listActionNodes)
notifyNodeChanged(node) notifyNodeChanged(node)
}
return true
} }
return true })
} }
}) mNodesRecyclerView?.adapter = mAdapter
} }
} }
// TODO notify menu updated?
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@@ -211,7 +226,7 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
fun rebuildList() { fun rebuildList() {
// Add elements to the list // Add elements to the list
mainGroup?.let { mainGroup -> mCurrentGroup?.let { mainGroup ->
mAdapter?.apply { mAdapter?.apply {
// Thrown an exception when sort cannot be performed // Thrown an exception when sort cannot be performed
rebuildList(mainGroup) rebuildList(mainGroup)
@@ -253,7 +268,7 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
R.id.menu_sort -> { R.id.menu_sort -> {
context?.let { context -> context?.let { context ->
val sortDialogFragment: SortDialogFragment = val sortDialogFragment: SortDialogFragment =
if (mDatabase?.isRecycleBinEnabled == true) { if (mRecycleBinEnable) {
SortDialogFragment.getInstance( SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context), PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context), PreferencesUtil.getAscendingSort(context),
@@ -275,7 +290,8 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
} }
} }
fun actionNodesCallback(nodes: List<Node>, fun actionNodesCallback(database: Database,
nodes: List<Node>,
menuListener: NodesActionMenuListener?, menuListener: NodesActionMenuListener?,
actionModeCallback: ActionMode.Callback) : ActionMode.Callback { actionModeCallback: ActionMode.Callback) : ActionMode.Callback {
@@ -289,49 +305,46 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
menu?.clear() menu?.clear()
if (nodeActionPasteMode != PasteMode.UNDEFINED) {
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu)
} else {
nodeActionSelectionMode = true
mode?.menuInflater?.inflate(R.menu.node_menu, menu)
mDatabase?.let { database -> // Open and Edit for a single item
if (nodeActionPasteMode != PasteMode.UNDEFINED) { if (nodes.size == 1) {
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu) // Edition
} else { if (readOnly
nodeActionSelectionMode = true || (mRecycleBinEnable && nodes[0] == mRecycleBin)) {
mode?.menuInflater?.inflate(R.menu.node_menu, menu)
// Open and Edit for a single item
if (nodes.size == 1) {
// Edition
if (readOnly
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
menu?.removeItem(R.id.menu_edit)
}
} else {
menu?.removeItem(R.id.menu_open)
menu?.removeItem(R.id.menu_edit) menu?.removeItem(R.id.menu_edit)
} }
} else {
// Move menu?.removeItem(R.id.menu_open)
if (readOnly menu?.removeItem(R.id.menu_edit)
|| isASearchResult) {
menu?.removeItem(R.id.menu_move)
}
// Copy (not allowed for group)
if (readOnly
|| isASearchResult
|| nodes.any { it.type == Type.GROUP }) {
menu?.removeItem(R.id.menu_copy)
}
// Deletion
if (readOnly
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
menu?.removeItem(R.id.menu_delete)
}
} }
// Add the number of items selected in title // Move
mode?.title = nodes.size.toString() if (readOnly
|| isASearchResult) {
menu?.removeItem(R.id.menu_move)
}
// Copy (not allowed for group)
if (readOnly
|| isASearchResult
|| nodes.any { it.type == Type.GROUP }) {
menu?.removeItem(R.id.menu_copy)
}
// Deletion
if (readOnly
|| (mRecycleBinEnable && nodes.any { it == mRecycleBin })) {
menu?.removeItem(R.id.menu_delete)
}
} }
// Add the number of items selected in title
mode?.title = nodes.size.toString()
return actionModeCallback.onPrepareActionMode(mode, menu) return actionModeCallback.onPrepareActionMode(mode, menu)
} }
@@ -339,25 +352,25 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
if (menuListener == null) if (menuListener == null)
return false return false
return when (item?.itemId) { return when (item?.itemId) {
R.id.menu_open -> menuListener.onOpenMenuClick(nodes[0]) R.id.menu_open -> menuListener.onOpenMenuClick(database, nodes[0])
R.id.menu_edit -> menuListener.onEditMenuClick(nodes[0]) R.id.menu_edit -> menuListener.onEditMenuClick(database, nodes[0])
R.id.menu_copy -> { R.id.menu_copy -> {
nodeActionPasteMode = PasteMode.PASTE_FROM_COPY nodeActionPasteMode = PasteMode.PASTE_FROM_COPY
mAdapter?.unselectActionNodes() mAdapter?.unselectActionNodes()
val returnValue = menuListener.onCopyMenuClick(nodes) val returnValue = menuListener.onCopyMenuClick(database, nodes)
nodeActionSelectionMode = false nodeActionSelectionMode = false
returnValue returnValue
} }
R.id.menu_move -> { R.id.menu_move -> {
nodeActionPasteMode = PasteMode.PASTE_FROM_MOVE nodeActionPasteMode = PasteMode.PASTE_FROM_MOVE
mAdapter?.unselectActionNodes() mAdapter?.unselectActionNodes()
val returnValue = menuListener.onMoveMenuClick(nodes) val returnValue = menuListener.onMoveMenuClick(database, nodes)
nodeActionSelectionMode = false nodeActionSelectionMode = false
returnValue returnValue
} }
R.id.menu_delete -> menuListener.onDeleteMenuClick(nodes) R.id.menu_delete -> menuListener.onDeleteMenuClick(database, nodes)
R.id.menu_paste -> { R.id.menu_paste -> {
val returnValue = menuListener.onPasteMenuClick(nodeActionPasteMode, nodes) val returnValue = menuListener.onPasteMenuClick(database, nodeActionPasteMode, nodes)
nodeActionPasteMode = PasteMode.UNDEFINED nodeActionPasteMode = PasteMode.UNDEFINED
nodeActionSelectionMode = false nodeActionSelectionMode = false
returnValue returnValue
@@ -435,20 +448,20 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
* Callback listener to redefine to do an action when a node is click * Callback listener to redefine to do an action when a node is click
*/ */
interface NodeClickListener { interface NodeClickListener {
fun onNodeClick(node: Node) fun onNodeClick(database: Database, node: Node)
fun onNodeSelected(nodes: List<Node>): Boolean fun onNodeSelected(database: Database, nodes: List<Node>): Boolean
} }
/** /**
* Menu listener to redefine to do an action in menu * Menu listener to redefine to do an action in menu
*/ */
interface NodesActionMenuListener { interface NodesActionMenuListener {
fun onOpenMenuClick(node: Node): Boolean fun onOpenMenuClick(database: Database, node: Node): Boolean
fun onEditMenuClick(node: Node): Boolean fun onEditMenuClick(database: Database, node: Node): Boolean
fun onCopyMenuClick(nodes: List<Node>): Boolean fun onCopyMenuClick(database: Database, nodes: List<Node>): Boolean
fun onMoveMenuClick(nodes: List<Node>): Boolean fun onMoveMenuClick(database: Database, nodes: List<Node>): Boolean
fun onDeleteMenuClick(nodes: List<Node>): Boolean fun onDeleteMenuClick(database: Database, nodes: List<Node>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<Node>): Boolean fun onPasteMenuClick(database: Database, pasteMode: PasteMode?, nodes: List<Node>): Boolean
} }
enum class PasteMode { enum class PasteMode {
@@ -467,22 +480,6 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
} }
companion object { companion object {
private val TAG = ListNodesFragment::class.java.name private val TAG = ListNodesFragment::class.java.name
private const val GROUP_KEY = "GROUP_KEY"
private const val IS_SEARCH = "IS_SEARCH"
fun newInstance(group: Group?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
val bundle = Bundle()
if (group != null) {
bundle.putParcelable(GROUP_KEY, group)
}
bundle.putBoolean(IS_SEARCH, isASearch)
ReadOnlyHelper.putReadOnlyInBundle(bundle, readOnly)
val listNodesFragment = ListNodesFragment()
listNodesFragment.arguments = bundle
return listNodesFragment
}
} }
} }

View File

@@ -43,6 +43,7 @@ object ReadOnlyHelper {
} }
} }
// TODO remove read only
fun retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState: Bundle?, arguments: Bundle?): Boolean { fun retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState: Bundle?, arguments: Bundle?): Boolean {
var readOnly = READ_ONLY_DEFAULT var readOnly = READ_ONLY_DEFAULT
if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) { if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {

View File

@@ -22,30 +22,43 @@ package com.kunzisoft.keepass.activities.lock
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.EmptyRecycleBinDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database 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.Node
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.* import com.kunzisoft.keepass.utils.*
abstract class LockingActivity : SpecialModeActivity() { abstract class LockingActivity : SpecialModeActivity(),
PasswordEncodingDialogFragment.Listener,
DeleteNodesDialogFragment.DeleteNodeListener {
protected var mTimeoutEnable: Boolean = true protected var mTimeoutEnable: Boolean = true
private var mLockReceiver: LockReceiver? = null private var mLockReceiver: LockReceiver? = null
private var mExitLock: Boolean = false private var mExitLock: Boolean = false
protected var mDatabase: Database? = null private var mDatabase: Database? = null
// Force readOnly if Entry Selection mode // Force readOnly if Entry Selection mode
protected var mReadOnly: Boolean protected var mReadOnly: Boolean
@@ -58,23 +71,19 @@ abstract class LockingActivity : SpecialModeActivity() {
private var mReadOnlyToSave: Boolean = false private var mReadOnlyToSave: Boolean = false
protected var mAutoSaveEnable: Boolean = true protected var mAutoSaveEnable: Boolean = true
var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null protected var mIconDrawableFactory: IconDrawableFactory? = null
private set
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
mDatabase = Database.getInstance()
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (savedInstanceState != null if (savedInstanceState != null
&& savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)) { && savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)
) {
mTimeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY) mTimeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY)
} else { } else {
if (intent != null) if (intent != null)
mTimeoutEnable = intent.getBooleanExtra(TIMEOUT_ENABLE_KEY, TIMEOUT_ENABLE_KEY_DEFAULT) mTimeoutEnable =
intent.getBooleanExtra(TIMEOUT_ENABLE_KEY, TIMEOUT_ENABLE_KEY_DEFAULT)
} }
if (mTimeoutEnable) { if (mTimeoutEnable) {
@@ -93,19 +102,193 @@ abstract class LockingActivity : SpecialModeActivity() {
mExitLock = false mExitLock = false
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onDatabaseRetrieved(database: Database?) {
super.onActivityResult(requestCode, resultCode, data) super.onDatabaseRetrieved(database)
if (resultCode == RESULT_EXIT_LOCK) { mDatabase = database
mExitLock = true // End activity if database not loaded
if (mDatabase?.loaded == true) { if (database?.loaded != true) {
lockAndExit() finish()
}
// check timeout
if (mTimeoutEnable) {
// After the first creation
// or If simply swipe with another application
// If the time is out -> close the Activity
TimeoutHelper.checkTimeAndLockIfTimeout(this)
// If onCreate already record time
database?.let { it ->
if (!mExitLock)
TimeoutHelper.recordTime(this, it)
}
}
// Force read only if the database is like that
mReadOnly = database?.isReadOnly != false || mReadOnly
mIconDrawableFactory = database?.iconDrawableFactory
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
// Reload the current activity
if (result.isSuccess) {
reload(database)
}
} }
} }
} }
override fun onPasswordEncodingValidateListener(databaseUri: Uri?,
mainCredential: MainCredential) {
assignPasswordValidated(databaseUri, mainCredential)
}
private fun assignPasswordValidated(databaseUri: Uri?,
mainCredential: MainCredential) {
if (databaseUri != null) {
assignDatabasePassword(databaseUri, mainCredential)
}
}
fun assignPassword(mainCredential: MainCredential) {
mDatabase?.let { database ->
database.fileUri?.let { databaseUri ->
// Show the progress dialog now or after dialog confirmation
if (database.validatePasswordEncoding(mainCredential)) {
assignPasswordValidated(databaseUri, mainCredential)
} else {
PasswordEncodingDialogFragment.getInstance(databaseUri, mainCredential)
.show(supportFragmentManager, "passwordEncodingTag")
}
}
}
}
fun createEntry(newEntry: Entry,
parent: Group) {
createDatabaseEntry(newEntry, parent, !mReadOnly && mAutoSaveEnable)
}
fun updateEntry(oldEntry: Entry,
entryToUpdate: Entry) {
updateDatabaseEntry(oldEntry, entryToUpdate, !mReadOnly && mAutoSaveEnable)
}
fun copyNodes(nodesToCopy: List<Node>,
newParent: Group) {
copyDatabaseNodes(nodesToCopy, newParent, !mReadOnly && mAutoSaveEnable)
}
fun moveNodes(nodesToMove: List<Node>,
newParent: Group) {
moveDatabaseNodes(nodesToMove, newParent, !mReadOnly && mAutoSaveEnable)
}
private fun eachNodeRecyclable(database: Database, nodes: List<Node>): Boolean {
return nodes.find { node ->
var cannotRecycle = true
if (node is Entry) {
cannotRecycle = !database.canRecycle(node)
} else if (node is Group) {
cannotRecycle = !database.canRecycle(node)
}
cannotRecycle
} == null
}
fun deleteNodes(nodes: List<Node>, recycleBin: Boolean = false) {
mDatabase?.let { database ->
// If recycle bin enabled, ensure it exists
if (database.isRecycleBinEnabled) {
database.ensureRecycleBinExists(resources)
}
// If recycle bin enabled and not in recycle bin, move in recycle bin
if (eachNodeRecyclable(database, nodes)) {
permanentlyDeleteNodes(nodes)
}
// else open the dialog to confirm deletion
else {
val deleteNodesDialogFragment: DeleteNodesDialogFragment =
if (recycleBin) {
EmptyRecycleBinDialogFragment.getInstance(nodes)
} else {
DeleteNodesDialogFragment.getInstance(nodes)
}
deleteNodesDialogFragment.show(supportFragmentManager, "deleteNodesDialogFragment")
}
}
}
override fun permanentlyDeleteNodes(nodes: List<Node>) {
deleteDatabaseNodes(nodes,!mReadOnly && mAutoSaveEnable)
}
fun createGroup(parent: Group,
groupInfo: GroupInfo?) {
// Build the group
mDatabase?.createGroup()?.let { newGroup ->
groupInfo?.let { info ->
newGroup.setGroupInfo(info)
}
// Not really needed here because added in runnable but safe
newGroup.parent = parent
createDatabaseGroup(newGroup, parent, !mReadOnly && mAutoSaveEnable)
}
}
fun updateGroup(oldGroup: Group,
groupInfo: GroupInfo) {
// If group updated save it in the database
val updateGroup = Group(oldGroup).let { updateGroup ->
updateGroup.apply {
// WARNING remove parent and children to keep memory
removeParent()
removeChildren()
this.setGroupInfo(groupInfo)
}
}
updateDatabaseGroup(oldGroup, updateGroup, !mReadOnly && mAutoSaveEnable)
}
fun restoreEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,) {
restoreDatabaseEntryHistory(mainEntry, entryHistoryPosition, !mReadOnly && mAutoSaveEnable)
}
fun deleteEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,) {
deleteDatabaseEntryHistory(mainEntry, entryHistoryPosition, !mReadOnly && mAutoSaveEnable)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_EXIT_LOCK) {
mExitLock = true
lockAndExit()
}
}
private fun reload(database: Database) {
// Reload the current activity
startActivity(intent)
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
database.wasReloaded = false
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (mDatabase?.wasReloaded == true) {
reload(mDatabase!!)
}
// If in ave or registration mode, don't allow read only // If in ave or registration mode, don't allow read only
if ((mSpecialMode == SpecialMode.SAVE if ((mSpecialMode == SpecialMode.SAVE
|| mSpecialMode == SpecialMode.REGISTRATION) || mSpecialMode == SpecialMode.REGISTRATION)
@@ -115,36 +298,19 @@ abstract class LockingActivity : SpecialModeActivity() {
finish() finish()
} }
mProgressDatabaseTaskProvider?.registerProgressTask()
// To refresh when back to normal workflow from selection workflow // To refresh when back to normal workflow from selection workflow
mReadOnlyToSave = ReadOnlyHelper.retrieveReadOnlyFromIntent(intent) mReadOnlyToSave = ReadOnlyHelper.retrieveReadOnlyFromIntent(intent)
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this) mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
invalidateOptionsMenu() invalidateOptionsMenu()
if (mTimeoutEnable) {
// End activity if database not loaded
// TODO generalize
if (mDatabase?.loaded != true) {
finish()
return
}
// After the first creation
// or If simply swipe with another application
// If the time is out -> close the Activity
TimeoutHelper.checkTimeAndLockIfTimeout(this)
// If onCreate already record time
mDatabase?.let { database ->
if (!mExitLock)
TimeoutHelper.recordTime(this, database)
}
}
LOCKING_ACTIVITY_UI_VISIBLE = true LOCKING_ACTIVITY_UI_VISIBLE = true
} }
protected fun checkTimeAndLockIfTimeoutOrResetTimeout(action: (() -> Unit)? = null) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, mDatabase, action)
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable) outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
@@ -153,8 +319,6 @@ abstract class LockingActivity : SpecialModeActivity() {
override fun onPause() { override fun onPause() {
LOCKING_ACTIVITY_UI_VISIBLE = false LOCKING_ACTIVITY_UI_VISIBLE = false
mProgressDatabaseTaskProvider?.unregisterProgressTask()
super.onPause() super.onPause()
if (mTimeoutEnable) { if (mTimeoutEnable) {

View File

@@ -0,0 +1,210 @@
package com.kunzisoft.keepass.activities.selection
import android.net.Uri
import android.os.Bundle
import androidx.activity.viewModels
import com.kunzisoft.keepass.activities.DatabaseRetrieval
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.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
private val mDatabaseViewModel: DatabaseViewModel by viewModels()
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabaseTaskProvider = DatabaseTaskProvider(this)
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
onDatabaseRetrieved(database)
}
mDatabaseTaskProvider?.onActionFinish = { database, actionTask, result ->
onDatabaseActionFinished(database, actionTask, result)
}
mDatabaseViewModel.saveDatabase.observe(this) { save ->
mDatabaseTaskProvider?.startDatabaseSave(save)
}
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
// TODO keepCurrentScreen()
mDatabaseTaskProvider?.startDatabaseReload(fixDuplicateUuid)
}
mDatabaseViewModel.saveName.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveName(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveDescription.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveDescription(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveDefaultUsername.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveName(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveColor.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveColor(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveCompression.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveCompression(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.removeUnlinkData.observe(this) {
mDatabaseTaskProvider?.startDatabaseRemoveUnlinkedData(it)
}
mDatabaseViewModel.saveRecycleBin.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveRecycleBin(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveTemplatesGroup.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveTemplatesGroup(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveMaxHistoryItems.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveMaxHistoryItems(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveMaxHistorySize.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveMaxHistorySize(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveEncryption.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveEncryption(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveKeyDerivation.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveKeyDerivation(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveIterations.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveIterations(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveMemoryUsage.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveMemoryUsage(it.oldValue, it.newValue, it.save)
}
mDatabaseViewModel.saveParallelism.observe(this) {
mDatabaseTaskProvider?.startDatabaseSaveParallelism(it.oldValue, it.newValue, it.save)
}
}
override fun onDatabaseRetrieved(database: Database?) {
mDatabase = database
mDatabaseViewModel.defineDatabase(database)
// optional method implementation
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
// optional method implementation
}
fun createDatabase(databaseUri: Uri,
mainCredential: MainCredential) {
mDatabaseTaskProvider?.startDatabaseCreate(databaseUri, mainCredential)
}
// TODO Database functions
fun loadDatabase(databaseUri: Uri,
mainCredential: MainCredential,
readOnly: Boolean,
cipherEntity: CipherDatabaseEntity?,
fixDuplicateUuid: Boolean) {
mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEntity, fixDuplicateUuid)
}
fun assignDatabasePassword(databaseUri: Uri,
mainCredential: MainCredential) {
mDatabaseTaskProvider?.startDatabaseAssignPassword(databaseUri, mainCredential)
}
fun saveDatabase() {
mDatabaseTaskProvider?.startDatabaseSave(mDatabase?.isReadOnly != true)
}
fun reloadDatabase() {
mDatabaseTaskProvider?.startDatabaseReload(false)
}
fun createDatabaseEntry(newEntry: Entry,
parent: Group,
save: Boolean) {
mDatabaseTaskProvider?.startDatabaseCreateEntry(newEntry, parent, save)
}
fun updateDatabaseEntry(oldEntry: Entry,
entryToUpdate: Entry,
save: Boolean) {
mDatabaseTaskProvider?.startDatabaseUpdateEntry(oldEntry, entryToUpdate, save)
}
fun copyDatabaseNodes(nodesToCopy: List<Node>,
newParent: Group,
save: Boolean) {
mDatabaseTaskProvider?.startDatabaseCopyNodes(nodesToCopy, newParent, save)
}
fun moveDatabaseNodes(nodesToMove: List<Node>,
newParent: Group,
save: Boolean) {
mDatabaseTaskProvider?.startDatabaseMoveNodes(nodesToMove, newParent, save)
}
fun deleteDatabaseNodes(nodesToDelete: List<Node>,
save: Boolean) {
mDatabaseTaskProvider?.startDatabaseDeleteNodes(nodesToDelete, save)
}
fun createDatabaseGroup(newGroup: Group,
parent: Group,
save: Boolean) {
mDatabaseTaskProvider?.startDatabaseCreateGroup(newGroup, parent, save)
}
fun updateDatabaseGroup(oldGroup: Group,
groupToUpdate: Group,
save: Boolean) {
mDatabaseTaskProvider?.startDatabaseUpdateGroup(oldGroup, groupToUpdate, save)
}
fun restoreDatabaseEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,
save: Boolean) {
mDatabaseTaskProvider?.startDatabaseRestoreEntryHistory(mainEntry, entryHistoryPosition, save)
}
fun deleteDatabaseEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,
save: Boolean) {
mDatabaseTaskProvider?.startDatabaseDeleteEntryHistory(mainEntry, entryHistoryPosition, save)
}
override fun onResume() {
super.onResume()
mDatabaseTaskProvider?.registerProgressTask()
}
override fun onPause() {
mDatabaseTaskProvider?.unregisterProgressTask()
super.onPause()
}
}

View File

@@ -3,6 +3,7 @@ package com.kunzisoft.keepass.activities.selection
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.room.Database
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode import com.kunzisoft.keepass.activities.helpers.SpecialMode
@@ -15,7 +16,7 @@ import com.kunzisoft.keepass.view.SpecialModeView
/** /**
* Activity to manage special mode (ie: selection mode) * Activity to manage special mode (ie: selection mode)
*/ */
abstract class SpecialModeActivity : StylishActivity() { abstract class SpecialModeActivity : DatabaseActivity() {
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
private var mTypeMode: TypeMode = TypeMode.DEFAULT private var mTypeMode: TypeMode = TypeMode.DEFAULT

View File

@@ -358,10 +358,10 @@ class NodeAdapter (private val context: Context,
// Assign click // Assign click
holder.container.setOnClickListener { holder.container.setOnClickListener {
mNodeClickCallback?.onNodeClick(subNode) mNodeClickCallback?.onNodeClick(database, subNode)
} }
holder.container.setOnLongClickListener { holder.container.setOnLongClickListener {
mNodeClickCallback?.onNodeLongClick(subNode) ?: false mNodeClickCallback?.onNodeLongClick(database, subNode) ?: false
} }
} }
@@ -380,8 +380,8 @@ class NodeAdapter (private val context: Context,
* Callback listener to redefine to do an action when a node is click * Callback listener to redefine to do an action when a node is click
*/ */
interface NodeClickCallback { interface NodeClickCallback {
fun onNodeClick(node: Node) fun onNodeClick(database: Database, node: Node)
fun onNodeLongClick(node: Node): Boolean fun onNodeLongClick(database: Database, node: Node): Boolean
} }
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

View File

@@ -12,10 +12,11 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.template.Template import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.database.element.template.TemplateField import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.icons.IconDrawableFactory
class TemplatesSelectorAdapter(private val context: Context, class TemplatesSelectorAdapter(private val context: Context,
private val database: Database?, private val iconDrawableFactory: IconDrawableFactory?,
private var templates: List<Template>): BaseAdapter() { private var templates: List<Template>): BaseAdapter() {
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
@@ -43,7 +44,7 @@ class TemplatesSelectorAdapter(private val context: Context,
} }
holder.icon?.let { icon -> holder.icon?.let { icon ->
database?.iconDrawableFactory?.assignDatabaseIcon(icon, template.icon, mIconColor) iconDrawableFactory?.assignDatabaseIcon(icon, template.icon, mIconColor)
} }
holder.name?.text = TemplateField.getLocalizedName(context, template.title) holder.name?.text = TemplateField.getLocalizedName(context, template.title)

View File

@@ -33,7 +33,7 @@ class App : MultiDexApplication() {
} }
override fun onTerminate() { override fun onTerminate() {
Database.getInstance().clearAndClose(this) // TODO Database.getInstance().clearAndClose(this)
super.onTerminate() super.onTerminate()
} }
} }

View File

@@ -56,13 +56,9 @@ class KeeAutofillService : AutofillService() {
var autofillInlineSuggestionsEnabled: Boolean = false var autofillInlineSuggestionsEnabled: Boolean = false
private var mLock = AtomicBoolean() private var mLock = AtomicBoolean()
private var mDatabase: Database? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
mDatabase = Database.getInstance()
getPreferences() getPreferences()
} }
@@ -102,7 +98,8 @@ class KeeAutofillService : AutofillService() {
} else { } else {
null null
} }
mDatabase?.let { database -> // TODO database
Database.getInstance()?.let { database ->
launchSelection(database, launchSelection(database,
searchInfo, searchInfo,
parseResult, parseResult,

View File

@@ -35,6 +35,7 @@ import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Co
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
@@ -83,9 +84,12 @@ import kotlinx.coroutines.launch
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) { class DatabaseTaskProvider(private val activity: FragmentActivity) {
var onActionFinish: ((actionTask: String, var onDatabaseRetrieved: ((database: Database?) -> Unit)? = null
var onActionFinish: ((database: Database,
actionTask: String,
result: ActionRunnable.Result) -> Unit)? = null result: ActionRunnable.Result) -> Unit)? = null
private var intentDatabaseTask = Intent(activity.applicationContext, DatabaseTaskNotificationService::class.java) private var intentDatabaseTask = Intent(activity.applicationContext, DatabaseTaskNotificationService::class.java)
@@ -99,16 +103,16 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener { private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) { override fun onStartAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?) {
startDialog(titleId, messageId, warningId) startDialog(titleId, messageId, warningId)
} }
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) { override fun onUpdateAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?) {
updateDialog(titleId, messageId, warningId) updateDialog(titleId, messageId, warningId)
} }
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) { override fun onStopAction(database: Database, actionTask: String, result: ActionRunnable.Result) {
onActionFinish?.invoke(actionTask, result) onActionFinish?.invoke(database, actionTask, result)
// Remove the progress task // Remove the progress task
stopDialog() stopDialog()
} }
@@ -144,6 +148,12 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
} }
} }
private var databaseListener = object: DatabaseTaskNotificationService.DatabaseListener {
override fun onDatabaseRetrieved(database: Database?) {
onDatabaseRetrieved?.invoke(database)
}
}
private fun startDialog(titleId: Int? = null, private fun startDialog(titleId: Int? = null,
messageId: Int? = null, messageId: Int? = null,
warningId: Int? = null) { warningId: Int? = null) {
@@ -187,16 +197,19 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
serviceConnection = object : ServiceConnection { serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) { override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply { mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
addActionTaskListener(actionTaskListener) addDatabaseListener(databaseListener)
addDatabaseFileInfoListener(databaseInfoListener) addDatabaseFileInfoListener(databaseInfoListener)
getService().checkAction() addActionTaskListener(actionTaskListener)
getService().checkDatabase()
getService().checkDatabaseInfo() getService().checkDatabaseInfo()
getService().checkAction()
} }
} }
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener) mBinder?.removeActionTaskListener(actionTaskListener)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
mBinder = null mBinder = null
} }
} }
@@ -257,8 +270,9 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
fun unregisterProgressTask() { fun unregisterProgressTask() {
stopDialog() stopDialog()
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener) mBinder?.removeActionTaskListener(actionTaskListener)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
mBinder = null mBinder = null
unBindService() unBindService()
@@ -639,6 +653,6 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
} }
companion object { companion object {
private val TAG = ProgressDatabaseTaskProvider::class.java.name private val TAG = DatabaseTaskProvider::class.java.name
} }
} }

View File

@@ -111,6 +111,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
// Remove entry info if the database is not loaded // Remove entry info if the database is not loaded
// or if entry info timestamp is before database loaded timestamp // or if entry info timestamp is before database loaded timestamp
// TODO Database
val database = Database.getInstance() val database = Database.getInstance()
val databaseTime = database.loadTimestamp val databaseTime = database.loadTimestamp
val entryTime = entryInfoTimestamp val entryTime = entryInfoTimestamp

View File

@@ -49,8 +49,6 @@ class AttachmentFileNotificationService: LockNotificationService() {
private val mainScope = CoroutineScope(Dispatchers.Main) private val mainScope = CoroutineScope(Dispatchers.Main)
private var mDatabase: Database? = Database.getInstance()
override fun retrieveChannelId(): String { override fun retrieveChannelId(): String {
return CHANNEL_ATTACHMENT_ID return CHANNEL_ATTACHMENT_ID
} }
@@ -289,7 +287,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
// Add action to the list on start // Add action to the list on start
attachmentNotificationList.add(attachmentNotification) attachmentNotificationList.add(attachmentNotification)
mDatabase?.let { database -> // TODO Database
Database.getInstance()?.let { database ->
mainScope.launch { mainScope.launch {
AttachmentFileAction(attachmentNotification, AttachmentFileAction(attachmentNotification,
database, database,

View File

@@ -58,12 +58,12 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
private val mainScope = CoroutineScope(Dispatchers.Main) private val mainScope = CoroutineScope(Dispatchers.Main)
private var mDatabaseListeners = LinkedList<DatabaseListener>()
private var mDatabaseInfoListeners = LinkedList<DatabaseInfoListener>()
private var mActionTaskBinder = ActionTaskBinder() private var mActionTaskBinder = ActionTaskBinder()
private var mActionTaskListeners = LinkedList<ActionTaskListener>() private var mActionTaskListeners = LinkedList<ActionTaskListener>()
private var mActionRunning = false private var mActionRunning = false
private var mDatabaseInfoListeners = LinkedList<DatabaseInfoListener>()
private var mIconId: Int = R.drawable.notification_ic_database_load private var mIconId: Int = R.drawable.notification_ic_database_load
private var mTitleId: Int = R.string.database_opened private var mTitleId: Int = R.string.database_opened
private var mMessageId: Int? = null private var mMessageId: Int? = null
@@ -81,13 +81,13 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun getService(): DatabaseTaskNotificationService = this@DatabaseTaskNotificationService fun getService(): DatabaseTaskNotificationService = this@DatabaseTaskNotificationService
fun addActionTaskListener(actionTaskListener: ActionTaskListener) { fun addDatabaseListener(databaseListener: DatabaseListener) {
if (!mActionTaskListeners.contains(actionTaskListener)) if (!mDatabaseListeners.contains(databaseListener))
mActionTaskListeners.add(actionTaskListener) mDatabaseListeners.add(databaseListener)
} }
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) { fun removeDatabaseListener(databaseListener: DatabaseListener) {
mActionTaskListeners.remove(actionTaskListener) mDatabaseListeners.remove(databaseListener)
} }
fun addDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) { fun addDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
@@ -98,12 +98,19 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun removeDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) { fun removeDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
mDatabaseInfoListeners.remove(databaseInfoListener) mDatabaseInfoListeners.remove(databaseInfoListener)
} }
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
if (!mActionTaskListeners.contains(actionTaskListener))
mActionTaskListeners.add(actionTaskListener)
}
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.remove(actionTaskListener)
}
} }
interface ActionTaskListener { interface DatabaseListener {
fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) fun onDatabaseRetrieved(database: Database?)
fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?)
fun onStopAction(actionTask: String, result: ActionRunnable.Result)
} }
interface DatabaseInfoListener { interface DatabaseInfoListener {
@@ -111,14 +118,15 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
newDatabaseInfo: SnapFileDatabaseInfo) newDatabaseInfo: SnapFileDatabaseInfo)
} }
/** interface ActionTaskListener {
* Force to call [ActionTaskListener.onStartAction] if the action is still running fun onStartAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?)
*/ fun onUpdateAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?)
fun checkAction() { fun onStopAction(database: Database, actionTask: String, result: ActionRunnable.Result)
if (mActionRunning) { }
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStartAction(mTitleId, mMessageId, mWarningId) fun checkDatabase() {
} mDatabaseListeners.forEach { databaseListener ->
databaseListener.onDatabaseRetrieved(mDatabase)
} }
} }
@@ -175,6 +183,17 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
} }
} }
/**
* Force to call [ActionTaskListener.onStartAction] if the action is still running
*/
fun checkAction() {
if (mActionRunning) {
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStartAction(mDatabase, mTitleId, mMessageId, mWarningId)
}
}
}
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
super.onBind(intent) super.onBind(intent)
return mActionTaskBinder return mActionTaskBinder
@@ -184,6 +203,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
mDatabase = Database.getInstance() mDatabase = Database.getInstance()
mDatabaseListeners.forEach { listener ->
listener.onDatabaseRetrieved(mDatabase)
}
// Create the notification // Create the notification
buildMessage(intent) buildMessage(intent)
@@ -243,7 +265,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
}) })
mActionTaskListeners.forEach { actionTaskListener -> mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStartAction(mTitleId, mMessageId, mWarningId) actionTaskListener.onStartAction(mDatabase, mTitleId, mMessageId, mWarningId)
} }
}, },
@@ -253,7 +275,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
{ result -> { result ->
try { try {
mActionTaskListeners.forEach { actionTaskListener -> mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStopAction(intentAction!!, result) actionTaskListener.onStopAction(mDatabase, intentAction!!, result)
} }
} finally { } finally {
// Save the database info before performing action // Save the database info before performing action
@@ -443,7 +465,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
override fun updateMessage(resId: Int) { override fun updateMessage(resId: Int) {
mMessageId = resId mMessageId = resId
mActionTaskListeners.forEach { actionTaskListener -> mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onUpdateAction(mTitleId, mMessageId, mWarningId) actionTaskListener.onUpdateAction(mDatabase, mTitleId, mMessageId, mWarningId)
} }
} }

View File

@@ -1,16 +1,42 @@
package com.kunzisoft.keepass.settings package com.kunzisoft.keepass.settings
import android.content.Context import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.kunzisoft.keepass.activities.DatabaseRetrieval
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabasePreferenceFragment : PreferenceFragmentCompat() { abstract class DatabasePreferenceFragment : PreferenceFragmentCompat(), DatabaseRetrieval {
protected var mDatabase: Database? = null private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
override fun onAttach(context: Context) { override fun onViewCreated(view: View,
super.onAttach(context) savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mDatabase = Database.getInstance() mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), database)
onDatabaseRetrieved(database)
}
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
// Can be overridden by a subclass
}
protected fun saveDatabase(save: Boolean) {
mDatabaseViewModel.saveDatabase(save)
}
protected fun reloadDatabase() {
mDatabaseViewModel.reloadDatabase(false)
} }
} }

View File

@@ -23,11 +23,14 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.preference.Preference import androidx.preference.Preference
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
class MainPreferenceFragment : DatabasePreferenceFragment() { class MainPreferenceFragment : DatabasePreferenceFragment() {
private var mCallback: Callback? = null private var mCallback: Callback? = null
private var mDatabaseLoaded: Boolean = false
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
@@ -43,6 +46,10 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
super.onDetach() super.onDetach()
} }
override fun onDatabaseRetrieved(database: Database?) {
mDatabaseLoaded = database?.loaded == true
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey) setPreferencesFromResource(R.xml.preferences, rootKey)
@@ -80,7 +87,8 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
mCallback?.onNestedPreferenceSelected(NestedSettingsFragment.Screen.DATABASE) mCallback?.onNestedPreferenceSelected(NestedSettingsFragment.Screen.DATABASE)
false false
} }
if (mDatabase?.loaded != true) { // TODO Check
if (mDatabaseLoaded) {
isEnabled = false isEnabled = false
} }
} }

View File

@@ -43,7 +43,9 @@ import com.kunzisoft.keepass.activities.stylish.Stylish
import com.kunzisoft.keepass.app.database.CipherDatabaseAction import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.Education import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.icons.IconPackChooser import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.settings.preference.IconPackListPreference import com.kunzisoft.keepass.settings.preference.IconPackListPreference
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
@@ -54,6 +56,12 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
private var deleteKeysAlertDialog: AlertDialog? = null private var deleteKeysAlertDialog: AlertDialog? = null
private var mIconDrawableFactory: IconDrawableFactory? = null
override fun onDatabaseRetrieved(database: Database?) {
this.mIconDrawableFactory = database?.iconDrawableFactory
}
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) { override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
// Load the preferences from an XML resource // Load the preferences from an XML resource
@@ -426,8 +434,9 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
} }
if (iconPackEnabled) { if (iconPackEnabled) {
mDatabase?.let { // TODO Check
IconPackChooser.setSelectedIconPack(it.iconDrawableFactory, iconPackId) mIconDrawableFactory?.let {
IconPackChooser.setSelectedIconPack(it, iconPackId)
} }
} }
iconPackEnabled iconPackEnabled

View File

@@ -24,6 +24,7 @@ import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.* import android.view.*
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
@@ -33,6 +34,7 @@ import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
@@ -40,9 +42,11 @@ import com.kunzisoft.keepass.settings.preference.*
import com.kunzisoft.keepass.settings.preferencedialogfragment.* import com.kunzisoft.keepass.settings.preferencedialogfragment.*
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
class NestedDatabaseSettingsFragment : NestedSettingsFragment() { class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
private var mDatabase: Database? = null
private var mDatabaseReadOnly: Boolean = false private var mDatabaseReadOnly: Boolean = false
private var mDatabaseAutoSaveEnabled: Boolean = true private var mDatabaseAutoSaveEnabled: Boolean = true
@@ -61,6 +65,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
private var mMemoryPref: InputKdfSizePreference? = null private var mMemoryPref: InputKdfSizePreference? = null
private var mParallelismPref: InputKdfNumberPreference? = null private var mParallelismPref: InputKdfNumberPreference? = null
override fun onDatabaseRetrieved(database: Database?) {
mDatabase = database
}
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) { override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
setHasOptionsMenu(true) setHasOptionsMenu(true)
@@ -161,8 +169,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
database.enableRecycleBin(recycleBinEnabled, resources) database.enableRecycleBin(recycleBinEnabled, resources)
refreshRecycleBinGroup() refreshRecycleBinGroup()
// Save the database if not in readonly mode // Save the database if not in readonly mode
(context as SettingsActivity?)?. saveDatabase(mDatabaseAutoSaveEnabled)
mProgressDatabaseTaskProvider?.startDatabaseSave(mDatabaseAutoSaveEnabled)
true true
} }
true true
@@ -195,8 +202,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
database.enableTemplates(templatesEnabled, resources) database.enableTemplates(templatesEnabled, resources)
refreshTemplatesGroup() refreshTemplatesGroup()
// Save the database if not in readonly mode // Save the database if not in readonly mode
(context as SettingsActivity?)?. saveDatabase(mDatabaseAutoSaveEnabled)
mProgressDatabaseTaskProvider?.startDatabaseSave(mDatabaseAutoSaveEnabled)
true true
} }
true true
@@ -621,25 +627,20 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
val settingActivity = activity as SettingsActivity?
return when (item.itemId) { return when (item.itemId) {
R.id.menu_save_database -> { R.id.menu_save_database -> {
settingActivity?.mProgressDatabaseTaskProvider?.startDatabaseSave(!mDatabaseReadOnly) saveDatabase(!mDatabaseReadOnly)
true true
} }
R.id.menu_reload_database -> { R.id.menu_reload_database -> {
settingActivity?.apply { reloadDatabase()
keepCurrentScreen()
mProgressDatabaseTaskProvider?.startDatabaseReload(false)
}
return true return true
} }
else -> { else -> {
// Check the time lock before launching settings // Check the time lock before launching settings
settingActivity?.let { // TODO activity menu
(activity as SettingsActivity?)?.let {
MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true) MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true)
} }
super.onOptionsItemSelected(item) super.onOptionsItemSelected(item)

View File

@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.activities.dialogs.UnderDevelopmentFeatureDialogFra
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
// TODO Move database fragment in sub class
abstract class NestedSettingsFragment : DatabasePreferenceFragment() { abstract class NestedSettingsFragment : DatabasePreferenceFragment() {
enum class Screen { enum class Screen {

View File

@@ -22,7 +22,6 @@ package com.kunzisoft.keepass.settings
import android.app.Activity import android.app.Activity
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.MenuItem import android.view.MenuItem
@@ -33,7 +32,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
@@ -41,6 +39,7 @@ import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrCha
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.view.showActionErrorIfNeeded import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import org.joda.time.DateTime import org.joda.time.DateTime
@@ -49,8 +48,7 @@ import java.util.*
open class SettingsActivity open class SettingsActivity
: LockingActivity(), : LockingActivity(),
MainPreferenceFragment.Callback, MainPreferenceFragment.Callback,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener, AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
PasswordEncodingDialogFragment.Listener {
private var backupManager: BackupManager? = null private var backupManager: BackupManager? = null
private var mExternalFileHelper: ExternalFileHelper? = null private var mExternalFileHelper: ExternalFileHelper? = null
@@ -60,14 +58,6 @@ open class SettingsActivity
private var toolbar: Toolbar? = null private var toolbar: Toolbar? = null
private var lockView: View? = null private var lockView: View? = null
/**
* Retrieve the main fragment to show in first
* @return The main fragment
*/
protected open fun retrieveMainFragment(): Fragment {
return MainPreferenceFragment()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -90,9 +80,6 @@ open class SettingsActivity
lockAndExit() lockAndExit()
} }
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
if (savedInstanceState == null) { if (savedInstanceState == null) {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, retrieveMainFragment()) .add(R.id.fragment_container, retrieveMainFragment())
@@ -103,29 +90,6 @@ open class SettingsActivity
backupManager = BackupManager(this) backupManager = BackupManager(this)
mProgressDatabaseTaskProvider?.onActionFinish = { actionTask, result ->
when (actionTask) {
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
// Reload the current activity
if (result.isSuccess) {
startActivity(intent)
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
} else {
this.showActionErrorIfNeeded(result)
finish()
}
}
else -> {
// Call result in fragment
(supportFragmentManager
.findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
?.onProgressDialogThreadResult(actionTask, result)
}
}
coordinatorLayout?.showActionErrorIfNeeded(result)
}
// To reload the current screen // To reload the current screen
if (intent.extras?.containsKey(FRAGMENT_ARG) == true) { if (intent.extras?.containsKey(FRAGMENT_ARG) == true) {
intent.extras?.getString(FRAGMENT_ARG)?.let { fragmentScreenName -> intent.extras?.getString(FRAGMENT_ARG)?.let { fragmentScreenName ->
@@ -136,6 +100,49 @@ open class SettingsActivity
} }
} }
/**
* Retrieve the main fragment to show in first
* @return The main fragment
*/
protected open fun retrieveMainFragment(): Fragment {
return MainPreferenceFragment()
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, database)
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
DatabaseTaskNotificationService.ACTION_DATABASE_RELOAD_TASK -> {
// Reload the current activity
if (result.isSuccess) {
startActivity(intent)
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
} else {
this.showActionErrorIfNeeded(result)
finish()
}
}
else -> {
// Call result in fragment
(supportFragmentManager
.findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?)
?.onProgressDialogThreadResult(actionTask, result)
}
}
coordinatorLayout?.showActionErrorIfNeeded(result)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> onBackPressed() android.R.id.home -> onBackPressed()
@@ -149,31 +156,8 @@ open class SettingsActivity
super.onStop() super.onStop()
} }
override fun onPasswordEncodingValidateListener(databaseUri: Uri?,
mainCredential: MainCredential) {
databaseUri?.let {
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
databaseUri,
mainCredential
)
}
}
override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) { override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) {
mDatabase?.let { database -> assignPassword(mainCredential)
database.fileUri?.let { databaseUri ->
// Show the progress dialog now or after dialog confirmation
if (database.validatePasswordEncoding(mainCredential)) {
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
databaseUri,
mainCredential
)
} else {
PasswordEncodingDialogFragment.getInstance(databaseUri, mainCredential)
.show(supportFragmentManager, "passwordEncodingTag")
}
}
}
} }
override fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential) {} override fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential) {}
@@ -243,7 +227,7 @@ open class SettingsActivity
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) { override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
if (mTimeoutEnable) if (mTimeoutEnable)
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, mDatabase) { checkTimeAndLockIfTimeoutOrResetTimeout {
replaceFragment(key, reload) replaceFragment(key, reload)
} }
else else

View File

@@ -45,6 +45,7 @@ class IconPackListPreference @JvmOverloads constructor(context: Context,
setEntries(entries.toTypedArray()) setEntries(entries.toTypedArray())
entryValues = values.toTypedArray() entryValues = values.toTypedArray()
// TODO database
IconPackChooser.getSelectedIconPack(context, Database.getInstance().iconDrawableFactory)?.let { selectedIconPack -> IconPackChooser.getSelectedIconPack(context, Database.getInstance().iconDrawableFactory)?.let { selectedIconPack ->
setDefaultValue(selectedIconPack.id) setDefaultValue(selectedIconPack.id)
} }

View File

@@ -36,6 +36,7 @@ import com.kunzisoft.androidclearchroma.colormode.ColorMode
import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment
import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment.* import com.kunzisoft.androidclearchroma.fragment.ChromaColorFragment.*
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() { class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
@@ -88,7 +89,7 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog
} }
val oldColor = database.customColor val oldColor = database.customColor
database.customColor = newColor database.customColor = newColor
mProgressDatabaseTaskProvider?.startDatabaseSaveColor(oldColor, newColor, mDatabaseAutoSaveEnable) saveColor(oldColor, newColor)
} }
onDialogClosed(true) onDialogClosed(true)

View File

@@ -63,7 +63,7 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat
database.compressionAlgorithm = newCompression database.compressionAlgorithm = newCompression
if (oldCompression != null && newCompression != null) if (oldCompression != null && newCompression != null)
mProgressDatabaseTaskProvider?.startDatabaseSaveCompression(oldCompression, newCompression, mDatabaseAutoSaveEnable) saveCompression(oldCompression, newCompression)
} }
} }
} }

View File

@@ -36,7 +36,7 @@ class DatabaseDefaultUsernamePreferenceDialogFragmentCompat : DatabaseSavePrefer
val newDefaultUsername = inputText val newDefaultUsername = inputText
val oldDefaultUsername = database.defaultUsername val oldDefaultUsername = database.defaultUsername
database.defaultUsername = newDefaultUsername database.defaultUsername = newDefaultUsername
mProgressDatabaseTaskProvider?.startDatabaseSaveDefaultUsername(oldDefaultUsername, newDefaultUsername, mDatabaseAutoSaveEnable) saveDefaultUsername(oldDefaultUsername, newDefaultUsername)
} }
} }
} }

View File

@@ -36,7 +36,7 @@ class DatabaseDescriptionPreferenceDialogFragmentCompat : DatabaseSavePreference
val newDescription = inputText val newDescription = inputText
val oldDescription = database.description val oldDescription = database.description
database.description = newDescription database.description = newDescription
mProgressDatabaseTaskProvider?.startDatabaseSaveDescription(oldDescription, newDescription, mDatabaseAutoSaveEnable) saveDescription(oldDescription, newDescription)
} }
} }
} }

View File

@@ -63,7 +63,7 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
database.encryptionAlgorithm = newAlgorithm database.encryptionAlgorithm = newAlgorithm
if (oldAlgorithm != null && newAlgorithm != null) if (oldAlgorithm != null && newAlgorithm != null)
mProgressDatabaseTaskProvider?.startDatabaseSaveEncryption(oldAlgorithm, newAlgorithm, mDatabaseAutoSaveEnable) saveEncryption(oldAlgorithm, newAlgorithm)
} }
} }
} }

View File

@@ -65,7 +65,7 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat
val oldKdfEngine = database.kdfEngine val oldKdfEngine = database.kdfEngine
if (newKdfEngine != null && oldKdfEngine != null) { if (newKdfEngine != null && oldKdfEngine != null) {
database.kdfEngine = newKdfEngine database.kdfEngine = newKdfEngine
mProgressDatabaseTaskProvider?.startDatabaseSaveKeyDerivation(oldKdfEngine, newKdfEngine, mDatabaseAutoSaveEnable) saveKeyDerivation(oldKdfEngine, newKdfEngine)
} }
} }
} }

View File

@@ -36,7 +36,7 @@ class DatabaseNamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogF
val newName = inputText val newName = inputText
val oldName = database.name val oldName = database.name
database.name = newName database.name = newName
mProgressDatabaseTaskProvider?.startDatabaseSaveName(oldName, newName, mDatabaseAutoSaveEnable) saveName(oldName, newName)
} }
} }
} }

View File

@@ -62,11 +62,7 @@ class DatabaseRecycleBinGroupPreferenceDialogFragmentCompat
val oldGroup = database.recycleBin val oldGroup = database.recycleBin
val newGroup = mGroupRecycleBin val newGroup = mGroupRecycleBin
database.setRecycleBin(newGroup) database.setRecycleBin(newGroup)
mProgressDatabaseTaskProvider?.startDatabaseSaveRecycleBin( saveRecycleBin(oldGroup, newGroup)
oldGroup,
newGroup,
mDatabaseAutoSaveEnable
)
} }
} }
} }

View File

@@ -39,7 +39,7 @@ class DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat : DatabaseSavePre
override fun onDialogClosed(positiveResult: Boolean) { override fun onDialogClosed(positiveResult: Boolean) {
mDatabase?.let { _ -> mDatabase?.let { _ ->
if (positiveResult) { if (positiveResult) {
mProgressDatabaseTaskProvider?.startDatabaseRemoveUnlinkedData(mDatabaseAutoSaveEnable) removeUnlinkedData()
} }
} }
} }

View File

@@ -20,28 +20,123 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.activities.DatabaseRetrieval
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsActivity import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseSavePreferenceDialogFragmentCompat : InputPreferenceDialogFragmentCompat() { abstract class DatabaseSavePreferenceDialogFragmentCompat
: InputPreferenceDialogFragmentCompat(), DatabaseRetrieval {
protected var mDatabaseAutoSaveEnable = true private var mDatabaseAutoSaveEnable = true
protected var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
protected var mDatabase: Database? = null
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
// Attach dialog thread to start action
if (context is SettingsActivity) {
mProgressDatabaseTaskProvider = context.mProgressDatabaseTaskProvider
}
this.mDatabaseAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(context) this.mDatabaseAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(context)
} }
override fun onDetach() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mProgressDatabaseTaskProvider = null super.onViewCreated(view, savedInstanceState)
super.onDetach()
mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
onDatabaseRetrieved(database)
}
}
override fun onDatabaseRetrieved(database: Database?) {
this.mDatabase = database
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
// Optional
}
protected fun saveColor(oldColor: String,
newColor: String) {
mDatabaseViewModel.saveColor(oldColor, newColor, mDatabaseAutoSaveEnable)
}
protected fun saveCompression(oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm) {
mDatabaseViewModel.saveCompression(oldCompression, newCompression, mDatabaseAutoSaveEnable)
}
protected fun saveDefaultUsername(oldUsername: String,
newUsername: String) {
mDatabaseViewModel.saveDefaultUsername(oldUsername, newUsername, mDatabaseAutoSaveEnable)
}
protected fun saveDescription(oldDescription: String,
newDescription: String) {
mDatabaseViewModel.saveDescription(oldDescription, newDescription, mDatabaseAutoSaveEnable)
}
protected fun saveEncryption(oldEncryption: EncryptionAlgorithm,
newEncryptionAlgorithm: EncryptionAlgorithm) {
mDatabaseViewModel.saveEncryption(oldEncryption, newEncryptionAlgorithm, mDatabaseAutoSaveEnable)
}
protected fun saveKeyDerivation(oldKeyDerivation: KdfEngine,
newKeyDerivation: KdfEngine) {
mDatabaseViewModel.saveKeyDerivation(oldKeyDerivation, newKeyDerivation, mDatabaseAutoSaveEnable)
}
protected fun saveName(oldName: String,
newName: String) {
mDatabaseViewModel.saveName(oldName, newName, mDatabaseAutoSaveEnable)
}
protected fun saveRecycleBin(oldGroup: Group?,
newGroup: Group?) {
mDatabaseViewModel.saveRecycleBin(oldGroup, newGroup, mDatabaseAutoSaveEnable)
}
protected fun removeUnlinkedData() {
mDatabaseViewModel.removeUnlinkedData(mDatabaseAutoSaveEnable)
}
protected fun saveTemplatesGroup(oldGroup: Group?,
newGroup: Group?) {
mDatabaseViewModel.saveTemplatesGroup(oldGroup, newGroup, mDatabaseAutoSaveEnable)
}
protected fun saveMaxHistoryItems(oldNumber: Int,
newNumber: Int) {
mDatabaseViewModel.saveMaxHistoryItems(oldNumber, newNumber, mDatabaseAutoSaveEnable)
}
protected fun saveMaxHistorySize(oldNumber: Long,
newNumber: Long) {
mDatabaseViewModel.saveMaxHistorySize(oldNumber, newNumber, mDatabaseAutoSaveEnable)
}
protected fun saveMemoryUsage(oldNumber: Long,
newNumber: Long) {
mDatabaseViewModel.saveMemoryUsage(oldNumber, newNumber, mDatabaseAutoSaveEnable)
}
protected fun saveParallelism(oldNumber: Long,
newNumber: Long) {
mDatabaseViewModel.saveParallelism(oldNumber, newNumber, mDatabaseAutoSaveEnable)
}
protected fun saveIterations(oldNumber: Long,
newNumber: Long) {
mDatabaseViewModel.saveIterations(oldNumber, newNumber, mDatabaseAutoSaveEnable)
} }
companion object { companion object {

View File

@@ -62,11 +62,7 @@ class DatabaseTemplatesGroupPreferenceDialogFragmentCompat
val oldGroup = database.templatesGroup val oldGroup = database.templatesGroup
val newGroup = mGroupTemplates val newGroup = mGroupTemplates
database.setTemplatesGroup(newGroup) database.setTemplatesGroup(newGroup)
mProgressDatabaseTaskProvider?.startDatabaseSaveTemplatesGroup( saveTemplatesGroup(oldGroup, newGroup)
oldGroup,
newGroup,
mDatabaseAutoSaveEnable
)
} }
} }
} }

View File

@@ -19,7 +19,6 @@
*/ */
package com.kunzisoft.keepass.settings.preferencedialogfragment package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
@@ -30,7 +29,6 @@ import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceDialogFragmentCompat import androidx.preference.PreferenceDialogFragmentCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() { abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
@@ -41,14 +39,6 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
private var mOnInputTextEditorActionListener: TextView.OnEditorActionListener? = null private var mOnInputTextEditorActionListener: TextView.OnEditorActionListener? = null
protected var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabase = Database.getInstance()
}
var inputText: String var inputText: String
get() = this.inputTextView?.text?.toString() ?: "" get() = this.inputTextView?.text?.toString() ?: ""
set(inputText) { set(inputText) {

View File

@@ -60,7 +60,7 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial
// Remove all history items // Remove all history items
database.removeOldestHistoryForEachEntry() database.removeOldestHistoryForEachEntry()
mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems, maxHistoryItems, mDatabaseAutoSaveEnable) saveMaxHistoryItems(oldMaxHistoryItems, maxHistoryItems)
} }
} }
} }

View File

@@ -80,7 +80,7 @@ class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialo
val oldMaxHistorySize = database.historyMaxSize val oldMaxHistorySize = database.historyMaxSize
database.historyMaxSize = numberOfBytes database.historyMaxSize = numberOfBytes
mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistorySize(oldMaxHistorySize, numberOfBytes, mDatabaseAutoSaveEnable) saveMaxHistorySize(oldMaxHistorySize, numberOfBytes)
} }
} }
} }

View File

@@ -61,7 +61,7 @@ class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
val oldMemoryUsage = database.memoryUsage val oldMemoryUsage = database.memoryUsage
database.memoryUsage = numberOfBytes database.memoryUsage = numberOfBytes
mProgressDatabaseTaskProvider?.startDatabaseSaveMemoryUsage(oldMemoryUsage, numberOfBytes, mDatabaseAutoSaveEnable) saveMemoryUsage(oldMemoryUsage, numberOfBytes)
} }
} }
} }

View File

@@ -44,10 +44,7 @@ class ParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
val oldParallelism = database.parallelism val oldParallelism = database.parallelism
database.parallelism = parallelism database.parallelism = parallelism
mProgressDatabaseTaskProvider?.startDatabaseSaveParallelism( saveParallelism(oldParallelism, parallelism)
oldParallelism,
parallelism,
mDatabaseAutoSaveEnable)
} }
} }
} }

View File

@@ -54,7 +54,7 @@ class RoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmen
database.numberKeyEncryptionRounds = Long.MAX_VALUE database.numberKeyEncryptionRounds = Long.MAX_VALUE
} }
mProgressDatabaseTaskProvider?.startDatabaseSaveIterations(oldRounds, rounds, mDatabaseAutoSaveEnable) saveIterations(oldRounds, rounds)
} }
} }
} }

View File

@@ -138,5 +138,5 @@ fun Context.closeDatabase() {
cancelAll() cancelAll()
} }
// Clear data // Clear data
Database.getInstance().clearAndClose(this) // TODO Database.getInstance().clearAndClose(this)
} }

View File

@@ -0,0 +1,178 @@
package com.kunzisoft.keepass.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
class DatabaseViewModel: ViewModel() {
val database : LiveData<Database?> get() = _database
private val _database = SingleLiveEvent<Database?>()
val saveDatabase : LiveData<Boolean> get() = _saveDatabase
private val _saveDatabase = SingleLiveEvent<Boolean>()
val reloadDatabase : LiveData<Boolean> get() = _reloadDatabase
private val _reloadDatabase = SingleLiveEvent<Boolean>()
val saveName : LiveData<SuperString> get() = _saveName
private val _saveName = SingleLiveEvent<SuperString>()
val saveDescription : LiveData<SuperString> get() = _saveDescription
private val _saveDescription = SingleLiveEvent<SuperString>()
val saveDefaultUsername : LiveData<SuperString> get() = _saveDefaultUsername
private val _saveDefaultUsername = SingleLiveEvent<SuperString>()
val saveColor : LiveData<SuperString> get() = _saveColor
private val _saveColor = SingleLiveEvent<SuperString>()
val saveCompression : LiveData<SuperCompression> get() = _saveCompression
private val _saveCompression = SingleLiveEvent<SuperCompression>()
val removeUnlinkData : LiveData<Boolean> get() = _removeUnlinkData
private val _removeUnlinkData = SingleLiveEvent<Boolean>()
val saveRecycleBin : LiveData<SuperGroup> get() = _saveRecycleBin
private val _saveRecycleBin = SingleLiveEvent<SuperGroup>()
val saveTemplatesGroup : LiveData<SuperGroup> get() = _saveTemplatesGroup
private val _saveTemplatesGroup = SingleLiveEvent<SuperGroup>()
val saveMaxHistoryItems : LiveData<SuperInt> get() = _saveMaxHistoryItems
private val _saveMaxHistoryItems = SingleLiveEvent<SuperInt>()
val saveMaxHistorySize : LiveData<SuperLong> get() = _saveMaxHistorySize
private val _saveMaxHistorySize = SingleLiveEvent<SuperLong>()
val saveEncryption : LiveData<SuperEncryption> get() = _saveEncryption
private val _saveEncryption = SingleLiveEvent<SuperEncryption>()
val saveKeyDerivation : LiveData<SuperKeyDerivation> get() = _saveKeyDerivation
private val _saveKeyDerivation = SingleLiveEvent<SuperKeyDerivation>()
val saveIterations : LiveData<SuperLong> get() = _saveIterations
private val _saveIterations = SingleLiveEvent<SuperLong>()
val saveMemoryUsage : LiveData<SuperLong> get() = _saveMemoryUsage
private val _saveMemoryUsage = SingleLiveEvent<SuperLong>()
val saveParallelism : LiveData<SuperLong> get() = _saveParallelism
private val _saveParallelism = SingleLiveEvent<SuperLong>()
fun defineDatabase(database: Database?) {
this._database.value = database
}
fun saveDatabase(save: Boolean) {
_saveDatabase.value = save
}
fun reloadDatabase(fixDuplicateUuid: Boolean) {
_reloadDatabase.value = fixDuplicateUuid
}
fun saveName(oldValue: String,
newValue: String,
save: Boolean) {
_saveName.value = SuperString(oldValue, newValue, save)
}
fun saveDescription(oldValue: String,
newValue: String,
save: Boolean) {
_saveDescription.value = SuperString(oldValue, newValue, save)
}
fun saveDefaultUsername(oldValue: String,
newValue: String,
save: Boolean) {
_saveDefaultUsername.value = SuperString(oldValue, newValue, save)
}
fun saveColor(oldValue: String,
newValue: String,
save: Boolean) {
_saveColor.value = SuperString(oldValue, newValue, save)
}
fun saveCompression(oldValue: CompressionAlgorithm,
newValue: CompressionAlgorithm,
save: Boolean) {
_saveCompression.value = SuperCompression(oldValue, newValue, save)
}
fun removeUnlinkedData(save: Boolean) {
_removeUnlinkData.value = save
}
fun saveRecycleBin(oldValue: Group?,
newValue: Group?,
save: Boolean) {
_saveRecycleBin.value = SuperGroup(oldValue, newValue, save)
}
fun saveTemplatesGroup(oldValue: Group?,
newValue: Group?,
save: Boolean) {
_saveTemplatesGroup.value = SuperGroup(oldValue, newValue, save)
}
fun saveMaxHistoryItems(oldValue: Int,
newValue: Int,
save: Boolean) {
_saveMaxHistoryItems.value = SuperInt(oldValue, newValue, save)
}
fun saveMaxHistorySize(oldValue: Long,
newValue: Long,
save: Boolean) {
_saveMaxHistorySize.value = SuperLong(oldValue, newValue, save)
}
fun saveEncryption(oldValue: EncryptionAlgorithm,
newValue: EncryptionAlgorithm,
save: Boolean) {
_saveEncryption.value = SuperEncryption(oldValue, newValue, save)
}
fun saveKeyDerivation(oldValue: KdfEngine,
newValue: KdfEngine,
save: Boolean) {
_saveKeyDerivation.value = SuperKeyDerivation(oldValue, newValue, save)
}
fun saveIterations(oldValue: Long,
newValue: Long,
save: Boolean) {
_saveIterations.value = SuperLong(oldValue, newValue, save)
}
fun saveMemoryUsage(oldValue: Long,
newValue: Long,
save: Boolean) {
_saveMemoryUsage.value = SuperLong(oldValue, newValue, save)
}
fun saveParallelism(oldValue: Long,
newValue: Long,
save: Boolean) {
_saveParallelism.value = SuperLong(oldValue, newValue, save)
}
data class SuperString(val oldValue: String, val newValue: String, val save: Boolean)
data class SuperInt(val oldValue: Int, val newValue: Int, val save: Boolean)
data class SuperLong(val oldValue: Long, val newValue: Long, val save: Boolean)
data class SuperCompression(val oldValue: CompressionAlgorithm, val newValue: CompressionAlgorithm, val save: Boolean)
data class SuperEncryption(val oldValue: EncryptionAlgorithm, val newValue: EncryptionAlgorithm, val save: Boolean)
data class SuperKeyDerivation(val oldValue: KdfEngine, val newValue: KdfEngine, val save: Boolean)
data class SuperGroup(val oldValue: Group?, val newValue: Group?, val save: Boolean)
}

View File

@@ -19,10 +19,9 @@ import java.util.*
class EntryEditViewModel: ViewModel() { class EntryEditViewModel: ViewModel() {
private val mDatabase: Database? = Database.getInstance() private var mDatabase: Database? = null
private var mParent: Group? = null
private var mParent : Group? = null private var mEntry: Entry? = null
private var mEntry : Entry? = null
private var mIsTemplate: Boolean = false private var mIsTemplate: Boolean = false
private val mTempAttachments = mutableListOf<EntryAttachmentState>() private val mTempAttachments = mutableListOf<EntryAttachmentState>()
@@ -80,6 +79,10 @@ class EntryEditViewModel: ViewModel() {
val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded
private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>() private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>()
fun setDatabase(database: Database?) {
this.mDatabase = database
}
fun initializeEntryToUpdate(entryId: NodeId<UUID>, fun initializeEntryToUpdate(entryId: NodeId<UUID>,
registerInfo: RegisterInfo?, registerInfo: RegisterInfo?,
searchInfo: SearchInfo?) { searchInfo: SearchInfo?) {
@@ -166,7 +169,7 @@ class EntryEditViewModel: ViewModel() {
} }
} }
// Set default username // Set default username
username = mDatabase.defaultUsername username = mDatabase?.defaultUsername ?: ""
// Warning only the entry recognize is parent, parent don't yet recognize the new entry // Warning only the entry recognize is parent, parent don't yet recognize the new entry
// Useful to recognize child state (ie: entry is a template) // Useful to recognize child state (ie: entry is a template)
parent = parentGroup parent = parentGroup
@@ -246,7 +249,7 @@ class EntryEditViewModel: ViewModel() {
val tempAttachment = tempAttachmentState.attachment val tempAttachment = tempAttachmentState.attachment
mDatabase?.attachmentPool?.let { binaryPool -> mDatabase?.attachmentPool?.let { binaryPool ->
if (!newEntry.getAttachments(binaryPool).contains(tempAttachment)) { if (!newEntry.getAttachments(binaryPool).contains(tempAttachment)) {
mDatabase.removeAttachmentIfNotUsed(tempAttachment) mDatabase?.removeAttachmentIfNotUsed(tempAttachment)
} }
} }
} }

View File

@@ -18,8 +18,7 @@ import java.util.*
class EntryViewModel: ViewModel() { class EntryViewModel: ViewModel() {
private val mDatabase: Database? = Database.getInstance() private var mDatabase: Database? = null
private var mEntryTemplate: Template? = null private var mEntryTemplate: Template? = null
private var mEntry: Entry? = null private var mEntry: Entry? = null
private var mLastEntryVersion: Entry? = null private var mLastEntryVersion: Entry? = null
@@ -48,59 +47,69 @@ class EntryViewModel: ViewModel() {
val historySelected : LiveData<EntryHistory> get() = _historySelected val historySelected : LiveData<EntryHistory> get() = _historySelected
private val _historySelected = SingleLiveEvent<EntryHistory>() private val _historySelected = SingleLiveEvent<EntryHistory>()
fun loadEntry(entryId: NodeId<UUID>, historyPosition: Int) { fun setDatabase(database: Database?) {
IOActionTask( mDatabase = database
{ }
// Manage current version and history
mLastEntryVersion = mDatabase?.getEntryById(entryId)
mEntry = if (historyPosition > -1) { fun loadEntry(entryId: NodeId<UUID>?, historyPosition: Int) {
mLastEntryVersion?.getHistory()?.get(historyPosition) if (entryId != null) {
} else { IOActionTask(
mLastEntryVersion {
} // Manage current version and history
mLastEntryVersion = mDatabase?.getEntryById(entryId)
mEntryTemplate = mEntry?.let { mEntry = if (historyPosition > -1) {
mDatabase?.getTemplate(it) mLastEntryVersion?.getHistory()?.get(historyPosition)
} ?: Template.STANDARD } else {
mLastEntryVersion
}
mHistoryPosition = historyPosition mEntryTemplate = mEntry?.let {
mDatabase?.getTemplate(it)
} ?: Template.STANDARD
// To simplify template field visibility mHistoryPosition = historyPosition
mEntry?.let { entry ->
// Add mLastEntryVersion to check the parent and define the template state
mDatabase?.decodeEntryWithTemplateConfiguration(entry, mLastEntryVersion)?.let {
// To update current modification time
it.touch(modified = false, touchParents = false)
// Build history info // To simplify template field visibility
val entryInfoHistory = it.getHistory().map { entryHistory -> mEntry?.let { entry ->
entryHistory.getEntryInfo(mDatabase) // Add mLastEntryVersion to check the parent and define the template state
} mDatabase?.decodeEntryWithTemplateConfiguration(entry, mLastEntryVersion)
?.let {
// To update current modification time
it.touch(modified = false, touchParents = false)
EntryInfoHistory( // Build history info
mEntryTemplate ?: Template.STANDARD, val entryInfoHistory = it.getHistory().map { entryHistory ->
it.getEntryInfo(mDatabase), entryHistory.getEntryInfo(mDatabase)
entryInfoHistory }
)
EntryInfoHistory(
mEntryTemplate ?: Template.STANDARD,
it.getEntryInfo(mDatabase),
entryInfoHistory
)
}
}
},
{ entryInfoHistory ->
if (entryInfoHistory != null) {
_template.value = entryInfoHistory.template
_entryInfo.value = entryInfoHistory.entryInfo
_entryIsHistory.value = mHistoryPosition != -1
_entryHistory.value = entryInfoHistory.entryHistory
} }
} }
}, ).execute()
{ entryInfoHistory -> } else {
if (entryInfoHistory != null) { mEntryTemplate = null
_template.value = entryInfoHistory.template mEntry = null
_entryInfo.value = entryInfoHistory.entryInfo mLastEntryVersion = null
_entryIsHistory.value = mHistoryPosition != -1 mHistoryPosition = -1
_entryHistory.value = entryInfoHistory.entryHistory }
}
}
).execute()
} }
fun updateEntry() { fun updateEntry() {
mEntry?.nodeId?.let { nodeId -> loadEntry(mEntry?.nodeId, mHistoryPosition)
loadEntry(nodeId, mHistoryPosition)
}
} }
// TODO Remove // TODO Remove

View File

@@ -0,0 +1,70 @@
package com.kunzisoft.keepass.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
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.NodeId
class GroupViewModel: ViewModel() {
private var mDatabase: Database? = null
val group : LiveData<SuperGroup> get() = _group
private val _group = MutableLiveData<SuperGroup>()
val onGroupSelected : LiveData<SuperGroup> get() = _onGroupSelected
private val _onGroupSelected = SingleLiveEvent<SuperGroup>()
fun setDatabase(database: Database?) {
this.mDatabase = database
}
fun loadGroup(groupId: NodeId<*>?) {
IOActionTask(
{
if (groupId != null) {
mDatabase?.getGroupById(groupId)
} else {
mDatabase?.rootGroup
}
},
{ group ->
if (group != null) {
_group.value = SuperGroup(group, mDatabase?.recycleBin == group)
}
}
).execute()
}
fun loadGroup(group: Group) {
_group.value = SuperGroup(group, mDatabase?.recycleBin == group)
}
fun loadGroupFromSearch(searchQuery: String,
omitBackup: Boolean) {
IOActionTask(
{
mDatabase?.createVirtualGroupFromSearch(searchQuery, omitBackup)
},
{ group ->
if (group != null) {
_group.value = SuperGroup(group, mDatabase?.recycleBin == group)
}
}
).execute()
}
fun selectGroup(group: Group) {
_onGroupSelected.value = SuperGroup(group, mDatabase?.recycleBin == group)
}
data class SuperGroup(val group: Group, val isRecycleBin: Boolean)
companion object {
private val TAG = GroupViewModel::class.java.name
}
}