mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'feature/DatabaseProvider' into develop
This commit is contained in:
@@ -2,6 +2,7 @@ KeePassDX(3.0.0)
|
||||
* Add / Manage dynamic templates #191
|
||||
* Manually select RecycleBin group and Templates group #191
|
||||
* Small changes #1035
|
||||
* Fix timeout in dialogs #716
|
||||
|
||||
KeePassDX(2.10.5)
|
||||
* Increase the saving speed of database #1028
|
||||
|
||||
@@ -112,8 +112,7 @@
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.GroupActivity"
|
||||
android:configChanges="keyboardHidden"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:launchMode="singleTask">
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value="com.kunzisoft.keepass.search.SearchResults"
|
||||
@@ -209,7 +208,7 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name="com.kunzisoft.keepass.magikeyboard.MagikIME"
|
||||
android:name="com.kunzisoft.keepass.magikeyboard.MagikeyboardService"
|
||||
android:label="@string/keyboard_label"
|
||||
android:permission="android.permission.BIND_INPUT_METHOD" >
|
||||
<meta-data android:name="android.view.im"
|
||||
|
||||
@@ -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.legacy.DatabaseModeActivity
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper.EXTRA_INLINE_SUGGESTIONS_REQUEST
|
||||
import com.kunzisoft.keepass.autofill.KeeAutofillService
|
||||
@@ -44,13 +43,18 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
class AutofillLauncherActivity : AppCompatActivity() {
|
||||
class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
|
||||
private var mDatabase: Database? = null
|
||||
override fun applyCustomStyle(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
override fun finishActivityIfReloadRequested(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
mDatabase = Database.getInstance()
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
|
||||
// Retrieve selection mode
|
||||
EntrySelectionHelper.retrieveSpecialModeFromIntent(intent).let { specialMode ->
|
||||
@@ -64,9 +68,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
||||
}
|
||||
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
|
||||
searchInfo.webDomain = concreteWebDomain
|
||||
mDatabase?.let { database ->
|
||||
launchSelection(database, searchInfo)
|
||||
}
|
||||
launchSelection(database, searchInfo)
|
||||
}
|
||||
}
|
||||
SpecialMode.REGISTRATION -> {
|
||||
@@ -75,9 +77,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
||||
val searchInfo = SearchInfo(registerInfo?.searchInfo)
|
||||
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
|
||||
searchInfo.webDomain = concreteWebDomain
|
||||
mDatabase?.let { database ->
|
||||
launchRegistration(database, searchInfo, registerInfo)
|
||||
}
|
||||
launchRegistration(database, searchInfo, registerInfo)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@@ -87,11 +87,9 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun launchSelection(database: Database,
|
||||
private fun launchSelection(database: Database?,
|
||||
searchInfo: SearchInfo) {
|
||||
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
|
||||
val autofillComponent = AutofillHelper.retrieveAutofillComponent(intent)
|
||||
@@ -111,18 +109,18 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
searchInfo,
|
||||
{ items ->
|
||||
{ openedDatabase, items ->
|
||||
// Items found
|
||||
AutofillHelper.buildResponseAndSetResult(this, database, items)
|
||||
AutofillHelper.buildResponseAndSetResult(this, openedDatabase, items)
|
||||
finish()
|
||||
},
|
||||
{
|
||||
{ openedDatabase ->
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForAutofillResult(this,
|
||||
database.isReadOnly,
|
||||
autofillComponent,
|
||||
searchInfo,
|
||||
false)
|
||||
openedDatabase,
|
||||
autofillComponent,
|
||||
searchInfo,
|
||||
false)
|
||||
},
|
||||
{
|
||||
// If database not open
|
||||
@@ -134,7 +132,7 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchRegistration(database: Database,
|
||||
private fun launchRegistration(database: Database?,
|
||||
searchInfo: SearchInfo,
|
||||
registerInfo: RegisterInfo?) {
|
||||
if (!KeeAutofillService.autofillAllowedFor(searchInfo.applicationId,
|
||||
@@ -144,24 +142,26 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
||||
showBlockRestartMessage()
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
} else {
|
||||
val readOnly = database.isReadOnly
|
||||
val readOnly = database?.isReadOnly != false
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
searchInfo,
|
||||
{ _ ->
|
||||
{ openedDatabase, _ ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForRegistration(this,
|
||||
registerInfo)
|
||||
openedDatabase,
|
||||
registerInfo)
|
||||
} else {
|
||||
showReadOnlySaveMessage()
|
||||
}
|
||||
},
|
||||
{
|
||||
{ openedDatabase ->
|
||||
if (!readOnly) {
|
||||
// Show the database UI to select the entry
|
||||
GroupActivity.launchForRegistration(this,
|
||||
registerInfo)
|
||||
openedDatabase,
|
||||
registerInfo)
|
||||
} else {
|
||||
showReadOnlySaveMessage()
|
||||
}
|
||||
|
||||
@@ -35,28 +35,26 @@ 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
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
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.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.education.EntryActivityEducation
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.otp.OtpType
|
||||
import com.kunzisoft.keepass.services.AttachmentFileNotificationService
|
||||
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
||||
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.*
|
||||
@@ -66,7 +64,7 @@ import com.kunzisoft.keepass.viewmodels.EntryViewModel
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class EntryActivity : LockingActivity() {
|
||||
class EntryActivity : DatabaseLockActivity() {
|
||||
|
||||
private var coordinatorLayout: CoordinatorLayout? = null
|
||||
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
|
||||
@@ -79,11 +77,17 @@ class EntryActivity : LockingActivity() {
|
||||
|
||||
private val mEntryViewModel: EntryViewModel by viewModels()
|
||||
|
||||
private var mMainEntryId: NodeId<UUID>? = null
|
||||
private var mHistoryPosition: Int = -1
|
||||
private var mEntryIsHistory: Boolean = false
|
||||
private var mUrl: String? = null
|
||||
|
||||
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||
private var mAttachmentsToDownload: HashMap<Int, Attachment> = HashMap()
|
||||
private var mExternalFileHelper: ExternalFileHelper? = null
|
||||
|
||||
private var iconColor: Int = 0
|
||||
private var mIcon: IconImage? = null
|
||||
private var mIconColor: Int = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -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)
|
||||
mIconColor = 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 ->
|
||||
mMainEntryId = 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")
|
||||
@@ -139,6 +138,30 @@ class EntryActivity : LockingActivity() {
|
||||
lockAndExit()
|
||||
}
|
||||
|
||||
mEntryViewModel.mainEntryId.observe(this) { mainEntryId ->
|
||||
this.mMainEntryId = mainEntryId
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
mEntryViewModel.historyPosition.observe(this) { historyPosition ->
|
||||
this.mHistoryPosition = historyPosition
|
||||
val entryIsHistory = historyPosition > -1
|
||||
this.mEntryIsHistory = entryIsHistory
|
||||
// Assign history dedicated view
|
||||
historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
|
||||
if (entryIsHistory) {
|
||||
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
||||
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
|
||||
taColorAccent.recycle()
|
||||
}
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
mEntryViewModel.url.observe(this) { url ->
|
||||
this.mUrl = url
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
mEntryViewModel.entryInfo.observe(this) { entryInfo ->
|
||||
// Manage entry copy to start notification if allowed (at the first start)
|
||||
if (savedInstanceState == null) {
|
||||
@@ -146,13 +169,14 @@ class EntryActivity : LockingActivity() {
|
||||
ClipboardEntryNotificationService.launchNotificationIfAllowed(this, entryInfo)
|
||||
// Manage entry to populate Magikeyboard and launch keyboard notification if allowed
|
||||
if (PreferencesUtil.isKeyboardEntrySelectionEnable(this)) {
|
||||
MagikIME.addEntryAndLaunchNotificationIfAllowed(this, entryInfo)
|
||||
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(this, entryInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Assign title icon
|
||||
mIcon = entryInfo.icon
|
||||
titleIconView?.let { iconView ->
|
||||
mDatabase?.iconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, iconColor)
|
||||
mIconDrawableFactory?.assignDatabaseIcon(iconView, entryInfo.icon, mIconColor)
|
||||
}
|
||||
|
||||
// Assign title text
|
||||
@@ -166,18 +190,6 @@ class EntryActivity : LockingActivity() {
|
||||
loadingView?.hideByFading()
|
||||
}
|
||||
|
||||
mEntryViewModel.entryIsHistory.observe(this) { entryIsHistory ->
|
||||
// Assign history dedicated view
|
||||
historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
|
||||
if (entryIsHistory) {
|
||||
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
||||
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
|
||||
taColorAccent.recycle()
|
||||
}
|
||||
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
mEntryViewModel.onOtpElementUpdated.observe(this) { otpElement ->
|
||||
if (otpElement == null)
|
||||
entryProgress?.visibility = View.GONE
|
||||
@@ -204,30 +216,55 @@ class EntryActivity : LockingActivity() {
|
||||
}
|
||||
|
||||
mEntryViewModel.historySelected.observe(this) { historySelected ->
|
||||
launch(this,
|
||||
historySelected.nodeId,
|
||||
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)
|
||||
finish()
|
||||
}
|
||||
mDatabase?.let { database ->
|
||||
launch(
|
||||
this,
|
||||
database,
|
||||
historySelected.nodeId,
|
||||
historySelected.historyPosition
|
||||
)
|
||||
}
|
||||
coordinatorLayout?.showActionErrorIfNeeded(result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun finishActivityIfReloadRequested(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun viewToInvalidateTimeout(): View? {
|
||||
return coordinatorLayout
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
|
||||
mEntryViewModel.loadEntry(mDatabase, mMainEntryId, mHistoryPosition)
|
||||
|
||||
// Assign title icon
|
||||
mIcon?.let { icon ->
|
||||
titleIconView?.let { iconView ->
|
||||
mIconDrawableFactory?.assignDatabaseIcon(iconView, icon, mIconColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
@@ -260,7 +297,7 @@ class EntryActivity : LockingActivity() {
|
||||
when (requestCode) {
|
||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
|
||||
// Reload the current id from database
|
||||
mEntryViewModel.updateEntry()
|
||||
mEntryViewModel.updateEntry(mDatabase)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,16 +320,14 @@ class EntryActivity : LockingActivity() {
|
||||
inflater.inflate(R.menu.entry, menu)
|
||||
inflater.inflate(R.menu.database, menu)
|
||||
|
||||
if (mEntryViewModel.getEntry()?.url?.isEmpty() != false) {
|
||||
if (mUrl?.isEmpty() != false) {
|
||||
menu.findItem(R.id.menu_goto_url)?.isVisible = false
|
||||
}
|
||||
|
||||
val entryIsHistory = mEntryViewModel.getEntryIsHistory()
|
||||
|
||||
if (entryIsHistory && !mReadOnly) {
|
||||
if (mEntryIsHistory && !mDatabaseReadOnly) {
|
||||
inflater.inflate(R.menu.entry_history, menu)
|
||||
}
|
||||
if (entryIsHistory || mReadOnly) {
|
||||
if (mEntryIsHistory || mDatabaseReadOnly) {
|
||||
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||
menu.findItem(R.id.menu_edit)?.isVisible = false
|
||||
}
|
||||
@@ -343,38 +378,42 @@ class EntryActivity : LockingActivity() {
|
||||
return true
|
||||
}
|
||||
R.id.menu_edit -> {
|
||||
mEntryViewModel.getEntry()?.let { entry ->
|
||||
EntryEditActivity.launch(this@EntryActivity, entry)
|
||||
mDatabase?.let { database ->
|
||||
mMainEntryId?.let { entryId ->
|
||||
EntryEditActivity.launchToUpdate(
|
||||
this,
|
||||
database,
|
||||
entryId
|
||||
)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.menu_goto_url -> {
|
||||
mEntryViewModel.getEntry()?.url?.let { url ->
|
||||
mUrl?.let { url ->
|
||||
UriUtil.gotoUrl(this, url)
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.menu_restore_entry_history -> {
|
||||
mEntryViewModel.getMainEntry()?.let { mainEntry ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseRestoreEntryHistory(
|
||||
mainEntry,
|
||||
mEntryViewModel.getEntryHistoryPosition(),
|
||||
!mReadOnly && mAutoSaveEnable)
|
||||
mMainEntryId?.let { mainEntryId ->
|
||||
restoreEntryHistory(
|
||||
mainEntryId,
|
||||
mHistoryPosition)
|
||||
}
|
||||
}
|
||||
R.id.menu_delete_entry_history -> {
|
||||
mEntryViewModel.getMainEntry()?.let { mainEntry ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseDeleteEntryHistory(
|
||||
mainEntry,
|
||||
mEntryViewModel.getEntryHistoryPosition(),
|
||||
!mReadOnly && mAutoSaveEnable)
|
||||
mMainEntryId?.let { mainEntryId ->
|
||||
deleteEntryHistory(
|
||||
mainEntryId,
|
||||
mHistoryPosition)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -384,7 +423,7 @@ class EntryActivity : LockingActivity() {
|
||||
override fun finish() {
|
||||
// Transit data in previous Activity after an update
|
||||
Intent().apply {
|
||||
putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntryViewModel.getEntry())
|
||||
putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mMainEntryId)
|
||||
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, this)
|
||||
}
|
||||
super.finish()
|
||||
@@ -401,25 +440,38 @@ class EntryActivity : LockingActivity() {
|
||||
/**
|
||||
* Open standard Entry activity
|
||||
*/
|
||||
fun launch(activity: Activity, entry: Entry, readOnly: Boolean) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entry.nodeId)
|
||||
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
||||
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
||||
fun launch(activity: Activity,
|
||||
database: Database,
|
||||
entryId: NodeId<UUID>) {
|
||||
if (database.loaded) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entryId)
|
||||
activity.startActivityForResult(
|
||||
intent,
|
||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open history Entry activity
|
||||
*/
|
||||
fun launch(activity: Activity, entryId: NodeId<UUID>, historyPosition: Int, readOnly: Boolean) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entryId)
|
||||
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
|
||||
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
||||
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
||||
fun launch(activity: Activity,
|
||||
database: Database,
|
||||
entryId: NodeId<UUID>,
|
||||
historyPosition: Int) {
|
||||
if (database.loaded) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entryId)
|
||||
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
|
||||
activity.startActivityForResult(
|
||||
intent,
|
||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +46,14 @@ import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Compani
|
||||
import com.kunzisoft.keepass.activities.fragments.EntryEditFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.adapters.TemplatesSelectorAdapter
|
||||
import com.kunzisoft.keepass.app.database.IOActionTask
|
||||
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.template.*
|
||||
@@ -62,10 +64,10 @@ import com.kunzisoft.keepass.services.AttachmentFileNotificationService
|
||||
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
|
||||
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
|
||||
@@ -75,7 +77,7 @@ import org.joda.time.DateTime
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class EntryEditActivity : LockingActivity(),
|
||||
class EntryEditActivity : DatabaseLockActivity(),
|
||||
EntryCustomFieldDialogFragment.EntryCustomFieldListener,
|
||||
GeneratePasswordDialogFragment.GeneratePasswordListener,
|
||||
SetOTPDialogFragment.CreateOtpListener,
|
||||
@@ -93,7 +95,18 @@ class EntryEditActivity : LockingActivity(),
|
||||
private var lockView: View? = null
|
||||
private var loadingView: ProgressBar? = null
|
||||
|
||||
private var mEntryId: NodeId<UUID>? = null
|
||||
private var mParentId: NodeId<*>? = null
|
||||
private var mRegisterInfo: RegisterInfo? = null
|
||||
private var mSearchInfo: SearchInfo? = null
|
||||
|
||||
private val mEntryEditViewModel: EntryEditViewModel by viewModels()
|
||||
private var mParent: Group? = null
|
||||
private var mEntry: Entry? = null
|
||||
private var mIsTemplate: Boolean = false
|
||||
|
||||
private var mAllowCustomFields = false
|
||||
private var mAllowOTP = false
|
||||
|
||||
// To manage attachments
|
||||
private var mExternalFileHelper: ExternalFileHelper? = null
|
||||
@@ -122,27 +135,26 @@ 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))
|
||||
|
||||
val registerInfo = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)
|
||||
val searchInfo = EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
|
||||
mRegisterInfo = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)
|
||||
mSearchInfo = EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
|
||||
|
||||
// Entry is retrieve, it's an entry to update
|
||||
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate ->
|
||||
intent.removeExtra(KEY_ENTRY)
|
||||
mEntryEditViewModel.initializeEntryToUpdate(entryToUpdate, registerInfo, searchInfo)
|
||||
mEntryId = entryToUpdate
|
||||
}
|
||||
|
||||
// Parent is retrieve, it's a new entry to create
|
||||
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let { parent ->
|
||||
intent.removeExtra(KEY_PARENT)
|
||||
mEntryEditViewModel.initializeEntryToCreate(parent, registerInfo, searchInfo)
|
||||
mParentId = parent
|
||||
}
|
||||
|
||||
retrieveEntry(mDatabase)
|
||||
|
||||
// To retrieve attachment
|
||||
mExternalFileHelper = ExternalFileHelper(this)
|
||||
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
|
||||
@@ -165,9 +177,15 @@ class EntryEditActivity : LockingActivity(),
|
||||
|
||||
mEntryEditViewModel.requestDateTimeSelection.observe(this) { dateInstant ->
|
||||
if (dateInstant.type == DateInstant.Type.TIME) {
|
||||
selectTime(dateInstant)
|
||||
// Launch the time picker
|
||||
val dateTime = DateTime(dateInstant.date)
|
||||
TimePickerFragment.getInstance(dateTime.hourOfDay, dateTime.minuteOfHour)
|
||||
.show(supportFragmentManager, "TimePickerFragment")
|
||||
} else {
|
||||
selectDate(dateInstant)
|
||||
// Launch the date picker
|
||||
val dateTime = DateTime(dateInstant.date)
|
||||
DatePickerFragment.getInstance(dateTime.year, dateTime.monthOfYear - 1, dateTime.dayOfMonth)
|
||||
.show(supportFragmentManager, "DatePickerFragment")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +245,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 +263,146 @@ 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 viewToInvalidateTimeout(): View? {
|
||||
return coordinatorLayout
|
||||
}
|
||||
|
||||
override fun finishActivityIfReloadRequested(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
mAllowCustomFields = database?.allowEntryCustomFields() == true
|
||||
mAllowOTP = database?.allowOTP == true
|
||||
retrieveEntry(database)
|
||||
}
|
||||
|
||||
private fun retrieveEntry(database: Database?) {
|
||||
database?.let {
|
||||
mEntryId?.let {
|
||||
IOActionTask(
|
||||
{
|
||||
// Create an Entry copy to modify from the database entry
|
||||
mEntry = database.getEntryById(it)
|
||||
// Retrieve the parent
|
||||
mEntry?.let { entry ->
|
||||
// If no parent, add root group as parent
|
||||
if (entry.parent == null) {
|
||||
entry.parent = database.rootGroup
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve entry after database action", e)
|
||||
// Define if current entry is a template (in direct template group)
|
||||
mIsTemplate = database.entryIsTemplate(mEntry)
|
||||
},
|
||||
{
|
||||
mEntryEditViewModel.loadTemplateEntry(
|
||||
database,
|
||||
mEntry,
|
||||
mIsTemplate,
|
||||
mRegisterInfo,
|
||||
mSearchInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
ACTION_DATABASE_RELOAD_TASK -> {
|
||||
// Close the current activity
|
||||
this.showActionErrorIfNeeded(result)
|
||||
finish()
|
||||
).execute()
|
||||
mEntryId = null
|
||||
}
|
||||
|
||||
mParentId?.let {
|
||||
IOActionTask(
|
||||
{
|
||||
mParent = database.getGroupById(it)
|
||||
mParent?.let { parentGroup ->
|
||||
mEntry = database.createEntry()?.apply {
|
||||
// Add the default icon from parent if not a folder
|
||||
val parentIcon = parentGroup.icon
|
||||
// Set default icon
|
||||
if (parentIcon.custom.isUnknown
|
||||
&& parentIcon.standard.id != IconImageStandard.FOLDER_ID
|
||||
) {
|
||||
icon = IconImage(parentIcon.standard)
|
||||
}
|
||||
if (!parentIcon.custom.isUnknown) {
|
||||
icon = IconImage(parentIcon.custom)
|
||||
}
|
||||
// Set default username
|
||||
username = database.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
|
||||
}
|
||||
}
|
||||
mIsTemplate = database.entryIsTemplate(mEntry)
|
||||
},
|
||||
{
|
||||
mEntryEditViewModel.loadTemplateEntry(
|
||||
database,
|
||||
mEntry,
|
||||
mIsTemplate,
|
||||
mRegisterInfo,
|
||||
mSearchInfo
|
||||
)
|
||||
}
|
||||
).execute()
|
||||
mParentId = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
coordinatorLayout?.showActionErrorIfNeeded(result)
|
||||
}
|
||||
coordinatorLayout?.showActionErrorIfNeeded(result)
|
||||
}
|
||||
|
||||
private fun entryValidatedForSave(actionTask: String, entry: Entry) {
|
||||
@@ -317,26 +410,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()
|
||||
}
|
||||
@@ -455,7 +544,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
*/
|
||||
private fun saveEntry() {
|
||||
mAttachmentFileBinderManager?.stopUploadAllAttachments()
|
||||
mEntryEditViewModel.requestEntryInfoUpdate()
|
||||
mEntryEditViewModel.requestEntryInfoUpdate(mDatabase, mEntry, mParent)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
@@ -466,25 +555,22 @@ 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
|
||||
}
|
||||
|
||||
menu?.findItem(R.id.menu_add_attachment)?.apply {
|
||||
// Attachment not compatible below KitKat
|
||||
isEnabled = !mEntryEditViewModel.entryIsTemplate()
|
||||
isEnabled = !mIsTemplate
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
||||
isVisible = isEnabled
|
||||
}
|
||||
|
||||
menu?.findItem(R.id.menu_add_otp)?.apply {
|
||||
val allowOTP = mDatabase?.allowOTP == true
|
||||
// OTP not compatible below KitKat
|
||||
isEnabled = allowOTP
|
||||
&& !mEntryEditViewModel.entryIsTemplate()
|
||||
isEnabled = mAllowOTP
|
||||
&& !mIsTemplate
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
||||
isVisible = isEnabled
|
||||
}
|
||||
@@ -513,7 +599,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(
|
||||
@@ -575,25 +661,6 @@ class EntryEditActivity : LockingActivity(),
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
// Launch the date picker
|
||||
private fun selectDate(dateInstant: DateInstant) {
|
||||
val dateTime = DateTime(dateInstant.date)
|
||||
val defaultYear = dateTime.year
|
||||
val defaultMonth = dateTime.monthOfYear - 1
|
||||
val defaultDay = dateTime.dayOfMonth
|
||||
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
|
||||
.show(supportFragmentManager, "DatePickerFragment")
|
||||
}
|
||||
|
||||
// Launch the time picker
|
||||
private fun selectTime(dateInstant: DateInstant) {
|
||||
val dateTime = DateTime(dateInstant.date)
|
||||
val defaultHour = dateTime.hourOfDay
|
||||
val defaultMinute = dateTime.minuteOfHour
|
||||
TimePickerFragment.getInstance(defaultHour, defaultMinute)
|
||||
.show(supportFragmentManager, "TimePickerFragment")
|
||||
}
|
||||
|
||||
override fun onDateSet(datePicker: DatePicker?, year: Int, month: Int, day: Int) {
|
||||
// To fix android 4.4 issue
|
||||
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
|
||||
@@ -650,7 +717,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
try {
|
||||
val bundle = Bundle()
|
||||
val intentEntry = Intent()
|
||||
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry)
|
||||
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry.nodeId)
|
||||
intentEntry.putExtras(bundle)
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_CREATE_ENTRY_TASK -> {
|
||||
@@ -681,59 +748,67 @@ class EntryEditActivity : LockingActivity(),
|
||||
const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129
|
||||
const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"
|
||||
|
||||
const val ENTRY_EDIT_FRAGMENT_TAG = "ENTRY_EDIT_FRAGMENT_TAG"
|
||||
|
||||
/**
|
||||
* Launch EntryEditActivity to update an existing entry
|
||||
*
|
||||
* @param activity from activity
|
||||
* @param entry Entry to update
|
||||
* Launch EntryEditActivity to update an existing entry by his [entryId]
|
||||
*/
|
||||
fun launch(activity: Activity,
|
||||
entry: Entry) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entry.nodeId)
|
||||
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
||||
fun launchToUpdate(activity: Activity,
|
||||
database: Database,
|
||||
entryId: NodeId<UUID>) {
|
||||
if (database.loaded && !database.isReadOnly) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entryId)
|
||||
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch EntryEditActivity to add a new entry
|
||||
*
|
||||
* @param activity from activity
|
||||
* @param group Group who will contains new entry
|
||||
* Launch EntryEditActivity to add a new entry in an existent group
|
||||
*/
|
||||
fun launch(activity: Activity,
|
||||
group: Group) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, group.nodeId)
|
||||
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
||||
fun launchToCreate(activity: Activity,
|
||||
database: Database,
|
||||
groupId: NodeId<*>) {
|
||||
if (database.loaded && !database.isReadOnly) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, groupId)
|
||||
activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun launchForSave(context: Context,
|
||||
entry: Entry,
|
||||
searchInfo: SearchInfo) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entry.nodeId)
|
||||
EntrySelectionHelper.startActivityForSaveModeResult(context,
|
||||
fun launchToUpdateForSave(context: Context,
|
||||
database: Database,
|
||||
entryId: NodeId<UUID>,
|
||||
searchInfo: SearchInfo) {
|
||||
if (database.loaded && !database.isReadOnly) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entryId)
|
||||
EntrySelectionHelper.startActivityForSaveModeResult(
|
||||
context,
|
||||
intent,
|
||||
searchInfo)
|
||||
searchInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun launchForSave(context: Context,
|
||||
group: Group,
|
||||
searchInfo: SearchInfo) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, group.nodeId)
|
||||
EntrySelectionHelper.startActivityForSaveModeResult(context,
|
||||
fun launchToCreateForSave(context: Context,
|
||||
database: Database,
|
||||
groupId: NodeId<*>,
|
||||
searchInfo: SearchInfo) {
|
||||
if (database.loaded && !database.isReadOnly) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, groupId)
|
||||
EntrySelectionHelper.startActivityForSaveModeResult(
|
||||
context,
|
||||
intent,
|
||||
searchInfo)
|
||||
searchInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,14 +816,19 @@ class EntryEditActivity : LockingActivity(),
|
||||
* Launch EntryEditActivity to add a new entry in keyboard selection
|
||||
*/
|
||||
fun launchForKeyboardSelectionResult(context: Context,
|
||||
group: Group,
|
||||
database: Database,
|
||||
groupId: NodeId<*>,
|
||||
searchInfo: SearchInfo? = null) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, group.nodeId)
|
||||
EntrySelectionHelper.startActivityForKeyboardSelectionModeResult(context,
|
||||
if (database.loaded && !database.isReadOnly) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, groupId)
|
||||
EntrySelectionHelper.startActivityForKeyboardSelectionModeResult(
|
||||
context,
|
||||
intent,
|
||||
searchInfo)
|
||||
searchInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,46 +837,61 @@ class EntryEditActivity : LockingActivity(),
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
fun launchForAutofillResult(activity: Activity,
|
||||
database: Database,
|
||||
autofillComponent: AutofillComponent,
|
||||
group: Group,
|
||||
groupId: NodeId<*>,
|
||||
searchInfo: SearchInfo? = null) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, group.nodeId)
|
||||
AutofillHelper.startActivityForAutofillResult(activity,
|
||||
if (database.loaded && !database.isReadOnly) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, groupId)
|
||||
AutofillHelper.startActivityForAutofillResult(
|
||||
activity,
|
||||
intent,
|
||||
autofillComponent,
|
||||
searchInfo)
|
||||
searchInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch EntryEditActivity to register an updated entry (from autofill)
|
||||
*/
|
||||
fun launchForRegistration(context: Context,
|
||||
entry: Entry,
|
||||
registerInfo: RegisterInfo? = null) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entry.nodeId)
|
||||
EntrySelectionHelper.startActivityForRegistrationModeResult(context,
|
||||
fun launchToUpdateForRegistration(context: Context,
|
||||
database: Database,
|
||||
entryId: NodeId<UUID>,
|
||||
registerInfo: RegisterInfo? = null) {
|
||||
if (database.loaded && !database.isReadOnly) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_ENTRY, entryId)
|
||||
EntrySelectionHelper.startActivityForRegistrationModeResult(
|
||||
context,
|
||||
intent,
|
||||
registerInfo)
|
||||
registerInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch EntryEditActivity to register a new entry (from autofill)
|
||||
*/
|
||||
fun launchForRegistration(context: Context,
|
||||
group: Group,
|
||||
registerInfo: RegisterInfo? = null) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, group.nodeId)
|
||||
EntrySelectionHelper.startActivityForRegistrationModeResult(context,
|
||||
fun launchToCreateForRegistration(context: Context,
|
||||
database: Database,
|
||||
groupId: NodeId<*>,
|
||||
registerInfo: RegisterInfo? = null) {
|
||||
if (database.loaded && !database.isReadOnly) {
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(context)) {
|
||||
val intent = Intent(context, EntryEditActivity::class.java)
|
||||
intent.putExtra(KEY_PARENT, groupId)
|
||||
EntrySelectionHelper.startActivityForRegistrationModeResult(
|
||||
context,
|
||||
intent,
|
||||
registerInfo)
|
||||
registerInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,13 @@ package com.kunzisoft.keepass.activities
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
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.legacy.DatabaseModeActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
@@ -39,14 +38,18 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
* Activity to search or select entry in database,
|
||||
* Commonly used with Magikeyboard
|
||||
*/
|
||||
class EntrySelectionLauncherActivity : AppCompatActivity() {
|
||||
class EntrySelectionLauncherActivity : DatabaseModeActivity() {
|
||||
|
||||
private var mDatabase: Database? = null
|
||||
override fun applyCustomStyle(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
mDatabase = Database.getInstance()
|
||||
override fun finishActivityIfReloadRequested(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
var sharedWebDomain: String? = null
|
||||
var otpString: String? = null
|
||||
|
||||
@@ -72,23 +75,19 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
|
||||
else -> {}
|
||||
}
|
||||
|
||||
|
||||
// Build domain search param
|
||||
val searchInfo = SearchInfo().apply {
|
||||
this.webDomain = sharedWebDomain
|
||||
this.otpString = otpString
|
||||
}
|
||||
|
||||
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
|
||||
searchInfo.webDomain = concreteWebDomain
|
||||
mDatabase?.let { database ->
|
||||
launch(database, searchInfo)
|
||||
}
|
||||
launch(database, searchInfo)
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
private fun launch(database: Database,
|
||||
private fun launch(database: Database?,
|
||||
searchInfo: SearchInfo) {
|
||||
|
||||
if (!searchInfo.containsOnlyNullValues()) {
|
||||
@@ -96,17 +95,19 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
|
||||
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
|
||||
|
||||
// If database is open
|
||||
val readOnly = database.isReadOnly
|
||||
val readOnly = database?.isReadOnly != false
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
searchInfo,
|
||||
{ items ->
|
||||
{ openedDatabase, items ->
|
||||
// Items found
|
||||
if (searchInfo.otpString != null) {
|
||||
if (!readOnly) {
|
||||
GroupActivity.launchForSaveResult(this,
|
||||
searchInfo,
|
||||
false)
|
||||
GroupActivity.launchForSaveResult(
|
||||
this,
|
||||
openedDatabase,
|
||||
searchInfo,
|
||||
false)
|
||||
} else {
|
||||
Toast.makeText(applicationContext,
|
||||
R.string.autofill_read_only_save,
|
||||
@@ -117,30 +118,32 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
|
||||
if (items.size == 1) {
|
||||
// Automatically populate keyboard
|
||||
val entryPopulate = items[0]
|
||||
populateKeyboardAndMoveAppToBackground(this,
|
||||
populateKeyboardAndMoveAppToBackground(
|
||||
this,
|
||||
entryPopulate,
|
||||
intent)
|
||||
} else {
|
||||
// Select the one we want
|
||||
GroupActivity.launchForKeyboardSelectionResult(this,
|
||||
readOnly,
|
||||
searchInfo,
|
||||
true)
|
||||
openedDatabase,
|
||||
searchInfo,
|
||||
true)
|
||||
}
|
||||
} else {
|
||||
GroupActivity.launchForSearchResult(this,
|
||||
readOnly,
|
||||
searchInfo,
|
||||
true)
|
||||
openedDatabase,
|
||||
searchInfo,
|
||||
true)
|
||||
}
|
||||
},
|
||||
{
|
||||
{ openedDatabase ->
|
||||
// Show the database UI to select the entry
|
||||
if (searchInfo.otpString != null) {
|
||||
if (!readOnly) {
|
||||
GroupActivity.launchForSaveResult(this,
|
||||
searchInfo,
|
||||
false)
|
||||
openedDatabase,
|
||||
searchInfo,
|
||||
false)
|
||||
} else {
|
||||
Toast.makeText(applicationContext,
|
||||
R.string.autofill_read_only_save,
|
||||
@@ -149,13 +152,14 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
|
||||
}
|
||||
} else if (readOnly || searchShareForMagikeyboard) {
|
||||
GroupActivity.launchForKeyboardSelectionResult(this,
|
||||
readOnly,
|
||||
searchInfo,
|
||||
false)
|
||||
openedDatabase,
|
||||
searchInfo,
|
||||
false)
|
||||
} else {
|
||||
GroupActivity.launchForSaveResult(this,
|
||||
searchInfo,
|
||||
false)
|
||||
openedDatabase,
|
||||
searchInfo,
|
||||
false)
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -189,7 +193,7 @@ fun populateKeyboardAndMoveAppToBackground(activity: Activity,
|
||||
intent: Intent,
|
||||
toast: Boolean = true) {
|
||||
// Populate Magikeyboard with entry
|
||||
MagikIME.addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
|
||||
MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(activity, entry, toast)
|
||||
// Consume the selection mode
|
||||
EntrySelectionHelper.removeModesFromIntent(intent)
|
||||
activity.moveTaskToBack(true)
|
||||
|
||||
@@ -45,12 +45,11 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
||||
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
|
||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||
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,12 +60,13 @@ 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
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
class FileDatabaseSelectActivity : DatabaseModeActivity(),
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
||||
|
||||
// Views
|
||||
@@ -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,54 @@ 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 != null) {
|
||||
launchGroupActivityIfLoaded(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,
|
||||
database,
|
||||
PreferencesUtil.enableReadOnlyDatabase(this@FileDatabaseSelectActivity)
|
||||
)
|
||||
}
|
||||
ACTION_DATABASE_LOAD_TASK -> {
|
||||
if (result.isSuccess) {
|
||||
launchGroupActivityIfLoaded(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,13 +267,14 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
{ onLaunchActivitySpecialMode() })
|
||||
}
|
||||
|
||||
private fun launchGroupActivity(database: Database) {
|
||||
GroupActivity.launch(this,
|
||||
private fun launchGroupActivityIfLoaded(database: Database) {
|
||||
if (database.loaded) {
|
||||
GroupActivity.launch(this,
|
||||
database,
|
||||
database.isReadOnly,
|
||||
{ onValidateSpecialMode() },
|
||||
{ onCancelSpecialMode() },
|
||||
{ onLaunchActivitySpecialMode() })
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValidateSpecialMode() {
|
||||
@@ -301,30 +314,11 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
mDatabase?.let { database ->
|
||||
launchGroupActivityIfLoaded(database)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
// Unregister progress task
|
||||
mProgressDatabaseTaskProvider?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
// only to keep the current activity
|
||||
@@ -334,15 +328,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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,8 +36,7 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.fragments.IconPickerFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
@@ -50,7 +49,7 @@ import com.kunzisoft.keepass.viewmodels.IconPickerViewModel
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
|
||||
class IconPickerActivity : LockingActivity() {
|
||||
class IconPickerActivity : DatabaseLockActivity() {
|
||||
|
||||
private lateinit var toolbar: Toolbar
|
||||
private lateinit var coordinatorLayout: CoordinatorLayout
|
||||
@@ -84,11 +83,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 +108,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 +141,24 @@ class IconPickerActivity : LockingActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun viewToInvalidateTimeout(): View? {
|
||||
return findViewById<ViewGroup>(R.id.icon_picker_container)
|
||||
}
|
||||
|
||||
override fun finishActivityIfReloadRequested(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
|
||||
if (database?.allowCustomIcons == true) {
|
||||
uploadButton.setOpenDocumentClickListener(mExternalFileHelper)
|
||||
} else {
|
||||
uploadButton.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateIconsSelectedViews() {
|
||||
if (mIconsSelected.isEmpty()) {
|
||||
mCustomIconsSelectionMode = false
|
||||
|
||||
@@ -31,12 +31,17 @@ import android.widget.ImageView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import com.igreenwood.loupe.Loupe
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
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() {
|
||||
class ImageViewerActivity : DatabaseLockActivity() {
|
||||
|
||||
private var imageContainerView: ViewGroup? = null
|
||||
private lateinit var imageView: ImageView
|
||||
private lateinit var progressView: View
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -48,46 +53,11 @@ class ImageViewerActivity : LockingActivity() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
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)
|
||||
imageContainerView = findViewById(R.id.image_viewer_container)
|
||||
imageView = findViewById(R.id.image_viewer_image)
|
||||
progressView = findViewById(R.id.image_viewer_progress)
|
||||
|
||||
// Approximately, to not OOM and allow a zoom
|
||||
val mImagePreviewMaxWidth = max(
|
||||
resources.displayMetrics.widthPixels * 2,
|
||||
resources.displayMetrics.heightPixels * 2
|
||||
)
|
||||
|
||||
try {
|
||||
progressView.visibility = View.VISIBLE
|
||||
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
|
||||
|
||||
supportActionBar?.title = attachment.name
|
||||
|
||||
val size = attachment.binaryData.getSize()
|
||||
supportActionBar?.subtitle = Formatter.formatFileSize(this, size)
|
||||
|
||||
mDatabase?.let { database ->
|
||||
BinaryDatabaseManager.loadBitmap(
|
||||
database,
|
||||
attachment.binaryData,
|
||||
mImagePreviewMaxWidth
|
||||
) { bitmapLoaded ->
|
||||
if (bitmapLoaded == null) {
|
||||
finish()
|
||||
} else {
|
||||
progressView.visibility = View.GONE
|
||||
imageView.setImageBitmap(bitmapLoaded)
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: finish()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to view the binary", e)
|
||||
finish()
|
||||
}
|
||||
|
||||
Loupe.create(imageView, imageContainerView) {
|
||||
Loupe.create(imageView, imageContainerView!!) {
|
||||
onViewTranslateListener = object : Loupe.OnViewTranslateListener {
|
||||
|
||||
override fun onStart(view: ImageView) {
|
||||
@@ -110,6 +80,53 @@ class ImageViewerActivity : LockingActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun viewToInvalidateTimeout(): View? {
|
||||
return imageContainerView
|
||||
}
|
||||
|
||||
override fun finishActivityIfReloadRequested(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -19,38 +19,41 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||
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 : DatabaseModeActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
override fun applyCustomStyle(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
val database = Database.getInstance()
|
||||
override fun finishActivityIfReloadRequested(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
val readOnly = database.isReadOnly
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
null,
|
||||
{
|
||||
// Not called
|
||||
// if items found directly returns before calling this activity
|
||||
},
|
||||
{
|
||||
// Select if not found
|
||||
GroupActivity.launchForKeyboardSelectionResult(this, readOnly)
|
||||
},
|
||||
{
|
||||
// Pass extra to get entry
|
||||
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this)
|
||||
}
|
||||
database,
|
||||
null,
|
||||
{ _, _ ->
|
||||
// Not called
|
||||
// if items found directly returns before calling this activity
|
||||
},
|
||||
{ openedDatabase ->
|
||||
// Select if not found
|
||||
GroupActivity.launchForKeyboardSelectionResult(this, openedDatabase)
|
||||
},
|
||||
{
|
||||
// Pass extra to get entry
|
||||
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this)
|
||||
}
|
||||
)
|
||||
finish()
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,12 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
||||
import com.kunzisoft.keepass.activities.helpers.*
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
|
||||
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
|
||||
@@ -71,7 +71,7 @@ import com.kunzisoft.keepass.view.asError
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.BuilderListener {
|
||||
open class PasswordActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
|
||||
|
||||
// Views
|
||||
private var toolbar: Toolbar? = null
|
||||
@@ -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
|
||||
@@ -97,11 +95,11 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
private var mExternalFileHelper: ExternalFileHelper? = null
|
||||
|
||||
private var mPermissionAsked = false
|
||||
private var readOnly: Boolean = false
|
||||
private var mReadOnly: Boolean = false
|
||||
private var mForceReadOnly: Boolean = false
|
||||
set(value) {
|
||||
infoContainerView?.visibility = if (value) {
|
||||
readOnly = true
|
||||
mReadOnly = true
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
@@ -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)
|
||||
@@ -136,7 +130,11 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
coordinatorLayout = findViewById(R.id.activity_password_coordinator_layout)
|
||||
|
||||
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
|
||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
||||
mReadOnly = if (savedInstanceState != null && savedInstanceState.containsKey(KEY_READ_ONLY)) {
|
||||
savedInstanceState.getBoolean(KEY_READ_ONLY)
|
||||
} else {
|
||||
PreferencesUtil.enableReadOnlyDatabase(this)
|
||||
}
|
||||
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
|
||||
|
||||
mExternalFileHelper = ExternalFileHelper(this@PasswordActivity)
|
||||
@@ -208,74 +206,112 @@ 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 (DatabaseLockActivity.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)
|
||||
}
|
||||
mDatabase?.let { database ->
|
||||
launchGroupActivityIfLoaded(database)
|
||||
}
|
||||
}
|
||||
|
||||
databaseUri?.let { databaseFileUri ->
|
||||
showProgressDialogAndLoadDatabase(
|
||||
databaseFileUri,
|
||||
mainCredential,
|
||||
readOnly,
|
||||
cipherEntity,
|
||||
true)
|
||||
}
|
||||
}
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
if (database != null) {
|
||||
launchGroupActivityIfLoaded(database)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
launchGroupActivityIfLoaded(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()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,14 +338,17 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
getUriFromIntent(intent)
|
||||
}
|
||||
|
||||
private fun launchGroupActivity(database: Database) {
|
||||
GroupActivity.launch(this,
|
||||
private fun launchGroupActivityIfLoaded(database: Database) {
|
||||
// Check if database really loaded
|
||||
if (database.loaded) {
|
||||
clearCredentialsViews(true)
|
||||
GroupActivity.launch(this,
|
||||
database,
|
||||
readOnly,
|
||||
{ onValidateSpecialMode() },
|
||||
{ onCancelSpecialMode() },
|
||||
{ onLaunchActivitySpecialMode() }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValidateSpecialMode() {
|
||||
@@ -359,41 +398,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 +421,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
} else {
|
||||
// Init Biometric elements
|
||||
advancedUnlockFragment?.loadDatabase(databaseFileUri,
|
||||
mAllowAutoOpenBiometricPrompt
|
||||
&& mProgressDatabaseTaskProvider?.isBinded() != true)
|
||||
mAllowAutoOpenBiometricPrompt)
|
||||
}
|
||||
|
||||
enableOrNotTheConfirmationButton()
|
||||
@@ -439,6 +442,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
|
||||
populatePasswordTextView(null)
|
||||
if (clearKeyFile) {
|
||||
mDatabaseKeyFileUri = null
|
||||
populateKeyFileTextView(null)
|
||||
}
|
||||
}
|
||||
@@ -468,10 +472,8 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mProgressDatabaseTaskProvider?.unregisterProgressTask()
|
||||
|
||||
// Reinit locking activity UI variable
|
||||
LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
|
||||
DatabaseLockActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
|
||||
mAllowAutoOpenBiometricPrompt = true
|
||||
|
||||
super.onPause()
|
||||
@@ -482,7 +484,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
mDatabaseKeyFileUri?.let {
|
||||
outState.putString(KEY_KEYFILE, it.toString())
|
||||
}
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
|
||||
outState.putBoolean(KEY_READ_ONLY, mReadOnly)
|
||||
outState.putBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT, false)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
@@ -520,7 +522,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
clearCredentialsViews()
|
||||
}
|
||||
|
||||
if (readOnly && (
|
||||
if (mReadOnly && (
|
||||
mSpecialMode == SpecialMode.SAVE
|
||||
|| mSpecialMode == SpecialMode.REGISTRATION)
|
||||
) {
|
||||
@@ -534,7 +536,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
showProgressDialogAndLoadDatabase(
|
||||
databaseUri,
|
||||
MainCredential(password, keyFileUri),
|
||||
readOnly,
|
||||
mReadOnly,
|
||||
cipherDatabaseEntity,
|
||||
false)
|
||||
}
|
||||
@@ -546,7 +548,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
readOnly: Boolean,
|
||||
cipherDatabaseEntity: CipherDatabaseEntity?,
|
||||
fixDuplicateUUID: Boolean) {
|
||||
mProgressDatabaseTaskProvider?.startDatabaseLoad(
|
||||
loadDatabase(
|
||||
databaseUri,
|
||||
mainCredential,
|
||||
readOnly,
|
||||
@@ -585,7 +587,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
// Check permission
|
||||
private fun checkPermission() {
|
||||
if (Build.VERSION.SDK_INT in 23..28
|
||||
&& !readOnly
|
||||
&& !mReadOnly
|
||||
&& !mPermissionAsked) {
|
||||
mPermissionAsked = true
|
||||
// Check self permission to show or not the dialog
|
||||
@@ -662,7 +664,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
}
|
||||
|
||||
private fun changeOpenFileReadIcon(togglePassword: MenuItem) {
|
||||
if (readOnly) {
|
||||
if (mReadOnly) {
|
||||
togglePassword.setTitle(R.string.menu_file_selection_read_only)
|
||||
togglePassword.setIcon(R.drawable.ic_read_only_white_24dp)
|
||||
} else {
|
||||
@@ -676,7 +678,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> finish()
|
||||
R.id.menu_open_file_read_mode_key -> {
|
||||
readOnly = !readOnly
|
||||
mReadOnly = !mReadOnly
|
||||
changeOpenFileReadIcon(item)
|
||||
}
|
||||
else -> MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
||||
@@ -703,8 +705,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
|
||||
var keyFileResult = false
|
||||
mExternalFileHelper?.let {
|
||||
keyFileResult = it.onOpenDocumentResult(requestCode, resultCode, data
|
||||
) { uri ->
|
||||
keyFileResult = it.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
|
||||
if (uri != null) {
|
||||
mDatabaseKeyFileUri = uri
|
||||
populateKeyFileTextView(uri)
|
||||
@@ -714,9 +715,9 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
if (!keyFileResult) {
|
||||
// this block if not a key file response
|
||||
when (resultCode) {
|
||||
LockingActivity.RESULT_EXIT_LOCK -> {
|
||||
DatabaseLockActivity.RESULT_EXIT_LOCK -> {
|
||||
clearCredentialsViews()
|
||||
mDatabase?.clearAndClose(this)
|
||||
closeDatabase()
|
||||
}
|
||||
Activity.RESULT_CANCELED -> {
|
||||
clearCredentialsViews()
|
||||
@@ -735,6 +736,7 @@ open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.Buil
|
||||
private const val KEY_KEYFILE = "keyFile"
|
||||
private const val VIEW_INTENT = "android.intent.action.VIEW"
|
||||
|
||||
private const val KEY_READ_ONLY = "KEY_READ_ONLY"
|
||||
private const val KEY_PASSWORD = "password"
|
||||
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
|
||||
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"
|
||||
|
||||
@@ -32,7 +32,6 @@ import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
@@ -41,7 +40,7 @@ import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||
|
||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||
class AssignMasterKeyDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var mMasterPassword: String? = null
|
||||
private var mKeyFile: Uri? = null
|
||||
|
||||
@@ -23,12 +23,11 @@ import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
|
||||
|
||||
|
||||
class DatabaseChangedDialogFragment : DialogFragment() {
|
||||
class DatabaseChangedDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
var actionDatabaseListener: ActionDatabaseChangedListener? = null
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||
|
||||
abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
|
||||
|
||||
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
|
||||
private var mDatabase: Database? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
mDatabaseViewModel.database.observe(this) { database ->
|
||||
this.mDatabase = database
|
||||
resetAppTimeoutWhenViewFocusedOrChanged()
|
||||
onDatabaseRetrieved(database)
|
||||
}
|
||||
|
||||
mDatabaseViewModel.actionFinished.observe(this) { result ->
|
||||
onDatabaseActionFinished(result.database, result.actionTask, result.result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
resetAppTimeoutWhenViewFocusedOrChanged()
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
// Can be overridden by a subclass
|
||||
}
|
||||
|
||||
override fun onDatabaseActionFinished(
|
||||
database: Database,
|
||||
actionTask: String,
|
||||
result: ActionRunnable.Result
|
||||
) {
|
||||
// Can be overridden by a subclass
|
||||
}
|
||||
|
||||
private fun resetAppTimeoutWhenViewFocusedOrChanged() {
|
||||
context?.let {
|
||||
dialog?.window?.decorView?.resetAppTimeoutWhenViewFocusedOrChanged(it, mDatabase?.loaded)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,8 @@ import android.app.DatePickerDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
|
||||
class DatePickerFragment : DialogFragment() {
|
||||
class DatePickerFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var mDefaultYear: Int = 2000
|
||||
private var mDefaultMonth: Int = 1
|
||||
|
||||
@@ -20,63 +20,38 @@
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
|
||||
import com.kunzisoft.keepass.viewmodels.NodesViewModel
|
||||
|
||||
open class DeleteNodesDialogFragment : DialogFragment() {
|
||||
class DeleteNodesDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var mNodesToDelete: List<Node> = ArrayList()
|
||||
private var mListener: DeleteNodeListener? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
mListener = context as DeleteNodeListener
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + DeleteNodeListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mListener = null
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
protected open fun retrieveMessage(): String {
|
||||
return getString(R.string.warning_permanently_delete_nodes)
|
||||
}
|
||||
private var mNodesToDelete: List<Node> = listOf()
|
||||
private val mNodesViewModel: NodesViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
||||
val database = Database.getInstance()
|
||||
|
||||
mNodesViewModel.nodesToDelete.observe(this) { nodes ->
|
||||
this.mNodesToDelete = nodes
|
||||
}
|
||||
var recycleBin = false
|
||||
arguments?.apply {
|
||||
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
|
||||
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
|
||||
mNodesToDelete = getListNodesFromBundle(database, this)
|
||||
}
|
||||
} ?: savedInstanceState?.apply {
|
||||
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
|
||||
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
|
||||
mNodesToDelete = getListNodesFromBundle(database, savedInstanceState)
|
||||
if (containsKey(RECYCLE_BIN_TAG)) {
|
||||
recycleBin = this.getBoolean(RECYCLE_BIN_TAG)
|
||||
}
|
||||
}
|
||||
activity?.let { activity ->
|
||||
// Use the Builder class for convenient dialog construction
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
|
||||
builder.setMessage(retrieveMessage())
|
||||
builder.setMessage(if (recycleBin)
|
||||
getString(R.string.warning_empty_recycle_bin)
|
||||
else
|
||||
getString(R.string.warning_permanently_delete_nodes))
|
||||
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
mListener?.permanentlyDeleteNodes(mNodesToDelete)
|
||||
mNodesViewModel.permanentlyDeleteNodes(mNodesToDelete)
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
||||
// Create the AlertDialog object and return it
|
||||
@@ -85,19 +60,14 @@ open class DeleteNodesDialogFragment : DialogFragment() {
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putAll(getBundleFromListNodes(mNodesToDelete))
|
||||
}
|
||||
|
||||
interface DeleteNodeListener {
|
||||
fun permanentlyDeleteNodes(nodes: List<Node>)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(nodesToDelete: List<Node>): DeleteNodesDialogFragment {
|
||||
private const val RECYCLE_BIN_TAG = "RECYCLE_BIN_TAG"
|
||||
|
||||
fun getInstance(recycleBin: Boolean): DeleteNodesDialogFragment {
|
||||
return DeleteNodesDialogFragment().apply {
|
||||
arguments = getBundleFromListNodes(nodesToDelete)
|
||||
arguments = Bundle().apply {
|
||||
putBoolean(RECYCLE_BIN_TAG, recycleBin)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
|
||||
|
||||
class EmptyRecycleBinDialogFragment : DeleteNodesDialogFragment() {
|
||||
|
||||
override fun retrieveMessage(): String {
|
||||
return getString(R.string.warning_empty_recycle_bin)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(nodesToDelete: List<Node>): EmptyRecycleBinDialogFragment {
|
||||
return EmptyRecycleBinDialogFragment().apply {
|
||||
arguments = getBundleFromListNodes(nodesToDelete)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,14 +31,13 @@ import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.element.Field
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
|
||||
|
||||
class EntryCustomFieldDialogFragment: DialogFragment() {
|
||||
class EntryCustomFieldDialogFragment: DatabaseDialogFragment() {
|
||||
|
||||
private var oldField: Field? = null
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Field
|
||||
@@ -34,7 +33,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||
import com.kunzisoft.keepass.view.applyFontVisibility
|
||||
|
||||
class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
class GeneratePasswordDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var mListener: GeneratePasswordListener? = null
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
@@ -28,30 +27,29 @@ import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.IconPickerActivity
|
||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
|
||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
|
||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.*
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.model.GroupInfo
|
||||
import com.kunzisoft.keepass.view.DateTimeEditFieldView
|
||||
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
|
||||
import org.joda.time.DateTime
|
||||
|
||||
class GroupEditDialogFragment : DialogFragment() {
|
||||
class GroupEditDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var mDatabase: Database? = null
|
||||
private val mGroupEditViewModel: GroupEditViewModel by activityViewModels()
|
||||
|
||||
private var mEditGroupListener: EditGroupListener? = null
|
||||
|
||||
private var mEditGroupDialogAction = EditGroupDialogAction.NONE
|
||||
private var mPopulateIconMethod: ((ImageView, IconImage) -> Unit)? = null
|
||||
private var mEditGroupDialogAction = NONE
|
||||
private var mGroupInfo = GroupInfo()
|
||||
private var mGroupNamesNotAllowed: List<String>? = null
|
||||
|
||||
private lateinit var iconButtonView: ImageView
|
||||
private var iconColor: Int = 0
|
||||
private var mIconColor: Int = 0
|
||||
private lateinit var nameTextLayoutView: TextInputLayout
|
||||
private lateinit var nameTextView: TextView
|
||||
private lateinit var notesTextLayoutView: TextInputLayout
|
||||
@@ -68,22 +66,51 @@ class GroupEditDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
// Verify that the host activity implements the callback interface
|
||||
try {
|
||||
// Instantiate the NoticeDialogListener so we can send events to the host
|
||||
mEditGroupListener = context as EditGroupListener
|
||||
} catch (e: ClassCastException) {
|
||||
// The activity doesn't implement the interface, throw exception
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + GroupEditDialogFragment::class.java.name)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
mGroupEditViewModel.onIconSelected.observe(this) { iconImage ->
|
||||
mGroupInfo.icon = iconImage
|
||||
mPopulateIconMethod?.invoke(iconButtonView, mGroupInfo.icon)
|
||||
}
|
||||
|
||||
mGroupEditViewModel.onDateSelected.observe(this) { viewModelDate ->
|
||||
// Save the date
|
||||
mGroupInfo.expiryTime = DateInstant(
|
||||
DateTime(mGroupInfo.expiryTime.date)
|
||||
.withYear(viewModelDate.year)
|
||||
.withMonthOfYear(viewModelDate.month + 1)
|
||||
.withDayOfMonth(viewModelDate.day)
|
||||
.toDate())
|
||||
expirationView.dateTime = mGroupInfo.expiryTime
|
||||
if (expirationView.dateTime.type == DateInstant.Type.DATE_TIME) {
|
||||
val instantTime = DateInstant(mGroupInfo.expiryTime.date, DateInstant.Type.TIME)
|
||||
// Trick to recall selection with time
|
||||
mGroupEditViewModel.requestDateTimeSelection(instantTime)
|
||||
}
|
||||
}
|
||||
|
||||
mGroupEditViewModel.onTimeSelected.observe(this) { viewModelTime ->
|
||||
// Save the time
|
||||
mGroupInfo.expiryTime = DateInstant(
|
||||
DateTime(mGroupInfo.expiryTime.date)
|
||||
.withHourOfDay(viewModelTime.hours)
|
||||
.withMinuteOfHour(viewModelTime.minutes)
|
||||
.toDate(), mGroupInfo.expiryTime.type)
|
||||
expirationView.dateTime = mGroupInfo.expiryTime
|
||||
}
|
||||
|
||||
mGroupEditViewModel.groupNamesNotAllowed.observe(this) { namesNotAllowed ->
|
||||
this.mGroupNamesNotAllowed = namesNotAllowed
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mEditGroupListener = null
|
||||
super.onDetach()
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
mPopulateIconMethod = { imageView, icon ->
|
||||
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
|
||||
}
|
||||
mPopulateIconMethod?.invoke(iconButtonView, mGroupInfo.icon)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
@@ -98,12 +125,9 @@ class GroupEditDialogFragment : DialogFragment() {
|
||||
|
||||
// Retrieve the textColor to tint the icon
|
||||
val ta = activity.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
|
||||
iconColor = ta.getColor(0, Color.WHITE)
|
||||
mIconColor = 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)) {
|
||||
@@ -120,32 +144,22 @@ class GroupEditDialogFragment : DialogFragment() {
|
||||
}
|
||||
|
||||
// populate info in views
|
||||
populateInfoToViews()
|
||||
expirationView.setOnDateClickListener = {
|
||||
expirationView.dateTime.date.let { expiresDate ->
|
||||
val dateTime = DateTime(expiresDate)
|
||||
val defaultYear = dateTime.year
|
||||
val defaultMonth = dateTime.monthOfYear-1
|
||||
val defaultDay = dateTime.dayOfMonth
|
||||
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
|
||||
.show(parentFragmentManager, "DatePickerFragment")
|
||||
}
|
||||
populateInfoToViews(mGroupInfo)
|
||||
|
||||
iconButtonView.setOnClickListener { _ ->
|
||||
mGroupEditViewModel.requestIconSelection(mGroupInfo.icon)
|
||||
}
|
||||
expirationView.setOnDateClickListener = { dateInstant ->
|
||||
mGroupEditViewModel.requestDateTimeSelection(dateInstant)
|
||||
}
|
||||
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
builder.setView(root)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
retrieveGroupInfoFromViews()
|
||||
mEditGroupListener?.cancelEditGroup(
|
||||
mEditGroupDialogAction,
|
||||
mGroupInfo)
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
iconButtonView.setOnClickListener { _ ->
|
||||
IconPickerActivity.launch(activity, mGroupInfo.icon)
|
||||
}
|
||||
|
||||
return builder.create()
|
||||
}
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
@@ -155,40 +169,34 @@ class GroupEditDialogFragment : DialogFragment() {
|
||||
super.onResume()
|
||||
|
||||
// To prevent auto dismiss
|
||||
val d = dialog as AlertDialog?
|
||||
if (d != null) {
|
||||
val positiveButton = d.getButton(Dialog.BUTTON_POSITIVE) as Button
|
||||
val alertDialog = dialog as AlertDialog?
|
||||
if (alertDialog != null) {
|
||||
val positiveButton = alertDialog.getButton(Dialog.BUTTON_POSITIVE) as Button
|
||||
positiveButton.setOnClickListener {
|
||||
retrieveGroupInfoFromViews()
|
||||
if (isValid()) {
|
||||
mEditGroupListener?.approveEditGroup(
|
||||
mEditGroupDialogAction,
|
||||
mGroupInfo)
|
||||
d.dismiss()
|
||||
when (mEditGroupDialogAction) {
|
||||
CREATION ->
|
||||
mGroupEditViewModel.approveGroupCreation(mGroupInfo)
|
||||
UPDATE ->
|
||||
mGroupEditViewModel.approveGroupUpdate(mGroupInfo)
|
||||
NONE -> {}
|
||||
}
|
||||
alertDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getExpiryTime(): DateInstant {
|
||||
retrieveGroupInfoFromViews()
|
||||
return mGroupInfo.expiryTime
|
||||
}
|
||||
|
||||
fun setExpiryTime(expiryTime: DateInstant) {
|
||||
mGroupInfo.expiryTime = expiryTime
|
||||
populateInfoToViews()
|
||||
}
|
||||
|
||||
private fun populateInfoToViews() {
|
||||
assignIconView()
|
||||
nameTextView.text = mGroupInfo.title
|
||||
notesTextLayoutView.visibility = if (mGroupInfo.notes == null) View.GONE else View.VISIBLE
|
||||
mGroupInfo.notes?.let {
|
||||
private fun populateInfoToViews(groupInfo: GroupInfo) {
|
||||
mGroupEditViewModel.selectIcon(groupInfo.icon)
|
||||
nameTextView.text = groupInfo.title
|
||||
notesTextLayoutView.visibility = if (groupInfo.notes == null) View.GONE else View.VISIBLE
|
||||
groupInfo.notes?.let {
|
||||
notesTextView.text = it
|
||||
}
|
||||
expirationView.activation = mGroupInfo.expires
|
||||
expirationView.dateTime = mGroupInfo.expiryTime
|
||||
expirationView.activation = groupInfo.expires
|
||||
expirationView.dateTime = groupInfo.expiryTime
|
||||
}
|
||||
|
||||
private fun retrieveGroupInfoFromViews() {
|
||||
@@ -202,15 +210,6 @@ class GroupEditDialogFragment : DialogFragment() {
|
||||
mGroupInfo.expiryTime = expirationView.dateTime
|
||||
}
|
||||
|
||||
private fun assignIconView() {
|
||||
mDatabase?.iconDrawableFactory?.assignDatabaseIcon(iconButtonView, mGroupInfo.icon, iconColor)
|
||||
}
|
||||
|
||||
fun setIcon(icon: IconImage) {
|
||||
mGroupInfo.icon = icon
|
||||
assignIconView()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
retrieveGroupInfoFromViews()
|
||||
outState.putInt(KEY_ACTION_ID, mEditGroupDialogAction.ordinal)
|
||||
@@ -219,7 +218,21 @@ class GroupEditDialogFragment : DialogFragment() {
|
||||
}
|
||||
|
||||
private fun isValid(): Boolean {
|
||||
val error = mEditGroupListener?.isValidGroupName(nameTextView.text.toString()) ?: Error(false, null)
|
||||
val name = nameTextView.text.toString()
|
||||
val error = when {
|
||||
name.isEmpty() -> {
|
||||
Error(true, R.string.error_no_name)
|
||||
}
|
||||
mGroupNamesNotAllowed == null -> {
|
||||
Error(true, R.string.error_word_reserved)
|
||||
}
|
||||
mGroupNamesNotAllowed?.find { it.equals(name, ignoreCase = true) } != null -> {
|
||||
Error(true, R.string.error_word_reserved)
|
||||
}
|
||||
else -> {
|
||||
Error(false, null)
|
||||
}
|
||||
}
|
||||
error.messageId?.let { messageId ->
|
||||
nameTextLayoutView.error = getString(messageId)
|
||||
} ?: kotlin.run {
|
||||
@@ -230,14 +243,6 @@ class GroupEditDialogFragment : DialogFragment() {
|
||||
|
||||
data class Error(val isError: Boolean, val messageId: Int?)
|
||||
|
||||
interface EditGroupListener {
|
||||
fun isValidGroupName(name: String): Error
|
||||
fun approveEditGroup(action: EditGroupDialogAction,
|
||||
groupInfo: GroupInfo)
|
||||
fun cancelEditGroup(action: EditGroupDialogAction,
|
||||
groupInfo: GroupInfo)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val TAG_CREATE_GROUP = "TAG_CREATE_GROUP"
|
||||
|
||||
@@ -25,14 +25,13 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
|
||||
/**
|
||||
* Custom Dialog to confirm big file to upload
|
||||
*/
|
||||
class ReplaceFileDialogFragment : DialogFragment() {
|
||||
class ReplaceFileDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var mActionChooseListener: ActionChooseListener? = null
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
@@ -49,7 +48,7 @@ import com.kunzisoft.keepass.otp.TokenCalculator
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import java.util.*
|
||||
|
||||
class SetOTPDialogFragment : DialogFragment() {
|
||||
class SetOTPDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var mCreateOTPElementListener: CreateOtpListener? = null
|
||||
|
||||
|
||||
@@ -22,16 +22,15 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.RadioGroup
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||
|
||||
class SortDialogFragment : DialogFragment() {
|
||||
class SortDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var mListener: SortSelectionListener? = null
|
||||
|
||||
|
||||
@@ -6,9 +6,8 @@ import android.app.TimePickerDialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateFormat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
|
||||
class TimePickerFragment : DialogFragment() {
|
||||
class TimePickerFragment : DatabaseDialogFragment() {
|
||||
|
||||
private var defaultHour: Int = 0
|
||||
private var defaultMinute: Int = 0
|
||||
|
||||
@@ -1,15 +1,51 @@
|
||||
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.legacy.DatabaseRetrieval
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||
|
||||
abstract class DatabaseFragment : StylishFragment() {
|
||||
abstract class DatabaseFragment : StylishFragment(), DatabaseRetrieval {
|
||||
|
||||
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
|
||||
protected var mDatabase: Database? = null
|
||||
|
||||
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 ->
|
||||
if (mDatabase == null || mDatabase != database) {
|
||||
this.mDatabase = database
|
||||
onDatabaseRetrieved(database)
|
||||
}
|
||||
}
|
||||
|
||||
mDatabaseViewModel.actionFinished.observe(viewLifecycleOwner) { result ->
|
||||
onDatabaseActionFinished(result.database, result.actionTask, result.result)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun resetAppTimeoutWhenViewFocusedOrChanged(view: View?) {
|
||||
context?.let {
|
||||
view?.resetAppTimeoutWhenViewFocusedOrChanged(it, mDatabase?.loaded)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDatabaseActionFinished(
|
||||
database: Database,
|
||||
actionTask: String,
|
||||
result: ActionRunnable.Result
|
||||
) {
|
||||
// Can be overridden by a subclass
|
||||
}
|
||||
|
||||
protected fun buildNewBinaryAttachment(): BinaryData? {
|
||||
return mDatabase?.buildNewBinaryAttachment()
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@@ -33,15 +32,13 @@ import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.ReplaceFileDialogFragment
|
||||
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.icons.IconDrawableFactory
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.StreamDirection
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.TemplateEditView
|
||||
import com.kunzisoft.keepass.view.collapse
|
||||
import com.kunzisoft.keepass.view.expand
|
||||
@@ -50,10 +47,6 @@ import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
|
||||
|
||||
class EntryEditFragment: DatabaseFragment() {
|
||||
|
||||
private var iconColor: Int = 0
|
||||
|
||||
var drawFactory: IconDrawableFactory? = null
|
||||
|
||||
private val mEntryEditViewModel: EntryEditViewModel by activityViewModels()
|
||||
|
||||
private lateinit var rootView: View
|
||||
@@ -62,11 +55,20 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
private lateinit var attachmentsListView: RecyclerView
|
||||
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
|
||||
|
||||
private var mAllowMultipleAttachments: Boolean = false
|
||||
|
||||
private var mIconColor: Int = 0
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
// Retrieve the textColor to tint the icon
|
||||
val taIconColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
|
||||
mIconColor = taIconColor?.getColor(0, Color.BLACK) ?: Color.BLACK
|
||||
taIconColor?.recycle()
|
||||
|
||||
return inflater.cloneInContext(contextThemed)
|
||||
.inflate(R.layout.fragment_entry_edit, container, false)
|
||||
}
|
||||
@@ -75,11 +77,6 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Retrieve the textColor to tint the icon
|
||||
val taIconColor = contextThemed?.theme?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
|
||||
iconColor = taIconColor?.getColor(0, Color.WHITE) ?: Color.WHITE
|
||||
taIconColor?.recycle()
|
||||
|
||||
rootView = view
|
||||
// Hide only the first time
|
||||
if (savedInstanceState == null) {
|
||||
@@ -89,12 +86,14 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
attachmentsContainerView = view.findViewById(R.id.entry_attachments_container)
|
||||
attachmentsListView = view.findViewById(R.id.entry_attachments_list)
|
||||
|
||||
view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), mDatabase)
|
||||
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
|
||||
attachmentsListView.apply {
|
||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
adapter = attachmentsAdapter
|
||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
}
|
||||
|
||||
templateView.apply {
|
||||
populateIconMethod = { imageView, icon ->
|
||||
drawFactory?.assignDatabaseIcon(imageView, icon, iconColor)
|
||||
}
|
||||
setOnIconClickListener {
|
||||
mEntryEditViewModel.requestIconSelection(templateView.getIcon())
|
||||
}
|
||||
@@ -109,20 +108,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()
|
||||
@@ -140,10 +125,12 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
}
|
||||
// To prevent flickering
|
||||
rootView.showByFading()
|
||||
// Apply timeout reset
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(rootView)
|
||||
}
|
||||
|
||||
mEntryEditViewModel.requestEntryInfoUpdate.observe(viewLifecycleOwner) {
|
||||
mEntryEditViewModel.saveEntryInfo(retrieveEntryInfo())
|
||||
mEntryEditViewModel.saveEntryInfo(it.database, it.entry, it.parent, retrieveEntryInfo())
|
||||
}
|
||||
|
||||
mEntryEditViewModel.onIconSelected.observe(viewLifecycleOwner) { iconImage ->
|
||||
@@ -206,10 +193,11 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
mEntryEditViewModel.onBuildNewAttachment.observe(viewLifecycleOwner) {
|
||||
val attachmentToUploadUri = it.attachmentToUploadUri
|
||||
val fileName = it.fileName
|
||||
mDatabase?.buildNewBinaryAttachment()?.let { binaryAttachment ->
|
||||
|
||||
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)
|
||||
@@ -224,17 +212,17 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
when (entryAttachmentState?.downloadState) {
|
||||
AttachmentState.START -> {
|
||||
putAttachment(entryAttachmentState)
|
||||
getAttachmentViewPosition(entryAttachmentState) {
|
||||
mEntryEditViewModel.binaryPreviewLoaded(entryAttachmentState, it)
|
||||
getAttachmentViewPosition(entryAttachmentState) { attachment, position ->
|
||||
mEntryEditViewModel.binaryPreviewLoaded(attachment, position)
|
||||
}
|
||||
}
|
||||
AttachmentState.IN_PROGRESS -> {
|
||||
putAttachment(entryAttachmentState)
|
||||
}
|
||||
AttachmentState.COMPLETE -> {
|
||||
putAttachment(entryAttachmentState) {
|
||||
getAttachmentViewPosition(entryAttachmentState) {
|
||||
mEntryEditViewModel.binaryPreviewLoaded(entryAttachmentState, it)
|
||||
putAttachment(entryAttachmentState) { entryAttachment ->
|
||||
getAttachmentViewPosition(entryAttachment) { attachment, position ->
|
||||
mEntryEditViewModel.binaryPreviewLoaded(attachment, position)
|
||||
}
|
||||
}
|
||||
mEntryEditViewModel.onAttachmentAction(null)
|
||||
@@ -249,16 +237,22 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
|
||||
drawFactory = mDatabase?.iconDrawableFactory
|
||||
}
|
||||
templateView.populateIconMethod = { imageView, icon ->
|
||||
database?.iconDrawableFactory?.assignDatabaseIcon(imageView, icon, mIconColor)
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
super.onDetach()
|
||||
mAllowMultipleAttachments = database?.allowMultipleAttachments == true
|
||||
|
||||
drawFactory = null
|
||||
attachmentsAdapter?.database = database
|
||||
attachmentsAdapter?.onListSizeChangedListener = { previousSize, newSize ->
|
||||
if (previousSize > 0 && newSize == 0) {
|
||||
attachmentsContainerView.collapse(true)
|
||||
} else if (previousSize == 0 && newSize == 1) {
|
||||
attachmentsContainerView.expand(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignEntryInfo(entryInfo: EntryInfo?) {
|
||||
@@ -305,15 +299,15 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
}
|
||||
|
||||
private fun putAttachment(attachment: EntryAttachmentState,
|
||||
onPreviewLoaded: (() -> Unit)? = null) {
|
||||
onPreviewLoaded: ((attachment: EntryAttachmentState) -> Unit)? = null) {
|
||||
// When only one attachment is allowed
|
||||
if (mDatabase?.allowMultipleAttachments == false) {
|
||||
if (!mAllowMultipleAttachments) {
|
||||
clearAttachments()
|
||||
}
|
||||
attachmentsContainerView.visibility = View.VISIBLE
|
||||
attachmentsAdapter?.putItem(attachment)
|
||||
attachmentsAdapter?.onBinaryPreviewLoaded = {
|
||||
onPreviewLoaded?.invoke()
|
||||
onPreviewLoaded?.invoke(attachment)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,10 +319,12 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
attachmentsAdapter?.clear()
|
||||
}
|
||||
|
||||
private fun getAttachmentViewPosition(attachment: EntryAttachmentState, position: (Float) -> Unit) {
|
||||
private fun getAttachmentViewPosition(attachment: EntryAttachmentState,
|
||||
position: (attachment: EntryAttachmentState, Float) -> Unit) {
|
||||
attachmentsListView.postDelayed({
|
||||
attachmentsAdapter?.indexOf(attachment)?.let { index ->
|
||||
position.invoke(attachmentsContainerView.y
|
||||
position.invoke(attachment,
|
||||
attachmentsContainerView.y
|
||||
+ attachmentsListView.y
|
||||
+ (attachmentsListView.getChildAt(index)?.y
|
||||
?: 0F)
|
||||
|
||||
@@ -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
|
||||
@@ -79,7 +78,6 @@ class EntryFragment: DatabaseFragment() {
|
||||
attachmentsListView = view.findViewById(R.id.entry_attachments_list)
|
||||
attachmentsListView.apply {
|
||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
adapter = attachmentsAdapter
|
||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
}
|
||||
|
||||
@@ -101,6 +99,7 @@ class EntryFragment: DatabaseFragment() {
|
||||
assignEntryInfo(entryInfo)
|
||||
// Smooth appearing
|
||||
rootView.showByFading()
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(rootView)
|
||||
}
|
||||
|
||||
mEntryViewModel.onAttachmentAction.observe(viewLifecycleOwner) { entryAttachmentState ->
|
||||
@@ -112,6 +111,15 @@ class EntryFragment: DatabaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
context?.let { context ->
|
||||
attachmentsAdapter = EntryAttachmentsItemsAdapter(context)
|
||||
attachmentsAdapter?.database = database
|
||||
}
|
||||
|
||||
attachmentsListView.adapter = attachmentsAdapter
|
||||
}
|
||||
|
||||
private fun loadTemplateSettings() {
|
||||
context?.let { context ->
|
||||
templateView.setFirstTimeAskAllowCopyProtectedFields(PreferencesUtil.isFirstTimeAskAllowCopyProtectedFields(context))
|
||||
|
||||
@@ -25,32 +25,40 @@ 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 androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.EntryEditActivity
|
||||
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
|
||||
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.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.viewmodels.GroupViewModel
|
||||
import java.util.*
|
||||
|
||||
class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListener {
|
||||
class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListener {
|
||||
|
||||
private var nodeClickListener: NodeClickListener? = null
|
||||
private var onScrollListener: OnScrollListener? = null
|
||||
|
||||
private var mNodesRecyclerView: RecyclerView? = null
|
||||
var mainGroup: Group? = null
|
||||
private set
|
||||
private var mLayoutManager: LinearLayoutManager? = null
|
||||
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
|
||||
@@ -61,12 +69,27 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
|
||||
private var notFoundView: View? = null
|
||||
private var isASearchResult: Boolean = false
|
||||
|
||||
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
|
||||
|
||||
private var mRecycleViewScrollListener = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
if (newState == SCROLL_STATE_IDLE) {
|
||||
mGroupViewModel.assignPosition(getFirstVisiblePosition())
|
||||
}
|
||||
}
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
onScrollListener?.onScrolled(dy)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
try {
|
||||
@@ -97,130 +120,134 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
|
||||
super.onSaveInstanceState(outState)
|
||||
override fun onDatabaseActionFinished(
|
||||
database: Database,
|
||||
actionTask: String,
|
||||
result: ActionRunnable.Result
|
||||
) {
|
||||
super.onDatabaseActionFinished(database, actionTask, result)
|
||||
|
||||
// Too many special cases to make specific additions or deletions,
|
||||
// rebuilt the list works well.
|
||||
if (result.isSuccess) {
|
||||
rebuildList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
// To apply theme
|
||||
val rootView = inflater.cloneInContext(contextThemed)
|
||||
.inflate(R.layout.fragment_list_nodes, container, false)
|
||||
mNodesRecyclerView = rootView.findViewById(R.id.nodes_list)
|
||||
notFoundView = rootView.findViewById(R.id.not_found_container)
|
||||
return inflater.cloneInContext(contextThemed)
|
||||
.inflate(R.layout.fragment_group, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
mNodesRecyclerView = view.findViewById(R.id.nodes_list)
|
||||
notFoundView = view.findViewById(R.id.not_found_container)
|
||||
|
||||
mLayoutManager = LinearLayoutManager(context)
|
||||
mNodesRecyclerView?.apply {
|
||||
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
layoutManager = mLayoutManager
|
||||
adapter = mAdapter
|
||||
}
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(view)
|
||||
|
||||
onScrollListener?.let { onScrollListener ->
|
||||
mNodesRecyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
onScrollListener.onScrolled(dy)
|
||||
}
|
||||
})
|
||||
mGroupViewModel.group.observe(viewLifecycleOwner) {
|
||||
mCurrentGroup = it.group
|
||||
isASearchResult = it.group.isVirtual
|
||||
rebuildList()
|
||||
it.showFromPosition?.let { position ->
|
||||
mNodesRecyclerView?.scrollToPosition(position)
|
||||
}
|
||||
}
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
mNodesRecyclerView?.addOnScrollListener(mRecycleViewScrollListener)
|
||||
activity?.intent?.let {
|
||||
specialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(it)
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh data
|
||||
try {
|
||||
rebuildList()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to rebuild the list during resume")
|
||||
e.printStackTrace()
|
||||
}
|
||||
override fun onPause() {
|
||||
|
||||
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
|
||||
// To show the " no search entry found "
|
||||
mNodesRecyclerView?.visibility = View.GONE
|
||||
notFoundView?.visibility = View.VISIBLE
|
||||
} else {
|
||||
mNodesRecyclerView?.visibility = View.VISIBLE
|
||||
notFoundView?.visibility = View.GONE
|
||||
}
|
||||
mNodesRecyclerView?.removeOnScrollListener(mRecycleViewScrollListener)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
fun getFirstVisiblePosition(): Int {
|
||||
return mLayoutManager?.findFirstVisibleItemPosition() ?: 0
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun rebuildList() {
|
||||
// Add elements to the list
|
||||
mainGroup?.let { mainGroup ->
|
||||
mAdapter?.apply {
|
||||
private fun rebuildList() {
|
||||
try {
|
||||
// Add elements to the list
|
||||
mCurrentGroup?.let { mainGroup ->
|
||||
// Thrown an exception when sort cannot be performed
|
||||
rebuildList(mainGroup)
|
||||
// To visually change the elements
|
||||
if (PreferencesUtil.APPEARANCE_CHANGED) {
|
||||
notifyDataSetChanged()
|
||||
PreferencesUtil.APPEARANCE_CHANGED = false
|
||||
}
|
||||
mAdapter?.rebuildList(mainGroup)
|
||||
}
|
||||
} catch (e:Exception) {
|
||||
Log.e(TAG, "Unable to rebuild the list", e)
|
||||
}
|
||||
|
||||
if (isASearchResult && mAdapter != null && mAdapter!!.isEmpty) {
|
||||
// To show the " no search entry found "
|
||||
notFoundView?.visibility = View.VISIBLE
|
||||
} else {
|
||||
notFoundView?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,8 +263,7 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
|
||||
mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
|
||||
rebuildList()
|
||||
} catch (e:Exception) {
|
||||
Log.e(TAG, "Unable to rebuild the list with the sort")
|
||||
e.printStackTrace()
|
||||
Log.e(TAG, "Unable to sort the list", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +279,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,94 +301,92 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
|
||||
}
|
||||
}
|
||||
|
||||
fun actionNodesCallback(nodes: List<Node>,
|
||||
fun actionNodesCallback(database: Database,
|
||||
nodes: List<Node>,
|
||||
menuListener: NodesActionMenuListener?,
|
||||
actionModeCallback: ActionMode.Callback) : ActionMode.Callback {
|
||||
onDestroyActionMode: (mode: ActionMode?) -> Unit) : ActionMode.Callback {
|
||||
|
||||
return object : ActionMode.Callback {
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
nodeActionSelectionMode = false
|
||||
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||
return actionModeCallback.onCreateActionMode(mode, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
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 (database.isReadOnly
|
||||
|| (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 (database.isReadOnly
|
||||
|| isASearchResult) {
|
||||
menu?.removeItem(R.id.menu_move)
|
||||
}
|
||||
|
||||
// Copy (not allowed for group)
|
||||
if (database.isReadOnly
|
||||
|| isASearchResult
|
||||
|| nodes.any { it.type == Type.GROUP }) {
|
||||
menu?.removeItem(R.id.menu_copy)
|
||||
}
|
||||
|
||||
// Deletion
|
||||
if (database.isReadOnly
|
||||
|| (mRecycleBinEnable && nodes.any { it == mRecycleBin })) {
|
||||
menu?.removeItem(R.id.menu_delete)
|
||||
}
|
||||
}
|
||||
return actionModeCallback.onPrepareActionMode(mode, menu)
|
||||
|
||||
// Add the number of items selected in title
|
||||
mode?.title = nodes.size.toString()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
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
|
||||
}
|
||||
else -> actionModeCallback.onActionItemClicked(mode, item)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +396,7 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
|
||||
mAdapter?.unselectActionNodes()
|
||||
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||
nodeActionSelectionMode = false
|
||||
actionModeCallback.onDestroyActionMode(mode)
|
||||
onDestroyActionMode(mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,71 +408,39 @@ class ListNodesFragment : DatabaseFragment(), SortDialogFragment.SortSelectionLi
|
||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
|
||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|
||||
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||
data?.getParcelableExtra<Node>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { changedNode ->
|
||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
|
||||
addNode(changedNode)
|
||||
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE)
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
data?.getParcelableExtra<NodeId<UUID>>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let {
|
||||
// Simply refresh the list
|
||||
rebuildList()
|
||||
// Scroll to the new entry
|
||||
mDatabase?.getEntryById(it)?.let { entry ->
|
||||
mAdapter?.indexOf(entry)?.let { position ->
|
||||
mNodesRecyclerView?.scrollToPosition(position)
|
||||
}
|
||||
}
|
||||
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun contains(node: Node): Boolean {
|
||||
return mAdapter?.contains(node) ?: false
|
||||
}
|
||||
|
||||
fun addNode(newNode: Node) {
|
||||
mAdapter?.addNode(newNode)
|
||||
}
|
||||
|
||||
fun addNodes(newNodes: List<Node>) {
|
||||
mAdapter?.addNodes(newNodes)
|
||||
}
|
||||
|
||||
fun updateNode(oldNode: Node, newNode: Node? = null) {
|
||||
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
|
||||
}
|
||||
|
||||
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
|
||||
mAdapter?.updateNodes(oldNodes, newNodes)
|
||||
}
|
||||
|
||||
fun removeNode(pwNode: Node) {
|
||||
mAdapter?.removeNode(pwNode)
|
||||
}
|
||||
|
||||
fun removeNodes(nodes: List<Node>) {
|
||||
mAdapter?.removeNodes(nodes)
|
||||
}
|
||||
|
||||
fun removeNodeAt(position: Int) {
|
||||
mAdapter?.removeNodeAt(position)
|
||||
}
|
||||
|
||||
fun removeNodesAt(positions: IntArray) {
|
||||
mAdapter?.removeNodesAt(positions)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +459,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
|
||||
}
|
||||
private val TAG = GroupFragment::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@@ -29,6 +28,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,24 +47,37 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
|
||||
|
||||
abstract fun retrieveMainLayoutId(): Int
|
||||
|
||||
abstract fun defineIconList()
|
||||
abstract fun defineIconList(database: Database?)
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
override fun onCreateView(inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View {
|
||||
return inflater.inflate(retrieveMainLayoutId(), container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// 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
|
||||
}
|
||||
iconsGridView = view.findViewById(R.id.icons_grid_view)
|
||||
iconPickerAdapter = IconPickerAdapter(requireContext(), tintColor)
|
||||
iconPickerAdapter.iconPickerListener = this
|
||||
iconsGridView.adapter = iconPickerAdapter
|
||||
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(view)
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
iconPickerAdapter.iconDrawableFactory = database?.iconDrawableFactory
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val populateList = launch {
|
||||
iconPickerAdapter.clear()
|
||||
defineIconList()
|
||||
defineIconList(database)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
populateList.join()
|
||||
@@ -73,21 +86,6 @@ abstract class IconFragment<T: IconImageDraw> : DatabaseFragment(),
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View {
|
||||
val root = inflater.inflate(retrieveMainLayoutId(), container, false)
|
||||
iconsGridView = root.findViewById(R.id.icons_grid_view)
|
||||
iconsGridView.adapter = iconPickerAdapter
|
||||
return root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
iconPickerAdapter.iconPickerListener = this
|
||||
}
|
||||
|
||||
fun onIconDeleteClicked() {
|
||||
iconActionSelectionMode = false
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ 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() {
|
||||
|
||||
private var iconPickerPagerAdapter: IconPickerPagerAdapter? = null
|
||||
private lateinit var viewPager: ViewPager2
|
||||
private lateinit var tabLayout: TabLayout
|
||||
|
||||
private val iconPickerViewModel: IconPickerViewModel by activityViewModels()
|
||||
|
||||
@@ -28,17 +30,11 @@ class IconPickerFragment : DatabaseFragment() {
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
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()
|
||||
tabLayout = view.findViewById(R.id.icon_picker_tabs)
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(view)
|
||||
|
||||
arguments?.apply {
|
||||
if (containsKey(ICON_TAB_ARG)) {
|
||||
@@ -52,6 +48,18 @@ class IconPickerFragment : DatabaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
|
||||
object ReadOnlyHelper {
|
||||
|
||||
private const val READ_ONLY_KEY = "READ_ONLY_KEY"
|
||||
|
||||
const val READ_ONLY_DEFAULT = false
|
||||
|
||||
fun retrieveReadOnlyFromIntent(intent: Intent): Boolean {
|
||||
return intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT)
|
||||
}
|
||||
|
||||
fun retrieveReadOnlyFromInstanceStateOrPreference(context: Context, savedInstanceState: Bundle?): Boolean {
|
||||
return if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {
|
||||
savedInstanceState.getBoolean(READ_ONLY_KEY)
|
||||
} else {
|
||||
PreferencesUtil.enableReadOnlyDatabase(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState: Bundle?, arguments: Bundle?): Boolean {
|
||||
var readOnly = READ_ONLY_DEFAULT
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {
|
||||
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY)
|
||||
} else if (arguments != null && arguments.containsKey(READ_ONLY_KEY)) {
|
||||
readOnly = arguments.getBoolean(READ_ONLY_KEY)
|
||||
}
|
||||
return readOnly
|
||||
}
|
||||
|
||||
fun retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState: Bundle?, intent: Intent?): Boolean {
|
||||
var readOnly = READ_ONLY_DEFAULT
|
||||
if (savedInstanceState != null && savedInstanceState.containsKey(READ_ONLY_KEY)) {
|
||||
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY)
|
||||
} else {
|
||||
if (intent != null)
|
||||
readOnly = intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT)
|
||||
}
|
||||
return readOnly
|
||||
}
|
||||
|
||||
fun putReadOnlyInIntent(intent: Intent, readOnly: Boolean) {
|
||||
intent.putExtra(READ_ONLY_KEY, readOnly)
|
||||
}
|
||||
|
||||
fun putReadOnlyInBundle(bundle: Bundle, readOnly: Boolean) {
|
||||
bundle.putBoolean(READ_ONLY_KEY, readOnly)
|
||||
}
|
||||
|
||||
fun onSaveInstanceState(outState: Bundle, readOnly: Boolean) {
|
||||
outState.putBoolean(READ_ONLY_KEY, readOnly)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.kunzisoft.keepass.activities.legacy
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.database.element.Group
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||
import java.util.*
|
||||
|
||||
abstract class DatabaseActivity: StylishActivity(), DatabaseRetrieval {
|
||||
|
||||
protected val mDatabaseViewModel: DatabaseViewModel by viewModels()
|
||||
protected var mDatabaseTaskProvider: DatabaseTaskProvider? = null
|
||||
protected var mDatabase: Database? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
mDatabaseTaskProvider = DatabaseTaskProvider(this)
|
||||
|
||||
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
|
||||
if (mDatabase == null || mDatabase != database) {
|
||||
onDatabaseRetrieved(database)
|
||||
}
|
||||
}
|
||||
mDatabaseTaskProvider?.onActionFinish = { database, actionTask, result ->
|
||||
onDatabaseActionFinished(database, actionTask, result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
mDatabase = database
|
||||
mDatabaseViewModel.defineDatabase(database)
|
||||
// optional method implementation
|
||||
}
|
||||
|
||||
override fun onDatabaseActionFinished(
|
||||
database: Database,
|
||||
actionTask: String,
|
||||
result: ActionRunnable.Result
|
||||
) {
|
||||
mDatabaseViewModel.onActionFinished(database, actionTask, result)
|
||||
// optional method implementation
|
||||
}
|
||||
|
||||
fun createDatabase(databaseUri: Uri,
|
||||
mainCredential: MainCredential) {
|
||||
mDatabaseTaskProvider?.startDatabaseCreate(databaseUri, mainCredential)
|
||||
}
|
||||
|
||||
fun loadDatabase(databaseUri: Uri,
|
||||
mainCredential: MainCredential,
|
||||
readOnly: Boolean,
|
||||
cipherEntity: CipherDatabaseEntity?,
|
||||
fixDuplicateUuid: Boolean) {
|
||||
mDatabaseTaskProvider?.startDatabaseLoad(databaseUri, mainCredential, readOnly, cipherEntity, fixDuplicateUuid)
|
||||
}
|
||||
|
||||
protected fun closeDatabase() {
|
||||
mDatabase?.clearAndClose(this)
|
||||
}
|
||||
|
||||
override fun reloadActivity() {
|
||||
super.reloadActivity()
|
||||
mDatabase?.wasReloaded = false
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (mDatabase?.wasReloaded == true) {
|
||||
reloadActivity()
|
||||
}
|
||||
mDatabaseTaskProvider?.registerProgressTask()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mDatabaseTaskProvider?.unregisterProgressTask()
|
||||
super.onPause()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.legacy
|
||||
|
||||
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 androidx.activity.viewModels
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
||||
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.database.element.node.NodeId
|
||||
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.*
|
||||
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.viewmodels.NodesViewModel
|
||||
import java.util.*
|
||||
|
||||
abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
PasswordEncodingDialogFragment.Listener {
|
||||
|
||||
private val mNodesViewModel: NodesViewModel by viewModels()
|
||||
|
||||
protected var mTimeoutEnable: Boolean = true
|
||||
|
||||
private var mLockReceiver: LockReceiver? = null
|
||||
private var mExitLock: Boolean = false
|
||||
|
||||
protected var mDatabaseReadOnly: Boolean = true
|
||||
private var mAutoSaveEnable: Boolean = true
|
||||
|
||||
protected var mIconDrawableFactory: IconDrawableFactory? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState != null
|
||||
&& 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)
|
||||
}
|
||||
|
||||
mNodesViewModel.nodesToPermanentlyDelete.observe(this) { nodes ->
|
||||
deleteDatabaseNodes(nodes)
|
||||
}
|
||||
|
||||
mDatabaseViewModel.saveDatabase.observe(this) { save ->
|
||||
mDatabaseTaskProvider?.startDatabaseSave(save)
|
||||
}
|
||||
|
||||
mDatabaseViewModel.reloadDatabase.observe(this) { fixDuplicateUuid ->
|
||||
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)
|
||||
}
|
||||
|
||||
mExitLock = false
|
||||
}
|
||||
|
||||
open fun finishActivityIfDatabaseNotLoaded(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
|
||||
// End activity if database not loaded
|
||||
if (finishActivityIfDatabaseNotLoaded() && (database == null || !database.loaded)) {
|
||||
finish()
|
||||
}
|
||||
|
||||
// Focus view to reinitialize timeout,
|
||||
// view is not necessary loaded so retry later in resume
|
||||
viewToInvalidateTimeout()
|
||||
?.resetAppTimeoutWhenViewFocusedOrChanged(this, database?.loaded)
|
||||
|
||||
database?.let {
|
||||
// check timeout
|
||||
if (mTimeoutEnable) {
|
||||
if (mLockReceiver == null) {
|
||||
mLockReceiver = LockReceiver {
|
||||
mDatabase = null
|
||||
closeDatabase(database)
|
||||
if (LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == null)
|
||||
LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = LOCKING_ACTIVITY_UI_VISIBLE
|
||||
// Add onActivityForResult response
|
||||
setResult(RESULT_EXIT_LOCK)
|
||||
closeOptionsMenu()
|
||||
finish()
|
||||
}
|
||||
registerLockReceiver(mLockReceiver)
|
||||
}
|
||||
|
||||
// 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
|
||||
if (!mExitLock)
|
||||
TimeoutHelper.recordTime(this, database.loaded)
|
||||
}
|
||||
|
||||
mDatabaseReadOnly = database.isReadOnly
|
||||
mIconDrawableFactory = database.iconDrawableFactory
|
||||
|
||||
checkRegister()
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun viewToInvalidateTimeout(): View?
|
||||
|
||||
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) {
|
||||
reloadActivity()
|
||||
} else {
|
||||
this.showActionErrorIfNeeded(result)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPasswordEncodingValidateListener(databaseUri: Uri?,
|
||||
mainCredential: MainCredential) {
|
||||
assignDatabasePassword(databaseUri, mainCredential)
|
||||
}
|
||||
|
||||
private fun assignDatabasePassword(databaseUri: Uri?,
|
||||
mainCredential: MainCredential) {
|
||||
if (databaseUri != null) {
|
||||
mDatabaseTaskProvider?.startDatabaseAssignPassword(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)) {
|
||||
assignDatabasePassword(databaseUri, mainCredential)
|
||||
} else {
|
||||
PasswordEncodingDialogFragment.getInstance(databaseUri, mainCredential)
|
||||
.show(supportFragmentManager, "passwordEncodingTag")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveDatabase() {
|
||||
mDatabaseTaskProvider?.startDatabaseSave(true)
|
||||
}
|
||||
|
||||
fun reloadDatabase() {
|
||||
mDatabaseTaskProvider?.startDatabaseReload(false)
|
||||
}
|
||||
|
||||
fun createEntry(newEntry: Entry,
|
||||
parent: Group) {
|
||||
mDatabaseTaskProvider?.startDatabaseCreateEntry(newEntry, parent, mAutoSaveEnable)
|
||||
}
|
||||
|
||||
fun updateEntry(oldEntry: Entry,
|
||||
entryToUpdate: Entry) {
|
||||
mDatabaseTaskProvider?.startDatabaseUpdateEntry(oldEntry, entryToUpdate, mAutoSaveEnable)
|
||||
}
|
||||
|
||||
fun copyNodes(nodesToCopy: List<Node>,
|
||||
newParent: Group) {
|
||||
mDatabaseTaskProvider?.startDatabaseCopyNodes(nodesToCopy, newParent, mAutoSaveEnable)
|
||||
}
|
||||
|
||||
fun moveNodes(nodesToMove: List<Node>,
|
||||
newParent: Group) {
|
||||
mDatabaseTaskProvider?.startDatabaseMoveNodes(nodesToMove, newParent, 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)) {
|
||||
deleteDatabaseNodes(nodes)
|
||||
}
|
||||
// else open the dialog to confirm deletion
|
||||
else {
|
||||
DeleteNodesDialogFragment.getInstance(recycleBin)
|
||||
.show(supportFragmentManager, "deleteNodesDialogFragment")
|
||||
mNodesViewModel.deleteNodes(nodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteDatabaseNodes(nodes: List<Node>) {
|
||||
mDatabaseTaskProvider?.startDatabaseDeleteNodes(nodes, 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
|
||||
mDatabaseTaskProvider?.startDatabaseCreateGroup(newGroup, parent, 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)
|
||||
}
|
||||
}
|
||||
mDatabaseTaskProvider?.startDatabaseUpdateGroup(oldGroup, updateGroup, mAutoSaveEnable)
|
||||
}
|
||||
|
||||
fun restoreEntryHistory(mainEntryId: NodeId<UUID>,
|
||||
entryHistoryPosition: Int) {
|
||||
mDatabaseTaskProvider
|
||||
?.startDatabaseRestoreEntryHistory(mainEntryId, entryHistoryPosition, mAutoSaveEnable)
|
||||
}
|
||||
|
||||
fun deleteEntryHistory(mainEntryId: NodeId<UUID>,
|
||||
entryHistoryPosition: Int) {
|
||||
mDatabaseTaskProvider?.startDatabaseDeleteEntryHistory(mainEntryId, entryHistoryPosition, 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 checkRegister() {
|
||||
// If in ave or registration mode, don't allow read only
|
||||
if ((mSpecialMode == SpecialMode.SAVE
|
||||
|| mSpecialMode == SpecialMode.REGISTRATION)
|
||||
&& mDatabaseReadOnly) {
|
||||
Toast.makeText(this, R.string.error_registration_read_only , Toast.LENGTH_LONG).show()
|
||||
EntrySelectionHelper.removeModesFromIntent(intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
checkRegister()
|
||||
|
||||
// To refresh when back to normal workflow from selection workflow
|
||||
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
|
||||
|
||||
// Invalidate timeout by touch
|
||||
mDatabase?.let { database ->
|
||||
viewToInvalidateTimeout()
|
||||
?.resetAppTimeoutWhenViewFocusedOrChanged(this, database.loaded)
|
||||
}
|
||||
|
||||
invalidateOptionsMenu()
|
||||
|
||||
LOCKING_ACTIVITY_UI_VISIBLE = true
|
||||
}
|
||||
|
||||
protected fun checkTimeAndLockIfTimeoutOrResetTimeout(action: (() -> Unit)? = null) {
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this,
|
||||
mDatabase?.loaded == true,
|
||||
action)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
LOCKING_ACTIVITY_UI_VISIBLE = false
|
||||
|
||||
super.onPause()
|
||||
|
||||
if (mTimeoutEnable) {
|
||||
// If the time is out during our navigation in activity -> close the Activity
|
||||
TimeoutHelper.checkTimeAndLockIfTimeout(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterLockReceiver(mLockReceiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
protected fun lockAndExit() {
|
||||
sendBroadcast(Intent(LOCK_ACTION))
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (mTimeoutEnable) {
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this,
|
||||
mDatabase?.loaded == true) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val TAG = "LockingActivity"
|
||||
|
||||
const val RESULT_EXIT_LOCK = 1450
|
||||
|
||||
const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY"
|
||||
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
|
||||
|
||||
private var LOCKING_ACTIVITY_UI_VISIBLE = false
|
||||
var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To reset the app timeout when a view is focused or changed
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context, databaseLoaded: Boolean?) {
|
||||
// Log.d(LockingActivity.TAG, "View prepared to reset app timeout")
|
||||
setOnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
// Log.d(LockingActivity.TAG, "View touched, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
|
||||
databaseLoaded ?: false)
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
setOnFocusChangeListener { _, _ ->
|
||||
// Log.d(LockingActivity.TAG, "View focused, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
|
||||
databaseLoaded ?: false)
|
||||
}
|
||||
if (this is ViewGroup) {
|
||||
for (i in 0..childCount) {
|
||||
getChildAt(i)?.resetAppTimeoutWhenViewFocusedOrChanged(context, databaseLoaded)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.kunzisoft.keepass.activities.selection
|
||||
package com.kunzisoft.keepass.activities.legacy
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
@@ -7,15 +7,14 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
||||
import com.kunzisoft.keepass.activities.helpers.TypeMode
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.SpecialModeView
|
||||
|
||||
/**
|
||||
* Activity to manage special mode (ie: selection mode)
|
||||
* Activity to manage database special mode (ie: selection mode)
|
||||
*/
|
||||
abstract class SpecialModeActivity : StylishActivity() {
|
||||
abstract class DatabaseModeActivity : DatabaseActivity() {
|
||||
|
||||
protected var mSpecialMode: SpecialMode = SpecialMode.DEFAULT
|
||||
private var mTypeMode: TypeMode = TypeMode.DEFAULT
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.kunzisoft.keepass.activities.legacy
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.lock
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
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.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.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.*
|
||||
|
||||
abstract class LockingActivity : SpecialModeActivity() {
|
||||
|
||||
protected var mTimeoutEnable: Boolean = true
|
||||
|
||||
private var mLockReceiver: LockReceiver? = null
|
||||
private var mExitLock: Boolean = false
|
||||
|
||||
protected var mDatabase: Database? = null
|
||||
|
||||
// Force readOnly if Entry Selection mode
|
||||
protected var mReadOnly: Boolean
|
||||
get() {
|
||||
return mReadOnlyToSave
|
||||
}
|
||||
set(value) {
|
||||
mReadOnlyToSave = value
|
||||
}
|
||||
private var mReadOnlyToSave: Boolean = false
|
||||
protected var mAutoSaveEnable: Boolean = true
|
||||
|
||||
var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
|
||||
private set
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
mDatabase = Database.getInstance()
|
||||
|
||||
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState != null
|
||||
&& 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)
|
||||
}
|
||||
|
||||
if (mTimeoutEnable) {
|
||||
mLockReceiver = LockReceiver {
|
||||
closeDatabase()
|
||||
if (LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK == null)
|
||||
LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = LOCKING_ACTIVITY_UI_VISIBLE
|
||||
// Add onActivityForResult response
|
||||
setResult(RESULT_EXIT_LOCK)
|
||||
closeOptionsMenu()
|
||||
finish()
|
||||
}
|
||||
registerLockReceiver(mLockReceiver)
|
||||
}
|
||||
|
||||
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 onResume() {
|
||||
super.onResume()
|
||||
|
||||
// If in ave or registration mode, don't allow read only
|
||||
if ((mSpecialMode == SpecialMode.SAVE
|
||||
|| mSpecialMode == SpecialMode.REGISTRATION)
|
||||
&& mReadOnly) {
|
||||
Toast.makeText(this, R.string.error_registration_read_only , Toast.LENGTH_LONG).show()
|
||||
EntrySelectionHelper.removeModesFromIntent(intent)
|
||||
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
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(TIMEOUT_ENABLE_KEY, mTimeoutEnable)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
LOCKING_ACTIVITY_UI_VISIBLE = false
|
||||
|
||||
mProgressDatabaseTaskProvider?.unregisterProgressTask()
|
||||
|
||||
super.onPause()
|
||||
|
||||
if (mTimeoutEnable) {
|
||||
// If the time is out during our navigation in activity -> close the Activity
|
||||
TimeoutHelper.checkTimeAndLockIfTimeout(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterLockReceiver(mLockReceiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
protected fun lockAndExit() {
|
||||
sendBroadcast(Intent(LOCK_ACTION))
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (mTimeoutEnable) {
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, mDatabase) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val TAG = "LockingActivity"
|
||||
|
||||
const val RESULT_EXIT_LOCK = 1450
|
||||
|
||||
const val TIMEOUT_ENABLE_KEY = "TIMEOUT_ENABLE_KEY"
|
||||
const val TIMEOUT_ENABLE_KEY_DEFAULT = true
|
||||
|
||||
private var LOCKING_ACTIVITY_UI_VISIBLE = false
|
||||
var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To reset the app timeout when a view is focused or changed
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context, database: Database?) {
|
||||
setOnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
//Log.d(LockingActivity.TAG, "View touched, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context, database)
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
setOnFocusChangeListener { _, _ ->
|
||||
//Log.d(LockingActivity.TAG, "View focused, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context, database)
|
||||
}
|
||||
if (this is ViewGroup) {
|
||||
for (i in 0..childCount) {
|
||||
getChildAt(i)?.resetAppTimeoutWhenViewFocusedOrChanged(context, database)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,11 @@ package com.kunzisoft.keepass.activities.stylish
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.kunzisoft.keepass.settings.NestedAppSettingsFragment.Companion.DATABASE_APPEARANCE_PREFERENCE_CHANGED
|
||||
|
||||
/**
|
||||
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
|
||||
@@ -35,6 +36,7 @@ abstract class StylishActivity : AppCompatActivity() {
|
||||
|
||||
@StyleRes
|
||||
private var themeId: Int = 0
|
||||
private var customStyle = true
|
||||
|
||||
/* (non-Javadoc) Workaround for HTC Linkify issues
|
||||
* @see android.app.Activity#startActivity(android.content.Intent)
|
||||
@@ -52,10 +54,30 @@ abstract class StylishActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
open fun applyCustomStyle(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
open fun finishActivityIfReloadRequested(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
open fun reloadActivity() {
|
||||
if (!finishActivityIfReloadRequested()) {
|
||||
startActivity(intent)
|
||||
}
|
||||
finish()
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
this.themeId = Stylish.getThemeId(this)
|
||||
setTheme(themeId)
|
||||
|
||||
customStyle = applyCustomStyle()
|
||||
if (customStyle) {
|
||||
this.themeId = Stylish.getThemeId(this)
|
||||
setTheme(themeId)
|
||||
}
|
||||
|
||||
// Several gingerbread devices have problems with FLAG_SECURE
|
||||
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
||||
@@ -63,7 +85,10 @@ abstract class StylishActivity : AppCompatActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (Stylish.getThemeId(this) != this.themeId) {
|
||||
|
||||
if ((customStyle && Stylish.getThemeId(this) != this.themeId)
|
||||
|| DATABASE_APPEARANCE_PREFERENCE_CHANGED) {
|
||||
DATABASE_APPEARANCE_PREFERENCE_CHANGED = false
|
||||
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
|
||||
this.recreate()
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ class NodeAdapter (private val context: Context,
|
||||
taTextColor.recycle()
|
||||
}
|
||||
|
||||
fun assignPreferences() {
|
||||
private fun assignPreferences() {
|
||||
this.mPrefSizeMultiplier = PreferencesUtil.getListTextSize(context)
|
||||
|
||||
notifyChangeSort(
|
||||
@@ -142,9 +142,19 @@ class NodeAdapter (private val context: Context,
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
|
||||
return oldItem.type == newItem.type
|
||||
var typeContentTheSame = true
|
||||
if (oldItem is Entry && newItem is Entry) {
|
||||
typeContentTheSame = oldItem.getVisualTitle() == newItem.getVisualTitle()
|
||||
&& oldItem.username == newItem.username
|
||||
&& oldItem.containsAttachment() == newItem.containsAttachment()
|
||||
} else if (oldItem is Group && newItem is Group) {
|
||||
typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries
|
||||
}
|
||||
return typeContentTheSame
|
||||
&& oldItem.type == newItem.type
|
||||
&& oldItem.title == newItem.title
|
||||
&& oldItem.icon == newItem.icon
|
||||
&& oldItem.isCurrentlyExpires == newItem.isCurrentlyExpires
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
|
||||
@@ -237,6 +247,10 @@ class NodeAdapter (private val context: Context,
|
||||
mNodeSortedList.endBatchedUpdates()
|
||||
}
|
||||
|
||||
fun indexOf(node: Node): Int {
|
||||
return mNodeSortedList.indexOf(node)
|
||||
}
|
||||
|
||||
fun notifyNodeChanged(node: Node) {
|
||||
notifyItemChanged(mNodeSortedList.indexOf(node))
|
||||
}
|
||||
@@ -346,7 +360,7 @@ class NodeAdapter (private val context: Context,
|
||||
if (mShowNumberEntries) {
|
||||
holder.numberChildren?.apply {
|
||||
text = (subNode as Group)
|
||||
.getNumberOfChildEntries(mEntryFilters)
|
||||
.numberOfChildEntries
|
||||
.toString()
|
||||
setTextSize(mTextSizeUnit, mNumberChildrenTextDefaultDimension, mPrefSizeMultiplier)
|
||||
visibility = View.VISIBLE
|
||||
@@ -358,10 +372,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 +394,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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.app
|
||||
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
class App : MultiDexApplication() {
|
||||
|
||||
@@ -31,9 +30,4 @@ class App : MultiDexApplication() {
|
||||
Stylish.load(this)
|
||||
PRNGFixes.apply()
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
Database.getInstance().clearAndClose(this)
|
||||
super.onTerminate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import androidx.autofill.inline.UiVersions
|
||||
import androidx.autofill.inline.v1.InlineSuggestionUi
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.AutofillLauncherActivity
|
||||
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.model.CreditCard
|
||||
@@ -50,22 +51,32 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
class KeeAutofillService : AutofillService() {
|
||||
|
||||
var applicationIdBlocklist: Set<String>? = null
|
||||
var webDomainBlocklist: Set<String>? = null
|
||||
var askToSaveData: Boolean = false
|
||||
var autofillInlineSuggestionsEnabled: Boolean = false
|
||||
private var mLock = AtomicBoolean()
|
||||
|
||||
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
|
||||
private var mDatabase: Database? = null
|
||||
private var applicationIdBlocklist: Set<String>? = null
|
||||
private var webDomainBlocklist: Set<String>? = null
|
||||
private var askToSaveData: Boolean = false
|
||||
private var autofillInlineSuggestionsEnabled: Boolean = false
|
||||
private var mLock = AtomicBoolean()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
mDatabase = Database.getInstance()
|
||||
mDatabaseTaskProvider = DatabaseTaskProvider(this)
|
||||
mDatabaseTaskProvider?.registerProgressTask()
|
||||
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
|
||||
this.mDatabase = database
|
||||
}
|
||||
|
||||
getPreferences()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mDatabaseTaskProvider?.unregisterProgressTask()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun getPreferences() {
|
||||
applicationIdBlocklist = PreferencesUtil.applicationIdBlocklist(this)
|
||||
webDomainBlocklist = PreferencesUtil.webDomainBlocklist(this)
|
||||
@@ -102,20 +113,18 @@ class KeeAutofillService : AutofillService() {
|
||||
} else {
|
||||
null
|
||||
}
|
||||
mDatabase?.let { database ->
|
||||
launchSelection(database,
|
||||
searchInfo,
|
||||
parseResult,
|
||||
inlineSuggestionsRequest,
|
||||
callback)
|
||||
}
|
||||
launchSelection(mDatabase,
|
||||
searchInfo,
|
||||
parseResult,
|
||||
inlineSuggestionsRequest,
|
||||
callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchSelection(database: Database,
|
||||
private fun launchSelection(database: Database?,
|
||||
searchInfo: SearchInfo,
|
||||
parseResult: StructureParser.Result,
|
||||
inlineSuggestionsRequest: InlineSuggestionsRequest?,
|
||||
@@ -123,9 +132,9 @@ class KeeAutofillService : AutofillService() {
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
searchInfo,
|
||||
{ items ->
|
||||
{ openedDatabase, items ->
|
||||
callback.onSuccess(
|
||||
AutofillHelper.buildResponse(this, database,
|
||||
AutofillHelper.buildResponse(this, openedDatabase,
|
||||
items, parseResult, inlineSuggestionsRequest)
|
||||
)
|
||||
},
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.action
|
||||
|
||||
import android.app.Service
|
||||
import android.content.*
|
||||
import android.content.Context.BIND_ABOVE_CLIENT
|
||||
import android.content.Context.BIND_NOT_FOREGROUND
|
||||
import android.content.Context.*
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
@@ -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,12 +84,23 @@ import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
/**
|
||||
* Utility class to connect an activity or a service to the DatabaseTaskNotificationService,
|
||||
* Useful to retrieve a database instance and sending tasks commands
|
||||
*/
|
||||
class DatabaseTaskProvider {
|
||||
|
||||
var onActionFinish: ((actionTask: String,
|
||||
private var activity: FragmentActivity? = null
|
||||
private var service: Service? = null
|
||||
private var context: Context
|
||||
|
||||
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)
|
||||
private var intentDatabaseTask: Intent
|
||||
|
||||
private var databaseTaskBroadcastReceiver: BroadcastReceiver? = null
|
||||
private var mBinder: DatabaseTaskNotificationService.ActionTaskBinder? = null
|
||||
@@ -98,17 +110,31 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
|
||||
private var databaseChangedDialogFragment: DatabaseChangedDialogFragment? = null
|
||||
|
||||
constructor(activity: FragmentActivity) {
|
||||
this.activity = activity
|
||||
this.context = activity
|
||||
this.intentDatabaseTask = Intent(activity.applicationContext,
|
||||
DatabaseTaskNotificationService::class.java)
|
||||
}
|
||||
|
||||
constructor(service: Service) {
|
||||
this.service = service
|
||||
this.context = service
|
||||
this.intentDatabaseTask = Intent(service.applicationContext,
|
||||
DatabaseTaskNotificationService::class.java)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -123,43 +149,55 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
private var databaseInfoListener = object: DatabaseTaskNotificationService.DatabaseInfoListener {
|
||||
override fun onDatabaseInfoChanged(previousDatabaseInfo: SnapFileDatabaseInfo,
|
||||
newDatabaseInfo: SnapFileDatabaseInfo) {
|
||||
activity.lifecycleScope.launch {
|
||||
if (databaseChangedDialogFragment == null) {
|
||||
databaseChangedDialogFragment = activity.supportFragmentManager
|
||||
.findFragmentByTag(DATABASE_CHANGED_DIALOG_TAG) as DatabaseChangedDialogFragment?
|
||||
databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
|
||||
}
|
||||
if (progressTaskDialogFragment == null) {
|
||||
databaseChangedDialogFragment = DatabaseChangedDialogFragment.getInstance(
|
||||
previousDatabaseInfo,
|
||||
newDatabaseInfo
|
||||
)
|
||||
databaseChangedDialogFragment?.actionDatabaseListener = mActionDatabaseListener
|
||||
databaseChangedDialogFragment?.show(
|
||||
activity.supportFragmentManager,
|
||||
DATABASE_CHANGED_DIALOG_TAG
|
||||
)
|
||||
activity?.let { activity ->
|
||||
activity.lifecycleScope.launch {
|
||||
if (databaseChangedDialogFragment == null) {
|
||||
databaseChangedDialogFragment = activity.supportFragmentManager
|
||||
.findFragmentByTag(DATABASE_CHANGED_DIALOG_TAG) as DatabaseChangedDialogFragment?
|
||||
databaseChangedDialogFragment?.actionDatabaseListener =
|
||||
mActionDatabaseListener
|
||||
}
|
||||
if (progressTaskDialogFragment == null) {
|
||||
databaseChangedDialogFragment = DatabaseChangedDialogFragment.getInstance(
|
||||
previousDatabaseInfo,
|
||||
newDatabaseInfo
|
||||
)
|
||||
databaseChangedDialogFragment?.actionDatabaseListener =
|
||||
mActionDatabaseListener
|
||||
databaseChangedDialogFragment?.show(
|
||||
activity.supportFragmentManager,
|
||||
DATABASE_CHANGED_DIALOG_TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
activity.lifecycleScope.launch {
|
||||
if (progressTaskDialogFragment == null) {
|
||||
progressTaskDialogFragment = activity.supportFragmentManager
|
||||
.findFragmentByTag(PROGRESS_TASK_DIALOG_TAG) as ProgressTaskDialogFragment?
|
||||
activity?.let { activity ->
|
||||
activity.lifecycleScope.launch {
|
||||
if (progressTaskDialogFragment == null) {
|
||||
progressTaskDialogFragment = activity.supportFragmentManager
|
||||
.findFragmentByTag(PROGRESS_TASK_DIALOG_TAG) as ProgressTaskDialogFragment?
|
||||
}
|
||||
if (progressTaskDialogFragment == null) {
|
||||
progressTaskDialogFragment = ProgressTaskDialogFragment()
|
||||
progressTaskDialogFragment?.show(
|
||||
activity.supportFragmentManager,
|
||||
PROGRESS_TASK_DIALOG_TAG
|
||||
)
|
||||
}
|
||||
updateDialog(titleId, messageId, warningId)
|
||||
}
|
||||
if (progressTaskDialogFragment == null) {
|
||||
progressTaskDialogFragment = ProgressTaskDialogFragment()
|
||||
progressTaskDialogFragment?.show(
|
||||
activity.supportFragmentManager,
|
||||
PROGRESS_TASK_DIALOG_TAG
|
||||
)
|
||||
}
|
||||
updateDialog(titleId, messageId, warningId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,16 +225,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
|
||||
}
|
||||
}
|
||||
@@ -206,7 +247,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
private fun bindService() {
|
||||
initServiceConnection()
|
||||
serviceConnection?.let {
|
||||
activity.bindService(intentDatabaseTask, it, BIND_NOT_FOREGROUND or BIND_ABOVE_CLIENT)
|
||||
context.bindService(intentDatabaseTask, it, BIND_AUTO_CREATE or BIND_NOT_FOREGROUND or BIND_ABOVE_CLIENT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +256,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
*/
|
||||
private fun unBindService() {
|
||||
serviceConnection?.let {
|
||||
activity.unbindService(it)
|
||||
context.unbindService(it)
|
||||
}
|
||||
serviceConnection = null
|
||||
}
|
||||
@@ -243,7 +284,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
}
|
||||
}
|
||||
}
|
||||
activity.registerReceiver(databaseTaskBroadcastReceiver,
|
||||
context.registerReceiver(databaseTaskBroadcastReceiver,
|
||||
IntentFilter().apply {
|
||||
addAction(DATABASE_START_TASK_ACTION)
|
||||
addAction(DATABASE_STOP_TASK_ACTION)
|
||||
@@ -257,14 +298,15 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
fun unregisterProgressTask() {
|
||||
stopDialog()
|
||||
|
||||
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
|
||||
mBinder?.removeActionTaskListener(actionTaskListener)
|
||||
mBinder?.removeDatabaseFileInfoListener(databaseInfoListener)
|
||||
mBinder?.removeDatabaseListener(databaseListener)
|
||||
mBinder = null
|
||||
|
||||
unBindService()
|
||||
|
||||
try {
|
||||
activity.unregisterReceiver(databaseTaskBroadcastReceiver)
|
||||
context.unregisterReceiver(databaseTaskBroadcastReceiver)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// If receiver not register, do nothing
|
||||
}
|
||||
@@ -275,7 +317,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
if (bundle != null)
|
||||
intentDatabaseTask.putExtras(bundle)
|
||||
intentDatabaseTask.action = actionTask
|
||||
activity.startService(intentDatabaseTask)
|
||||
context.startService(intentDatabaseTask)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to perform database action", e)
|
||||
Toast.makeText(activity, R.string.error_start_database_action, Toast.LENGTH_LONG).show()
|
||||
@@ -388,9 +430,7 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
nodesPaste.forEach { nodeVersioned ->
|
||||
when (nodeVersioned.type) {
|
||||
Type.GROUP -> {
|
||||
(nodeVersioned as Group).nodeId?.let { groupId ->
|
||||
groupsIdToCopy.add(groupId)
|
||||
}
|
||||
groupsIdToCopy.add((nodeVersioned as Group).nodeId)
|
||||
}
|
||||
Type.ENTRY -> {
|
||||
entriesIdToCopy.add((nodeVersioned as Entry).nodeId)
|
||||
@@ -433,22 +473,22 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
-----------------
|
||||
*/
|
||||
|
||||
fun startDatabaseRestoreEntryHistory(mainEntry: Entry,
|
||||
fun startDatabaseRestoreEntryHistory(mainEntryId: NodeId<UUID>,
|
||||
entryHistoryPosition: Int,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
|
||||
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntryId)
|
||||
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
, ACTION_DATABASE_RESTORE_ENTRY_HISTORY)
|
||||
}
|
||||
|
||||
fun startDatabaseDeleteEntryHistory(mainEntry: Entry,
|
||||
fun startDatabaseDeleteEntryHistory(mainEntryId: NodeId<UUID>,
|
||||
entryHistoryPosition: Int,
|
||||
save: Boolean) {
|
||||
start(Bundle().apply {
|
||||
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
|
||||
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntryId)
|
||||
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
|
||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||
}
|
||||
@@ -639,6 +679,6 @@ class ProgressDatabaseTaskProvider(private val activity: FragmentActivity) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ProgressDatabaseTaskProvider::class.java.name
|
||||
private val TAG = DatabaseTaskProvider::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -34,54 +34,52 @@ class UpdateEntryRunnable constructor(
|
||||
afterActionNodesFinish: AfterActionNodesFinish?)
|
||||
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
|
||||
|
||||
// Keep backup of original values in case save fails
|
||||
private var mBackupEntryHistory: Entry = Entry(mOldEntry)
|
||||
|
||||
override fun nodeAction() {
|
||||
// WARNING : Re attribute parent removed in entry edit activity to save memory
|
||||
mNewEntry.addParentFrom(mOldEntry)
|
||||
if (mOldEntry.nodeId == mNewEntry.nodeId) {
|
||||
// WARNING : Re attribute parent removed in entry edit activity to save memory
|
||||
mNewEntry.addParentFrom(mOldEntry)
|
||||
|
||||
// Build oldest attachments
|
||||
val oldEntryAttachments = mOldEntry.getAttachments(database.attachmentPool, true)
|
||||
val newEntryAttachments = mNewEntry.getAttachments(database.attachmentPool, true)
|
||||
val attachmentsToRemove = ArrayList<Attachment>(oldEntryAttachments)
|
||||
// Not use equals because only check name
|
||||
newEntryAttachments.forEach { newAttachment ->
|
||||
oldEntryAttachments.forEach { oldAttachment ->
|
||||
if (oldAttachment.name == newAttachment.name
|
||||
&& oldAttachment.binaryData == newAttachment.binaryData)
|
||||
attachmentsToRemove.remove(oldAttachment)
|
||||
// Build oldest attachments
|
||||
val oldEntryAttachments = mOldEntry.getAttachments(database.attachmentPool, true)
|
||||
val newEntryAttachments = mNewEntry.getAttachments(database.attachmentPool, true)
|
||||
val attachmentsToRemove = ArrayList<Attachment>(oldEntryAttachments)
|
||||
// Not use equals because only check name
|
||||
newEntryAttachments.forEach { newAttachment ->
|
||||
oldEntryAttachments.forEach { oldAttachment ->
|
||||
if (oldAttachment.name == newAttachment.name
|
||||
&& oldAttachment.binaryData == newAttachment.binaryData
|
||||
)
|
||||
attachmentsToRemove.remove(oldAttachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update entry with new values
|
||||
mOldEntry.updateWith(mNewEntry)
|
||||
mNewEntry.touch(modified = true, touchParents = true)
|
||||
// Update entry with new values
|
||||
mNewEntry.touch(modified = true, touchParents = true)
|
||||
|
||||
// Create an entry history (an entry history don't have history)
|
||||
mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false))
|
||||
database.removeOldestEntryHistory(mOldEntry, database.attachmentPool)
|
||||
// Create an entry history (an entry history don't have history)
|
||||
mNewEntry.addEntryToHistory(Entry(mOldEntry, copyHistory = false))
|
||||
database.removeOldestEntryHistory(mNewEntry, database.attachmentPool)
|
||||
|
||||
// Only change data in index
|
||||
database.updateEntry(mOldEntry)
|
||||
// Only change data in index
|
||||
database.updateEntry(mNewEntry)
|
||||
|
||||
// Remove oldest attachments
|
||||
attachmentsToRemove.forEach {
|
||||
database.removeAttachmentIfNotUsed(it)
|
||||
// Remove oldest attachments
|
||||
attachmentsToRemove.forEach {
|
||||
database.removeAttachmentIfNotUsed(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun nodeFinish(): ActionNodesValues {
|
||||
if (!result.isSuccess) {
|
||||
mOldEntry.updateWith(mBackupEntryHistory)
|
||||
// If we fail to save, back out changes to global structure
|
||||
database.updateEntry(mOldEntry)
|
||||
}
|
||||
|
||||
val oldNodesReturn = ArrayList<Node>()
|
||||
oldNodesReturn.add(mBackupEntryHistory)
|
||||
oldNodesReturn.add(mOldEntry)
|
||||
val newNodesReturn = ArrayList<Node>()
|
||||
newNodesReturn.add(mOldEntry)
|
||||
newNodesReturn.add(mNewEntry)
|
||||
return ActionNodesValues(oldNodesReturn, newNodesReturn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,33 +33,30 @@ class UpdateGroupRunnable constructor(
|
||||
afterActionNodesFinish: AfterActionNodesFinish?)
|
||||
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
|
||||
|
||||
// Keep backup of original values in case save fails
|
||||
private val mBackupGroup: Group = Group(mOldGroup)
|
||||
|
||||
override fun nodeAction() {
|
||||
// WARNING : Re attribute parent and children removed in group activity to save memory
|
||||
mNewGroup.addParentFrom(mOldGroup)
|
||||
mNewGroup.addChildrenFrom(mOldGroup)
|
||||
if (mOldGroup.nodeId == mNewGroup.nodeId) {
|
||||
// WARNING : Re attribute parent and children removed in group activity to save memory
|
||||
mNewGroup.addParentFrom(mOldGroup)
|
||||
mNewGroup.addChildrenFrom(mOldGroup)
|
||||
|
||||
// Update group with new values
|
||||
mOldGroup.updateWith(mNewGroup)
|
||||
mOldGroup.touch(modified = true, touchParents = true)
|
||||
// Update group with new values
|
||||
mNewGroup.touch(modified = true, touchParents = true)
|
||||
|
||||
// Only change data in index
|
||||
database.updateGroup(mOldGroup)
|
||||
// Only change data in index
|
||||
database.updateGroup(mNewGroup)
|
||||
}
|
||||
}
|
||||
|
||||
override fun nodeFinish(): ActionNodesValues {
|
||||
if (!result.isSuccess) {
|
||||
// If we fail to save, back out changes to global structure
|
||||
mOldGroup.updateWith(mBackupGroup)
|
||||
database.updateGroup(mOldGroup)
|
||||
}
|
||||
|
||||
val oldNodesReturn = ArrayList<Node>()
|
||||
oldNodesReturn.add(mBackupGroup)
|
||||
oldNodesReturn.add(mOldGroup)
|
||||
val newNodesReturn = ArrayList<Node>()
|
||||
newNodesReturn.add(mOldGroup)
|
||||
newNodesReturn.add(mNewGroup)
|
||||
return ActionNodesValues(oldNodesReturn, newNodesReturn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,6 +670,7 @@ class Database {
|
||||
searchInOther = true
|
||||
searchInUUIDs = false
|
||||
searchInTags = false
|
||||
searchInTemplates = false
|
||||
}, omitBackup, max)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,15 +45,6 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
var entryKDBX: EntryKDBX? = null
|
||||
private set
|
||||
|
||||
fun updateWith(entry: Entry, copyHistory: Boolean = true) {
|
||||
entry.entryKDB?.let {
|
||||
this.entryKDB?.updateWith(it)
|
||||
}
|
||||
entry.entryKDBX?.let {
|
||||
this.entryKDBX?.updateWith(it, copyHistory)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this constructor to copy an Entry with exact same values
|
||||
*/
|
||||
@@ -64,7 +55,12 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
if (entry.entryKDBX != null) {
|
||||
this.entryKDBX = EntryKDBX()
|
||||
}
|
||||
updateWith(entry, copyHistory)
|
||||
entry.entryKDB?.let {
|
||||
this.entryKDB?.updateWith(it)
|
||||
}
|
||||
entry.entryKDBX?.let {
|
||||
this.entryKDBX?.updateWith(it, copyHistory)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(entry: EntryKDB) {
|
||||
|
||||
@@ -44,14 +44,7 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
// Virtual group is used to defined a detached database group
|
||||
var isVirtual = false
|
||||
|
||||
fun updateWith(group: Group) {
|
||||
group.groupKDB?.let {
|
||||
this.groupKDB?.updateWith(it)
|
||||
}
|
||||
group.groupKDBX?.let {
|
||||
this.groupKDBX?.updateWith(it)
|
||||
}
|
||||
}
|
||||
var numberOfChildEntries: Int = 0
|
||||
|
||||
/**
|
||||
* Use this constructor to copy a Group
|
||||
@@ -65,7 +58,12 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
if (this.groupKDBX == null)
|
||||
this.groupKDBX = GroupKDBX()
|
||||
}
|
||||
updateWith(group)
|
||||
group.groupKDB?.let {
|
||||
this.groupKDB?.updateWith(it)
|
||||
}
|
||||
group.groupKDBX?.let {
|
||||
this.groupKDBX?.updateWith(it)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(group: GroupKDB) {
|
||||
@@ -118,8 +116,8 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
dest.writeByte((if (isVirtual) 1 else 0).toByte())
|
||||
}
|
||||
|
||||
override val nodeId: NodeId<*>?
|
||||
get() = groupKDBX?.nodeId ?: groupKDB?.nodeId
|
||||
override val nodeId: NodeId<*>
|
||||
get() = groupKDBX?.nodeId ?: groupKDB?.nodeId ?: NodeIdUUID()
|
||||
|
||||
override var title: String
|
||||
get() = groupKDB?.title ?: groupKDBX?.title ?: ""
|
||||
@@ -270,6 +268,20 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
ArrayList()
|
||||
}
|
||||
|
||||
fun getFilteredChildGroups(filters: Array<ChildFilter>): List<Group> {
|
||||
return groupKDB?.getChildGroups()?.map {
|
||||
Group(it).apply {
|
||||
this.refreshNumberOfChildEntries(filters)
|
||||
}
|
||||
} ?:
|
||||
groupKDBX?.getChildGroups()?.map {
|
||||
Group(it).apply {
|
||||
this.refreshNumberOfChildEntries(filters)
|
||||
}
|
||||
} ?:
|
||||
ArrayList()
|
||||
}
|
||||
|
||||
override fun getChildEntries(): List<Entry> {
|
||||
return groupKDB?.getChildEntries()?.map {
|
||||
Entry(it)
|
||||
@@ -306,8 +318,8 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
ArrayList()
|
||||
}
|
||||
|
||||
fun getNumberOfChildEntries(filters: Array<ChildFilter> = emptyArray()): Int {
|
||||
return getFilteredChildEntries(filters).size
|
||||
fun refreshNumberOfChildEntries(filters: Array<ChildFilter> = emptyArray()) {
|
||||
this.numberOfChildEntries = getFilteredChildEntries(filters).size
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,7 +331,9 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
}
|
||||
|
||||
fun getFilteredChildren(filters: Array<ChildFilter>): List<Node> {
|
||||
return getChildGroups() + getFilteredChildEntries(filters)
|
||||
val nodes = getFilteredChildGroups(filters) + getFilteredChildEntries(filters)
|
||||
refreshNumberOfChildEntries(filters)
|
||||
return nodes
|
||||
}
|
||||
|
||||
override fun addChildGroup(group: Group) {
|
||||
@@ -340,6 +354,24 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateChildGroup(group: Group) {
|
||||
group.groupKDB?.let {
|
||||
groupKDB?.updateChildGroup(it)
|
||||
}
|
||||
group.groupKDBX?.let {
|
||||
groupKDBX?.updateChildGroup(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateChildEntry(entry: Entry) {
|
||||
entry.entryKDB?.let {
|
||||
groupKDB?.updateChildEntry(it)
|
||||
}
|
||||
entry.entryKDBX?.let {
|
||||
groupKDBX?.updateChildEntry(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeChildGroup(group: Group) {
|
||||
group.groupKDB?.let {
|
||||
groupKDB?.removeChildGroup(it)
|
||||
|
||||
@@ -242,13 +242,6 @@ abstract class DatabaseVersioned<
|
||||
}
|
||||
}
|
||||
|
||||
fun updateGroupIndex(group: Group) {
|
||||
val groupId = group.nodeId
|
||||
if (groupIndexes.containsKey(groupId)) {
|
||||
groupIndexes[groupId] = group
|
||||
}
|
||||
}
|
||||
|
||||
fun removeGroupIndex(group: Group) {
|
||||
this.groupIndexes.remove(group.nodeId)
|
||||
}
|
||||
@@ -291,13 +284,6 @@ abstract class DatabaseVersioned<
|
||||
}
|
||||
}
|
||||
|
||||
fun updateEntryIndex(entry: Entry) {
|
||||
val entryId = entry.nodeId
|
||||
if (entryIndexes.containsKey(entryId)) {
|
||||
entryIndexes[entryId] = entry
|
||||
}
|
||||
}
|
||||
|
||||
fun removeEntryIndex(entry: Entry) {
|
||||
this.entryIndexes.remove(entry.nodeId)
|
||||
}
|
||||
@@ -329,7 +315,11 @@ abstract class DatabaseVersioned<
|
||||
}
|
||||
|
||||
fun updateGroup(group: Group) {
|
||||
updateGroupIndex(group)
|
||||
group.parent?.updateChildGroup(group)
|
||||
val groupId = group.nodeId
|
||||
if (groupIndexes.containsKey(groupId)) {
|
||||
groupIndexes[groupId] = group
|
||||
}
|
||||
}
|
||||
|
||||
fun removeGroupFrom(groupToRemove: Group, parent: Group?) {
|
||||
@@ -346,7 +336,11 @@ abstract class DatabaseVersioned<
|
||||
}
|
||||
|
||||
open fun updateEntry(entry: Entry) {
|
||||
updateEntryIndex(entry)
|
||||
entry.parent?.updateChildEntry(entry)
|
||||
val entryId = entry.nodeId
|
||||
if (entryIndexes.containsKey(entryId)) {
|
||||
entryIndexes[entryId] = entry
|
||||
}
|
||||
}
|
||||
|
||||
open fun removeEntryFrom(entryToRemove: Entry, parent: Group?) {
|
||||
|
||||
@@ -98,6 +98,24 @@ abstract class GroupVersioned
|
||||
this.childEntries.add(entry)
|
||||
}
|
||||
|
||||
override fun updateChildGroup(group: Group) {
|
||||
val index = this.childGroups.indexOfFirst { it.nodeId == group.nodeId }
|
||||
if (index >= 0) {
|
||||
val oldGroup = this.childGroups.removeAt(index)
|
||||
group.nodeIndexInParentForNaturalOrder = oldGroup.nodeIndexInParentForNaturalOrder
|
||||
this.childGroups.add(index, group)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateChildEntry(entry: Entry) {
|
||||
val index = this.childEntries.indexOfFirst { it.nodeId == entry.nodeId }
|
||||
if (index >= 0) {
|
||||
val oldEntry = this.childEntries.removeAt(index)
|
||||
entry.nodeIndexInParentForNaturalOrder = oldEntry.nodeIndexInParentForNaturalOrder
|
||||
this.childEntries.add(index, entry)
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeChildGroup(group: Group) {
|
||||
this.childGroups.remove(group)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ interface GroupVersionedInterface<Group: GroupVersionedInterface<Group, Entry>,
|
||||
|
||||
fun addChildEntry(entry: Entry)
|
||||
|
||||
fun updateChildGroup(group: Group)
|
||||
|
||||
fun updateChildEntry(entry: Entry)
|
||||
|
||||
fun removeChildGroup(group: Group)
|
||||
|
||||
fun removeChildEntry(entry: Entry)
|
||||
|
||||
@@ -51,6 +51,8 @@ class SearchHelper {
|
||||
override fun operate(node: Entry): Boolean {
|
||||
if (incrementEntry >= max)
|
||||
return false
|
||||
if (database.entryIsTemplate(node) && !searchParameters.searchInTemplates)
|
||||
return false
|
||||
if (entryContainsString(database, node, searchParameters)) {
|
||||
searchGroup?.addChildEntry(node)
|
||||
incrementEntry++
|
||||
@@ -92,12 +94,15 @@ class SearchHelper {
|
||||
* Utility method to perform actions if item is found or not after an auto search in [database]
|
||||
*/
|
||||
fun checkAutoSearchInfo(context: Context,
|
||||
database: Database,
|
||||
database: Database?,
|
||||
searchInfo: SearchInfo?,
|
||||
onItemsFound: (items: List<EntryInfo>) -> Unit,
|
||||
onItemNotFound: () -> Unit,
|
||||
onItemsFound: (openedDatabase: Database,
|
||||
items: List<EntryInfo>) -> Unit,
|
||||
onItemNotFound: (openedDatabase: Database) -> Unit,
|
||||
onDatabaseClosed: () -> Unit) {
|
||||
if (database.loaded && TimeoutHelper.checkTime(context)) {
|
||||
if (database == null || !database.loaded) {
|
||||
onDatabaseClosed.invoke()
|
||||
} else if (TimeoutHelper.checkTime(context)) {
|
||||
var searchWithoutUI = false
|
||||
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
|
||||
&& searchInfo != null
|
||||
@@ -108,18 +113,16 @@ class SearchHelper {
|
||||
PreferencesUtil.omitBackup(context),
|
||||
MAX_SEARCH_ENTRY
|
||||
)?.let { searchGroup ->
|
||||
if (searchGroup.getNumberOfChildEntries() > 0) {
|
||||
if (searchGroup.numberOfChildEntries > 0) {
|
||||
searchWithoutUI = true
|
||||
onItemsFound.invoke(
|
||||
onItemsFound.invoke(database,
|
||||
searchGroup.getChildEntriesInfo(database))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!searchWithoutUI) {
|
||||
onItemNotFound.invoke()
|
||||
onItemNotFound.invoke(database)
|
||||
}
|
||||
} else {
|
||||
onDatabaseClosed.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,6 @@ class SearchParameters {
|
||||
var searchInOther = true
|
||||
var searchInUUIDs = false
|
||||
var searchInTags = true
|
||||
|
||||
var searchInTemplates = false
|
||||
}
|
||||
|
||||
@@ -64,6 +64,11 @@ class IconDrawableFactory(private val retrieveBinaryCache : () -> BinaryCache?,
|
||||
*/
|
||||
private val standardIconMap = HashMap<CacheKey, WeakReference<Drawable>>()
|
||||
|
||||
/**
|
||||
* To load an icon pack only if current one is different
|
||||
*/
|
||||
private var mCurrentIconPack: IconPack? = null
|
||||
|
||||
/**
|
||||
* Get the [SuperDrawable] [iconDraw] (from cache, or build it and add it to the cache if not exists yet), then tint it with [tintColor] if needed
|
||||
*/
|
||||
@@ -79,7 +84,11 @@ class IconDrawableFactory(private val retrieveBinaryCache : () -> BinaryCache?,
|
||||
return SuperDrawable(it)
|
||||
}
|
||||
}
|
||||
val iconPack = IconPackChooser.getSelectedIconPack(context, this)
|
||||
val iconPack = IconPackChooser.getSelectedIconPack(context)
|
||||
if (mCurrentIconPack != iconPack) {
|
||||
this.mCurrentIconPack = iconPack
|
||||
this.clearCache()
|
||||
}
|
||||
iconPack?.iconToResId(icon.standard.id)?.let { iconId ->
|
||||
return SuperDrawable(getIconDrawable(context.resources, iconId, width, tintColor), iconPack.tintable())
|
||||
} ?: run {
|
||||
|
||||
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.icons
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.settings.NestedAppSettingsFragment.Companion.DATABASE_APPEARANCE_PREFERENCE_CHANGED
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import java.util.*
|
||||
|
||||
@@ -90,10 +90,14 @@ object IconPackChooser {
|
||||
|
||||
}
|
||||
|
||||
fun setSelectedIconPack(iconDrawableFactory: IconDrawableFactory, iconPackIdString: String?) {
|
||||
fun setSelectedIconPack(iconPackIdString: String?) {
|
||||
for (iconPack in iconPackList) {
|
||||
if (iconPack.id == iconPackIdString) {
|
||||
iconDrawableFactory.clearCache()
|
||||
// To change list items appearance
|
||||
if (iconPackSelected != null
|
||||
&& iconPackSelected != iconPack) {
|
||||
DATABASE_APPEARANCE_PREFERENCE_CHANGED = true
|
||||
}
|
||||
iconPackSelected = iconPack
|
||||
break
|
||||
}
|
||||
@@ -106,10 +110,11 @@ object IconPackChooser {
|
||||
* @param context Context to build the icon pack if not already build
|
||||
* @return IconPack currently in usage
|
||||
*/
|
||||
fun getSelectedIconPack(context: Context, iconDrawableFactory: IconDrawableFactory): IconPack? {
|
||||
fun getSelectedIconPack(context: Context): IconPack? {
|
||||
build(context)
|
||||
if (iconPackSelected == null)
|
||||
setSelectedIconPack(iconDrawableFactory, PreferencesUtil.getIconPackSelectedId(context))
|
||||
if (iconPackSelected == null) {
|
||||
setSelectedIconPack(PreferencesUtil.getIconPackSelectedId(context))
|
||||
}
|
||||
return iconPackSelected
|
||||
}
|
||||
|
||||
|
||||
@@ -38,15 +38,19 @@ import android.widget.TextView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity
|
||||
import com.kunzisoft.keepass.adapters.FieldsAdapter
|
||||
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.database.element.Field
|
||||
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
||||
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.*
|
||||
|
||||
class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
class MagikeyboardService : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
|
||||
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
|
||||
private var mDatabase: Database? = null
|
||||
|
||||
private var keyboardView: KeyboardView? = null
|
||||
private var entryText: TextView? = null
|
||||
@@ -61,6 +65,11 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
mDatabaseTaskProvider = DatabaseTaskProvider(this)
|
||||
mDatabaseTaskProvider?.registerProgressTask()
|
||||
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
|
||||
this.mDatabase = database
|
||||
}
|
||||
// Remove the entry and lock the keyboard when the lock signal is receive
|
||||
lockReceiver = LockReceiver {
|
||||
removeEntryInfo()
|
||||
@@ -111,13 +120,13 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
|
||||
// Remove entry info if the database is not loaded
|
||||
// or if entry info timestamp is before database loaded timestamp
|
||||
val database = Database.getInstance()
|
||||
val databaseTime = database.loadTimestamp
|
||||
val databaseTime = mDatabase?.loadTimestamp
|
||||
val entryTime = entryInfoTimestamp
|
||||
if (!database.loaded
|
||||
|| databaseTime == null
|
||||
|| entryTime == null
|
||||
|| entryTime < databaseTime) {
|
||||
if (mDatabase == null
|
||||
|| mDatabase?.loaded != true
|
||||
|| databaseTime == null
|
||||
|| entryTime == null
|
||||
|| entryTime < databaseTime) {
|
||||
removeEntryInfo()
|
||||
}
|
||||
assignKeyboardView()
|
||||
@@ -323,11 +332,12 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
override fun onDestroy() {
|
||||
dismissCustomKeys()
|
||||
unregisterLockReceiver(lockReceiver)
|
||||
mDatabaseTaskProvider?.unregisterProgressTask()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = MagikIME::class.java.name
|
||||
private val TAG = MagikeyboardService::class.java.name
|
||||
|
||||
const val KEY_BACK_KEYBOARD = 600
|
||||
const val KEY_CHANGE_KEYBOARD = 601
|
||||
@@ -27,6 +27,7 @@ import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.action.DatabaseTaskProvider
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
@@ -41,6 +42,10 @@ import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class AttachmentFileNotificationService: LockNotificationService() {
|
||||
|
||||
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
|
||||
private var mDatabase: Database? = null
|
||||
private val mPendingCommands: MutableList<Intent?> = mutableListOf()
|
||||
|
||||
override val notificationId: Int = 10000
|
||||
private val attachmentNotificationList = CopyOnWriteArrayList<AttachmentNotification>()
|
||||
|
||||
@@ -49,8 +54,6 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
private var mDatabase: Database? = Database.getInstance()
|
||||
|
||||
override fun retrieveChannelId(): String {
|
||||
return CHANNEL_ATTACHMENT_ID
|
||||
}
|
||||
@@ -82,6 +85,23 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
mDatabaseTaskProvider = DatabaseTaskProvider(this)
|
||||
mDatabaseTaskProvider?.registerProgressTask()
|
||||
mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
|
||||
this.mDatabase = database
|
||||
// Execute each command in wait state
|
||||
val commandIterator = this.mPendingCommands.iterator()
|
||||
while (commandIterator.hasNext()) {
|
||||
val command = commandIterator.next()
|
||||
actionRequested(command)
|
||||
commandIterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ActionTaskListener {
|
||||
fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState)
|
||||
}
|
||||
@@ -93,6 +113,18 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
// Wait for database to execute action request
|
||||
if (mDatabase != null) {
|
||||
actionRequested(intent)
|
||||
} else {
|
||||
mPendingCommands.add(intent)
|
||||
}
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
|
||||
private fun actionRequested(intent: Intent?) {
|
||||
|
||||
val downloadFileUri: Uri? = if (intent?.hasExtra(FILE_URI_KEY) == true) {
|
||||
intent.getParcelableExtra(FILE_URI_KEY)
|
||||
} else null
|
||||
@@ -100,16 +132,16 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
when(intent?.action) {
|
||||
ACTION_ATTACHMENT_FILE_START_UPLOAD -> {
|
||||
actionStartUploadOrDownload(downloadFileUri,
|
||||
intent,
|
||||
StreamDirection.UPLOAD)
|
||||
intent,
|
||||
StreamDirection.UPLOAD)
|
||||
}
|
||||
ACTION_ATTACHMENT_FILE_STOP_UPLOAD -> {
|
||||
actionStopUpload()
|
||||
}
|
||||
ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> {
|
||||
actionStartUploadOrDownload(downloadFileUri,
|
||||
intent,
|
||||
StreamDirection.DOWNLOAD)
|
||||
intent,
|
||||
StreamDirection.DOWNLOAD)
|
||||
}
|
||||
ACTION_ATTACHMENT_REMOVE -> {
|
||||
intent.getParcelableExtra<Attachment>(ATTACHMENT_KEY)?.let { entryAttachment ->
|
||||
@@ -130,8 +162,6 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -249,6 +279,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
notificationManager?.cancel(attachmentNotification.notificationId)
|
||||
}
|
||||
attachmentNotificationList.clear()
|
||||
mDatabaseTaskProvider?.unregisterProgressTask()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@@ -302,5 +302,9 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
||||
if (!startService)
|
||||
context.stopService(intent)
|
||||
}
|
||||
|
||||
fun removeNotification(context: Context?) {
|
||||
context?.stopService(Intent(context, ClipboardEntryNotificationService::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.os.*
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.GroupActivity
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.database.action.*
|
||||
import com.kunzisoft.keepass.database.action.history.DeleteEntryHistoryDatabaseRunnable
|
||||
@@ -54,15 +53,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|
||||
override val notificationId: Int = 575
|
||||
|
||||
private lateinit var mDatabase: Database
|
||||
private var mDatabase: Database? = null
|
||||
|
||||
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 mTaskRemovedRequested = false
|
||||
|
||||
private var mIconId: Int = R.drawable.notification_ic_database_load
|
||||
private var mTitleId: Int = R.string.database_opened
|
||||
@@ -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,20 +118,21 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkDatabaseInfo() {
|
||||
try {
|
||||
mDatabase.fileUri?.let {
|
||||
mDatabase?.fileUri?.let {
|
||||
val previousDatabaseInfo = mSnapFileDatabaseInfo
|
||||
val lastFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||
FileDatabaseInfo(applicationContext, it))
|
||||
@@ -165,7 +173,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|
||||
fun saveDatabaseInfo() {
|
||||
try {
|
||||
mDatabase.fileUri?.let {
|
||||
mDatabase?.fileUri?.let {
|
||||
mSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||
FileDatabaseInfo(applicationContext, it))
|
||||
Log.i(TAG, "Database file saved $mSnapFileDatabaseInfo")
|
||||
@@ -175,6 +183,19 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force to call [ActionTaskListener.onStartAction] if the action is still running
|
||||
*/
|
||||
fun checkAction() {
|
||||
mDatabase?.let { database ->
|
||||
if (mActionRunning) {
|
||||
mActionTaskListeners.forEach { actionTaskListener ->
|
||||
actionTaskListener.onStartAction(database, mTitleId, mMessageId, mWarningId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
return mActionTaskBinder
|
||||
@@ -183,33 +204,39 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
mDatabase = Database.getInstance()
|
||||
val database = Database.getInstance()
|
||||
if (mDatabase != database) {
|
||||
mDatabase = database
|
||||
mDatabaseListeners.forEach { listener ->
|
||||
listener.onDatabaseRetrieved(mDatabase)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the notification
|
||||
buildMessage(intent)
|
||||
buildMessage(intent, database.isReadOnly)
|
||||
|
||||
val intentAction = intent?.action
|
||||
|
||||
if (intentAction == null && !mDatabase.loaded) {
|
||||
if (intentAction == null && !database.loaded) {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
val actionRunnable: ActionRunnable? = when (intentAction) {
|
||||
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent)
|
||||
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent)
|
||||
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask()
|
||||
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent)
|
||||
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent)
|
||||
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent)
|
||||
ACTION_DATABASE_CREATE_ENTRY_TASK -> buildDatabaseCreateEntryActionTask(intent)
|
||||
ACTION_DATABASE_UPDATE_ENTRY_TASK -> buildDatabaseUpdateEntryActionTask(intent)
|
||||
ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent)
|
||||
ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent)
|
||||
ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent)
|
||||
ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent)
|
||||
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent)
|
||||
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent)
|
||||
ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK -> buildDatabaseRemoveUnlinkedDataActionTask(intent)
|
||||
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent, database)
|
||||
ACTION_DATABASE_LOAD_TASK -> buildDatabaseLoadActionTask(intent, database)
|
||||
ACTION_DATABASE_RELOAD_TASK -> buildDatabaseReloadActionTask(database)
|
||||
ACTION_DATABASE_ASSIGN_PASSWORD_TASK -> buildDatabaseAssignPasswordActionTask(intent, database)
|
||||
ACTION_DATABASE_CREATE_GROUP_TASK -> buildDatabaseCreateGroupActionTask(intent, database)
|
||||
ACTION_DATABASE_UPDATE_GROUP_TASK -> buildDatabaseUpdateGroupActionTask(intent, database)
|
||||
ACTION_DATABASE_CREATE_ENTRY_TASK -> buildDatabaseCreateEntryActionTask(intent, database)
|
||||
ACTION_DATABASE_UPDATE_ENTRY_TASK -> buildDatabaseUpdateEntryActionTask(intent, database)
|
||||
ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent, database)
|
||||
ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent, database)
|
||||
ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent, database)
|
||||
ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent, database)
|
||||
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent, database)
|
||||
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent, database)
|
||||
ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK -> buildDatabaseRemoveUnlinkedDataActionTask(intent, database)
|
||||
ACTION_DATABASE_UPDATE_NAME_TASK,
|
||||
ACTION_DATABASE_UPDATE_DESCRIPTION_TASK,
|
||||
ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK,
|
||||
@@ -222,8 +249,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK,
|
||||
ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK,
|
||||
ACTION_DATABASE_UPDATE_PARALLELISM_TASK,
|
||||
ACTION_DATABASE_UPDATE_ITERATIONS_TASK -> buildDatabaseUpdateElementActionTask(intent)
|
||||
ACTION_DATABASE_SAVE -> buildDatabaseSave(intent)
|
||||
ACTION_DATABASE_UPDATE_ITERATIONS_TASK -> buildDatabaseUpdateElementActionTask(intent, database)
|
||||
ACTION_DATABASE_SAVE -> buildDatabaseSave(intent, database)
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -243,7 +270,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
})
|
||||
|
||||
mActionTaskListeners.forEach { actionTaskListener ->
|
||||
actionTaskListener.onStartAction(mTitleId, mMessageId, mWarningId)
|
||||
actionTaskListener.onStartAction(database, mTitleId, mMessageId, mWarningId)
|
||||
}
|
||||
|
||||
},
|
||||
@@ -253,17 +280,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
{ result ->
|
||||
try {
|
||||
mActionTaskListeners.forEach { actionTaskListener ->
|
||||
actionTaskListener.onStopAction(intentAction!!, result)
|
||||
mTaskRemovedRequested = false
|
||||
actionTaskListener.onStopAction(database, intentAction!!, result)
|
||||
}
|
||||
} finally {
|
||||
// Save the database info before performing action
|
||||
if (intentAction == ACTION_DATABASE_LOAD_TASK) {
|
||||
saveDatabaseInfo()
|
||||
}
|
||||
val save = !database.isReadOnly
|
||||
&& (intentAction == ACTION_DATABASE_SAVE
|
||||
|| intent?.getBooleanExtra(SAVE_DATABASE_KEY, false) == true)
|
||||
// Save the database info after performing save action
|
||||
if (intentAction == ACTION_DATABASE_SAVE
|
||||
|| intent?.getBooleanExtra(SAVE_DATABASE_KEY, false) == true) {
|
||||
mDatabase.fileUri?.let {
|
||||
if (save) {
|
||||
database.fileUri?.let {
|
||||
val newSnapFileDatabaseInfo = SnapFileDatabaseInfo.fromFileDatabaseInfo(
|
||||
FileDatabaseInfo(applicationContext, it))
|
||||
mLastLocalSaveTime = System.currentTimeMillis()
|
||||
@@ -272,8 +302,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
removeIntentData(intent)
|
||||
TimeoutHelper.releaseTemporarilyDisableTimeout()
|
||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
|
||||
if (!mDatabase.loaded) {
|
||||
// Stop service after save if user remove task
|
||||
if (save && mTaskRemovedRequested) {
|
||||
actionOnLock()
|
||||
} else if (TimeoutHelper.checkTimeAndLockIfTimeout(this@DatabaseTaskNotificationService)) {
|
||||
if (!database.loaded) {
|
||||
stopSelf()
|
||||
} else {
|
||||
// Restart the service to open lock notification
|
||||
@@ -283,6 +316,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
} catch (e: IllegalStateException) {}
|
||||
}
|
||||
}
|
||||
mTaskRemovedRequested = false
|
||||
}
|
||||
|
||||
sendBroadcast(Intent(DATABASE_STOP_TASK_ACTION))
|
||||
@@ -306,13 +340,13 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildMessage(intent: Intent?) {
|
||||
private fun buildMessage(intent: Intent?, readOnly: Boolean) {
|
||||
// Assign elements for updates
|
||||
val intentAction = intent?.action
|
||||
|
||||
var saveAction = false
|
||||
if (intent != null && intent.hasExtra(SAVE_DATABASE_KEY)) {
|
||||
saveAction = intent.getBooleanExtra(SAVE_DATABASE_KEY, saveAction)
|
||||
saveAction = !readOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, saveAction)
|
||||
}
|
||||
|
||||
mIconId = if (intentAction == null)
|
||||
@@ -364,25 +398,31 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
|
||||
if (intentAction == null) {
|
||||
// Database is normally open
|
||||
if (mDatabase.loaded) {
|
||||
// Build Intents for notification action
|
||||
val pendingDatabaseIntent = PendingIntent.getActivity(this,
|
||||
mDatabase?.let { database ->
|
||||
// Database is normally open
|
||||
if (database.loaded) {
|
||||
// Build Intents for notification action
|
||||
val pendingDatabaseIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
Intent(this, GroupActivity::class.java).apply {
|
||||
ReadOnlyHelper.putReadOnlyInIntent(this, mDatabase.isReadOnly)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val pendingDeleteIntent = PendingIntent.getBroadcast(this,
|
||||
4576, Intent(LOCK_ACTION), 0)
|
||||
// Add actions in notifications
|
||||
notificationBuilder.apply {
|
||||
setContentText(mDatabase.name + " (" + mDatabase.version + ")")
|
||||
setContentIntent(pendingDatabaseIntent)
|
||||
// Unfortunately swipe is disabled in lollipop+
|
||||
setDeleteIntent(pendingDeleteIntent)
|
||||
addAction(R.drawable.ic_lock_white_24dp, getString(R.string.lock),
|
||||
pendingDeleteIntent)
|
||||
Intent(this, GroupActivity::class.java),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
val pendingDeleteIntent = PendingIntent.getBroadcast(
|
||||
this,
|
||||
4576, Intent(LOCK_ACTION), 0
|
||||
)
|
||||
// Add actions in notifications
|
||||
notificationBuilder.apply {
|
||||
setContentText(database.name + " (" + database.version + ")")
|
||||
setContentIntent(pendingDatabaseIntent)
|
||||
// Unfortunately swipe is disabled in lollipop+
|
||||
setDeleteIntent(pendingDeleteIntent)
|
||||
addAction(
|
||||
R.drawable.ic_lock_white_24dp, getString(R.string.lock),
|
||||
pendingDeleteIntent
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,14 +482,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|
||||
override fun updateMessage(resId: Int) {
|
||||
mMessageId = resId
|
||||
mActionTaskListeners.forEach { actionTaskListener ->
|
||||
actionTaskListener.onUpdateAction(mTitleId, mMessageId, mWarningId)
|
||||
mDatabase?.let { database ->
|
||||
mActionTaskListeners.forEach { actionTaskListener ->
|
||||
actionTaskListener.onUpdateAction(database, mTitleId, mMessageId, mWarningId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun actionOnLock() {
|
||||
if (!TimeoutHelper.temporarilyDisableTimeout) {
|
||||
closeDatabase()
|
||||
if (!TimeoutHelper.temporarilyDisableLock) {
|
||||
closeDatabase(mDatabase)
|
||||
// Remove the lock timer (no more needed if it exists)
|
||||
TimeoutHelper.cancelLockTimer(this)
|
||||
// Service is stopped after receive the broadcast
|
||||
@@ -457,7 +499,14 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseCreateActionTask(intent: Intent): ActionRunnable? {
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
if (TimeoutHelper.temporarilyDisableLock) {
|
||||
mTaskRemovedRequested = true
|
||||
}
|
||||
super.onTaskRemoved(rootIntent)
|
||||
}
|
||||
|
||||
private fun buildDatabaseCreateActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
|
||||
if (intent.hasExtra(DATABASE_URI_KEY)
|
||||
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
|
||||
@@ -469,12 +518,12 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
return null
|
||||
|
||||
return CreateDatabaseRunnable(this,
|
||||
mDatabase,
|
||||
databaseUri,
|
||||
getString(R.string.database_default_name),
|
||||
getString(R.string.database),
|
||||
getString(R.string.template_group_name),
|
||||
mainCredential
|
||||
database,
|
||||
databaseUri,
|
||||
getString(R.string.database_default_name),
|
||||
getString(R.string.database),
|
||||
getString(R.string.template_group_name),
|
||||
mainCredential
|
||||
) { result ->
|
||||
result.data = Bundle().apply {
|
||||
putParcelable(DATABASE_URI_KEY, databaseUri)
|
||||
@@ -486,7 +535,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseLoadActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseLoadActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
|
||||
if (intent.hasExtra(DATABASE_URI_KEY)
|
||||
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
|
||||
@@ -504,7 +553,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|
||||
return LoadDatabaseRunnable(
|
||||
this,
|
||||
mDatabase,
|
||||
database,
|
||||
databaseUri,
|
||||
mainCredential,
|
||||
readOnly,
|
||||
@@ -525,10 +574,10 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseReloadActionTask(): ActionRunnable {
|
||||
private fun buildDatabaseReloadActionTask(database: Database): ActionRunnable {
|
||||
return ReloadDatabaseRunnable(
|
||||
this,
|
||||
mDatabase,
|
||||
database,
|
||||
this
|
||||
) { result ->
|
||||
// No need to add each info to reload database
|
||||
@@ -536,15 +585,15 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseAssignPasswordActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseAssignPasswordActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(DATABASE_URI_KEY)
|
||||
&& intent.hasExtra(MAIN_CREDENTIAL_KEY)
|
||||
) {
|
||||
val databaseUri: Uri = intent.getParcelableExtra(DATABASE_URI_KEY) ?: return null
|
||||
AssignPasswordInDatabaseRunnable(this,
|
||||
mDatabase,
|
||||
databaseUri,
|
||||
intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
|
||||
database,
|
||||
databaseUri,
|
||||
intent.getParcelableExtra(MAIN_CREDENTIAL_KEY) ?: MainCredential()
|
||||
)
|
||||
} else {
|
||||
null
|
||||
@@ -561,7 +610,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseCreateGroupActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseCreateGroupActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(GROUP_KEY)
|
||||
&& intent.hasExtra(PARENT_ID_KEY)
|
||||
&& intent.hasExtra(SAVE_DATABASE_KEY)
|
||||
@@ -573,20 +622,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|| newGroup == null)
|
||||
return null
|
||||
|
||||
mDatabase.getGroupById(parentId)?.let { parent ->
|
||||
database.getGroupById(parentId)?.let { parent ->
|
||||
AddGroupRunnable(this,
|
||||
mDatabase,
|
||||
newGroup,
|
||||
parent,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
database,
|
||||
newGroup,
|
||||
parent,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseUpdateGroupActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseUpdateGroupActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(GROUP_ID_KEY)
|
||||
&& intent.hasExtra(GROUP_KEY)
|
||||
&& intent.hasExtra(SAVE_DATABASE_KEY)
|
||||
@@ -598,20 +647,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|| newGroup == null)
|
||||
return null
|
||||
|
||||
mDatabase.getGroupById(groupId)?.let { oldGroup ->
|
||||
database.getGroupById(groupId)?.let { oldGroup ->
|
||||
UpdateGroupRunnable(this,
|
||||
mDatabase,
|
||||
oldGroup,
|
||||
newGroup,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
database,
|
||||
oldGroup,
|
||||
newGroup,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseCreateEntryActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseCreateEntryActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(ENTRY_KEY)
|
||||
&& intent.hasExtra(PARENT_ID_KEY)
|
||||
&& intent.hasExtra(SAVE_DATABASE_KEY)
|
||||
@@ -623,20 +672,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|| newEntry == null)
|
||||
return null
|
||||
|
||||
mDatabase.getGroupById(parentId)?.let { parent ->
|
||||
database.getGroupById(parentId)?.let { parent ->
|
||||
AddEntryRunnable(this,
|
||||
mDatabase,
|
||||
newEntry,
|
||||
parent,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
database,
|
||||
newEntry,
|
||||
parent,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseUpdateEntryActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseUpdateEntryActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(ENTRY_ID_KEY)
|
||||
&& intent.hasExtra(ENTRY_KEY)
|
||||
&& intent.hasExtra(SAVE_DATABASE_KEY)
|
||||
@@ -648,20 +697,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|| newEntry == null)
|
||||
return null
|
||||
|
||||
mDatabase.getEntryById(entryId)?.let { oldEntry ->
|
||||
database.getEntryById(entryId)?.let { oldEntry ->
|
||||
UpdateEntryRunnable(this,
|
||||
mDatabase,
|
||||
oldEntry,
|
||||
newEntry,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
database,
|
||||
oldEntry,
|
||||
newEntry,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseCopyNodesActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseCopyNodesActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(GROUPS_ID_KEY)
|
||||
&& intent.hasExtra(ENTRIES_ID_KEY)
|
||||
&& intent.hasExtra(PARENT_ID_KEY)
|
||||
@@ -669,20 +718,20 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
) {
|
||||
val parentId: NodeId<*> = intent.getParcelableExtra(PARENT_ID_KEY) ?: return null
|
||||
|
||||
mDatabase.getGroupById(parentId)?.let { newParent ->
|
||||
database.getGroupById(parentId)?.let { newParent ->
|
||||
CopyNodesRunnable(this,
|
||||
mDatabase,
|
||||
getListNodesFromBundle(mDatabase, intent.extras!!),
|
||||
newParent,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
database,
|
||||
getListNodesFromBundle(database, intent.extras!!),
|
||||
newParent,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseMoveNodesActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseMoveNodesActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(GROUPS_ID_KEY)
|
||||
&& intent.hasExtra(ENTRIES_ID_KEY)
|
||||
&& intent.hasExtra(PARENT_ID_KEY)
|
||||
@@ -690,73 +739,73 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
) {
|
||||
val parentId: NodeId<*> = intent.getParcelableExtra(PARENT_ID_KEY) ?: return null
|
||||
|
||||
mDatabase.getGroupById(parentId)?.let { newParent ->
|
||||
database.getGroupById(parentId)?.let { newParent ->
|
||||
MoveNodesRunnable(this,
|
||||
mDatabase,
|
||||
getListNodesFromBundle(mDatabase, intent.extras!!),
|
||||
newParent,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
database,
|
||||
getListNodesFromBundle(database, intent.extras!!),
|
||||
newParent,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseDeleteNodesActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseDeleteNodesActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(GROUPS_ID_KEY)
|
||||
&& intent.hasExtra(ENTRIES_ID_KEY)
|
||||
&& intent.hasExtra(SAVE_DATABASE_KEY)
|
||||
) {
|
||||
DeleteNodesRunnable(this,
|
||||
mDatabase,
|
||||
getListNodesFromBundle(mDatabase, intent.extras!!),
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
database,
|
||||
getListNodesFromBundle(database, intent.extras!!),
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false),
|
||||
AfterActionNodesRunnable())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseRestoreEntryHistoryActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseRestoreEntryHistoryActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(ENTRY_ID_KEY)
|
||||
&& intent.hasExtra(ENTRY_HISTORY_POSITION_KEY)
|
||||
&& intent.hasExtra(SAVE_DATABASE_KEY)
|
||||
) {
|
||||
val entryId: NodeId<UUID> = intent.getParcelableExtra(ENTRY_ID_KEY) ?: return null
|
||||
|
||||
mDatabase.getEntryById(entryId)?.let { mainEntry ->
|
||||
database.getEntryById(entryId)?.let { mainEntry ->
|
||||
RestoreEntryHistoryDatabaseRunnable(this,
|
||||
mDatabase,
|
||||
mainEntry,
|
||||
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
|
||||
database,
|
||||
mainEntry,
|
||||
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseDeleteEntryHistoryActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseDeleteEntryHistoryActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(ENTRY_ID_KEY)
|
||||
&& intent.hasExtra(ENTRY_HISTORY_POSITION_KEY)
|
||||
&& intent.hasExtra(SAVE_DATABASE_KEY)
|
||||
) {
|
||||
val entryId: NodeId<UUID> = intent.getParcelableExtra(ENTRY_ID_KEY) ?: return null
|
||||
|
||||
mDatabase.getEntryById(entryId)?.let { mainEntry ->
|
||||
database.getEntryById(entryId)?.let { mainEntry ->
|
||||
DeleteEntryHistoryDatabaseRunnable(this,
|
||||
mDatabase,
|
||||
mainEntry,
|
||||
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
|
||||
database,
|
||||
mainEntry,
|
||||
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseUpdateCompressionActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseUpdateCompressionActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(OLD_ELEMENT_KEY)
|
||||
&& intent.hasExtra(NEW_ELEMENT_KEY)
|
||||
&& intent.hasExtra(SAVE_DATABASE_KEY)) {
|
||||
@@ -769,10 +818,10 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
return null
|
||||
|
||||
return UpdateCompressionBinariesDatabaseRunnable(this,
|
||||
mDatabase,
|
||||
oldElement,
|
||||
newElement,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
|
||||
database,
|
||||
oldElement,
|
||||
newElement,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
|
||||
).apply {
|
||||
mAfterSaveDatabase = { result ->
|
||||
result.data = intent.extras
|
||||
@@ -783,12 +832,12 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseRemoveUnlinkedDataActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseRemoveUnlinkedDataActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
|
||||
|
||||
return RemoveUnlinkedDataDatabaseRunnable(this,
|
||||
mDatabase,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
|
||||
database,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
|
||||
).apply {
|
||||
mAfterSaveDatabase = { result ->
|
||||
result.data = intent.extras
|
||||
@@ -799,11 +848,11 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseUpdateElementActionTask(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
|
||||
return SaveDatabaseRunnable(this,
|
||||
mDatabase,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
|
||||
database,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false)
|
||||
).apply {
|
||||
mAfterSaveDatabase = { result ->
|
||||
result.data = intent.extras
|
||||
@@ -817,22 +866,16 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
/**
|
||||
* Save database without parameter
|
||||
*/
|
||||
private fun buildDatabaseSave(intent: Intent): ActionRunnable? {
|
||||
private fun buildDatabaseSave(intent: Intent, database: Database): ActionRunnable? {
|
||||
return if (intent.hasExtra(SAVE_DATABASE_KEY)) {
|
||||
SaveDatabaseRunnable(this,
|
||||
mDatabase,
|
||||
intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
|
||||
database,
|
||||
!database.isReadOnly && intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (mDatabase.loaded)
|
||||
actionOnLock()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = DatabaseTaskNotificationService::class.java.name
|
||||
@@ -916,9 +959,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
nodes.forEach { nodeVersioned ->
|
||||
when (nodeVersioned.type) {
|
||||
Type.GROUP -> {
|
||||
(nodeVersioned as Group).nodeId?.let { groupId ->
|
||||
groupsId.add(groupId)
|
||||
}
|
||||
groupsId.add((nodeVersioned as Group).nodeId)
|
||||
}
|
||||
Type.ENTRY -> {
|
||||
entriesId.add((nodeVersioned as Entry).nodeId)
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
@@ -117,14 +117,14 @@ class KeyboardEntryNotificationService : LockNotificationService() {
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
MagikIME.removeEntry(this)
|
||||
MagikeyboardService.removeEntry(this)
|
||||
|
||||
super.onTaskRemoved(rootIntent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
// Remove the entry from the keyboard
|
||||
MagikIME.removeEntry(this)
|
||||
MagikeyboardService.removeEntry(this)
|
||||
|
||||
pendingDeleteIntent?.cancel()
|
||||
|
||||
@@ -165,7 +165,7 @@ class KeyboardEntryNotificationService : LockNotificationService() {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
MagikIME.removeEntry(context)
|
||||
MagikeyboardService.removeEntry(context)
|
||||
}
|
||||
|
||||
if (!startService)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.kunzisoft.keepass.services
|
||||
|
||||
import android.content.Intent
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.LockReceiver
|
||||
import com.kunzisoft.keepass.utils.registerLockReceiver
|
||||
import com.kunzisoft.keepass.utils.unregisterLockReceiver
|
||||
@@ -42,7 +43,9 @@ abstract class LockNotificationService : NotificationService() {
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
stopSelf()
|
||||
if (!TimeoutHelper.temporarilyDisableLock) {
|
||||
actionOnLock()
|
||||
}
|
||||
super.onTaskRemoved(rootIntent)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||
|
||||
class AutofillSettingsActivity : SpecialModeActivity() {
|
||||
class AutofillSettingsActivity : DatabaseModeActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
abstract class DatabasePreferenceFragment : PreferenceFragmentCompat() {
|
||||
|
||||
protected var mDatabase: Database? = null
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
mDatabase = Database.getInstance()
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import androidx.appcompat.widget.Toolbar
|
||||
import android.view.MenuItem
|
||||
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||
|
||||
class MagikeyboardSettingsActivity : SpecialModeActivity() {
|
||||
class MagikeyboardSettingsActivity : DatabaseModeActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -21,13 +21,20 @@ 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.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||
|
||||
class MainPreferenceFragment : DatabasePreferenceFragment() {
|
||||
class MainPreferenceFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private var mCallback: Callback? = null
|
||||
|
||||
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
|
||||
private var mDatabaseLoaded: Boolean = false
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
@@ -42,6 +49,18 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
|
||||
mCallback = null
|
||||
super.onDetach()
|
||||
}
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
|
||||
mDatabaseLoaded = database?.loaded == true
|
||||
checkDatabaseLoaded()
|
||||
}
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
|
||||
private fun checkDatabaseLoaded() {
|
||||
findPreference<Preference>(getString(R.string.settings_database_key))
|
||||
?.isEnabled = mDatabaseLoaded
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||
@@ -80,9 +99,6 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
|
||||
mCallback?.onNestedPreferenceSelected(NestedSettingsFragment.Screen.DATABASE)
|
||||
false
|
||||
}
|
||||
if (mDatabase?.loaded != true) {
|
||||
isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.settings_database_security_key))?.apply {
|
||||
@@ -98,6 +114,8 @@ class MainPreferenceFragment : DatabasePreferenceFragment() {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
checkDatabaseLoaded()
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.settings.preference.IconPackListPreference
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
@@ -190,6 +191,13 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
false
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.clipboard_notifications_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (!(newValue as Boolean)) {
|
||||
ClipboardEntryNotificationService.removeNotification(context)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener {
|
||||
UriUtil.gotoUrl(requireContext(), R.string.clipboard_explanation_url)
|
||||
false
|
||||
@@ -384,8 +392,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
private fun onCreateAppearancePreferences(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_appearance, rootKey)
|
||||
|
||||
// To change list items appearance
|
||||
PreferencesUtil.APPEARANCE_CHANGED = true
|
||||
DATABASE_APPEARANCE_PREFERENCE_CHANGED = true
|
||||
|
||||
activity?.let { activity ->
|
||||
findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||
@@ -402,7 +409,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
Stylish.assignStyle(activity, styleIdString)
|
||||
// Relaunch the current activity to redraw theme
|
||||
(activity as? SettingsActivity?)?.apply {
|
||||
relaunchCurrentScreen()
|
||||
reloadActivity()
|
||||
}
|
||||
}
|
||||
styleEnabled
|
||||
@@ -410,7 +417,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
|
||||
findPreference<ListPreference>(getString(R.string.setting_style_brightness_key))?.setOnPreferenceChangeListener { _, _ ->
|
||||
(activity as? SettingsActivity?)?.apply {
|
||||
relaunchCurrentScreen()
|
||||
reloadActivity()
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -426,9 +433,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
}
|
||||
}
|
||||
if (iconPackEnabled) {
|
||||
mDatabase?.let {
|
||||
IconPackChooser.setSelectedIconPack(it.iconDrawableFactory, iconPackId)
|
||||
}
|
||||
IconPackChooser.setSelectedIconPack(iconPackId)
|
||||
}
|
||||
iconPackEnabled
|
||||
}
|
||||
@@ -507,5 +512,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
companion object {
|
||||
private const val REQUEST_CODE_AUTOFILL = 5201
|
||||
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
|
||||
|
||||
var DATABASE_APPEARANCE_PREFERENCE_CHANGED = false
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,18 @@ 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
|
||||
import com.kunzisoft.androidclearchroma.ChromaUtil
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
|
||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
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,12 +43,17 @@ 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() {
|
||||
class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetrieval {
|
||||
|
||||
private val mDatabaseViewModel: DatabaseViewModel by activityViewModels()
|
||||
private var mDatabase: Database? = null
|
||||
private var mDatabaseReadOnly: Boolean = false
|
||||
private var mDatabaseAutoSaveEnabled: Boolean = true
|
||||
|
||||
private var mScreen: Screen? = null
|
||||
|
||||
private var dbNamePref: InputTextPreference? = null
|
||||
private var dbDescriptionPref: InputTextPreference? = null
|
||||
private var dbDefaultUsername: InputTextPreference? = null
|
||||
@@ -61,179 +69,220 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
private var mMemoryPref: InputKdfSizePreference? = null
|
||||
private var mParallelismPref: InputKdfNumberPreference? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
|
||||
mDatabase = database
|
||||
view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), database?.loaded)
|
||||
onDatabaseRetrieved(database)
|
||||
}
|
||||
|
||||
mDatabaseViewModel.actionFinished.observe(viewLifecycleOwner) {
|
||||
onDatabaseActionFinished(it.database, it.actionTask, it.result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
mDatabaseReadOnly = mDatabase?.isReadOnly == true
|
||||
|| ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
|
||||
|
||||
mScreen = screen
|
||||
val database = mDatabase
|
||||
// Load the preferences from an XML resource
|
||||
when (screen) {
|
||||
Screen.DATABASE -> {
|
||||
onCreateDatabasePreference(rootKey)
|
||||
setPreferencesFromResource(R.xml.preferences_database, rootKey)
|
||||
if (database?.loaded == true)
|
||||
onCreateDatabasePreference(database)
|
||||
}
|
||||
Screen.DATABASE_SECURITY -> {
|
||||
onCreateDatabaseSecurityPreference(rootKey)
|
||||
setPreferencesFromResource(R.xml.preferences_database_security, rootKey)
|
||||
if (database?.loaded == true)
|
||||
onCreateDatabaseSecurityPreference(database)
|
||||
}
|
||||
Screen.DATABASE_MASTER_KEY -> {
|
||||
onCreateDatabaseMasterKeyPreference(rootKey)
|
||||
setPreferencesFromResource(R.xml.preferences_database_master_key, rootKey)
|
||||
if (database?.loaded == true)
|
||||
onCreateDatabaseMasterKeyPreference(database)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDatabasePreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_database, rootKey)
|
||||
private fun saveDatabase(save: Boolean) {
|
||||
mDatabaseViewModel.saveDatabase(save)
|
||||
}
|
||||
|
||||
mDatabase?.let { database ->
|
||||
if (database.loaded) {
|
||||
private fun reloadDatabase() {
|
||||
mDatabaseViewModel.reloadDatabase(false)
|
||||
}
|
||||
|
||||
val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_general_key))
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
mDatabase = database
|
||||
mDatabaseReadOnly = database?.isReadOnly == true
|
||||
|
||||
// Database name
|
||||
dbNamePref = findPreference(getString(R.string.database_name_key))
|
||||
if (database.allowName) {
|
||||
dbNamePref?.summary = database.name
|
||||
} else {
|
||||
dbGeneralPrefCategory?.removePreference(dbNamePref)
|
||||
}
|
||||
|
||||
// Database description
|
||||
dbDescriptionPref = findPreference(getString(R.string.database_description_key))
|
||||
if (database.allowDescription) {
|
||||
dbDescriptionPref?.summary = database.description
|
||||
} else {
|
||||
dbGeneralPrefCategory?.removePreference(dbDescriptionPref)
|
||||
}
|
||||
|
||||
// Database default username
|
||||
dbDefaultUsername = findPreference(getString(R.string.database_default_username_key))
|
||||
if (database.allowDefaultUsername) {
|
||||
dbDefaultUsername?.summary = database.defaultUsername
|
||||
} else {
|
||||
dbDefaultUsername?.isEnabled = false
|
||||
// TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername)
|
||||
}
|
||||
|
||||
// Database custom color
|
||||
dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key))
|
||||
if (database.allowCustomColor) {
|
||||
dbCustomColorPref?.apply {
|
||||
try {
|
||||
color = Color.parseColor(database.customColor)
|
||||
summary = database.customColor
|
||||
} catch (e: Exception) {
|
||||
color = DialogColorPreference.DISABLE_COLOR
|
||||
summary = ""
|
||||
}
|
||||
mDatabase?.let {
|
||||
if (it.loaded) {
|
||||
when (mScreen) {
|
||||
Screen.DATABASE -> {
|
||||
onCreateDatabasePreference(it)
|
||||
}
|
||||
} else {
|
||||
dbCustomColorPref?.isEnabled = false
|
||||
// TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref)
|
||||
}
|
||||
|
||||
// Version
|
||||
findPreference<Preference>(getString(R.string.database_version_key))
|
||||
?.summary = database.version
|
||||
|
||||
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_data_key))
|
||||
|
||||
// Database compression
|
||||
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
|
||||
if (database.allowDataCompression) {
|
||||
dbDataCompressionPref?.summary = (database.compressionAlgorithm
|
||||
?: CompressionAlgorithm.None).getName(resources)
|
||||
} else {
|
||||
dbCompressionPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
val dbRecycleBinPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_recycle_bin_key))
|
||||
recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key))
|
||||
|
||||
// Recycle bin
|
||||
if (database.allowConfigurableRecycleBin) {
|
||||
val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key))
|
||||
recycleBinEnablePref?.apply {
|
||||
isChecked = database.isRecycleBinEnabled
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val recycleBinEnabled = newValue as Boolean
|
||||
database.enableRecycleBin(recycleBinEnabled, resources)
|
||||
refreshRecycleBinGroup()
|
||||
// Save the database if not in readonly mode
|
||||
(context as SettingsActivity?)?.
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSave(mDatabaseAutoSaveEnabled)
|
||||
true
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
Screen.DATABASE_SECURITY -> {
|
||||
onCreateDatabaseSecurityPreference(it)
|
||||
}
|
||||
// Change the recycle bin group
|
||||
recycleBinGroupPref?.setOnPreferenceClickListener {
|
||||
|
||||
true
|
||||
Screen.DATABASE_MASTER_KEY -> {
|
||||
onCreateDatabaseMasterKeyPreference(it)
|
||||
}
|
||||
// Recycle Bin group
|
||||
refreshRecycleBinGroup()
|
||||
} else {
|
||||
recycleBinGroupPref?.onPreferenceClickListener = null
|
||||
dbRecycleBinPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
// Templates
|
||||
val templatesGroupPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_templates_key))
|
||||
templatesGroupPref = findPreference(getString(R.string.templates_group_uuid_key))
|
||||
if (database.allowConfigurableTemplatesGroup) {
|
||||
val templatesEnablePref: SwitchPreference? = findPreference(getString(R.string.templates_group_enable_key))
|
||||
templatesEnablePref?.apply {
|
||||
isChecked = database.isTemplatesEnabled
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val templatesEnabled = newValue as Boolean
|
||||
database.enableTemplates(templatesEnabled, resources)
|
||||
refreshTemplatesGroup()
|
||||
// Save the database if not in readonly mode
|
||||
(context as SettingsActivity?)?.
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSave(mDatabaseAutoSaveEnabled)
|
||||
true
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
// Recycle Bin group
|
||||
refreshTemplatesGroup()
|
||||
} else {
|
||||
templatesGroupPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
// History
|
||||
findPreference<PreferenceCategory>(getString(R.string.database_category_history_key))
|
||||
?.isVisible = database.manageHistory == true
|
||||
|
||||
// Max history items
|
||||
dbMaxHistoryItemsPref = findPreference<InputNumberPreference>(getString(R.string.max_history_items_key))?.apply {
|
||||
summary = database.historyMaxItems.toString()
|
||||
}
|
||||
|
||||
// Max history size
|
||||
dbMaxHistorySizePref = findPreference<InputNumberPreference>(getString(R.string.max_history_size_key))?.apply {
|
||||
summary = database.historyMaxSize.toString()
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.e(javaClass.name, "Database isn't ready")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshRecycleBinGroup() {
|
||||
private fun onCreateDatabasePreference(database: Database) {
|
||||
val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_general_key))
|
||||
|
||||
// Database name
|
||||
dbNamePref = findPreference(getString(R.string.database_name_key))
|
||||
if (database.allowName) {
|
||||
dbNamePref?.summary = database.name
|
||||
} else {
|
||||
dbGeneralPrefCategory?.removePreference(dbNamePref)
|
||||
}
|
||||
|
||||
// Database description
|
||||
dbDescriptionPref = findPreference(getString(R.string.database_description_key))
|
||||
if (database.allowDescription) {
|
||||
dbDescriptionPref?.summary = database.description
|
||||
} else {
|
||||
dbGeneralPrefCategory?.removePreference(dbDescriptionPref)
|
||||
}
|
||||
|
||||
// Database default username
|
||||
dbDefaultUsername = findPreference(getString(R.string.database_default_username_key))
|
||||
if (database.allowDefaultUsername) {
|
||||
dbDefaultUsername?.summary = database.defaultUsername
|
||||
} else {
|
||||
dbDefaultUsername?.isEnabled = false
|
||||
// TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername)
|
||||
}
|
||||
|
||||
// Database custom color
|
||||
dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key))
|
||||
if (database.allowCustomColor) {
|
||||
dbCustomColorPref?.apply {
|
||||
try {
|
||||
color = Color.parseColor(database.customColor)
|
||||
summary = database.customColor
|
||||
} catch (e: Exception) {
|
||||
color = DialogColorPreference.DISABLE_COLOR
|
||||
summary = ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dbCustomColorPref?.isEnabled = false
|
||||
// TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref)
|
||||
}
|
||||
|
||||
// Version
|
||||
findPreference<Preference>(getString(R.string.database_version_key))
|
||||
?.summary = database.version
|
||||
|
||||
val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_data_key))
|
||||
|
||||
// Database compression
|
||||
dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key))
|
||||
if (database.allowDataCompression) {
|
||||
dbDataCompressionPref?.summary = (database.compressionAlgorithm
|
||||
?: CompressionAlgorithm.None).getName(resources)
|
||||
} else {
|
||||
dbCompressionPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
val dbRecycleBinPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_recycle_bin_key))
|
||||
recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key))
|
||||
|
||||
// Recycle bin
|
||||
if (database.allowConfigurableRecycleBin) {
|
||||
val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key))
|
||||
recycleBinEnablePref?.apply {
|
||||
isChecked = database.isRecycleBinEnabled
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val recycleBinEnabled = newValue as Boolean
|
||||
database.enableRecycleBin(recycleBinEnabled, resources)
|
||||
refreshRecycleBinGroup(database)
|
||||
// Save the database if not in readonly mode
|
||||
saveDatabase(mDatabaseAutoSaveEnabled)
|
||||
true
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
// Change the recycle bin group
|
||||
recycleBinGroupPref?.setOnPreferenceClickListener {
|
||||
|
||||
true
|
||||
}
|
||||
// Recycle Bin group
|
||||
refreshRecycleBinGroup(database)
|
||||
} else {
|
||||
recycleBinGroupPref?.onPreferenceClickListener = null
|
||||
dbRecycleBinPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
// Templates
|
||||
val templatesGroupPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_templates_key))
|
||||
templatesGroupPref = findPreference(getString(R.string.templates_group_uuid_key))
|
||||
if (database.allowConfigurableTemplatesGroup) {
|
||||
val templatesEnablePref: SwitchPreference? = findPreference(getString(R.string.templates_group_enable_key))
|
||||
templatesEnablePref?.apply {
|
||||
isChecked = database.isTemplatesEnabled
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val templatesEnabled = newValue as Boolean
|
||||
database.enableTemplates(templatesEnabled, resources)
|
||||
refreshTemplatesGroup(database)
|
||||
// Save the database if not in readonly mode
|
||||
saveDatabase(mDatabaseAutoSaveEnabled)
|
||||
true
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
// Recycle Bin group
|
||||
refreshTemplatesGroup(database)
|
||||
} else {
|
||||
templatesGroupPrefCategory?.isVisible = false
|
||||
}
|
||||
|
||||
// History
|
||||
findPreference<PreferenceCategory>(getString(R.string.database_category_history_key))
|
||||
?.isVisible = database.manageHistory == true
|
||||
|
||||
// Max history items
|
||||
dbMaxHistoryItemsPref = findPreference<InputNumberPreference>(getString(R.string.max_history_items_key))?.apply {
|
||||
summary = database.historyMaxItems.toString()
|
||||
}
|
||||
|
||||
// Max history size
|
||||
dbMaxHistorySizePref = findPreference<InputNumberPreference>(getString(R.string.max_history_size_key))?.apply {
|
||||
summary = database.historyMaxSize.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshRecycleBinGroup(database: Database?) {
|
||||
recycleBinGroupPref?.apply {
|
||||
if (mDatabase?.isRecycleBinEnabled == true) {
|
||||
summary = mDatabase?.recycleBin?.toString()
|
||||
if (database?.isRecycleBinEnabled == true) {
|
||||
summary = database.recycleBin?.toString()
|
||||
isEnabled = true
|
||||
} else {
|
||||
summary = null
|
||||
@@ -242,10 +291,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshTemplatesGroup() {
|
||||
private fun refreshTemplatesGroup(database: Database?) {
|
||||
templatesGroupPref?.apply {
|
||||
if (mDatabase?.isTemplatesEnabled == true) {
|
||||
summary = mDatabase?.templatesGroup?.toString()
|
||||
if (database?.isTemplatesEnabled == true) {
|
||||
summary = database.templatesGroup?.toString()
|
||||
isEnabled = true
|
||||
} else {
|
||||
summary = null
|
||||
@@ -254,60 +303,44 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDatabaseSecurityPreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_database_security, rootKey)
|
||||
private fun onCreateDatabaseSecurityPreference(database: Database) {
|
||||
// Encryption Algorithm
|
||||
mEncryptionAlgorithmPref = findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))?.apply {
|
||||
summary = database.getEncryptionAlgorithmName()
|
||||
}
|
||||
|
||||
mDatabase?.let { database ->
|
||||
if (database.loaded) {
|
||||
// Encryption Algorithm
|
||||
mEncryptionAlgorithmPref = findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))?.apply {
|
||||
summary = database.getEncryptionAlgorithmName()
|
||||
}
|
||||
// Key derivation function
|
||||
mKeyDerivationPref = findPreference<DialogListExplanationPreference>(getString(R.string.key_derivation_function_key))?.apply {
|
||||
summary = database.getKeyDerivationName()
|
||||
}
|
||||
|
||||
// Key derivation function
|
||||
mKeyDerivationPref = findPreference<DialogListExplanationPreference>(getString(R.string.key_derivation_function_key))?.apply {
|
||||
summary = database.getKeyDerivationName()
|
||||
}
|
||||
// Round encryption
|
||||
mRoundPref = findPreference<InputKdfNumberPreference>(getString(R.string.transform_rounds_key))?.apply {
|
||||
summary = database.numberKeyEncryptionRounds.toString()
|
||||
}
|
||||
|
||||
// Round encryption
|
||||
mRoundPref = findPreference<InputKdfNumberPreference>(getString(R.string.transform_rounds_key))?.apply {
|
||||
summary = database.numberKeyEncryptionRounds.toString()
|
||||
}
|
||||
// Memory Usage
|
||||
mMemoryPref = findPreference<InputKdfSizePreference>(getString(R.string.memory_usage_key))?.apply {
|
||||
summary = database.memoryUsage.toString()
|
||||
}
|
||||
|
||||
// Memory Usage
|
||||
mMemoryPref = findPreference<InputKdfSizePreference>(getString(R.string.memory_usage_key))?.apply {
|
||||
summary = database.memoryUsage.toString()
|
||||
}
|
||||
|
||||
// Parallelism
|
||||
mParallelismPref = findPreference<InputKdfNumberPreference>(getString(R.string.parallelism_key))?.apply {
|
||||
summary = database.parallelism.toString()
|
||||
}
|
||||
} else {
|
||||
Log.e(javaClass.name, "Database isn't ready")
|
||||
}
|
||||
// Parallelism
|
||||
mParallelismPref = findPreference<InputKdfNumberPreference>(getString(R.string.parallelism_key))?.apply {
|
||||
summary = database.parallelism.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCreateDatabaseMasterKeyPreference(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_database_master_key, rootKey)
|
||||
|
||||
mDatabase?.let { database ->
|
||||
if (database.loaded) {
|
||||
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
AssignMasterKeyDialogFragment.getInstance(database.allowNoMasterKey)
|
||||
.show(parentFragmentManager, "passwordDialog")
|
||||
false
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
private fun onCreateDatabaseMasterKeyPreference(database: Database) {
|
||||
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
|
||||
isEnabled = if (!mDatabaseReadOnly) {
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
AssignMasterKeyDialogFragment.getInstance(database.allowNoMasterKey)
|
||||
.show(parentFragmentManager, "passwordDialog")
|
||||
false
|
||||
}
|
||||
true
|
||||
} else {
|
||||
Log.e(javaClass.name, "Database isn't ready")
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,8 +366,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onProgressDialogThreadResult(actionTask: String,
|
||||
result: ActionRunnable.Result) {
|
||||
// TODO check error
|
||||
override fun onDatabaseActionFinished(database: Database,
|
||||
actionTask: String,
|
||||
result: ActionRunnable.Result) {
|
||||
result.data?.let { data ->
|
||||
if (data.containsKey(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||
&& data.containsKey(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)) {
|
||||
@@ -351,7 +386,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
if (result.isSuccess) {
|
||||
newName
|
||||
} else {
|
||||
mDatabase?.name = oldName
|
||||
database.name = oldName
|
||||
oldName
|
||||
}
|
||||
dbNamePref?.summary = nameToShow
|
||||
@@ -363,7 +398,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
if (result.isSuccess) {
|
||||
newDescription
|
||||
} else {
|
||||
mDatabase?.description = oldDescription
|
||||
database.description = oldDescription
|
||||
oldDescription
|
||||
}
|
||||
dbDescriptionPref?.summary = descriptionToShow
|
||||
@@ -415,7 +450,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
oldRecycleBin
|
||||
}
|
||||
mDatabase?.setRecycleBin(recycleBinToShow)
|
||||
refreshRecycleBinGroup()
|
||||
refreshRecycleBinGroup(database)
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_TEMPLATES_GROUP_TASK -> {
|
||||
val oldTemplatesGroup = data.getParcelable<Group?>(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||
@@ -427,7 +462,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
oldTemplatesGroup
|
||||
}
|
||||
mDatabase?.setTemplatesGroup(templatesGroupToShow)
|
||||
refreshTemplatesGroup()
|
||||
refreshTemplatesGroup(database)
|
||||
}
|
||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK -> {
|
||||
val oldMaxHistoryItems = data.getInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||
@@ -563,10 +598,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
dialogFragment = DatabaseTemplatesGroupPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
getString(R.string.max_history_items_key) -> {
|
||||
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
dialogFragment = DatabaseMaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
getString(R.string.max_history_size_key) -> {
|
||||
dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
dialogFragment = DatabaseMaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
|
||||
// Security
|
||||
@@ -582,13 +617,13 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
||||
dialogFragment = keyDerivationDialogFragment
|
||||
}
|
||||
getString(R.string.transform_rounds_key) -> {
|
||||
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
dialogFragment = DatabaseRoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
getString(R.string.memory_usage_key) -> {
|
||||
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
dialogFragment = DatabaseMemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
getString(R.string.parallelism_key) -> {
|
||||
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
dialogFragment = DatabaseParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||
}
|
||||
else -> otherDialogFragment = true
|
||||
}
|
||||
@@ -621,37 +656,27 @@ 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 {
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true)
|
||||
// TODO activity menu
|
||||
(activity as SettingsActivity?)?.let {
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(it, item, true)
|
||||
}
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, mDatabaseReadOnly)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT"
|
||||
}
|
||||
|
||||
@@ -22,13 +22,12 @@ package com.kunzisoft.keepass.settings
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreference
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.UnderDevelopmentFeatureDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
|
||||
abstract class NestedSettingsFragment : DatabasePreferenceFragment() {
|
||||
abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
enum class Screen {
|
||||
APPLICATION, FORM_FILLING, ADVANCED_UNLOCK, APPEARANCE, DATABASE, DATABASE_SECURITY, DATABASE_MASTER_KEY
|
||||
@@ -47,9 +46,6 @@ abstract class NestedSettingsFragment : DatabasePreferenceFragment() {
|
||||
|
||||
abstract fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?)
|
||||
|
||||
open fun onProgressDialogThreadResult(actionTask: String,
|
||||
result: ActionRunnable.Result) {}
|
||||
|
||||
protected fun preferenceInDevelopment(preferenceInDev: Preference) {
|
||||
preferenceInDev.setOnPreferenceClickListener { preference ->
|
||||
try { // don't check if we can
|
||||
@@ -65,7 +61,7 @@ abstract class NestedSettingsFragment : DatabasePreferenceFragment() {
|
||||
|
||||
private const val TAG_KEY = "NESTED_KEY"
|
||||
|
||||
fun newInstance(key: Screen, databaseReadOnly: Boolean = ReadOnlyHelper.READ_ONLY_DEFAULT)
|
||||
fun newInstance(key: Screen)
|
||||
: NestedSettingsFragment {
|
||||
val fragment: NestedSettingsFragment = when (key) {
|
||||
Screen.APPLICATION,
|
||||
@@ -79,7 +75,6 @@ abstract class NestedSettingsFragment : DatabasePreferenceFragment() {
|
||||
// supply arguments to bundle.
|
||||
val args = Bundle()
|
||||
args.putInt(TAG_KEY, key.ordinal)
|
||||
ReadOnlyHelper.putReadOnlyInBundle(args, databaseReadOnly)
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
|
||||
@@ -37,8 +37,6 @@ import java.util.*
|
||||
|
||||
object PreferencesUtil {
|
||||
|
||||
var APPEARANCE_CHANGED = false
|
||||
|
||||
fun saveDefaultDatabasePath(context: Context, defaultDatabaseUri: Uri?) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
prefs?.edit()?.apply {
|
||||
|
||||
@@ -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,24 +32,20 @@ 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
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
|
||||
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
|
||||
import java.util.*
|
||||
|
||||
open class SettingsActivity
|
||||
: LockingActivity(),
|
||||
: DatabaseLockActivity(),
|
||||
MainPreferenceFragment.Callback,
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||
PasswordEncodingDialogFragment.Listener {
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
|
||||
|
||||
private var backupManager: BackupManager? = null
|
||||
private var mExternalFileHelper: ExternalFileHelper? = null
|
||||
@@ -60,14 +55,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 +77,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 +87,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 +97,37 @@ open class SettingsActivity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the main fragment to show in first
|
||||
* @return The main fragment
|
||||
*/
|
||||
protected open fun retrieveMainFragment(): Fragment {
|
||||
return MainPreferenceFragment()
|
||||
}
|
||||
|
||||
override fun viewToInvalidateTimeout(): View? {
|
||||
return coordinatorLayout
|
||||
}
|
||||
|
||||
override fun finishActivityIfDatabaseNotLoaded(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDatabaseActionFinished(
|
||||
database: Database,
|
||||
actionTask: String,
|
||||
result: ActionRunnable.Result
|
||||
) {
|
||||
super.onDatabaseActionFinished(database, actionTask, result)
|
||||
|
||||
coordinatorLayout?.showActionErrorIfNeeded(result)
|
||||
}
|
||||
|
||||
override fun reloadActivity() {
|
||||
keepCurrentScreen()
|
||||
super.reloadActivity()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> onBackPressed()
|
||||
@@ -149,31 +141,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) {}
|
||||
@@ -215,7 +184,7 @@ open class SettingsActivity
|
||||
setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
||||
R.anim.slide_in_left, R.anim.slide_out_right)
|
||||
}
|
||||
replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, mReadOnly), TAG_NESTED)
|
||||
replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key), TAG_NESTED)
|
||||
addToBackStack(TAG_NESTED)
|
||||
commit()
|
||||
}
|
||||
@@ -224,17 +193,10 @@ open class SettingsActivity
|
||||
hideOrShowLockButton(key)
|
||||
}
|
||||
|
||||
fun relaunchCurrentScreen() {
|
||||
keepCurrentScreen()
|
||||
startActivity(intent)
|
||||
finish()
|
||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||
}
|
||||
|
||||
/**
|
||||
* To keep the current screen when activity is reloaded
|
||||
*/
|
||||
fun keepCurrentScreen() {
|
||||
private fun keepCurrentScreen() {
|
||||
(supportFragmentManager.findFragmentByTag(TAG_NESTED) as? NestedSettingsFragment?)
|
||||
?.getScreen()?.let { fragmentKey ->
|
||||
intent.putExtra(FRAGMENT_ARG, fragmentKey.name)
|
||||
@@ -243,7 +205,7 @@ open class SettingsActivity
|
||||
|
||||
override fun onNestedPreferenceSelected(key: NestedSettingsFragment.Screen, reload: Boolean) {
|
||||
if (mTimeoutEnable)
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, mDatabase) {
|
||||
checkTimeAndLockIfTimeoutOrResetTimeout {
|
||||
replaceFragment(key, reload)
|
||||
}
|
||||
else
|
||||
@@ -264,8 +226,8 @@ open class SettingsActivity
|
||||
|
||||
// Import app properties result
|
||||
try {
|
||||
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { selectedfileUri ->
|
||||
selectedfileUri?.let { uri ->
|
||||
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { selectedFileUri ->
|
||||
selectedFileUri?.let { uri ->
|
||||
val appProperties = Properties()
|
||||
contentResolver?.openInputStream(uri)?.use { inputStream ->
|
||||
appProperties.load(inputStream)
|
||||
@@ -273,7 +235,7 @@ open class SettingsActivity
|
||||
PreferencesUtil.setAppProperties(this, appProperties)
|
||||
|
||||
// Restart the current activity
|
||||
relaunchCurrentScreen()
|
||||
reloadActivity()
|
||||
Toast.makeText(this, R.string.success_import_app_properties, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
@@ -299,7 +261,7 @@ open class SettingsActivity
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this, R.string.error_export_app_properties, Toast.LENGTH_LONG).show()
|
||||
Log.e(LockingActivity.TAG, "Unable to export app properties", e)
|
||||
Log.e(DatabaseLockActivity.TAG, "Unable to export app properties", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,9 +281,8 @@ open class SettingsActivity
|
||||
private const val TAG_NESTED = "TAG_NESTED"
|
||||
private const val FRAGMENT_ARG = "FRAGMENT_ARG"
|
||||
|
||||
fun launch(activity: Activity, readOnly: Boolean, timeoutEnable: Boolean) {
|
||||
fun launch(activity: Activity, timeoutEnable: Boolean) {
|
||||
val intent = Intent(activity, SettingsActivity::class.java)
|
||||
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
||||
intent.putExtra(TIMEOUT_ENABLE_KEY, timeoutEnable)
|
||||
if (!timeoutEnable) {
|
||||
activity.startActivity(intent)
|
||||
|
||||
@@ -45,7 +45,7 @@ class IconPackListPreference @JvmOverloads constructor(context: Context,
|
||||
|
||||
setEntries(entries.toTypedArray())
|
||||
entryValues = values.toTypedArray()
|
||||
IconPackChooser.getSelectedIconPack(context, Database.getInstance().iconDrawableFactory)?.let { selectedIconPack ->
|
||||
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
|
||||
setDefaultValue(selectedIconPack.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -46,51 +47,12 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog
|
||||
var onColorSelectedListener: ((enable: Boolean, color: Int) -> Unit)? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
||||
val alertDialogBuilder = AlertDialog.Builder(requireActivity())
|
||||
|
||||
rootView = requireActivity().layoutInflater.inflate(R.layout.pref_dialog_input_color, null)
|
||||
enableSwitchView = rootView.findViewById(R.id.switch_element)
|
||||
|
||||
val fragmentManager = childFragmentManager
|
||||
chromaColorFragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_COLORS) as ChromaColorFragment?
|
||||
|
||||
mDatabase?.let { database ->
|
||||
val initColor = try {
|
||||
enableSwitchView.isChecked = true
|
||||
Color.parseColor(database.customColor)
|
||||
} catch (e: Exception) {
|
||||
enableSwitchView.isChecked = false
|
||||
DEFAULT_COLOR
|
||||
}
|
||||
arguments?.putInt(ARG_INITIAL_COLOR, initColor)
|
||||
}
|
||||
|
||||
if (chromaColorFragment == null) {
|
||||
chromaColorFragment = newInstance(arguments)
|
||||
fragmentManager.beginTransaction().apply {
|
||||
add(com.kunzisoft.androidclearchroma.R.id.color_dialog_container, chromaColorFragment!!, TAG_FRAGMENT_COLORS)
|
||||
commit()
|
||||
}
|
||||
}
|
||||
|
||||
alertDialogBuilder.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val currentColor = chromaColorFragment!!.currentColor
|
||||
val customColorEnable = enableSwitchView.isChecked
|
||||
|
||||
onColorSelectedListener?.invoke(customColorEnable, currentColor)
|
||||
|
||||
mDatabase?.let { database ->
|
||||
val newColor = if (customColorEnable) {
|
||||
ChromaUtil.getFormattedColorString(currentColor, false)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val oldColor = database.customColor
|
||||
database.customColor = newColor
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveColor(oldColor, newColor, mDatabaseAutoSaveEnable)
|
||||
}
|
||||
|
||||
onDialogClosed(true)
|
||||
dismiss()
|
||||
}
|
||||
@@ -111,8 +73,50 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
// Nothing here
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
|
||||
database?.let {
|
||||
val initColor = try {
|
||||
enableSwitchView.isChecked = true
|
||||
Color.parseColor(it.customColor)
|
||||
} catch (e: Exception) {
|
||||
enableSwitchView.isChecked = false
|
||||
DEFAULT_COLOR
|
||||
}
|
||||
arguments?.putInt(ARG_INITIAL_COLOR, initColor)
|
||||
}
|
||||
|
||||
val fragmentManager = childFragmentManager
|
||||
chromaColorFragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_COLORS) as ChromaColorFragment?
|
||||
|
||||
if (chromaColorFragment == null) {
|
||||
chromaColorFragment = newInstance(arguments)
|
||||
fragmentManager.beginTransaction().apply {
|
||||
add(com.kunzisoft.androidclearchroma.R.id.color_dialog_container, chromaColorFragment!!, TAG_FRAGMENT_COLORS)
|
||||
commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
if (positiveResult) {
|
||||
val customColorEnable = enableSwitchView.isChecked
|
||||
chromaColorFragment?.currentColor?.let { currentColor ->
|
||||
onColorSelectedListener?.invoke(customColorEnable, currentColor)
|
||||
database?.let {
|
||||
val newColor = if (customColorEnable) {
|
||||
ChromaUtil.getFormattedColorString(currentColor, false)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
val oldColor = database.customColor
|
||||
database.customColor = newColor
|
||||
saveColor(oldColor, newColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
|
||||
|
||||
@@ -31,6 +32,8 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat
|
||||
: DatabaseSavePreferenceDialogFragmentCompat(),
|
||||
ListRadioItemAdapter.RadioItemSelectedCallback<CompressionAlgorithm> {
|
||||
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
private var mCompressionAdapter: ListRadioItemAdapter<CompressionAlgorithm>? = null
|
||||
private var compressionSelected: CompressionAlgorithm? = null
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
@@ -38,32 +41,38 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat
|
||||
|
||||
setExplanationText(R.string.database_data_compression_summary)
|
||||
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.pref_dialog_list)
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
mRecyclerView = view.findViewById(R.id.pref_dialog_list)
|
||||
mRecyclerView?.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
activity?.let { activity ->
|
||||
val compressionAdapter = ListRadioItemAdapter<CompressionAlgorithm>(activity)
|
||||
compressionAdapter.setRadioItemSelectedCallback(this)
|
||||
recyclerView.adapter = compressionAdapter
|
||||
|
||||
mDatabase?.let { database ->
|
||||
compressionSelected = database.compressionAlgorithm
|
||||
compressionAdapter.setItems(database.availableCompressionAlgorithms, compressionSelected)
|
||||
}
|
||||
mCompressionAdapter = ListRadioItemAdapter<CompressionAlgorithm>(activity)
|
||||
mCompressionAdapter?.setRadioItemSelectedCallback(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
setExplanationText(R.string.database_data_compression_summary)
|
||||
|
||||
mRecyclerView?.adapter = mCompressionAdapter
|
||||
|
||||
database?.let {
|
||||
compressionSelected = it.compressionAlgorithm
|
||||
mCompressionAdapter?.setItems(it.availableCompressionAlgorithms, compressionSelected)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
if (compressionSelected != null) {
|
||||
val newCompression = compressionSelected
|
||||
val oldCompression = database.compressionAlgorithm
|
||||
database.compressionAlgorithm = newCompression
|
||||
|
||||
if (oldCompression != null && newCompression != null)
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveCompression(oldCompression, newCompression, mDatabaseAutoSaveEnable)
|
||||
saveCompression(oldCompression, newCompression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,23 +20,23 @@
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
class DatabaseDefaultUsernamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
|
||||
inputText = mDatabase?.defaultUsername?: ""
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
inputText = database?.defaultUsername?: ""
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
mDatabase?.let { database ->
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
database?.let {
|
||||
if (positiveResult) {
|
||||
val newDefaultUsername = inputText
|
||||
val oldDefaultUsername = database.defaultUsername
|
||||
database.defaultUsername = newDefaultUsername
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveDefaultUsername(oldDefaultUsername, newDefaultUsername, mDatabaseAutoSaveEnable)
|
||||
saveDefaultUsername(oldDefaultUsername, newDefaultUsername)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,23 +20,23 @@
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
class DatabaseDescriptionPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
|
||||
inputText = mDatabase?.description ?: ""
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
inputText = database?.description ?: ""
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
mDatabase?.let { database ->
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
database?.let {
|
||||
if (positiveResult) {
|
||||
val newDescription = inputText
|
||||
val oldDescription = database.description
|
||||
database.description = newDescription
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveDescription(oldDescription, newDescription, mDatabaseAutoSaveEnable)
|
||||
saveDescription(oldDescription, newDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,15 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
|
||||
|
||||
class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
|
||||
: DatabaseSavePreferenceDialogFragmentCompat(),
|
||||
ListRadioItemAdapter.RadioItemSelectedCallback<EncryptionAlgorithm> {
|
||||
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
private var mEncryptionAlgorithmAdapter: ListRadioItemAdapter<EncryptionAlgorithm>? = null
|
||||
private var algorithmSelected: EncryptionAlgorithm? = null
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
@@ -38,32 +41,35 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
|
||||
|
||||
setExplanationText(R.string.encryption_explanation)
|
||||
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.pref_dialog_list)
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
mRecyclerView = view.findViewById(R.id.pref_dialog_list)
|
||||
mRecyclerView?.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
activity?.let { activity ->
|
||||
val encryptionAlgorithmAdapter = ListRadioItemAdapter<EncryptionAlgorithm>(activity)
|
||||
encryptionAlgorithmAdapter.setRadioItemSelectedCallback(this)
|
||||
recyclerView.adapter = encryptionAlgorithmAdapter
|
||||
|
||||
mDatabase?.let { database ->
|
||||
algorithmSelected = database.encryptionAlgorithm
|
||||
encryptionAlgorithmAdapter.setItems(database.availableEncryptionAlgorithms, algorithmSelected)
|
||||
}
|
||||
mEncryptionAlgorithmAdapter = ListRadioItemAdapter(activity)
|
||||
mEncryptionAlgorithmAdapter?.setRadioItemSelectedCallback(this)
|
||||
mRecyclerView?.adapter = mEncryptionAlgorithmAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
database?.let {
|
||||
algorithmSelected = database.encryptionAlgorithm
|
||||
mEncryptionAlgorithmAdapter?.setItems(database.availableEncryptionAlgorithms, algorithmSelected)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
if (algorithmSelected != null) {
|
||||
val newAlgorithm = algorithmSelected
|
||||
val oldAlgorithm = database.encryptionAlgorithm
|
||||
database.encryptionAlgorithm = newAlgorithm
|
||||
|
||||
if (oldAlgorithm != null && newAlgorithm != null)
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveEncryption(oldAlgorithm, newAlgorithm, mDatabaseAutoSaveEnable)
|
||||
saveEncryption(oldAlgorithm, newAlgorithm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
|
||||
|
||||
class DatabaseKeyDerivationPreferenceDialogFragmentCompat
|
||||
@@ -33,6 +34,7 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat
|
||||
ListRadioItemAdapter.RadioItemSelectedCallback<KdfEngine> {
|
||||
|
||||
private var kdfEngineSelected: KdfEngine? = null
|
||||
private var mKdfAdapter: ListRadioItemAdapter<KdfEngine>? = null
|
||||
private var roundPreference: Preference? = null
|
||||
private var memoryPreference: Preference? = null
|
||||
private var parallelismPreference: Preference? = null
|
||||
@@ -46,26 +48,30 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
activity?.let { activity ->
|
||||
val kdfAdapter = ListRadioItemAdapter<KdfEngine>(activity)
|
||||
kdfAdapter.setRadioItemSelectedCallback(this)
|
||||
recyclerView.adapter = kdfAdapter
|
||||
|
||||
mDatabase?.let { database ->
|
||||
kdfEngineSelected = database.kdfEngine
|
||||
kdfAdapter.setItems(database.availableKdfEngines, kdfEngineSelected)
|
||||
}
|
||||
mKdfAdapter = ListRadioItemAdapter(activity)
|
||||
mKdfAdapter?.setRadioItemSelectedCallback(this)
|
||||
recyclerView.adapter = mKdfAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
database?.let {
|
||||
kdfEngineSelected = database.kdfEngine
|
||||
mKdfAdapter?.setItems(database.availableKdfEngines, kdfEngineSelected)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
if (database.allowKdfModification) {
|
||||
val newKdfEngine = kdfEngineSelected
|
||||
val oldKdfEngine = database.kdfEngine
|
||||
if (newKdfEngine != null && oldKdfEngine != null) {
|
||||
database.kdfEngine = newKdfEngine
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveKeyDerivation(oldKdfEngine, newKdfEngine, mDatabaseAutoSaveEnable)
|
||||
saveKeyDerivation(oldKdfEngine, newKdfEngine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,18 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
class DatabaseMaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
|
||||
setExplanationText(R.string.max_history_items_summary)
|
||||
mDatabase?.historyMaxItems?.let { maxItemsDatabase ->
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
database?.historyMaxItems?.let { maxItemsDatabase ->
|
||||
inputText = maxItemsDatabase.toString()
|
||||
setSwitchAction({ isChecked ->
|
||||
inputText = if (!isChecked) {
|
||||
@@ -42,9 +46,10 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
var maxHistoryItems: Int = try {
|
||||
inputText.toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
@@ -60,7 +65,7 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial
|
||||
// Remove all history items
|
||||
database.removeOldestHistoryForEachEntry()
|
||||
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems, maxHistoryItems, mDatabaseAutoSaveEnable)
|
||||
saveMaxHistoryItems(oldMaxHistoryItems, maxHistoryItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,8 +75,8 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial
|
||||
const val DEFAULT_MAX_HISTORY_ITEMS = 10
|
||||
const val NONE_MAX_HISTORY_ITEMS = -1
|
||||
|
||||
fun newInstance(key: String): MaxHistoryItemsPreferenceDialogFragmentCompat {
|
||||
val fragment = MaxHistoryItemsPreferenceDialogFragmentCompat()
|
||||
fun newInstance(key: String): DatabaseMaxHistoryItemsPreferenceDialogFragmentCompat {
|
||||
val fragment = DatabaseMaxHistoryItemsPreferenceDialogFragmentCompat()
|
||||
val bundle = Bundle(1)
|
||||
bundle.putString(ARG_KEY, key)
|
||||
fragment.arguments = bundle
|
||||
@@ -22,19 +22,23 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.utils.DataByte
|
||||
|
||||
class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
class DatabaseMaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
|
||||
private var dataByte = DataByte(2L, DataByte.ByteFormat.MEBIBYTE)
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
|
||||
setExplanationText(R.string.max_history_size_summary)
|
||||
mDatabase?.historyMaxSize?.let { maxItemsDatabase ->
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
database?.historyMaxSize?.let { maxItemsDatabase ->
|
||||
dataByte = DataByte(maxItemsDatabase, DataByte.ByteFormat.BYTE)
|
||||
.toBetterByteFormat()
|
||||
.toBetterByteFormat()
|
||||
inputText = dataByte.number.toString()
|
||||
if (dataByte.number >= 0) {
|
||||
setUnitText(dataByte.format.stringId)
|
||||
@@ -54,13 +58,13 @@ class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialo
|
||||
}
|
||||
showInputText(isChecked)
|
||||
}, maxItemsDatabase > INFINITE_MAX_HISTORY_SIZE)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
val maxHistorySize: Long = try {
|
||||
inputText.toLong()
|
||||
} catch (e: NumberFormatException) {
|
||||
@@ -80,7 +84,7 @@ class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialo
|
||||
val oldMaxHistorySize = database.historyMaxSize
|
||||
database.historyMaxSize = numberOfBytes
|
||||
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveMaxHistorySize(oldMaxHistorySize, numberOfBytes, mDatabaseAutoSaveEnable)
|
||||
saveMaxHistorySize(oldMaxHistorySize, numberOfBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,8 +96,8 @@ class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialo
|
||||
private val INFINITE_MAX_HISTORY_SIZE_DATA_BYTE = DataByte(INFINITE_MAX_HISTORY_SIZE, DataByte.ByteFormat.MEBIBYTE)
|
||||
private val DEFAULT_MAX_HISTORY_SIZE_DATA_BYTE = DataByte(6L, DataByte.ByteFormat.MEBIBYTE)
|
||||
|
||||
fun newInstance(key: String): MaxHistorySizePreferenceDialogFragmentCompat {
|
||||
val fragment = MaxHistorySizePreferenceDialogFragmentCompat()
|
||||
fun newInstance(key: String): DatabaseMaxHistorySizePreferenceDialogFragmentCompat {
|
||||
val fragment = DatabaseMaxHistorySizePreferenceDialogFragmentCompat()
|
||||
val bundle = Bundle(1)
|
||||
bundle.putString(ARG_KEY, key)
|
||||
fragment.arguments = bundle
|
||||
@@ -22,27 +22,32 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.utils.DataByte
|
||||
|
||||
class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
class DatabaseMemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
|
||||
private var dataByte = DataByte(MIN_MEMORY_USAGE, DataByte.ByteFormat.BYTE)
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
|
||||
setExplanationText(R.string.memory_usage_explanation)
|
||||
|
||||
val memoryBytes = mDatabase?.memoryUsage ?: MIN_MEMORY_USAGE
|
||||
dataByte = DataByte(memoryBytes, DataByte.ByteFormat.BYTE)
|
||||
.toBetterByteFormat()
|
||||
inputText = dataByte.number.toString()
|
||||
setUnitText(dataByte.format.stringId)
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
database?.let {
|
||||
val memoryBytes = database.memoryUsage
|
||||
dataByte = DataByte(memoryBytes, DataByte.ByteFormat.BYTE)
|
||||
.toBetterByteFormat()
|
||||
inputText = dataByte.number.toString()
|
||||
setUnitText(dataByte.format.stringId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
var newMemoryUsage: Long = try {
|
||||
inputText.toLong()
|
||||
} catch (e: NumberFormatException) {
|
||||
@@ -61,7 +66,7 @@ class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
|
||||
val oldMemoryUsage = database.memoryUsage
|
||||
database.memoryUsage = numberOfBytes
|
||||
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveMemoryUsage(oldMemoryUsage, numberOfBytes, mDatabaseAutoSaveEnable)
|
||||
saveMemoryUsage(oldMemoryUsage, numberOfBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,8 +75,8 @@ class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
|
||||
|
||||
const val MIN_MEMORY_USAGE = 1L
|
||||
|
||||
fun newInstance(key: String): MemoryUsagePreferenceDialogFragmentCompat {
|
||||
val fragment = MemoryUsagePreferenceDialogFragmentCompat()
|
||||
fun newInstance(key: String): DatabaseMemoryUsagePreferenceDialogFragmentCompat {
|
||||
val fragment = DatabaseMemoryUsagePreferenceDialogFragmentCompat()
|
||||
val bundle = Bundle(1)
|
||||
bundle.putString(ARG_KEY, key)
|
||||
fragment.arguments = bundle
|
||||
@@ -20,23 +20,23 @@
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
class DatabaseNamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
|
||||
inputText = mDatabase?.name ?: ""
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
inputText = database?.name ?: ""
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
val newName = inputText
|
||||
val oldName = database.name
|
||||
database.name = newName
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveName(oldName, newName, mDatabaseAutoSaveEnable)
|
||||
saveName(oldName, newName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,19 +22,23 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
class ParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
class DatabaseParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
|
||||
setExplanationText(R.string.parallelism_explanation)
|
||||
inputText = mDatabase?.parallelism?.toString() ?: MIN_PARALLELISM.toString()
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
inputText = database?.parallelism?.toString() ?: MIN_PARALLELISM.toString()
|
||||
}
|
||||
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
val parallelism: Long = try {
|
||||
inputText.toLong()
|
||||
} catch (e: NumberFormatException) {
|
||||
@@ -44,10 +48,7 @@ class ParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
|
||||
val oldParallelism = database.parallelism
|
||||
database.parallelism = parallelism
|
||||
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveParallelism(
|
||||
oldParallelism,
|
||||
parallelism,
|
||||
mDatabaseAutoSaveEnable)
|
||||
saveParallelism(oldParallelism, parallelism)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,8 +57,8 @@ class ParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr
|
||||
|
||||
const val MIN_PARALLELISM = 1L
|
||||
|
||||
fun newInstance(key: String): ParallelismPreferenceDialogFragmentCompat {
|
||||
val fragment = ParallelismPreferenceDialogFragmentCompat()
|
||||
fun newInstance(key: String): DatabaseParallelismPreferenceDialogFragmentCompat {
|
||||
val fragment = DatabaseParallelismPreferenceDialogFragmentCompat()
|
||||
val bundle = Bundle(1)
|
||||
bundle.putString(ARG_KEY, key)
|
||||
fragment.arguments = bundle
|
||||
@@ -24,6 +24,7 @@ import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.Group
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
|
||||
|
||||
@@ -31,6 +32,7 @@ class DatabaseRecycleBinGroupPreferenceDialogFragmentCompat
|
||||
: DatabaseSavePreferenceDialogFragmentCompat(),
|
||||
ListRadioItemAdapter.RadioItemSelectedCallback<Group> {
|
||||
|
||||
private var mGroupsAdapter: ListRadioItemAdapter<Group>? = null
|
||||
private var mGroupRecycleBin: Group? = null
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
@@ -40,14 +42,17 @@ class DatabaseRecycleBinGroupPreferenceDialogFragmentCompat
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
activity?.let { activity ->
|
||||
val groupsAdapter = ListRadioItemAdapter<Group>(activity)
|
||||
groupsAdapter.setRadioItemSelectedCallback(this)
|
||||
recyclerView.adapter = groupsAdapter
|
||||
mGroupsAdapter = ListRadioItemAdapter(activity)
|
||||
mGroupsAdapter?.setRadioItemSelectedCallback(this)
|
||||
recyclerView.adapter = mGroupsAdapter
|
||||
}
|
||||
}
|
||||
|
||||
mDatabase?.let { database ->
|
||||
mGroupRecycleBin = database.recycleBin
|
||||
groupsAdapter.setItems(database.getAllGroupsWithoutRoot(), mGroupRecycleBin)
|
||||
}
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
database?.let {
|
||||
mGroupRecycleBin = database.recycleBin
|
||||
mGroupsAdapter?.setItems(database.getAllGroupsWithoutRoot(), mGroupRecycleBin)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +60,15 @@ class DatabaseRecycleBinGroupPreferenceDialogFragmentCompat
|
||||
mGroupRecycleBin = item
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
if (database.allowConfigurableRecycleBin) {
|
||||
val oldGroup = database.recycleBin
|
||||
val newGroup = mGroupRecycleBin
|
||||
database.setRecycleBin(newGroup)
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveRecycleBin(
|
||||
oldGroup,
|
||||
newGroup,
|
||||
mDatabaseAutoSaveEnable
|
||||
)
|
||||
saveRecycleBin(oldGroup, newGroup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
class DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
|
||||
@@ -36,10 +37,11 @@ class DatabaseRemoveUnlinkedDataPreferenceDialogFragmentCompat : DatabaseSavePre
|
||||
}.toString()
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
mDatabase?.let { _ ->
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
database?.let {
|
||||
if (positiveResult) {
|
||||
mProgressDatabaseTaskProvider?.startDatabaseRemoveUnlinkedData(mDatabaseAutoSaveEnable)
|
||||
removeUnlinkedData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,19 +23,23 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
|
||||
class RoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
class DatabaseRoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat() {
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
super.onBindDialogView(view)
|
||||
|
||||
explanationText = getString(R.string.rounds_explanation)
|
||||
inputText = mDatabase?.numberKeyEncryptionRounds?.toString() ?: MIN_ITERATIONS.toString()
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
inputText = database?.numberKeyEncryptionRounds?.toString() ?: MIN_ITERATIONS.toString()
|
||||
}
|
||||
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
var rounds: Long = try {
|
||||
inputText.toLong()
|
||||
} catch (e: NumberFormatException) {
|
||||
@@ -54,7 +58,7 @@ class RoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmen
|
||||
database.numberKeyEncryptionRounds = Long.MAX_VALUE
|
||||
}
|
||||
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveIterations(oldRounds, rounds, mDatabaseAutoSaveEnable)
|
||||
saveIterations(oldRounds, rounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,8 +67,8 @@ class RoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmen
|
||||
|
||||
const val MIN_ITERATIONS = 1L
|
||||
|
||||
fun newInstance(key: String): RoundsPreferenceDialogFragmentCompat {
|
||||
val fragment = RoundsPreferenceDialogFragmentCompat()
|
||||
fun newInstance(key: String): DatabaseRoundsPreferenceDialogFragmentCompat {
|
||||
val fragment = DatabaseRoundsPreferenceDialogFragmentCompat()
|
||||
val bundle = Bundle(1)
|
||||
bundle.putString(ARG_KEY, key)
|
||||
fragment.arguments = bundle
|
||||
@@ -20,28 +20,134 @@
|
||||
package com.kunzisoft.keepass.settings.preferencedialogfragment
|
||||
|
||||
import android.content.Context
|
||||
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.kunzisoft.keepass.activities.legacy.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()
|
||||
private 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 onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mDatabaseViewModel.database.observe(this) { database ->
|
||||
onDatabaseRetrieved(database)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
onDatabaseRetrieved(mDatabase)
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
this.mDatabase = database
|
||||
}
|
||||
|
||||
override fun onDatabaseActionFinished(
|
||||
database: Database,
|
||||
actionTask: String,
|
||||
result: ActionRunnable.Result
|
||||
) {
|
||||
// Not used
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
onDialogClosed(mDatabase, positiveResult)
|
||||
}
|
||||
|
||||
open fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
// To inherit to save element in database
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.Group
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
|
||||
|
||||
@@ -31,6 +32,7 @@ class DatabaseTemplatesGroupPreferenceDialogFragmentCompat
|
||||
: DatabaseSavePreferenceDialogFragmentCompat(),
|
||||
ListRadioItemAdapter.RadioItemSelectedCallback<Group> {
|
||||
|
||||
private var mGroupsAdapter: ListRadioItemAdapter<Group>? = null
|
||||
private var mGroupTemplates: Group? = null
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
@@ -40,14 +42,17 @@ class DatabaseTemplatesGroupPreferenceDialogFragmentCompat
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
activity?.let { activity ->
|
||||
val groupsAdapter = ListRadioItemAdapter<Group>(activity)
|
||||
groupsAdapter.setRadioItemSelectedCallback(this)
|
||||
recyclerView.adapter = groupsAdapter
|
||||
mGroupsAdapter = ListRadioItemAdapter(activity)
|
||||
mGroupsAdapter?.setRadioItemSelectedCallback(this)
|
||||
recyclerView.adapter = mGroupsAdapter
|
||||
}
|
||||
}
|
||||
|
||||
mDatabase?.let { database ->
|
||||
mGroupTemplates = database.templatesGroup
|
||||
groupsAdapter.setItems(database.getAllGroupsWithoutRoot(), mGroupTemplates)
|
||||
}
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
database?.let {
|
||||
mGroupTemplates = database.templatesGroup
|
||||
mGroupsAdapter?.setItems(database.getAllGroupsWithoutRoot(), mGroupTemplates)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +60,15 @@ class DatabaseTemplatesGroupPreferenceDialogFragmentCompat
|
||||
mGroupTemplates = item
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
override fun onDialogClosed(database: Database?, positiveResult: Boolean) {
|
||||
super.onDialogClosed(database, positiveResult)
|
||||
if (positiveResult) {
|
||||
mDatabase?.let { database ->
|
||||
database?.let {
|
||||
if (database.allowConfigurableTemplatesGroup) {
|
||||
val oldGroup = database.templatesGroup
|
||||
val newGroup = mGroupTemplates
|
||||
database.setTemplatesGroup(newGroup)
|
||||
mProgressDatabaseTaskProvider?.startDatabaseSaveTemplatesGroup(
|
||||
oldGroup,
|
||||
newGroup,
|
||||
mDatabaseAutoSaveEnable
|
||||
)
|
||||
saveTemplatesGroup(oldGroup, newGroup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||
|
||||
@@ -39,7 +38,7 @@ object TimeoutHelper {
|
||||
private const val TAG = "TimeoutHelper"
|
||||
|
||||
private var lastAppTimeoutRecord: Long? = null
|
||||
var temporarilyDisableTimeout = false
|
||||
var temporarilyDisableLock = false
|
||||
private set
|
||||
|
||||
private fun getLockPendingIntent(context: Context): PendingIntent {
|
||||
@@ -53,8 +52,8 @@ object TimeoutHelper {
|
||||
* Start the lock timer by creating an alarm,
|
||||
* if the method is recalled with a previous lock timer pending, the previous one is deleted
|
||||
*/
|
||||
private fun startLockTimer(context: Context, database: Database) {
|
||||
if (database.loaded) {
|
||||
private fun startLockTimer(context: Context, databaseLoaded: Boolean) {
|
||||
if (databaseLoaded) {
|
||||
val timeout = PreferencesUtil.getAppTimeout(context)
|
||||
if (timeout != NEVER) {
|
||||
// No timeout don't start timeout service
|
||||
@@ -84,14 +83,14 @@ object TimeoutHelper {
|
||||
/**
|
||||
* Record the current time, to check it later with checkTime and start a new lock timer
|
||||
*/
|
||||
fun recordTime(context: Context, database: Database) {
|
||||
fun recordTime(context: Context, databaseLoaded: Boolean) {
|
||||
// To prevent spam registration, record after at least 2 seconds
|
||||
if (lastAppTimeoutRecord == null
|
||||
|| lastAppTimeoutRecord!! + 2000 <= System.currentTimeMillis()) {
|
||||
Log.d(TAG, "Record app timeout")
|
||||
// Record timeout time in case timeout service is killed
|
||||
PreferencesUtil.saveCurrentTime(context)
|
||||
startLockTimer(context, database)
|
||||
startLockTimer(context, databaseLoaded)
|
||||
lastAppTimeoutRecord = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
@@ -103,7 +102,7 @@ object TimeoutHelper {
|
||||
*/
|
||||
fun checkTime(context: Context, timeoutAction: (() -> Unit)? = null): Boolean {
|
||||
// No effect if temporarily disable
|
||||
if (temporarilyDisableTimeout)
|
||||
if (temporarilyDisableLock)
|
||||
return true
|
||||
|
||||
// Check whether the timeout has expired
|
||||
@@ -147,12 +146,10 @@ object TimeoutHelper {
|
||||
* Check the time previously record then, if timeout lock the database, else reset the timer
|
||||
*/
|
||||
fun checkTimeAndLockIfTimeoutOrResetTimeout(context: Context,
|
||||
database: Database?,
|
||||
databaseLoaded: Boolean,
|
||||
action: (() -> Unit)? = null) {
|
||||
if (checkTimeAndLockIfTimeout(context)) {
|
||||
database?.let {
|
||||
recordTime(context, it)
|
||||
}
|
||||
recordTime(context, databaseLoaded)
|
||||
action?.invoke()
|
||||
}
|
||||
}
|
||||
@@ -161,13 +158,13 @@ object TimeoutHelper {
|
||||
* Temporarily disable timeout, checkTime() function always return true
|
||||
*/
|
||||
fun temporarilyDisableTimeout() {
|
||||
temporarilyDisableTimeout = true
|
||||
temporarilyDisableLock = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the temporarily disable timeout
|
||||
*/
|
||||
fun releaseTemporarilyDisableTimeout() {
|
||||
temporarilyDisableTimeout = false
|
||||
temporarilyDisableLock = false
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import android.os.Build
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService
|
||||
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.services.KeyboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
@@ -51,7 +51,7 @@ class LockReceiver(var lockAction: () -> Unit) : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
// If allowed, lock and exit
|
||||
if (!TimeoutHelper.temporarilyDisableTimeout) {
|
||||
if (!TimeoutHelper.temporarilyDisableLock) {
|
||||
intent.action?.let {
|
||||
when (it) {
|
||||
Intent.ACTION_SCREEN_ON -> {
|
||||
@@ -125,10 +125,10 @@ fun Context.unregisterLockReceiver(lockReceiver: LockReceiver?) {
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.closeDatabase() {
|
||||
fun Context.closeDatabase(database: Database?) {
|
||||
// Stop the Magikeyboard service
|
||||
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||
MagikIME.removeEntry(this)
|
||||
MagikeyboardService.removeEntry(this)
|
||||
|
||||
// Stop the notification service
|
||||
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
|
||||
@@ -138,5 +138,5 @@ fun Context.closeDatabase() {
|
||||
cancelAll()
|
||||
}
|
||||
// Clear data
|
||||
Database.getInstance().clearAndClose(this)
|
||||
database?.clearAndClose(this)
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import android.view.MenuItem
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.AboutActivity
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper.READ_ONLY_DEFAULT
|
||||
import com.kunzisoft.keepass.settings.SettingsActivity
|
||||
|
||||
object MenuUtil {
|
||||
@@ -52,7 +51,6 @@ object MenuUtil {
|
||||
*/
|
||||
fun onDefaultMenuOptionsItemSelected(activity: Activity,
|
||||
item: MenuItem,
|
||||
readOnly: Boolean = READ_ONLY_DEFAULT,
|
||||
timeoutEnable: Boolean = false) {
|
||||
when (item.itemId) {
|
||||
R.id.menu_contribute -> {
|
||||
@@ -60,7 +58,7 @@ object MenuUtil {
|
||||
}
|
||||
R.id.menu_app_settings -> {
|
||||
// To avoid flickering when launch settings in a LockingActivity
|
||||
SettingsActivity.launch(activity, readOnly, timeoutEnable)
|
||||
SettingsActivity.launch(activity, timeoutEnable)
|
||||
}
|
||||
R.id.menu_about -> {
|
||||
val intent = Intent(activity, AboutActivity::class.java)
|
||||
|
||||
@@ -28,8 +28,8 @@ import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.util.AttributeSet
|
||||
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME.Companion.KEY_BACK_KEYBOARD
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME.Companion.KEY_CHANGE_KEYBOARD
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService.Companion.KEY_BACK_KEYBOARD
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikeyboardService.Companion.KEY_CHANGE_KEYBOARD
|
||||
|
||||
class MagikeyboardView : KeyboardView {
|
||||
|
||||
|
||||
@@ -71,6 +71,16 @@ abstract class TemplateAbstractView<
|
||||
|
||||
// To show icon image
|
||||
var populateIconMethod: ((ImageView, IconImage) -> Unit)? = null
|
||||
set(value) {
|
||||
field = value
|
||||
refreshIcon()
|
||||
}
|
||||
|
||||
fun refreshIcon() {
|
||||
mEntryInfo?.icon?.let {
|
||||
populateIconMethod?.invoke(entryIconView, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTemplate(template: Template?) {
|
||||
if (mTemplate != template) {
|
||||
|
||||
@@ -193,9 +193,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
|
||||
override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<FieldId> {
|
||||
mEntryInfo?.let { entryInfo ->
|
||||
setIcon(entryInfo.icon)
|
||||
}
|
||||
refreshIcon()
|
||||
return super.populateViewsWithEntryInfo(showEmptyFields)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
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
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
|
||||
class DatabaseViewModel: ViewModel() {
|
||||
|
||||
val database : LiveData<Database?> get() = _database
|
||||
private val _database = MutableLiveData<Database?>()
|
||||
|
||||
val actionFinished : LiveData<ActionResult> get() = _actionFinished
|
||||
private val _actionFinished = SingleLiveEvent<ActionResult>()
|
||||
|
||||
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 onActionFinished(database: Database,
|
||||
actionTask: String,
|
||||
result: ActionRunnable.Result) {
|
||||
this._actionFinished.value = ActionResult(database, actionTask, result)
|
||||
}
|
||||
|
||||
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 ActionResult(val database: Database,
|
||||
val actionTask: String,
|
||||
val result: ActionRunnable.Result)
|
||||
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)
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package com.kunzisoft.keepass.viewmodels
|
||||
import android.net.Uri
|
||||
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.*
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
@@ -12,26 +11,18 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.template.Template
|
||||
import com.kunzisoft.keepass.model.*
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.view.DataDate
|
||||
import com.kunzisoft.keepass.view.DataTime
|
||||
import java.util.*
|
||||
|
||||
|
||||
class EntryEditViewModel: ViewModel() {
|
||||
|
||||
private val mDatabase: Database? = Database.getInstance()
|
||||
|
||||
private var mParent : Group? = null
|
||||
private var mEntry : Entry? = null
|
||||
private var mIsTemplate: Boolean = false
|
||||
class EntryEditViewModel: NodeEditViewModel() {
|
||||
|
||||
private val mTempAttachments = mutableListOf<EntryAttachmentState>()
|
||||
|
||||
val entryInfo : LiveData<EntryInfo> get() = _entryInfo
|
||||
private val _entryInfo = MutableLiveData<EntryInfo>()
|
||||
|
||||
val requestEntryInfoUpdate : LiveData<Void?> get() = _requestEntryInfoUpdate
|
||||
private val _requestEntryInfoUpdate = SingleLiveEvent<Void?>()
|
||||
val requestEntryInfoUpdate : LiveData<EntryUpdate> get() = _requestEntryInfoUpdate
|
||||
private val _requestEntryInfoUpdate = SingleLiveEvent<EntryUpdate>()
|
||||
val onEntrySaved : LiveData<EntrySave> get() = _onEntrySaved
|
||||
private val _onEntrySaved = SingleLiveEvent<EntrySave>()
|
||||
|
||||
@@ -40,11 +31,6 @@ class EntryEditViewModel: ViewModel() {
|
||||
val onTemplateChanged : LiveData<Template> get() = _onTemplateChanged
|
||||
private val _onTemplateChanged = SingleLiveEvent<Template>()
|
||||
|
||||
val requestIconSelection : LiveData<IconImage> get() = _requestIconSelection
|
||||
private val _requestIconSelection = SingleLiveEvent<IconImage>()
|
||||
val onIconSelected : LiveData<IconImage> get() = _onIconSelected
|
||||
private val _onIconSelected = SingleLiveEvent<IconImage>()
|
||||
|
||||
val requestPasswordSelection : LiveData<Field> get() = _requestPasswordSelection
|
||||
private val _requestPasswordSelection = SingleLiveEvent<Field>()
|
||||
val onPasswordSelected : LiveData<Field> get() = _onPasswordSelected
|
||||
@@ -57,13 +43,6 @@ class EntryEditViewModel: ViewModel() {
|
||||
val onCustomFieldError : LiveData<Void?> get() = _onCustomFieldError
|
||||
private val _onCustomFieldError = SingleLiveEvent<Void?>()
|
||||
|
||||
val requestDateTimeSelection : LiveData<DateInstant> get() = _requestDateTimeSelection
|
||||
private val _requestDateTimeSelection = SingleLiveEvent<DateInstant>()
|
||||
val onDateSelected : LiveData<DataDate> get() = _onDateSelected
|
||||
private val _onDateSelected = SingleLiveEvent<DataDate>()
|
||||
val onTimeSelected : LiveData<DataTime> get() = _onTimeSelected
|
||||
private val _onTimeSelected = SingleLiveEvent<DataTime>()
|
||||
|
||||
val requestSetupOtp : LiveData<Void?> get() = _requestSetupOtp
|
||||
private val _requestSetupOtp = SingleLiveEvent<Void?>()
|
||||
val onOtpCreated : LiveData<OtpElement> get() = _onOtpCreated
|
||||
@@ -80,41 +59,16 @@ class EntryEditViewModel: ViewModel() {
|
||||
val onBinaryPreviewLoaded : LiveData<AttachmentPosition> get() = _onBinaryPreviewLoaded
|
||||
private val _onBinaryPreviewLoaded = SingleLiveEvent<AttachmentPosition>()
|
||||
|
||||
fun initializeEntryToUpdate(entryId: NodeId<UUID>,
|
||||
registerInfo: RegisterInfo?,
|
||||
searchInfo: SearchInfo?) {
|
||||
IOActionTask(
|
||||
{
|
||||
mEntry = getEntry(entryId)
|
||||
mIsTemplate = isEntryATemplate()
|
||||
},
|
||||
{
|
||||
loadTemplateEntry(registerInfo, searchInfo)
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
|
||||
fun initializeEntryToCreate(parentId: NodeId<*>,
|
||||
registerInfo: RegisterInfo?,
|
||||
searchInfo: SearchInfo?) {
|
||||
fun loadTemplateEntry(database: Database,
|
||||
entry: Entry?,
|
||||
isTemplate: Boolean,
|
||||
registerInfo: RegisterInfo?,
|
||||
searchInfo: SearchInfo?) {
|
||||
IOActionTask(
|
||||
{
|
||||
mParent = getGroup(parentId)
|
||||
mEntry = createEntry(mParent)
|
||||
mIsTemplate = isEntryATemplate()
|
||||
},
|
||||
{
|
||||
loadTemplateEntry(registerInfo, searchInfo)
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
|
||||
private fun loadTemplateEntry(registerInfo: RegisterInfo?,
|
||||
searchInfo: SearchInfo?) {
|
||||
IOActionTask(
|
||||
{
|
||||
val templates = getTemplates()
|
||||
val entryTemplate = getEntryTemplate()
|
||||
val templates = database.getTemplates(isTemplate)
|
||||
val entryTemplate = entry?.let { database.getTemplate(it) } ?: Template.STANDARD
|
||||
TemplatesLoad(templates, entryTemplate)
|
||||
},
|
||||
{ templatesLoad ->
|
||||
@@ -124,7 +78,24 @@ class EntryEditViewModel: ViewModel() {
|
||||
|
||||
IOActionTask(
|
||||
{
|
||||
loadEntryInfo(registerInfo, searchInfo)
|
||||
var entryInfo: EntryInfo? = null
|
||||
// Decode the entry / load entry info
|
||||
entry?.let {
|
||||
database.decodeEntryWithTemplateConfiguration(it).let { entry ->
|
||||
// Load entry info
|
||||
entry.getEntryInfo(database, true).let { tempEntryInfo ->
|
||||
// Retrieve data from registration
|
||||
(registerInfo?.searchInfo ?: searchInfo)?.let { tempSearchInfo ->
|
||||
tempEntryInfo.saveSearchInfo(database, tempSearchInfo)
|
||||
}
|
||||
registerInfo?.let { regInfo ->
|
||||
tempEntryInfo.saveRegisterInfo(database, regInfo)
|
||||
}
|
||||
entryInfo = tempEntryInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
entryInfo
|
||||
},
|
||||
{ entryInfo ->
|
||||
_entryInfo.value = entryInfo
|
||||
@@ -134,125 +105,46 @@ class EntryEditViewModel: ViewModel() {
|
||||
).execute()
|
||||
}
|
||||
|
||||
private fun getEntry(entryId: NodeId<UUID>): Entry? {
|
||||
// Create an Entry copy to modify from the database entry
|
||||
val tempEntry = mDatabase?.getEntryById(entryId)
|
||||
// Retrieve the parent
|
||||
tempEntry?.let { entry ->
|
||||
// If no parent, add root group as parent
|
||||
if (entry.parent == null) {
|
||||
entry.parent = mDatabase?.rootGroup
|
||||
}
|
||||
}
|
||||
return tempEntry
|
||||
}
|
||||
|
||||
private fun getGroup(groupId: NodeId<*>): Group? {
|
||||
return mDatabase?.getGroupById(groupId)
|
||||
}
|
||||
|
||||
private fun createEntry(parentGroup: Group?): Entry? {
|
||||
return mDatabase?.createEntry()?.apply {
|
||||
// Add the default icon from parent if not a folder
|
||||
val parentIcon = parentGroup?.icon
|
||||
// Set default icon
|
||||
if (parentIcon != null) {
|
||||
if (parentIcon.custom.isUnknown
|
||||
&& parentIcon.standard.id != IconImageStandard.FOLDER_ID) {
|
||||
icon = IconImage(parentIcon.standard)
|
||||
}
|
||||
if (!parentIcon.custom.isUnknown) {
|
||||
icon = IconImage(parentIcon.custom)
|
||||
}
|
||||
}
|
||||
// Set default username
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEntryATemplate(): Boolean {
|
||||
// Define is current entry is a template (in direct template group)
|
||||
return mDatabase?.entryIsTemplate(mEntry) ?: false
|
||||
}
|
||||
|
||||
private fun getTemplates(): List<Template> {
|
||||
return mDatabase?.getTemplates(mIsTemplate) ?: listOf()
|
||||
}
|
||||
|
||||
private fun getEntryTemplate(): Template {
|
||||
return mEntry?.let { mDatabase?.getTemplate(it) } ?: Template.STANDARD
|
||||
}
|
||||
|
||||
private fun loadEntryInfo(registerInfo: RegisterInfo?, searchInfo: SearchInfo?): EntryInfo? {
|
||||
// Decode the entry
|
||||
mEntry?.let {
|
||||
mDatabase?.decodeEntryWithTemplateConfiguration(it)?.let { entry ->
|
||||
// Load entry info
|
||||
entry.getEntryInfo(mDatabase, true).let { tempEntryInfo ->
|
||||
// Retrieve data from registration
|
||||
(registerInfo?.searchInfo ?: searchInfo)?.let { tempSearchInfo ->
|
||||
tempEntryInfo.saveSearchInfo(mDatabase, tempSearchInfo)
|
||||
}
|
||||
registerInfo?.let { regInfo ->
|
||||
tempEntryInfo.saveRegisterInfo(mDatabase, regInfo)
|
||||
}
|
||||
|
||||
internalUpdateEntryInfo(tempEntryInfo)
|
||||
return tempEntryInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun changeTemplate(template: Template) {
|
||||
if (_onTemplateChanged.value != template) {
|
||||
_onTemplateChanged.value = template
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Move
|
||||
fun entryIsTemplate(): Boolean {
|
||||
return mIsTemplate
|
||||
fun requestEntryInfoUpdate(database: Database?, entry: Entry?, parent: Group?) {
|
||||
_requestEntryInfoUpdate.value = EntryUpdate(database, entry, parent)
|
||||
}
|
||||
|
||||
fun requestEntryInfoUpdate() {
|
||||
_requestEntryInfoUpdate.call()
|
||||
}
|
||||
|
||||
fun saveEntryInfo(entryInfo: EntryInfo) {
|
||||
fun saveEntryInfo(database: Database?, entry: Entry?, parent: Group?, entryInfo: EntryInfo) {
|
||||
IOActionTask(
|
||||
{
|
||||
internalUpdateEntryInfo(entryInfo)
|
||||
mEntry?.let { oldEntry ->
|
||||
removeTempAttachmentsNotCompleted(entryInfo)
|
||||
entry?.let { oldEntry ->
|
||||
// Create a clone
|
||||
var newEntry = Entry(oldEntry)
|
||||
|
||||
// Build info
|
||||
newEntry.setEntryInfo(mDatabase, entryInfo)
|
||||
newEntry.setEntryInfo(database, entryInfo)
|
||||
|
||||
// Encode entry properties for template
|
||||
_onTemplateChanged.value?.let { template ->
|
||||
newEntry =
|
||||
mDatabase?.encodeEntryWithTemplateConfiguration(newEntry, template)
|
||||
database?.encodeEntryWithTemplateConfiguration(newEntry, template)
|
||||
?: newEntry
|
||||
}
|
||||
|
||||
// Delete temp attachment if not used
|
||||
mTempAttachments.forEach { tempAttachmentState ->
|
||||
val tempAttachment = tempAttachmentState.attachment
|
||||
mDatabase?.attachmentPool?.let { binaryPool ->
|
||||
database?.attachmentPool?.let { binaryPool ->
|
||||
if (!newEntry.getAttachments(binaryPool).contains(tempAttachment)) {
|
||||
mDatabase.removeAttachmentIfNotUsed(tempAttachment)
|
||||
database.removeAttachmentIfNotUsed(tempAttachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return entry to save
|
||||
EntrySave(oldEntry, newEntry, mParent)
|
||||
EntrySave(oldEntry, newEntry, parent)
|
||||
}
|
||||
},
|
||||
{ entrySave ->
|
||||
@@ -263,7 +155,7 @@ class EntryEditViewModel: ViewModel() {
|
||||
).execute()
|
||||
}
|
||||
|
||||
private fun internalUpdateEntryInfo(entryInfo: EntryInfo) {
|
||||
private fun removeTempAttachmentsNotCompleted(entryInfo: EntryInfo) {
|
||||
// Do not save entry in upload progression
|
||||
mTempAttachments.forEach { attachmentState ->
|
||||
if (attachmentState.streamDirection == StreamDirection.UPLOAD) {
|
||||
@@ -284,14 +176,6 @@ class EntryEditViewModel: ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun requestIconSelection(oldIconImage: IconImage) {
|
||||
_requestIconSelection.value = oldIconImage
|
||||
}
|
||||
|
||||
fun selectIcon(iconImage: IconImage) {
|
||||
_onIconSelected.value = iconImage
|
||||
}
|
||||
|
||||
fun requestPasswordSelection(passwordField: Field) {
|
||||
_requestPasswordSelection.value = passwordField
|
||||
}
|
||||
@@ -320,18 +204,6 @@ class EntryEditViewModel: ViewModel() {
|
||||
_onCustomFieldError.call()
|
||||
}
|
||||
|
||||
fun requestDateTimeSelection(dateInstant: DateInstant) {
|
||||
_requestDateTimeSelection.value = dateInstant
|
||||
}
|
||||
|
||||
fun selectDate(year: Int, month: Int, day: Int) {
|
||||
_onDateSelected.value = DataDate(year, month, day)
|
||||
}
|
||||
|
||||
fun selectTime(hours: Int, minutes: Int) {
|
||||
_onTimeSelected.value = DataTime(hours, minutes)
|
||||
}
|
||||
|
||||
fun setupOtp() {
|
||||
_requestSetupOtp.call()
|
||||
}
|
||||
@@ -353,8 +225,11 @@ class EntryEditViewModel: ViewModel() {
|
||||
}
|
||||
|
||||
fun onAttachmentAction(entryAttachmentState: EntryAttachmentState?) {
|
||||
if (entryAttachmentState?.downloadState == AttachmentState.START) {
|
||||
// Add in temp list
|
||||
// Add in temp list
|
||||
if (mTempAttachments.contains(entryAttachmentState)) {
|
||||
mTempAttachments.remove(entryAttachmentState)
|
||||
}
|
||||
if (entryAttachmentState != null) {
|
||||
mTempAttachments.add(entryAttachmentState)
|
||||
}
|
||||
_onAttachmentAction.value = entryAttachmentState
|
||||
@@ -365,6 +240,7 @@ class EntryEditViewModel: ViewModel() {
|
||||
}
|
||||
|
||||
data class TemplatesLoad(val templates: List<Template>, val defaultTemplate: Template)
|
||||
data class EntryUpdate(val database: Database?, val entry: Entry?, val parent: Group?)
|
||||
data class EntrySave(val oldEntry: Entry, val newEntry: Entry, val parent: Group?)
|
||||
data class FieldEdition(val oldField: Field?, val newField: Field?)
|
||||
data class AttachmentBuild(val attachmentToUploadUri: Uri, val fileName: String)
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* Copyright 2021 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
@@ -6,7 +25,6 @@ import androidx.lifecycle.ViewModel
|
||||
import com.kunzisoft.keepass.app.database.IOActionTask
|
||||
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.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.template.Template
|
||||
@@ -18,22 +36,21 @@ import java.util.*
|
||||
|
||||
class EntryViewModel: ViewModel() {
|
||||
|
||||
private val mDatabase: Database? = Database.getInstance()
|
||||
|
||||
private var mEntryTemplate: Template? = null
|
||||
private var mEntry: Entry? = null
|
||||
private var mLastEntryVersion: Entry? = null
|
||||
private var mHistoryPosition: Int = -1
|
||||
|
||||
val template : LiveData<Template> get() = _template
|
||||
private val _template = MutableLiveData<Template>()
|
||||
|
||||
val mainEntryId : LiveData<NodeId<UUID>?> get() = _mainEntryId
|
||||
private val _mainEntryId = MutableLiveData<NodeId<UUID>?>()
|
||||
|
||||
val historyPosition : LiveData<Int> get() = _historyPosition
|
||||
private val _historyPosition = MutableLiveData<Int>()
|
||||
|
||||
val url : LiveData<String?> get() = _url
|
||||
private val _url = MutableLiveData<String?>()
|
||||
|
||||
val entryInfo : LiveData<EntryInfo> get() = _entryInfo
|
||||
private val _entryInfo = MutableLiveData<EntryInfo>()
|
||||
|
||||
val entryIsHistory : LiveData<Boolean> get() = _entryIsHistory
|
||||
private val _entryIsHistory = MutableLiveData<Boolean>()
|
||||
|
||||
val entryHistory : LiveData<List<EntryInfo>> get() = _entryHistory
|
||||
private val _entryHistory = MutableLiveData<List<EntryInfo>>()
|
||||
|
||||
@@ -48,79 +65,66 @@ 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 loadEntry(database: Database?, entryId: NodeId<UUID>?, historyPosition: Int) {
|
||||
if (entryId != null) {
|
||||
IOActionTask(
|
||||
{
|
||||
database?.getEntryById(entryId)
|
||||
},
|
||||
{ mainEntry ->
|
||||
// Manage current version and history
|
||||
_mainEntryId.value = mainEntry?.nodeId
|
||||
_historyPosition.value = historyPosition
|
||||
|
||||
mEntry = if (historyPosition > -1) {
|
||||
mLastEntryVersion?.getHistory()?.get(historyPosition)
|
||||
} else {
|
||||
mLastEntryVersion
|
||||
}
|
||||
|
||||
mEntryTemplate = mEntry?.let {
|
||||
mDatabase?.getTemplate(it)
|
||||
} ?: Template.STANDARD
|
||||
|
||||
mHistoryPosition = historyPosition
|
||||
|
||||
// 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)
|
||||
|
||||
// Build history info
|
||||
val entryInfoHistory = it.getHistory().map { entryHistory ->
|
||||
entryHistory.getEntryInfo(mDatabase)
|
||||
}
|
||||
|
||||
EntryInfoHistory(
|
||||
mEntryTemplate ?: Template.STANDARD,
|
||||
it.getEntryInfo(mDatabase),
|
||||
entryInfoHistory
|
||||
)
|
||||
val currentEntry = if (historyPosition > -1) {
|
||||
mainEntry?.getHistory()?.get(historyPosition)
|
||||
} else {
|
||||
mainEntry
|
||||
}
|
||||
}
|
||||
},
|
||||
{ entryInfoHistory ->
|
||||
if (entryInfoHistory != null) {
|
||||
_template.value = entryInfoHistory.template
|
||||
_entryInfo.value = entryInfoHistory.entryInfo
|
||||
_entryIsHistory.value = mHistoryPosition != -1
|
||||
_entryHistory.value = entryInfoHistory.entryHistory
|
||||
}
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
_url.value = currentEntry?.url
|
||||
|
||||
fun updateEntry() {
|
||||
mEntry?.nodeId?.let { nodeId ->
|
||||
loadEntry(nodeId, mHistoryPosition)
|
||||
IOActionTask(
|
||||
{
|
||||
val entryTemplate = currentEntry?.let {
|
||||
database?.getTemplate(it)
|
||||
} ?: Template.STANDARD
|
||||
|
||||
// To simplify template field visibility
|
||||
currentEntry?.let { entry ->
|
||||
// Add mainEntry to check the parent and define the template state
|
||||
database?.decodeEntryWithTemplateConfiguration(entry, mainEntry)
|
||||
?.let {
|
||||
// To update current modification time
|
||||
it.touch(modified = false, touchParents = false)
|
||||
|
||||
// Build history info
|
||||
val entryInfoHistory = it.getHistory().map { entryHistory ->
|
||||
entryHistory.getEntryInfo(database)
|
||||
}
|
||||
|
||||
EntryInfoHistory(
|
||||
entryTemplate,
|
||||
it.getEntryInfo(database),
|
||||
entryInfoHistory
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ entryInfoHistory ->
|
||||
if (entryInfoHistory != null) {
|
||||
_template.value = entryInfoHistory.template
|
||||
_entryInfo.value = entryInfoHistory.entryInfo
|
||||
_entryHistory.value = entryInfoHistory.entryHistory
|
||||
}
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
fun getEntry(): Entry? {
|
||||
return mEntry
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
fun getMainEntry(): Entry? {
|
||||
return mLastEntryVersion
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
fun getEntryHistoryPosition(): Int {
|
||||
return mHistoryPosition
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
fun getEntryIsHistory(): Boolean {
|
||||
return entryIsHistory.value ?: false
|
||||
fun updateEntry(database: Database?) {
|
||||
loadEntry(database, _mainEntryId.value, _historyPosition.value ?: -1)
|
||||
}
|
||||
|
||||
fun onOtpElementUpdated(optElement: OtpElement?) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.kunzisoft.keepass.model.GroupInfo
|
||||
|
||||
class GroupEditViewModel: NodeEditViewModel() {
|
||||
|
||||
val groupNamesNotAllowed : LiveData<List<String>?> get() = _groupNamesNotAllowed
|
||||
private val _groupNamesNotAllowed = MutableLiveData<List<String>?>()
|
||||
|
||||
val onGroupCreated : LiveData<GroupInfo> get() = _onGroupCreated
|
||||
private val _onGroupCreated = SingleLiveEvent<GroupInfo>()
|
||||
|
||||
val onGroupUpdated : LiveData<GroupInfo> get() = _onGroupUpdated
|
||||
private val _onGroupUpdated = SingleLiveEvent<GroupInfo>()
|
||||
|
||||
fun setGroupNamesNotAllowed(groupNames: List<String>?) {
|
||||
this._groupNamesNotAllowed.value = groupNames
|
||||
}
|
||||
|
||||
fun approveGroupCreation(groupInfo: GroupInfo) {
|
||||
this._onGroupCreated.value = groupInfo
|
||||
}
|
||||
|
||||
fun approveGroupUpdate(groupInfo: GroupInfo) {
|
||||
this._onGroupUpdated.value = groupInfo
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user