From 348a5c3eb7336e0e7b1e73c036e5b5d4a9e73382 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Thu, 26 Aug 2021 10:57:35 +0200 Subject: [PATCH 01/36] Fix save entry #1057 --- .../com/kunzisoft/keepass/activities/EntryEditActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index d747eba28..1eddf3273 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -143,13 +143,13 @@ class EntryEditActivity : DatabaseLockActivity(), // Entry is retrieve, it's an entry to update intent.getParcelableExtra>(KEY_ENTRY)?.let { entryToUpdate -> - intent.removeExtra(KEY_ENTRY) + //intent.removeExtra(KEY_ENTRY) mEntryId = entryToUpdate } // Parent is retrieve, it's a new entry to create intent.getParcelableExtra>(KEY_PARENT)?.let { parent -> - intent.removeExtra(KEY_PARENT) + //intent.removeExtra(KEY_PARENT) mParentId = parent } From 3b9b034d80467657a369055c508779f93d3cfb45 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Thu, 26 Aug 2021 12:03:33 +0200 Subject: [PATCH 02/36] Fix replace field --- .../keepass/view/TemplateAbstractView.kt | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt index 9a214e859..9d1c152d3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt @@ -515,7 +515,9 @@ abstract class TemplateAbstractView< return if (!isStandardFieldName(customField.name)) { customFieldsContainerView.visibility = View.VISIBLE if (indexCustomFieldIdByName(customField.name) >= 0) { - replaceCustomField(customField, customField, focus) + // Update a custom field with a new value, + // new field name must be the same as old field name + replaceCustomField(customField, customField, false, focus) } else { val newCustomView = buildViewForCustomField(customField) newCustomView?.let { @@ -544,10 +546,10 @@ abstract class TemplateAbstractView< return put } - /** - * Update a custom field and keep the old value - */ - private fun replaceCustomField(oldField: Field, newField: Field, focus: Boolean): Boolean { + private fun replaceCustomField(oldField: Field, + newField: Field, + keepOldValue: Boolean, + focus: Boolean): Boolean { if (!isStandardFieldName(newField.name)) { customFieldIdByName(oldField.name)?.viewId?.let { viewId -> customFieldsContainerView.findViewById(viewId)?.let { viewToReplace -> @@ -557,8 +559,12 @@ abstract class TemplateAbstractView< val indexInParent = parentGroup.indexOfChild(viewToReplace) parentGroup.removeView(viewToReplace) - val newCustomFieldWithValue = Field(newField.name, - ProtectedString(newField.protectedValue.isProtected, oldValue)) + val newCustomFieldWithValue = if (keepOldValue) + Field(newField.name, + ProtectedString(newField.protectedValue.isProtected, oldValue) + ) + else + newField val oldPosition = indexCustomFieldIdByName(oldField.name) if (oldPosition >= 0) mCustomFieldIds.removeAt(oldPosition) @@ -583,8 +589,11 @@ abstract class TemplateAbstractView< return false } + /** + * Update a custom field and keep the old value + */ fun replaceCustomField(oldField: Field, newField: Field): Boolean { - val replace = replaceCustomField(oldField, newField, true) + val replace = replaceCustomField(oldField, newField, keepOldValue = true, focus = true) retrieveCustomFieldsFromView() return replace } From 21c3ccd6378caa6c2690add9486b4f619f89f7aa Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Thu, 26 Aug 2021 13:51:18 +0200 Subject: [PATCH 03/36] Fix view events --- .../main/java/com/igreenwood/loupe/Loupe.kt | 6 +++++ .../keepass/activities/ImageViewerActivity.kt | 14 +++++++++- .../dialogs/DatabaseDialogFragment.kt | 27 +++++++++++++++---- .../dialogs/SetOTPDialogFragment.kt | 13 ++++++++- .../activities/fragments/DatabaseFragment.kt | 4 +-- .../activities/legacy/DatabaseLockActivity.kt | 19 ++++++++----- .../NestedDatabaseSettingsFragment.kt | 4 +-- 7 files changed, 69 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/igreenwood/loupe/Loupe.kt b/app/src/main/java/com/igreenwood/loupe/Loupe.kt index e94a84411..95a1e453f 100644 --- a/app/src/main/java/com/igreenwood/loupe/Loupe.kt +++ b/app/src/main/java/com/igreenwood/loupe/Loupe.kt @@ -30,6 +30,7 @@ package com.igreenwood.loupe import android.animation.Animator import android.animation.ObjectAnimator import android.animation.ValueAnimator +import android.annotation.SuppressLint import android.graphics.Matrix import android.graphics.PointF import android.graphics.Rect @@ -108,6 +109,8 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener, var viewDragFriction = DEFAULT_VIEW_DRAG_FRICTION // drag distance threshold in dp for swipe to dismiss var dragDismissDistanceInDp = DEFAULT_DRAG_DISMISS_DISTANCE_IN_DP + // on view touched + var onViewTouchedListener: View.OnTouchListener? = null // on view translate listener var onViewTranslateListener: OnViewTranslateListener? = null // on scale changed @@ -272,7 +275,10 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener, private var imageViewRef: WeakReference = WeakReference(imageView) private var containerRef: WeakReference = WeakReference(container) + @SuppressLint("ClickableViewAccessibility") override fun onTouch(view: View?, event: MotionEvent?): Boolean { + onViewTouchedListener?.onTouch(view, event) + event ?: return false val imageView = imageViewRef.get() ?: return false val container = containerRef.get() ?: return false diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt index cb9bedae5..9cc56f9f5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/ImageViewerActivity.kt @@ -19,6 +19,7 @@ */ package com.kunzisoft.keepass.activities +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.os.Bundle @@ -43,6 +44,7 @@ class ImageViewerActivity : DatabaseLockActivity() { private lateinit var imageView: ImageView private lateinit var progressView: View + @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -52,12 +54,21 @@ class ImageViewerActivity : DatabaseLockActivity() { setSupportActionBar(toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true) + toolbar.setOnTouchListener { _, _ -> + resetAppTimeout() + false + } imageContainerView = findViewById(R.id.image_viewer_container) imageView = findViewById(R.id.image_viewer_image) progressView = findViewById(R.id.image_viewer_progress) Loupe.create(imageView, imageContainerView!!) { + onViewTouchedListener = View.OnTouchListener { _, _ -> + // to reset timeout when Loupe image view touched + resetAppTimeout() + false + } onViewTranslateListener = object : Loupe.OnViewTranslateListener { override fun onStart(view: ImageView) { @@ -81,7 +92,8 @@ class ImageViewerActivity : DatabaseLockActivity() { } override fun viewToInvalidateTimeout(): View? { - return imageContainerView + // Null to manually manage events + return null } override fun finishActivityIfReloadRequested(): Boolean { diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt index 6b655bc0b..1cacb3d3c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt @@ -4,9 +4,10 @@ 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.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.tasks.ActionRunnable +import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.viewmodels.DatabaseViewModel abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval { @@ -19,7 +20,7 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval { mDatabaseViewModel.database.observe(this) { database -> this.mDatabase = database - resetAppTimeoutWhenViewFocusedOrChanged() + resetAppTimeoutOnTouchOrFocus() onDatabaseRetrieved(database) } @@ -31,7 +32,7 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - resetAppTimeoutWhenViewFocusedOrChanged() + resetAppTimeoutOnTouchOrFocus() } override fun onDatabaseRetrieved(database: Database?) { @@ -46,9 +47,25 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval { // Can be overridden by a subclass } - private fun resetAppTimeoutWhenViewFocusedOrChanged() { + fun resetAppTimeout() { context?.let { - dialog?.window?.decorView?.resetAppTimeoutWhenViewFocusedOrChanged(it, mDatabase?.loaded) + TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(it, + mDatabase?.loaded ?: false) + } + } + + open fun overrideTimeoutTouchAndFocusEvents(): Boolean { + return false + } + + private fun resetAppTimeoutOnTouchOrFocus() { + if (!overrideTimeoutTouchAndFocusEvents()) { + context?.let { + dialog?.window?.decorView?.resetAppTimeoutWhenViewTouchedOrFocused( + it, + mDatabase?.loaded + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt index 25d4de6bb..77b18d107 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt @@ -79,11 +79,15 @@ class SetOTPDialogFragment : DatabaseDialogFragment() { private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus -> if (!isFocus) mManualEvent = true + else + resetAppTimeout() } + @SuppressLint("ClickableViewAccessibility") private var mOnTouchListener = View.OnTouchListener { _, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { mManualEvent = true + resetAppTimeout() } } false @@ -94,6 +98,10 @@ class SetOTPDialogFragment : DatabaseDialogFragment() { private var mPeriodWellFormed = false private var mDigitsWellFormed = false + override fun overrideTimeoutTouchAndFocusEvents(): Boolean { + return true + } + override fun onAttach(context: Context) { super.onAttach(context) // Verify that the host activity implements the callback interface @@ -224,8 +232,11 @@ class SetOTPDialogFragment : DatabaseDialogFragment() { val builder = AlertDialog.Builder(activity) builder.apply { setView(root) - .setPositiveButton(android.R.string.ok) {_, _ -> } + .setPositiveButton(android.R.string.ok) { _, _ -> + resetAppTimeout() + } .setNegativeButton(android.R.string.cancel) { _, _ -> + resetAppTimeout() } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/DatabaseFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/DatabaseFragment.kt index ea94ee0e4..671f5fff7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/DatabaseFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/DatabaseFragment.kt @@ -4,7 +4,7 @@ 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.legacy.resetAppTimeoutWhenViewTouchedOrFocused import com.kunzisoft.keepass.activities.stylish.StylishFragment import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.binary.BinaryData @@ -33,7 +33,7 @@ abstract class DatabaseFragment : StylishFragment(), DatabaseRetrieval { protected fun resetAppTimeoutWhenViewFocusedOrChanged(view: View?) { context?.let { - view?.resetAppTimeoutWhenViewFocusedOrChanged(it, mDatabase?.loaded) + view?.resetAppTimeoutWhenViewTouchedOrFocused(it, mDatabase?.loaded) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt index b4c95623b..626773cb1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/legacy/DatabaseLockActivity.kt @@ -169,7 +169,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(), // Focus view to reinitialize timeout, // view is not necessary loaded so retry later in resume viewToInvalidateTimeout() - ?.resetAppTimeoutWhenViewFocusedOrChanged(this, database?.loaded) + ?.resetAppTimeoutWhenViewTouchedOrFocused(this, database?.loaded) database?.let { // check timeout @@ -383,7 +383,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(), // Invalidate timeout by touch mDatabase?.let { database -> viewToInvalidateTimeout() - ?.resetAppTimeoutWhenViewFocusedOrChanged(this, database.loaded) + ?.resetAppTimeoutWhenViewTouchedOrFocused(this, database.loaded) } invalidateOptionsMenu() @@ -422,6 +422,11 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(), sendBroadcast(Intent(LOCK_ACTION)) } + fun resetAppTimeout() { + TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, + mDatabase?.loaded ?: false) + } + override fun onBackPressed() { if (mTimeoutEnable) { TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, @@ -451,12 +456,12 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(), * 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") +fun View.resetAppTimeoutWhenViewTouchedOrFocused(context: Context, databaseLoaded: Boolean?) { + // Log.d(DatabaseLockActivity.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") + // Log.d(DatabaseLockActivity.TAG, "View touched, try to reset app timeout") TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context, databaseLoaded ?: false) } @@ -464,13 +469,13 @@ fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context, databaseLoade false } setOnFocusChangeListener { _, _ -> - // Log.d(LockingActivity.TAG, "View focused, try to reset app timeout") + // Log.d(DatabaseLockActivity.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) + getChildAt(i)?.resetAppTimeoutWhenViewTouchedOrFocused(context, databaseLoaded) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt index d72b36ebb..dfbdbb0a1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt @@ -32,7 +32,7 @@ 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.legacy.resetAppTimeoutWhenViewFocusedOrChanged +import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine import com.kunzisoft.keepass.database.element.Database @@ -74,7 +74,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev mDatabaseViewModel.database.observe(viewLifecycleOwner) { database -> mDatabase = database - view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), database?.loaded) + view.resetAppTimeoutWhenViewTouchedOrFocused(requireContext(), database?.loaded) onDatabaseRetrieved(database) } From 9cce5f645fc34070e4f6615cbb21a21feaa12057 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Thu, 26 Aug 2021 17:17:59 +0200 Subject: [PATCH 04/36] Show OTP Token in entry list --- .../kunzisoft/keepass/adapters/NodeAdapter.kt | 25 ++++ .../com/kunzisoft/keepass/model/OtpModel.kt | 2 +- .../com/kunzisoft/keepass/otp/OtpElement.kt | 13 ++ app/src/main/res/color/entry_info_color.xml | 5 + .../foreground_progress_circle.xml | 13 -- .../drawable/foreground_progress_circle.xml | 2 +- .../main/res/layout/item_list_nodes_entry.xml | 112 +++++++++++++----- .../main/res/layout/item_list_nodes_group.xml | 26 ++-- app/src/main/res/values/styles.xml | 11 ++ 9 files changed, 155 insertions(+), 54 deletions(-) create mode 100644 app/src/main/res/color/entry_info_color.xml delete mode 100644 app/src/main/res/drawable-v21/foreground_progress_circle.xml diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt index d14c49b72..86748fb88 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt @@ -26,6 +26,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView +import android.widget.ProgressBar import android.widget.TextView import androidx.annotation.ColorInt import androidx.core.content.ContextCompat @@ -148,6 +149,7 @@ class NodeAdapter (private val context: Context, if (oldItem is Entry && newItem is Entry) { typeContentTheSame = oldItem.getVisualTitle() == newItem.getVisualTitle() && oldItem.username == newItem.username + && oldItem.getOtpElement() == newItem.getOtpElement() && oldItem.containsAttachment() == newItem.containsAttachment() } else if (oldItem is Group && newItem is Group) { typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries @@ -357,6 +359,25 @@ class NodeAdapter (private val context: Context, } } + val otpElement = entry.getOtpElement() + holder.otpContainer?.removeCallbacks(holder.otpRunnable) + holder.otpRunnable = null + if (otpElement != null && otpElement.token.isNotEmpty()) { + holder.otpProgress?.apply { + max = otpElement.period + progress = otpElement.secondsRemaining + } + holder.otpToken?.text = otpElement.token + holder.otpRunnable = Runnable { + holder.otpProgress?.progress = otpElement.secondsRemaining + holder.otpToken?.text = otpElement.token + holder.otpContainer?.postDelayed(holder.otpRunnable, 200) + } + holder.otpContainer?.post(holder.otpRunnable) + holder.otpContainer?.visibility = View.VISIBLE + } else { + holder.otpContainer?.visibility = View.GONE + } holder.attachmentIcon?.visibility = if (entry.containsAttachment()) View.VISIBLE else View.GONE @@ -413,6 +434,10 @@ class NodeAdapter (private val context: Context, var text: TextView = itemView.findViewById(R.id.node_text) var subText: TextView = itemView.findViewById(R.id.node_subtext) var meta: TextView = itemView.findViewById(R.id.node_meta) + var otpContainer: ViewGroup? = itemView.findViewById(R.id.node_otp_container) + var otpProgress: ProgressBar? = itemView.findViewById(R.id.node_otp_progress) + var otpToken: TextView? = itemView.findViewById(R.id.node_otp_token) + var otpRunnable: Runnable? = null var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers) var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon) } diff --git a/app/src/main/java/com/kunzisoft/keepass/model/OtpModel.kt b/app/src/main/java/com/kunzisoft/keepass/model/OtpModel.kt index 106375784..ecfff03f9 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/OtpModel.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/OtpModel.kt @@ -56,7 +56,7 @@ class OtpModel() : Parcelable { if (this === other) return true if (javaClass != other?.javaClass) return false - other as OtpElement + other as OtpModel if (type != other.type) return false // Token type is important only if it's a TOTP diff --git a/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt b/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt index 5b34eabc9..d07ace49f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt +++ b/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt @@ -185,6 +185,19 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) { return secondsRemaining == otpModel.period } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is OtpElement) return false + + if (otpModel != other.otpModel) return false + + return true + } + + override fun hashCode(): Int { + return otpModel.hashCode() + } + companion object { const val MIN_HOTP_COUNTER = 0 const val MAX_HOTP_COUNTER = Long.MAX_VALUE diff --git a/app/src/main/res/color/entry_info_color.xml b/app/src/main/res/color/entry_info_color.xml new file mode 100644 index 000000000..680ff0687 --- /dev/null +++ b/app/src/main/res/color/entry_info_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/foreground_progress_circle.xml b/app/src/main/res/drawable-v21/foreground_progress_circle.xml deleted file mode 100644 index 521ee45fa..000000000 --- a/app/src/main/res/drawable-v21/foreground_progress_circle.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/foreground_progress_circle.xml b/app/src/main/res/drawable/foreground_progress_circle.xml index a968913b1..b0089f311 100644 --- a/app/src/main/res/drawable/foreground_progress_circle.xml +++ b/app/src/main/res/drawable/foreground_progress_circle.xml @@ -7,6 +7,6 @@ android:innerRadiusRatio="2.5" android:thickness="2dp" android:useLevel="true"> - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_list_nodes_entry.xml b/app/src/main/res/layout/item_list_nodes_entry.xml index 527e192f7..37b9afc23 100644 --- a/app/src/main/res/layout/item_list_nodes_entry.xml +++ b/app/src/main/res/layout/item_list_nodes_entry.xml @@ -25,15 +25,20 @@ android:layout_width="match_parent" android:layout_height="wrap_content" style="@style/KeepassDXStyle.Selectable.Item"> + + + + app:layout_constraintEnd_toStartOf="@+id/node_options" + app:layout_constraintRight_toLeftOf="@+id/node_options"> + + android:maxLines="2" + tools:text="Node Title" /> + + tools:text="Node SubTitle" /> + + tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" /> - + app:layout_constraintEnd_toEndOf="parent"> + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_list_nodes_group.xml b/app/src/main/res/layout/item_list_nodes_group.xml index 4e6c9d2b8..7f48e8f17 100644 --- a/app/src/main/res/layout/item_list_nodes_group.xml +++ b/app/src/main/res/layout/item_list_nodes_group.xml @@ -25,12 +25,16 @@ android:layout_width="match_parent" android:layout_height="wrap_content" style="@style/KeepassDXStyle.Selectable.Item"> + @@ -66,14 +70,14 @@ + android:layout_toEndOf="@+id/node_icon" + android:layout_toRightOf="@+id/node_icon" + android:orientation="vertical"> + + + tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" /> @color/entry_title_color @color/entry_title_color + bold 16sp + @@ -475,6 +482,10 @@ + @@ -482,8 +481,8 @@ - From 69bf098c849c1e87e9b68bdf79285bc27bbfc4a0 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sat, 28 Aug 2021 16:44:39 +0200 Subject: [PATCH 31/36] Fix Kitkat OTP token --- .../keepass/view/TemplateAbstractView.kt | 120 +++++++++--------- .../keepass/view/TemplateEditView.kt | 2 +- .../kunzisoft/keepass/view/TemplateView.kt | 17 ++- 3 files changed, 67 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt index bf5937047..1bcef3cd7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt @@ -39,7 +39,7 @@ abstract class TemplateAbstractView< private var mTemplate: Template? = null protected var mEntryInfo: EntryInfo? = null - protected var mCustomFieldIds = mutableListOf() + private var mViewFields = mutableListOf() protected var mFontInVisibility: Boolean = PreferencesUtil.fieldFontIsInVisibility(context) protected var mHideProtectedValue: Boolean = PreferencesUtil.hideProtectedValue(context) @@ -104,7 +104,7 @@ abstract class TemplateAbstractView< templateContainerView.removeAllViews() customFieldsContainerView.removeAllViews() notReferencedFieldsContainerView.removeAllViews() - mCustomFieldIds.clear() + mViewFields.clear() mTemplate?.let { template -> @@ -231,13 +231,13 @@ abstract class TemplateAbstractView< // Add new custom view id to the custom field list if (fieldTag == FIELD_CUSTOM_TAG) { - val indexOldItem = indexCustomFieldIdByName(field.name) + val indexOldItem = getIndexViewFieldByName(field.name) if (indexOldItem >= 0) - mCustomFieldIds.removeAt(indexOldItem) + mViewFields.removeAt(indexOldItem) if (itemView?.id != null) { - mCustomFieldIds.add( - FieldId( - itemView.id, + mViewFields.add( + ViewField( + itemView, field ) ) @@ -320,7 +320,7 @@ abstract class TemplateAbstractView< /** * Return empty custom fields */ - protected open fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List { + protected open fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List { mEntryInfo?.let { entryInfo -> populateEntryFieldView(FIELD_TITLE_TAG, @@ -350,15 +350,14 @@ abstract class TemplateAbstractView< showEmptyFields) customFieldsContainerView.removeAllViews() - val emptyCustomFields = mutableListOf().also { it.addAll(mCustomFieldIds) } + val emptyCustomFields = mutableListOf().also { it.addAll(mViewFields) } entryInfo.customFields.forEach { customField -> - val indexFieldViewId = indexCustomFieldIdByName(customField.name) + val indexFieldViewId = getIndexViewFieldByName(customField.name) if (indexFieldViewId >= 0) { // Template contains the custom view - val customFieldId = mCustomFieldIds[indexFieldViewId] - emptyCustomFields.remove(customFieldId) - templateContainerView.findViewById(customFieldId.viewId) - ?.let { customView -> + val viewField = mViewFields[indexFieldViewId] + emptyCustomFields.remove(viewField) + viewField.view.let { customView -> if (customView is GenericTextFieldView) { customView.value = customField.protectedValue.stringValue customView.applyFontVisibility(mFontInVisibility) @@ -459,22 +458,22 @@ abstract class TemplateAbstractView< * ------------- */ - protected data class FieldId(var viewId: Int, var field: Field) + protected data class ViewField(var view: View, var field: Field) private fun isStandardFieldName(name: String): Boolean { return TemplateField.isStandardFieldName(name) } - private fun customFieldIdByName(name: String): FieldId? { - return mCustomFieldIds.find { it.field.name.equals(name, true) } + protected fun getViewFieldByName(name: String): ViewField? { + return mViewFields.find { it.field.name.equals(name, true) } } - protected fun indexCustomFieldIdByName(name: String): Int { - return mCustomFieldIds.indexOfFirst { it.field.name.equals(name, true) } + private fun getIndexViewFieldByName(name: String): Int { + return mViewFields.indexOfFirst { it.field.name.equals(name, true) } } private fun retrieveCustomFieldsFromView(templateFieldNotEmpty: Boolean = false) { - mEntryInfo?.customFields = mCustomFieldIds.mapNotNull { + mEntryInfo?.customFields = mViewFields.mapNotNull { getCustomField(it.field.name, templateFieldNotEmpty) }.toMutableList() } @@ -485,9 +484,8 @@ abstract class TemplateAbstractView< } private fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field? { - customFieldIdByName(fieldName)?.let { fieldId -> - val editView: View? = templateContainerView.findViewById(fieldId.viewId) - ?: customFieldsContainerView.findViewById(fieldId.viewId) + getViewFieldByName(fieldName)?.let { fieldId -> + val editView: View? = fieldId.view if (editView is GenericFieldView) { // Do not return field with a default value val defaultViewValue = if (editView.value == editView.default) "" else editView.value @@ -514,7 +512,7 @@ abstract class TemplateAbstractView< return if (!isStandardFieldName(customField.name)) { customFieldsContainerView.visibility = View.VISIBLE - if (indexCustomFieldIdByName(customField.name) >= 0) { + if (getIndexViewFieldByName(customField.name) >= 0) { // Update a custom field with a new value, // new field name must be the same as old field name replaceCustomField(customField, customField, false, focus) @@ -522,14 +520,14 @@ abstract class TemplateAbstractView< val newCustomView = buildViewForCustomField(customField) newCustomView?.let { customFieldsContainerView.addView(newCustomView) - val fieldId = FieldId( - newCustomView.id, + val fieldId = ViewField( + newCustomView, customField ) - val indexOldItem = indexCustomFieldIdByName(fieldId.field.name) + val indexOldItem = getIndexViewFieldByName(fieldId.field.name) if (indexOldItem >= 0) - mCustomFieldIds.removeAt(indexOldItem) - mCustomFieldIds.add(indexOldItem, fieldId) + mViewFields.removeAt(indexOldItem) + mViewFields.add(indexOldItem, fieldId) if (focus) newCustomView.requestFocus() } @@ -551,39 +549,37 @@ abstract class TemplateAbstractView< keepOldValue: Boolean, focus: Boolean): Boolean { if (!isStandardFieldName(newField.name)) { - customFieldIdByName(oldField.name)?.viewId?.let { viewId -> - customFieldsContainerView.findViewById(viewId)?.let { viewToReplace -> - val oldValue = getCustomField(oldField.name).protectedValue.toString() + getViewFieldByName(oldField.name)?.view?.let { viewToReplace -> + val oldValue = getCustomField(oldField.name).protectedValue.toString() - val parentGroup = viewToReplace.parent as ViewGroup - val indexInParent = parentGroup.indexOfChild(viewToReplace) - parentGroup.removeView(viewToReplace) + val parentGroup = viewToReplace.parent as ViewGroup + val indexInParent = parentGroup.indexOfChild(viewToReplace) + parentGroup.removeView(viewToReplace) - val newCustomFieldWithValue = if (keepOldValue) - Field(newField.name, - ProtectedString(newField.protectedValue.isProtected, oldValue) + val newCustomFieldWithValue = if (keepOldValue) + Field(newField.name, + ProtectedString(newField.protectedValue.isProtected, oldValue) + ) + else + newField + val oldPosition = getIndexViewFieldByName(oldField.name) + if (oldPosition >= 0) + mViewFields.removeAt(oldPosition) + + val newCustomView = buildViewForCustomField(newCustomFieldWithValue) + newCustomView?.let { + parentGroup.addView(newCustomView, indexInParent) + mViewFields.add( + oldPosition, + ViewField( + newCustomView, + newCustomFieldWithValue ) - else - newField - val oldPosition = indexCustomFieldIdByName(oldField.name) - if (oldPosition >= 0) - mCustomFieldIds.removeAt(oldPosition) - - val newCustomView = buildViewForCustomField(newCustomFieldWithValue) - newCustomView?.let { - parentGroup.addView(newCustomView, indexInParent) - mCustomFieldIds.add( - oldPosition, - FieldId( - newCustomView.id, - newCustomFieldWithValue - ) - ) - if (focus) - newCustomView.requestFocus() - } - return true + ) + if (focus) + newCustomView.requestFocus() } + return true } } return false @@ -599,12 +595,12 @@ abstract class TemplateAbstractView< } fun removeCustomField(oldCustomField: Field) { - val indexOldField = indexCustomFieldIdByName(oldCustomField.name) + val indexOldField = getIndexViewFieldByName(oldCustomField.name) if (indexOldField >= 0) { - mCustomFieldIds[indexOldField].viewId.let { viewId -> - customFieldsContainerView.removeViewById(viewId) + mViewFields[indexOldField].let { fieldView -> + customFieldsContainerView.removeViewById(fieldView.view.id) } - mCustomFieldIds.removeAt(indexOldField) + mViewFields.removeAt(indexOldField) } retrieveCustomFieldsFromView() } diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt index 4e20c8138..f1e967227 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt @@ -192,7 +192,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context, } } - override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List { + override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List { refreshIcon() return super.populateViewsWithEntryInfo(showEmptyFields) } diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt index c452f7485..51925f679 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/TemplateView.kt @@ -117,22 +117,22 @@ class TemplateView @JvmOverloads constructor(context: Context, return findViewWithTag(FIELD_PASSWORD_TAG)?.getCopyButtonView() } - override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List { + override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List { val emptyCustomFields = super.populateViewsWithEntryInfo(false) // Hide empty custom fields emptyCustomFields.forEach { customFieldId -> - templateContainerView.findViewById(customFieldId.viewId) - ?.isVisible = false + customFieldId.view.isVisible = false } + removeOtpRunnable() mEntryInfo?.let { entryInfo -> // Assign specific OTP dynamic view - removeOtpRunnable() entryInfo.otpModel?.let { assignOtp(it) } } + return emptyCustomFields } @@ -149,11 +149,10 @@ class TemplateView @JvmOverloads constructor(context: Context, private var mOnOtpElementUpdated: ((OtpElement?) -> Unit)? = null private fun getOtpTokenView(): TextFieldView? { - val indexFieldViewId = indexCustomFieldIdByName(OTP_TOKEN_FIELD) - if (indexFieldViewId >= 0) { - // Template contains the custom view - val customFieldId = mCustomFieldIds[indexFieldViewId] - return findViewById(customFieldId.viewId) + getViewFieldByName(OTP_TOKEN_FIELD)?.let { viewField -> + val view = viewField.view + if (view is TextFieldView) + return view } return null } From 41b822fb6ce8b1f154fb304ac5bdcfd6878d87af Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sat, 28 Aug 2021 16:58:50 +0200 Subject: [PATCH 32/36] Replace linear progress bar --- app/src/main/res/layout/activity_entry.xml | 6 +++--- app/src/main/res/layout/fragment_progress.xml | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/layout/activity_entry.xml b/app/src/main/res/layout/activity_entry.xml index 5fd3ad192..fe37fea78 100644 --- a/app/src/main/res/layout/activity_entry.xml +++ b/app/src/main/res/layout/activity_entry.xml @@ -70,16 +70,16 @@ tools:targetApi="lollipop"> - + android:layout_height="wrap_content" /> diff --git a/app/src/main/res/layout/fragment_progress.xml b/app/src/main/res/layout/fragment_progress.xml index a89331aee..28d246380 100644 --- a/app/src/main/res/layout/fragment_progress.xml +++ b/app/src/main/res/layout/fragment_progress.xml @@ -18,7 +18,9 @@ along with KeePassDX. If not, see . --> - Date: Sat, 28 Aug 2021 17:30:06 +0200 Subject: [PATCH 33/36] Simpler activity group layout --- app/src/main/res/layout/activity_group.xml | 177 ++++++++++----------- 1 file changed, 83 insertions(+), 94 deletions(-) diff --git a/app/src/main/res/layout/activity_group.xml b/app/src/main/res/layout/activity_group.xml index faf4dc2c8..e0e98fb7b 100644 --- a/app/src/main/res/layout/activity_group.xml +++ b/app/src/main/res/layout/activity_group.xml @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . --> - + android:theme="?attr/toolbarSpecialAppearance" /> + android:layout_height="match_parent" + android:layout_below="@+id/special_mode_view" + android:layout_above="@+id/toolbar_action"> - - - - + + - + + - - + - - - - - - - + tools:text="3" + style="@style/KeepassDXStyle.TextAppearance.Info" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + + + - + + - - - + + android:layout_alignParentBottom="true" /> + android:layout_alignParentBottom="true"/> - \ No newline at end of file + \ No newline at end of file From 9514032f2558bcecc7844c5d15da9b30a322012e Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sat, 28 Aug 2021 17:52:23 +0200 Subject: [PATCH 34/36] Refresh otp every second is sufficient --- app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt index c4b2398de..00d50ce23 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt @@ -442,7 +442,7 @@ class NodeAdapter (private val context: Context, } fun postDelayed() { - view?.postDelayed(this, 500) + view?.postDelayed(this, 1000) } } From 1cd7940a17f099b9f083699cc733fdd1b4f22649 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Sat, 28 Aug 2021 18:02:29 +0200 Subject: [PATCH 35/36] Replace constraint by relative layout --- app/src/main/res/layout/activity_group.xml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/layout/activity_group.xml b/app/src/main/res/layout/activity_group.xml index e0e98fb7b..4379eb081 100644 --- a/app/src/main/res/layout/activity_group.xml +++ b/app/src/main/res/layout/activity_group.xml @@ -75,7 +75,7 @@ android:orientation="horizontal" android:gravity="center_vertical" android:baselineAligned="false"> - @@ -86,19 +86,14 @@ android:layout_gravity="end|center_vertical" android:layout_marginStart="6dp" android:layout_marginEnd="6dp" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" android:scaleType="fitXY" /> - + style="@style/KeepassDXStyle.TextAppearance.Info" /> + Date: Sat, 28 Aug 2021 18:35:32 +0200 Subject: [PATCH 36/36] Little adjustment --- app/src/main/res/layout/activity_group.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_group.xml b/app/src/main/res/layout/activity_group.xml index 4379eb081..ac545f712 100644 --- a/app/src/main/res/layout/activity_group.xml +++ b/app/src/main/res/layout/activity_group.xml @@ -99,8 +99,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:layout_gravity="start|center_vertical" - android:layout_marginLeft="@dimen/image_list_margin_vertical" - android:layout_marginStart="@dimen/image_list_margin_vertical" + android:layout_marginLeft="14dp" + android:layout_marginStart="14dp" android:layout_weight="1">