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.IntentSender
import android.os.Build
import android.os.Bundle
import android.view.inputmethod.InlineSuggestionsRequest
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
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.EXTRA_INLINE_SUGGESTIONS_REQUEST
import com.kunzisoft.keepass.autofill.KeeAutofillService
@@ -44,14 +43,14 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.LOCK_ACTION
@RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : AppCompatActivity() {
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
mDatabase = Database.getInstance()
class AutofillLauncherActivity : DatabaseActivity() {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
// End activity if database not loaded
if (database?.loaded != true) {
finish()
}
// Retrieve selection mode
EntrySelectionHelper.retrieveSpecialModeFromIntent(intent).let { specialMode ->
when (specialMode) {
@@ -64,7 +63,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
}
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
mDatabase?.let { database ->
database?.let { database ->
launchSelection(database, searchInfo)
}
}
@@ -75,7 +74,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
val searchInfo = SearchInfo(registerInfo?.searchInfo)
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
mDatabase?.let { database ->
database?.let { database ->
launchRegistration(database, searchInfo, registerInfo)
}
}
@@ -87,8 +86,6 @@ class AutofillLauncherActivity : AppCompatActivity() {
}
}
}
super.onCreate(savedInstanceState)
}
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.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R
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.resetAppTimeoutWhenViewFocusedOrChanged
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.node.NodeId
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_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.*
@@ -79,6 +80,9 @@ class EntryActivity : LockingActivity() {
private val mEntryViewModel: EntryViewModel by viewModels()
private var mEntryId: NodeId<UUID>? = null
private var mHistoryPosition: Int = -1
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
private var mExternalFileHelper: ExternalFileHelper? = null
@@ -108,23 +112,18 @@ class EntryActivity : LockingActivity() {
collapsingToolbarLayout?.title = " "
toolbar?.title = " "
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
// Retrieve the textColor to tint the icon
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
iconColor = taIconColor.getColor(0, Color.BLACK)
taIconColor.recycle()
mReadOnly = mDatabase?.isReadOnly != false || mReadOnly
// Get Entry from UUID
try {
intent.getParcelableExtra<NodeId<UUID>?>(KEY_ENTRY)?.let { entryId ->
mEntryId = entryId
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)
mEntryViewModel.loadEntry(entryId, historyPosition)
}
} catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key")
@@ -152,7 +151,7 @@ class EntryActivity : LockingActivity() {
// Assign title icon
titleIconView?.let { iconView ->
mDatabase?.iconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, iconColor)
mIconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, iconColor)
}
// Assign title text
@@ -209,23 +208,36 @@ class EntryActivity : LockingActivity() {
historySelected.historyPosition,
mReadOnly)
}
}
mProgressDatabaseTaskProvider?.onActionFinish = { 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()
}
ACTION_DATABASE_RELOAD_TASK -> {
// Close the current activity
this.showActionErrorIfNeeded(result)
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, database)
mEntryViewModel.setDatabase(database)
mEntryViewModel.loadEntry(mEntryId, mHistoryPosition)
}
override fun onDatabaseActionFinished(
database: Database,
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()
}
}
coordinatorLayout?.showActionErrorIfNeeded(result)
ACTION_DATABASE_RELOAD_TASK -> {
// Close the current activity
this.showActionErrorIfNeeded(result)
finish()
}
}
coordinatorLayout?.showActionErrorIfNeeded(result)
}
override fun onResume() {
@@ -356,25 +368,23 @@ class EntryActivity : LockingActivity() {
}
R.id.menu_restore_entry_history -> {
mEntryViewModel.getMainEntry()?.let { mainEntry ->
mProgressDatabaseTaskProvider?.startDatabaseRestoreEntryHistory(
restoreEntryHistory(
mainEntry,
mEntryViewModel.getEntryHistoryPosition(),
!mReadOnly && mAutoSaveEnable)
mEntryViewModel.getEntryHistoryPosition())
}
}
R.id.menu_delete_entry_history -> {
mEntryViewModel.getMainEntry()?.let { mainEntry ->
mProgressDatabaseTaskProvider?.startDatabaseDeleteEntryHistory(
deleteEntryHistory(
mainEntry,
mEntryViewModel.getEntryHistoryPosition(),
!mReadOnly && mAutoSaveEnable)
mEntryViewModel.getEntryHistoryPosition())
}
}
R.id.menu_save_database -> {
mProgressDatabaseTaskProvider?.startDatabaseSave(!mReadOnly)
saveDatabase()
}
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)
}

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

View File

@@ -27,6 +27,8 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.R
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.search.SearchHelper
import com.kunzisoft.keepass.magikeyboard.MagikIME
@@ -39,14 +41,10 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
* Activity to search or select entry in database,
* Commonly used with Magikeyboard
*/
class EntrySelectionLauncherActivity : AppCompatActivity() {
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
mDatabase = Database.getInstance()
class EntrySelectionLauncherActivity : DatabaseActivity() {
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
var sharedWebDomain: String? = null
var otpString: String? = null
@@ -72,20 +70,22 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
else -> {}
}
// Build domain search param
val searchInfo = SearchInfo().apply {
this.webDomain = sharedWebDomain
this.otpString = otpString
}
// End activity if database not loaded
if (database?.loaded != true) {
finish()
}
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
mDatabase?.let { database ->
database?.let { database ->
launch(database, searchInfo)
}
}
super.onCreate(savedInstanceState)
}
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.autofill.AutofillComponent
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
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.DATABASE_URI_KEY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.viewmodels.DatabaseFilesViewModel
@@ -85,15 +85,9 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private var mExternalFileHelper: ExternalFileHelper? = null
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabase = Database.getInstance()
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
setContentView(R.layout.activity_file_selection)
@@ -195,36 +189,52 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
mAdapterDatabaseHistory?.setDefaultDatabase(it)
}
// Attach the dialog thread to this activity
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply {
onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
result.data?.getParcelable<Uri?>(DATABASE_URI_KEY)?.let { databaseUri ->
val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential()
databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri)
}
GroupActivity.launch(this@FileDatabaseSelectActivity,
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity))
}
ACTION_DATABASE_LOAD_TASK -> {
val database = mDatabase
if (result.isSuccess
&& database?.loaded == true) {
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()
}
// Construct adapter with listeners
if (PreferencesUtil.showRecentFiles(this@FileDatabaseSelectActivity)) {
databaseFilesViewModel.loadListOfDatabases()
} else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
if (database?.loaded == true) {
launchGroupActivity(database)
}
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
result.data?.getParcelable<Uri?>(DATABASE_URI_KEY)?.let { databaseUri ->
val mainCredential = result.data?.getParcelable(DatabaseTaskNotificationService.MAIN_CREDENTIAL_KEY) ?: MainCredential()
databaseFilesViewModel.addDatabaseFile(databaseUri, mainCredential.keyFileUri)
}
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
}
}
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) {
@@ -334,15 +321,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
}
override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) {
try {
mDatabaseFileUri?.let { databaseUri ->
// Create the new database
mProgressDatabaseTaskProvider?.startDatabaseCreate(
databaseUri,
mainCredential
)
createDatabase(databaseUri, mainCredential)
}
} catch (e: Exception) {
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 mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -84,11 +86,6 @@ class IconPickerActivity : LockingActivity() {
mExternalFileHelper = ExternalFileHelper(this)
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?.setOnClickListener {
@@ -114,9 +111,6 @@ class IconPickerActivity : LockingActivity() {
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 ->
mIconImage.standard = iconStandard
// 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() {
if (mIconsSelected.isEmpty()) {
mCustomIconsSelectionMode = false

View File

@@ -33,11 +33,15 @@ import com.igreenwood.loupe.Loupe
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.BinaryDatabaseManager
import kotlin.math.max
class ImageViewerActivity : LockingActivity() {
private lateinit var imageView: ImageView
private lateinit var progressView: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -49,43 +53,8 @@ class ImageViewerActivity : LockingActivity() {
supportActionBar?.setDisplayShowHomeEnabled(true)
val imageContainerView: ViewGroup = findViewById(R.id.image_viewer_container)
val imageView: ImageView = findViewById(R.id.image_viewer_image)
val progressView: View = 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()
}
imageView = findViewById(R.id.image_viewer_image)
progressView = findViewById(R.id.image_viewer_progress)
Loupe.create(imageView, imageContainerView) {
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 {
when (item.itemId) {
android.R.id.home -> finish()

View File

@@ -19,22 +19,20 @@
*/
package com.kunzisoft.keepass.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.activities.selection.DatabaseActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.search.SearchHelper
/**
* Activity to select entry in database and populate it in Magikeyboard
*/
class MagikeyboardLauncherActivity : AppCompatActivity() {
class MagikeyboardLauncherActivity : DatabaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val database = Database.getInstance()
val readOnly = database.isReadOnly
SearchHelper.checkAutoSearchInfo(this,
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
database?.let {
val readOnly = database.isReadOnly
SearchHelper.checkAutoSearchInfo(this,
database,
null,
{
@@ -49,8 +47,8 @@ class MagikeyboardLauncherActivity : AppCompatActivity() {
// Pass extra to get entry
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this)
}
)
)
}
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.AutofillHelper
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.exception.DuplicateUuidDatabaseException
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.READ_ONLY_KEY
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.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
@@ -87,8 +87,6 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
private val databaseFileViewModel: DatabaseFileViewModel by viewModels()
private var mDatabase: Database? = null
private var mDefaultDatabase: Boolean = false
private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null
@@ -109,15 +107,11 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
field = value
}
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var mAllowAutoOpenBiometricPrompt: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mDatabase = Database.getInstance()
setContentView(R.layout.activity_password)
toolbar = findViewById(R.id.toolbar)
@@ -208,74 +202,107 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri)
}
}
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply {
onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck advanced unlock if error
advancedUnlockFragment?.initAdvancedUnlockMode()
override fun onResume() {
super.onResume()
if (result.isSuccess) {
mDatabaseKeyFileUri = null
clearCredentialsViews(true)
mDatabase?.let { database ->
launchGroupActivity(database)
}
} else {
var resultError = ""
val resultException = result.exception
val resultMessage = result.message
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this@PasswordActivity)
if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources)
// Back to previous keyboard is setting activated
if (PreferencesUtil.isKeyboardPreviousDatabaseCredentialsEnable(this@PasswordActivity)) {
sendBroadcast(Intent(BACK_PREVIOUS_KEYBOARD_ACTION))
}
when (resultException) {
is DuplicateUuidDatabaseException -> {
// Relaunch loading if we need to fix UUID
showLoadDatabaseDuplicateUuidMessage {
// 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)
}
var databaseUri: Uri? = null
var mainCredential: MainCredential = MainCredential()
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
checkPermission()
}
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)
}
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
if (database?.loaded == true) {
// If the database isn't accessible make sure to clear the password field, if it
// was saved in the instance state
clearCredentialsViews()
launchGroupActivity(database)
}
}
databaseUri?.let { databaseFileUri ->
showProgressDialogAndLoadDatabase(
databaseFileUri,
mainCredential,
readOnly,
cipherEntity,
true)
}
}
override fun onDatabaseActionFinished(
database: Database,
actionTask: String,
result: ActionRunnable.Result
) {
super.onDatabaseActionFinished(database, actionTask, result)
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
if (mDefaultDatabase) {
databaseFileViewModel.removeDefaultDatabase()
}
databaseUri?.let { databaseFileUri ->
showProgressDialogAndLoadDatabase(
databaseFileUri,
mainCredential,
readOnly,
cipherEntity,
true)
}
}
}
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
is FileNotFoundDatabaseException -> {
// Remove this default database inaccessible
if (mDefaultDatabase) {
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?) {
// Define Key File text
if (mRememberKeyFile) {
@@ -417,8 +409,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
} else {
// Init Biometric elements
advancedUnlockFragment?.loadDatabase(databaseFileUri,
mAllowAutoOpenBiometricPrompt
&& mProgressDatabaseTaskProvider?.isBinded() != true)
mAllowAutoOpenBiometricPrompt)
// TODO && mDatabaseTaskProvider?.isBinded() != true)
}
enableOrNotTheConfirmationButton()
@@ -468,8 +460,6 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
}
override fun onPause() {
mProgressDatabaseTaskProvider?.unregisterProgressTask()
// Reinit locking activity UI variable
LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
mAllowAutoOpenBiometricPrompt = true
@@ -546,7 +536,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?,
fixDuplicateUUID: Boolean) {
mProgressDatabaseTaskProvider?.startDatabaseLoad(
loadDatabase(
databaseUri,
mainCredential,
readOnly,
@@ -716,7 +706,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK -> {
clearCredentialsViews()
mDatabase?.clearAndClose(this)
// TODO Database
Database.getInstance().clearAndClose(this)
}
Activity.RESULT_CANCELED -> {
clearCredentialsViews()

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
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.template.TemplateField
import com.kunzisoft.keepass.model.EntryAttachmentState
@@ -63,8 +64,6 @@ class EntryFragment: DatabaseFragment() {
context?.let { context ->
mClipboardHelper = ClipboardHelper(context)
attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
attachmentsAdapter?.database = mDatabase
}
rootView = view
@@ -75,14 +74,6 @@ class EntryFragment: DatabaseFragment() {
templateView = view.findViewById(R.id.entry_template)
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)
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() {
context?.let { context ->
templateView.setFirstTimeAskAllowCopyProtectedFields(PreferencesUtil.isFirstTimeAskAllowCopyProtectedFields(context))

View File

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

View File

@@ -29,6 +29,7 @@ import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
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.viewmodels.IconPickerViewModel
import kotlinx.coroutines.CoroutineScope
@@ -47,31 +48,7 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
abstract fun retrieveMainLayoutId(): Int
abstract fun defineIconList()
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()
}
}
}
abstract fun defineIconList(database: Database?)
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
@@ -88,6 +65,28 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
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() {
iconActionSelectionMode = false
}

View File

@@ -10,6 +10,7 @@ import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.IconPickerPagerAdapter
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
class IconPickerFragment : DatabaseFragment() {
@@ -28,17 +29,7 @@ class IconPickerFragment : DatabaseFragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewPager = view.findViewById(R.id.icon_picker_pager)
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()
super.onViewCreated(view, savedInstanceState)
arguments?.apply {
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 {
STANDARD, CUSTOM
}

View File

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

View File

@@ -25,6 +25,7 @@ import android.os.Bundle
import android.util.Log
import android.view.*
import androidx.appcompat.view.ActionMode
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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.SpecialMode
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.SortNodeEnum
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.viewmodels.GroupViewModel
import java.util.*
class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListener {
@@ -47,10 +50,12 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
private var onScrollListener: OnScrollListener? = null
private var mNodesRecyclerView: RecyclerView? = null
var mainGroup: Group? = null
private set
private var mAdapter: NodeAdapter? = null
private val mGroupViewModel: GroupViewModel by activityViewModels()
private var mCurrentGroup: Group? = null
var nodeActionSelectionMode = false
private set
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
@@ -64,6 +69,9 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
private var readOnly: Boolean = false
private var specialMode: SpecialMode = SpecialMode.DEFAULT
private var mRecycleBinEnable: Boolean = false
private var mRecycleBin: Group? = null
val isEmpty: Boolean
get() = mAdapter == null || mAdapter?.itemCount?:0 <= 0
@@ -99,56 +107,63 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
setHasOptionsMenu(true)
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
}
arguments?.let { args ->
// Contains all the group in element
if (args.containsKey(GROUP_KEY)) {
mainGroup = args.getParcelable(GROUP_KEY)
}
if (args.containsKey(IS_SEARCH)) {
isASearchResult = args.getBoolean(IS_SEARCH)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mGroupViewModel.group.observe(viewLifecycleOwner) {
mCurrentGroup = it.group
isASearchResult = it.group.isVirtual
rebuildList()
}
}
override fun onDatabaseRetrieved(database: Database?) {
mRecycleBinEnable = database?.isRecycleBinEnabled == true
mRecycleBin = database?.recycleBin
contextThemed?.let { context ->
mDatabase?.let { database ->
mAdapter = NodeAdapter(context, database)
}
mAdapter?.apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
override fun onNodeClick(node: Node) {
if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
listActionNodes.remove(node)
database?.let { database ->
mAdapter = NodeAdapter(context, database).apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
override fun onNodeClick(database: Database, node: Node) {
if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
listActionNodes.remove(node)
} else {
// Add selected item if not already selected
listActionNodes.add(node)
}
nodeClickListener?.onNodeSelected(database, listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
} else {
// Add selected item if not already selected
listActionNodes.add(node)
nodeClickListener?.onNodeClick(database, node)
}
nodeClickListener?.onNodeSelected(listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
} else {
nodeClickListener?.onNodeClick(node)
}
}
override fun onNodeLongClick(node: Node): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
listActionNodes.add(node)
override fun onNodeLongClick(database: Database, node: Node): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
listActionNodes.add(node)
nodeClickListener?.onNodeSelected(listActionNodes)
nodeClickListener?.onNodeSelected(database, listActionNodes)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
setActionNodes(listActionNodes)
notifyNodeChanged(node)
}
return true
}
return true
}
})
})
}
mNodesRecyclerView?.adapter = mAdapter
}
}
// TODO notify menu updated?
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -211,7 +226,7 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
@Throws(IllegalArgumentException::class)
fun rebuildList() {
// Add elements to the list
mainGroup?.let { mainGroup ->
mCurrentGroup?.let { mainGroup ->
mAdapter?.apply {
// Thrown an exception when sort cannot be performed
rebuildList(mainGroup)
@@ -253,7 +268,7 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
R.id.menu_sort -> {
context?.let { context ->
val sortDialogFragment: SortDialogFragment =
if (mDatabase?.isRecycleBinEnabled == true) {
if (mRecycleBinEnable) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(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?,
actionModeCallback: ActionMode.Callback) : ActionMode.Callback {
@@ -289,49 +305,46 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
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 ->
if (nodeActionPasteMode != PasteMode.UNDEFINED) {
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu)
} else {
nodeActionSelectionMode = true
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)
// Open and Edit for a single item
if (nodes.size == 1) {
// Edition
if (readOnly
|| (mRecycleBinEnable && nodes[0] == mRecycleBin)) {
menu?.removeItem(R.id.menu_edit)
}
// Move
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
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
menu?.removeItem(R.id.menu_delete)
}
} else {
menu?.removeItem(R.id.menu_open)
menu?.removeItem(R.id.menu_edit)
}
// Add the number of items selected in title
mode?.title = nodes.size.toString()
// Move
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)
}
@@ -339,25 +352,25 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
if (menuListener == null)
return false
return when (item?.itemId) {
R.id.menu_open -> menuListener.onOpenMenuClick(nodes[0])
R.id.menu_edit -> menuListener.onEditMenuClick(nodes[0])
R.id.menu_open -> menuListener.onOpenMenuClick(database, nodes[0])
R.id.menu_edit -> menuListener.onEditMenuClick(database, nodes[0])
R.id.menu_copy -> {
nodeActionPasteMode = PasteMode.PASTE_FROM_COPY
mAdapter?.unselectActionNodes()
val returnValue = menuListener.onCopyMenuClick(nodes)
val returnValue = menuListener.onCopyMenuClick(database, nodes)
nodeActionSelectionMode = false
returnValue
}
R.id.menu_move -> {
nodeActionPasteMode = PasteMode.PASTE_FROM_MOVE
mAdapter?.unselectActionNodes()
val returnValue = menuListener.onMoveMenuClick(nodes)
val returnValue = menuListener.onMoveMenuClick(database, nodes)
nodeActionSelectionMode = false
returnValue
}
R.id.menu_delete -> menuListener.onDeleteMenuClick(nodes)
R.id.menu_delete -> menuListener.onDeleteMenuClick(database, nodes)
R.id.menu_paste -> {
val returnValue = menuListener.onPasteMenuClick(nodeActionPasteMode, nodes)
val returnValue = menuListener.onPasteMenuClick(database, nodeActionPasteMode, nodes)
nodeActionPasteMode = PasteMode.UNDEFINED
nodeActionSelectionMode = false
returnValue
@@ -435,20 +448,20 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickListener {
fun onNodeClick(node: Node)
fun onNodeSelected(nodes: List<Node>): Boolean
fun onNodeClick(database: Database, node: Node)
fun onNodeSelected(database: Database, nodes: List<Node>): Boolean
}
/**
* Menu listener to redefine to do an action in menu
*/
interface NodesActionMenuListener {
fun onOpenMenuClick(node: Node): Boolean
fun onEditMenuClick(node: Node): Boolean
fun onCopyMenuClick(nodes: List<Node>): Boolean
fun onMoveMenuClick(nodes: List<Node>): Boolean
fun onDeleteMenuClick(nodes: List<Node>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<Node>): Boolean
fun onOpenMenuClick(database: Database, node: Node): Boolean
fun onEditMenuClick(database: Database, node: Node): Boolean
fun onCopyMenuClick(database: Database, nodes: List<Node>): Boolean
fun onMoveMenuClick(database: Database, nodes: List<Node>): Boolean
fun onDeleteMenuClick(database: Database, nodes: List<Node>): Boolean
fun onPasteMenuClick(database: Database, pasteMode: PasteMode?, nodes: List<Node>): Boolean
}
enum class PasteMode {
@@ -467,22 +480,6 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
}
companion object {
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 {
var readOnly = READ_ONLY_DEFAULT
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.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
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.ReadOnlyHelper
import com.kunzisoft.keepass.activities.helpers.SpecialMode
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.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.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.*
abstract class LockingActivity : SpecialModeActivity() {
abstract class LockingActivity : SpecialModeActivity(),
PasswordEncodingDialogFragment.Listener,
DeleteNodesDialogFragment.DeleteNodeListener {
protected var mTimeoutEnable: Boolean = true
private var mLockReceiver: LockReceiver? = null
private var mExitLock: Boolean = false
protected var mDatabase: Database? = null
private var mDatabase: Database? = null
// Force readOnly if Entry Selection mode
protected var mReadOnly: Boolean
@@ -58,23 +71,19 @@ abstract class LockingActivity : SpecialModeActivity() {
private var mReadOnlyToSave: Boolean = false
protected var mAutoSaveEnable: Boolean = true
var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private set
protected var mIconDrawableFactory: IconDrawableFactory? = null
override fun onCreate(savedInstanceState: Bundle?) {
mDatabase = Database.getInstance()
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this)
super.onCreate(savedInstanceState)
if (savedInstanceState != null
&& savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)) {
&& savedInstanceState.containsKey(TIMEOUT_ENABLE_KEY)
) {
mTimeoutEnable = savedInstanceState.getBoolean(TIMEOUT_ENABLE_KEY)
} else {
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) {
@@ -93,19 +102,193 @@ abstract class LockingActivity : SpecialModeActivity() {
mExitLock = false
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_EXIT_LOCK) {
mExitLock = true
if (mDatabase?.loaded == true) {
lockAndExit()
override fun onDatabaseRetrieved(database: Database?) {
super.onDatabaseRetrieved(database)
mDatabase = database
// End activity if database not loaded
if (database?.loaded != true) {
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() {
super.onResume()
if (mDatabase?.wasReloaded == true) {
reload(mDatabase!!)
}
// If in ave or registration mode, don't allow read only
if ((mSpecialMode == SpecialMode.SAVE
|| mSpecialMode == SpecialMode.REGISTRATION)
@@ -115,36 +298,19 @@ abstract class LockingActivity : SpecialModeActivity() {
finish()
}
mProgressDatabaseTaskProvider?.registerProgressTask()
// To refresh when back to normal workflow from selection workflow
mReadOnlyToSave = ReadOnlyHelper.retrieveReadOnlyFromIntent(intent)
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
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
}
protected fun checkTimeAndLockIfTimeoutOrResetTimeout(action: (() -> Unit)? = null) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, mDatabase, action)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
super.onSaveInstanceState(outState)
@@ -153,8 +319,6 @@ abstract class LockingActivity : SpecialModeActivity() {
override fun onPause() {
LOCKING_ACTIVITY_UI_VISIBLE = false
mProgressDatabaseTaskProvider?.unregisterProgressTask()
super.onPause()
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.view.View
import android.widget.Toast
import androidx.room.Database
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
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)
*/
abstract class SpecialModeActivity : StylishActivity() {
abstract class SpecialModeActivity : DatabaseActivity() {
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
private var mTypeMode: TypeMode = TypeMode.DEFAULT

View File

@@ -358,10 +358,10 @@ class NodeAdapter (private val context: Context,
// Assign click
holder.container.setOnClickListener {
mNodeClickCallback?.onNodeClick(subNode)
mNodeClickCallback?.onNodeClick(database, subNode)
}
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
*/
interface NodeClickCallback {
fun onNodeClick(node: Node)
fun onNodeLongClick(node: Node): Boolean
fun onNodeClick(database: Database, node: Node)
fun onNodeLongClick(database: Database, node: Node): Boolean
}
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.template.Template
import com.kunzisoft.keepass.database.element.template.TemplateField
import com.kunzisoft.keepass.icons.IconDrawableFactory
class TemplatesSelectorAdapter(private val context: Context,
private val database: Database?,
private val iconDrawableFactory: IconDrawableFactory?,
private var templates: List<Template>): BaseAdapter() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
@@ -43,7 +44,7 @@ class TemplatesSelectorAdapter(private val context: Context,
}
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)

View File

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

View File

@@ -56,13 +56,9 @@ class KeeAutofillService : AutofillService() {
var autofillInlineSuggestionsEnabled: Boolean = false
private var mLock = AtomicBoolean()
private var mDatabase: Database? = null
override fun onCreate() {
super.onCreate()
mDatabase = Database.getInstance()
getPreferences()
}
@@ -102,7 +98,8 @@ class KeeAutofillService : AutofillService() {
} else {
null
}
mDatabase?.let { database ->
// TODO database
Database.getInstance()?.let { database ->
launchSelection(database,
searchInfo,
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.database.crypto.EncryptionAlgorithm
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.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
@@ -83,9 +84,12 @@ import kotlinx.coroutines.launch
import java.util.*
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
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 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)
}
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) {
override fun onUpdateAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?) {
updateDialog(titleId, messageId, warningId)
}
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
onActionFinish?.invoke(actionTask, result)
override fun onStopAction(database: Database, actionTask: String, result: ActionRunnable.Result) {
onActionFinish?.invoke(database, actionTask, result)
// Remove the progress task
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,
messageId: Int? = null,
warningId: Int? = null) {
@@ -187,16 +197,19 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder?)?.apply {
addActionTaskListener(actionTaskListener)
addDatabaseListener(databaseListener)
addDatabaseFileInfoListener(databaseInfoListener)
getService().checkAction()
addActionTaskListener(actionTaskListener)
getService().checkDatabase()
getService().checkDatabaseInfo()
getService().checkAction()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
mBinder = null
}
}
@@ -257,8 +270,9 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
fun unregisterProgressTask() {
stopDialog()
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeActionTaskListener(actionTaskListener)
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
mBinder?.removeDatabaseListener(databaseListener)
mBinder = null
unBindService()
@@ -639,6 +653,6 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
}
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
// or if entry info timestamp is before database loaded timestamp
// TODO Database
val database = Database.getInstance()
val databaseTime = database.loadTimestamp
val entryTime = entryInfoTimestamp

View File

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

View File

@@ -58,12 +58,12 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
private val mainScope = CoroutineScope(Dispatchers.Main)
private var mDatabaseListeners = LinkedList<DatabaseListener>()
private var mDatabaseInfoListeners = LinkedList<DatabaseInfoListener>()
private var mActionTaskBinder = ActionTaskBinder()
private var mActionTaskListeners = LinkedList<ActionTaskListener>()
private var mActionRunning = false
private var mDatabaseInfoListeners = LinkedList<DatabaseInfoListener>()
private var mIconId: Int = R.drawable.notification_ic_database_load
private var mTitleId: Int = R.string.database_opened
private var mMessageId: Int? = null
@@ -81,13 +81,13 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun getService(): DatabaseTaskNotificationService = this@DatabaseTaskNotificationService
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
if (!mActionTaskListeners.contains(actionTaskListener))
mActionTaskListeners.add(actionTaskListener)
fun addDatabaseListener(databaseListener: DatabaseListener) {
if (!mDatabaseListeners.contains(databaseListener))
mDatabaseListeners.add(databaseListener)
}
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.remove(actionTaskListener)
fun removeDatabaseListener(databaseListener: DatabaseListener) {
mDatabaseListeners.remove(databaseListener)
}
fun addDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
@@ -98,12 +98,19 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
fun removeDatabaseFileInfoListener(databaseInfoListener: DatabaseInfoListener) {
mDatabaseInfoListeners.remove(databaseInfoListener)
}
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
if (!mActionTaskListeners.contains(actionTaskListener))
mActionTaskListeners.add(actionTaskListener)
}
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.remove(actionTaskListener)
}
}
interface ActionTaskListener {
fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?)
fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?)
fun onStopAction(actionTask: String, result: ActionRunnable.Result)
interface DatabaseListener {
fun onDatabaseRetrieved(database: Database?)
}
interface DatabaseInfoListener {
@@ -111,14 +118,15 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
newDatabaseInfo: SnapFileDatabaseInfo)
}
/**
* Force to call [ActionTaskListener.onStartAction] if the action is still running
*/
fun checkAction() {
if (mActionRunning) {
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStartAction(mTitleId, mMessageId, mWarningId)
}
interface ActionTaskListener {
fun onStartAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?)
fun onUpdateAction(database: Database, titleId: Int?, messageId: Int?, warningId: Int?)
fun onStopAction(database: Database, actionTask: String, result: ActionRunnable.Result)
}
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? {
super.onBind(intent)
return mActionTaskBinder
@@ -184,6 +203,9 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
super.onStartCommand(intent, flags, startId)
mDatabase = Database.getInstance()
mDatabaseListeners.forEach { listener ->
listener.onDatabaseRetrieved(mDatabase)
}
// Create the notification
buildMessage(intent)
@@ -243,7 +265,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
})
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStartAction(mTitleId, mMessageId, mWarningId)
actionTaskListener.onStartAction(mDatabase, mTitleId, mMessageId, mWarningId)
}
},
@@ -253,7 +275,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
{ result ->
try {
mActionTaskListeners.forEach { actionTaskListener ->
actionTaskListener.onStopAction(intentAction!!, result)
actionTaskListener.onStopAction(mDatabase, intentAction!!, result)
}
} finally {
// Save the database info before performing action
@@ -443,7 +465,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
override fun updateMessage(resId: Int) {
mMessageId = resId
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
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
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.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) {
super.onAttach(context)
override fun onViewCreated(view: View,
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 androidx.preference.Preference
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
class MainPreferenceFragment : DatabasePreferenceFragment() {
private var mCallback: Callback? = null
private var mDatabaseLoaded: Boolean = false
override fun onAttach(context: Context) {
super.onAttach(context)
@@ -43,6 +46,10 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
super.onDetach()
}
override fun onDatabaseRetrieved(database: Database?) {
mDatabaseLoaded = database?.loaded == true
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
@@ -80,7 +87,8 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
mCallback?.onNestedPreferenceSelected(NestedSettingsFragment.Screen.DATABASE)
false
}
if (mDatabase?.loaded != true) {
// TODO Check
if (mDatabaseLoaded) {
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.FileDatabaseHistoryAction
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.Education
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.icons.IconPackChooser
import com.kunzisoft.keepass.settings.preference.IconPackListPreference
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
@@ -54,6 +56,12 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
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?) {
// Load the preferences from an XML resource
@@ -426,8 +434,9 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
}
}
if (iconPackEnabled) {
mDatabase?.let {
IconPackChooser.setSelectedIconPack(it.iconDrawableFactory, iconPackId)
// TODO Check
mIconDrawableFactory?.let {
IconPackChooser.setSelectedIconPack(it, iconPackId)
}
}
iconPackEnabled

View File

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

View File

@@ -22,7 +22,6 @@ package com.kunzisoft.keepass.settings
import android.app.Activity
import android.app.backup.BackupManager
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
@@ -33,7 +32,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment
import com.kunzisoft.keepass.R
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.ReadOnlyHelper
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.model.MainCredential
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import org.joda.time.DateTime
@@ -49,8 +48,7 @@ import java.util.*
open class SettingsActivity
: LockingActivity(),
MainPreferenceFragment.Callback,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
PasswordEncodingDialogFragment.Listener {
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
private var backupManager: BackupManager? = null
private var mExternalFileHelper: ExternalFileHelper? = null
@@ -60,14 +58,6 @@ open class SettingsActivity
private var toolbar: Toolbar? = 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?) {
super.onCreate(savedInstanceState)
@@ -90,9 +80,6 @@ open class SettingsActivity
lockAndExit()
}
// Focus view to reinitialize timeout
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this, mDatabase)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, retrieveMainFragment())
@@ -103,29 +90,6 @@ open class SettingsActivity
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
if (intent.extras?.containsKey(FRAGMENT_ARG) == true) {
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 {
when (item.itemId) {
android.R.id.home -> onBackPressed()
@@ -149,31 +156,8 @@ open class SettingsActivity
super.onStop()
}
override fun onPasswordEncodingValidateListener(databaseUri: Uri?,
mainCredential: MainCredential) {
databaseUri?.let {
mProgressDatabaseTaskProvider?.startDatabaseAssignPassword(
databaseUri,
mainCredential
)
}
}
override fun onAssignKeyDialogPositiveClick(mainCredential: MainCredential) {
mDatabase?.let { database ->
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")
}
}
}
assignPassword(mainCredential)
}
override fun onAssignKeyDialogNegativeClick(mainCredential: MainCredential) {}
@@ -243,7 +227,7 @@ open class SettingsActivity
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
if (mTimeoutEnable)
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, mDatabase) {
checkTimeAndLockIfTimeoutOrResetTimeout {
replaceFragment(key, reload)
}
else

View File

@@ -45,6 +45,7 @@ class IconPackListPreference @JvmOverloads constructor(context: Context,
setEntries(entries.toTypedArray())
entryValues = values.toTypedArray()
// TODO database
IconPackChooser.getSelectedIconPack(context, Database.getInstance().iconDrawableFactory)?.let { selectedIconPack ->
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.keepass.R
import com.kunzisoft.keepass.database.element.Database
class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
@@ -88,7 +89,7 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog
}
val oldColor = database.customColor
database.customColor = newColor
mProgressDatabaseTaskProvider?.startDatabaseSaveColor(oldColor, newColor, mDatabaseAutoSaveEnable)
saveColor(oldColor, newColor)
}
onDialogClosed(true)

View File

@@ -63,7 +63,7 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat
database.compressionAlgorithm = newCompression
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 oldDefaultUsername = database.defaultUsername
database.defaultUsername = newDefaultUsername
mProgressDatabaseTaskProvider?.startDatabaseSaveDefaultUsername(oldDefaultUsername, newDefaultUsername, mDatabaseAutoSaveEnable)
saveDefaultUsername(oldDefaultUsername, newDefaultUsername)
}
}
}

View File

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

View File

@@ -63,7 +63,7 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
database.encryptionAlgorithm = newAlgorithm
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
if (newKdfEngine != null && oldKdfEngine != null) {
database.kdfEngine = newKdfEngine
mProgressDatabaseTaskProvider?.startDatabaseSaveKeyDerivation(oldKdfEngine, newKdfEngine, mDatabaseAutoSaveEnable)
saveKeyDerivation(oldKdfEngine, newKdfEngine)
}
}
}

View File

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

View File

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

View File

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

View File

@@ -20,28 +20,123 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment
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.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
protected var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
private var mDatabaseAutoSaveEnable = true
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
protected var mDatabase: Database? = null
override fun onAttach(context: Context) {
super.onAttach(context)
// Attach dialog thread to start action
if (context is SettingsActivity) {
mProgressDatabaseTaskProvider = context.mProgressDatabaseTaskProvider
}
this.mDatabaseAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(context)
}
override fun onDetach() {
mProgressDatabaseTaskProvider = null
super.onDetach()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
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 {

View File

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

View File

@@ -19,7 +19,6 @@
*/
package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
@@ -30,7 +29,6 @@ import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceDialogFragmentCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
@@ -41,14 +39,6 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom
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
get() = this.inputTextView?.text?.toString() ?: ""
set(inputText) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -138,5 +138,5 @@ fun Context.closeDatabase() {
cancelAll()
}
// 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() {
private val mDatabase: Database? = Database.getInstance()
private var mParent : Group? = null
private var mEntry : Entry? = null
private var mDatabase: Database? = null
private var mParent: Group? = null
private var mEntry: Entry? = null
private var mIsTemplate: Boolean = false
private val mTempAttachments = mutableListOf<EntryAttachmentState>()
@@ -80,6 +79,10 @@ class EntryEditViewModel: ViewModel() {
val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded
private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>()
fun setDatabase(database: Database?) {
this.mDatabase = database
}
fun initializeEntryToUpdate(entryId: NodeId<UUID>,
registerInfo: RegisterInfo?,
searchInfo: SearchInfo?) {
@@ -166,7 +169,7 @@ class EntryEditViewModel: ViewModel() {
}
}
// Set default username
username = mDatabase.defaultUsername
username = mDatabase?.defaultUsername ?: ""
// 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)
parent = parentGroup
@@ -246,7 +249,7 @@ class EntryEditViewModel: ViewModel() {
val tempAttachment = tempAttachmentState.attachment
mDatabase?.attachmentPool?.let { binaryPool ->
if (!newEntry.getAttachments(binaryPool).contains(tempAttachment)) {
mDatabase.removeAttachmentIfNotUsed(tempAttachment)
mDatabase?.removeAttachmentIfNotUsed(tempAttachment)
}
}
}

View File

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