Merge branch 'Kunzisoft:develop' into develop

This commit is contained in:
Uli
2021-08-29 10:50:08 +02:00
committed by GitHub
65 changed files with 1073 additions and 667 deletions

View File

@@ -1,9 +1,10 @@
KeePassDX(3.0.0) KeePassDX(3.0.0)
* Add / Manage dynamic templates #191 * Add / Manage dynamic templates #191
* Manually select RecycleBin group and Templates group #191 * Manually select RecycleBin group and Templates group #191
* Setting to display OTP Token in list #655
* Fix timeout in dialogs #716 * Fix timeout in dialogs #716
* Check URI permissions #626 * Check URI permissions #626
* Small changes #1035 #1043 #942 * Improvements #1035 #1043 #942 #1021 #1027
KeePassDX(2.10.5) KeePassDX(2.10.5)
* Increase the saving speed of database #1028 * Increase the saving speed of database #1028

View File

@@ -30,6 +30,7 @@ package com.igreenwood.loupe
import android.animation.Animator import android.animation.Animator
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.graphics.Matrix import android.graphics.Matrix
import android.graphics.PointF import android.graphics.PointF
import android.graphics.Rect import android.graphics.Rect
@@ -108,6 +109,8 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
var viewDragFriction = DEFAULT_VIEW_DRAG_FRICTION var viewDragFriction = DEFAULT_VIEW_DRAG_FRICTION
// drag distance threshold in dp for swipe to dismiss // drag distance threshold in dp for swipe to dismiss
var dragDismissDistanceInDp = DEFAULT_DRAG_DISMISS_DISTANCE_IN_DP var dragDismissDistanceInDp = DEFAULT_DRAG_DISMISS_DISTANCE_IN_DP
// on view touched
var onViewTouchedListener: View.OnTouchListener? = null
// on view translate listener // on view translate listener
var onViewTranslateListener: OnViewTranslateListener? = null var onViewTranslateListener: OnViewTranslateListener? = null
// on scale changed // on scale changed
@@ -272,7 +275,10 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
private var imageViewRef: WeakReference<ImageView> = WeakReference(imageView) private var imageViewRef: WeakReference<ImageView> = WeakReference(imageView)
private var containerRef: WeakReference<ViewGroup> = WeakReference(container) private var containerRef: WeakReference<ViewGroup> = WeakReference(container)
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(view: View?, event: MotionEvent?): Boolean { override fun onTouch(view: View?, event: MotionEvent?): Boolean {
onViewTouchedListener?.onTouch(view, event)
event ?: return false event ?: return false
val imageView = imageViewRef.get() ?: return false val imageView = imageViewRef.get() ?: return false
val container = containerRef.get() ?: return false val container = containerRef.get() ?: return false

View File

@@ -157,11 +157,6 @@ class EntryActivity : DatabaseLockActivity() {
invalidateOptionsMenu() invalidateOptionsMenu()
} }
mEntryViewModel.url.observe(this) { url ->
this.mUrl = url
invalidateOptionsMenu()
}
mEntryViewModel.entryInfo.observe(this) { entryInfo -> mEntryViewModel.entryInfo.observe(this) { entryInfo ->
// Manage entry copy to start notification if allowed (at the first start) // Manage entry copy to start notification if allowed (at the first start)
if (savedInstanceState == null) { if (savedInstanceState == null) {
@@ -184,6 +179,8 @@ class EntryActivity : DatabaseLockActivity() {
collapsingToolbarLayout?.title = entryTitle collapsingToolbarLayout?.title = entryTitle
toolbar?.title = entryTitle toolbar?.title = entryTitle
mUrl = entryInfo.url
// Refresh Menu // Refresh Menu
invalidateOptionsMenu() invalidateOptionsMenu()

View File

@@ -143,13 +143,13 @@ class EntryEditActivity : DatabaseLockActivity(),
// Entry is retrieve, it's an entry to update // Entry is retrieve, it's an entry to update
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate -> intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate ->
intent.removeExtra(KEY_ENTRY) //intent.removeExtra(KEY_ENTRY)
mEntryId = entryToUpdate mEntryId = entryToUpdate
} }
// Parent is retrieve, it's a new entry to create // Parent is retrieve, it's a new entry to create
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let { parent -> intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let { parent ->
intent.removeExtra(KEY_PARENT) //intent.removeExtra(KEY_PARENT)
mParentId = parent mParentId = parent
} }
@@ -166,7 +166,37 @@ class EntryEditActivity : DatabaseLockActivity(),
// Save button // Save button
validateButton?.setOnClickListener { saveEntry() } validateButton?.setOnClickListener { saveEntry() }
mEntryEditViewModel.entryInfo.observe(this) { mEntryEditViewModel.templatesEntry.observe(this) { templatesEntry ->
// Change template dynamically
templatesEntry?.templates?.let { templates ->
val defaultTemplate = templatesEntry.defaultTemplate
templateSelectorSpinner?.apply {
// Build template selector
if (templates.isNotEmpty()) {
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
) {
mEntryEditViewModel.changeTemplate(templates[position])
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
} else {
visibility = View.GONE
}
}
}
loadingView?.hideByFading() loadingView?.hideByFading()
} }
@@ -238,27 +268,6 @@ class EntryEditActivity : DatabaseLockActivity(),
mAttachmentFileBinderManager?.removeBinaryAttachment(it) mAttachmentFileBinderManager?.removeBinaryAttachment(it)
} }
// Change template dynamically
mEntryEditViewModel.templates.observe(this) { templatesLoaded ->
val templates = templatesLoaded.templates
val defaultTemplate = templatesLoaded.defaultTemplate
templateSelectorSpinner?.apply {
// Build template selector
if (templates.isNotEmpty()) {
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) {
mEntryEditViewModel.changeTemplate(templates[position])
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
} else {
visibility = View.GONE
}
}
}
// Build new entry from the entry info retrieved // Build new entry from the entry info retrieved
mEntryEditViewModel.onEntrySaved.observe(this) { entrySave -> mEntryEditViewModel.onEntrySaved.observe(this) { entrySave ->
// Open a progress dialog and save entry // Open a progress dialog and save entry

View File

@@ -19,6 +19,7 @@
*/ */
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@@ -43,6 +44,7 @@ class ImageViewerActivity : DatabaseLockActivity() {
private lateinit var imageView: ImageView private lateinit var imageView: ImageView
private lateinit var progressView: View private lateinit var progressView: View
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -52,12 +54,21 @@ class ImageViewerActivity : DatabaseLockActivity() {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
toolbar.setOnTouchListener { _, _ ->
resetAppTimeout()
false
}
imageContainerView = findViewById(R.id.image_viewer_container) imageContainerView = findViewById(R.id.image_viewer_container)
imageView = findViewById(R.id.image_viewer_image) imageView = findViewById(R.id.image_viewer_image)
progressView = findViewById(R.id.image_viewer_progress) progressView = findViewById(R.id.image_viewer_progress)
Loupe.create(imageView, imageContainerView!!) { Loupe.create(imageView, imageContainerView!!) {
onViewTouchedListener = View.OnTouchListener { _, _ ->
// to reset timeout when Loupe image view touched
resetAppTimeout()
false
}
onViewTranslateListener = object : Loupe.OnViewTranslateListener { onViewTranslateListener = object : Loupe.OnViewTranslateListener {
override fun onStart(view: ImageView) { override fun onStart(view: ImageView) {
@@ -81,7 +92,8 @@ class ImageViewerActivity : DatabaseLockActivity() {
} }
override fun viewToInvalidateTimeout(): View? { override fun viewToInvalidateTimeout(): View? {
return imageContainerView // Null to manually manage events
return null
} }
override fun finishActivityIfReloadRequested(): Boolean { override fun finishActivityIfReloadRequested(): Boolean {

View File

@@ -4,9 +4,10 @@ import android.os.Bundle
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval 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.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval { abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
@@ -19,7 +20,7 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
mDatabaseViewModel.database.observe(this) { database -> mDatabaseViewModel.database.observe(this) { database ->
this.mDatabase = database this.mDatabase = database
resetAppTimeoutWhenViewFocusedOrChanged() resetAppTimeoutOnTouchOrFocus()
onDatabaseRetrieved(database) onDatabaseRetrieved(database)
} }
@@ -31,7 +32,7 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
resetAppTimeoutWhenViewFocusedOrChanged() resetAppTimeoutOnTouchOrFocus()
} }
override fun onDatabaseRetrieved(database: Database?) { override fun onDatabaseRetrieved(database: Database?) {
@@ -46,9 +47,25 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
// Can be overridden by a subclass // Can be overridden by a subclass
} }
private fun resetAppTimeoutWhenViewFocusedOrChanged() { fun resetAppTimeout() {
context?.let { 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
)
}
} }
} }
} }

View File

@@ -4,8 +4,10 @@ import android.app.DatePickerDialog
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment
class DatePickerFragment : DatabaseDialogFragment() { // Not as DatabaseDialogFragment because crash on KitKat
class DatePickerFragment : DialogFragment() {
private var mDefaultYear: Int = 2000 private var mDefaultYear: Int = 2000
private var mDefaultMonth: Int = 1 private var mDefaultMonth: Int = 1

View File

@@ -19,11 +19,11 @@
*/ */
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
import android.app.AlertDialog
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential

View File

@@ -79,11 +79,15 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus -> private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
if (!isFocus) if (!isFocus)
mManualEvent = true mManualEvent = true
else
resetAppTimeout()
} }
@SuppressLint("ClickableViewAccessibility")
private var mOnTouchListener = View.OnTouchListener { _, event -> private var mOnTouchListener = View.OnTouchListener { _, event ->
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
mManualEvent = true mManualEvent = true
resetAppTimeout()
} }
} }
false false
@@ -94,6 +98,10 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
private var mPeriodWellFormed = false private var mPeriodWellFormed = false
private var mDigitsWellFormed = false private var mDigitsWellFormed = false
override fun overrideTimeoutTouchAndFocusEvents(): Boolean {
return true
}
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
// Verify that the host activity implements the callback interface // Verify that the host activity implements the callback interface
@@ -224,8 +232,11 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
builder.apply { builder.apply {
setView(root) setView(root)
.setPositiveButton(android.R.string.ok) {_, _ -> } .setPositiveButton(android.R.string.ok) { _, _ ->
resetAppTimeout()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> .setNegativeButton(android.R.string.cancel) { _, _ ->
resetAppTimeout()
} }
} }

View File

@@ -6,8 +6,10 @@ import android.app.TimePickerDialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.text.format.DateFormat import android.text.format.DateFormat
import androidx.fragment.app.DialogFragment
class TimePickerFragment : DatabaseDialogFragment() { // Not as DatabaseDialogFragment because crash on KitKat
class TimePickerFragment : DialogFragment() {
private var defaultHour: Int = 0 private var defaultHour: Int = 0
private var defaultMinute: Int = 0 private var defaultMinute: Int = 0

View File

@@ -4,7 +4,7 @@ import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval 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.activities.stylish.StylishFragment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
@@ -33,7 +33,7 @@ abstract class DatabaseFragment : StylishFragment(), DatabaseRetrieval {
protected fun resetAppTimeoutWhenViewFocusedOrChanged(view: View?) { protected fun resetAppTimeoutWhenViewFocusedOrChanged(view: View?) {
context?.let { context?.let {
view?.resetAppTimeoutWhenViewFocusedOrChanged(it, mDatabase?.loaded) view?.resetAppTimeoutWhenViewTouchedOrFocused(it, mDatabase?.loaded)
} }
} }

View File

@@ -118,10 +118,11 @@ class EntryEditFragment: DatabaseFragment() {
templateView.setTemplate(template) templateView.setTemplate(template)
} }
mEntryEditViewModel.entryInfo.observe(viewLifecycleOwner) { entryInfo -> mEntryEditViewModel.templatesEntry.observe(viewLifecycleOwner) { templateEntry ->
templateView.setTemplate(templateEntry.defaultTemplate)
// Load entry info only the first time to keep change locally // Load entry info only the first time to keep change locally
if (savedInstanceState == null) { if (savedInstanceState == null) {
assignEntryInfo(entryInfo) assignEntryInfo(templateEntry.entryInfo)
} }
// To prevent flickering // To prevent flickering
rootView.showByFading() rootView.showByFading()

View File

@@ -219,6 +219,8 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
activity?.intent?.let { activity?.intent?.let {
specialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(it) specialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(it)
} }
rebuildList()
} }
override fun onPause() { override fun onPause() {

View File

@@ -169,7 +169,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
// Focus view to reinitialize timeout, // Focus view to reinitialize timeout,
// view is not necessary loaded so retry later in resume // view is not necessary loaded so retry later in resume
viewToInvalidateTimeout() viewToInvalidateTimeout()
?.resetAppTimeoutWhenViewFocusedOrChanged(this, database?.loaded) ?.resetAppTimeoutWhenViewTouchedOrFocused(this, database?.loaded)
database?.let { database?.let {
// check timeout // check timeout
@@ -383,7 +383,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
// Invalidate timeout by touch // Invalidate timeout by touch
mDatabase?.let { database -> mDatabase?.let { database ->
viewToInvalidateTimeout() viewToInvalidateTimeout()
?.resetAppTimeoutWhenViewFocusedOrChanged(this, database.loaded) ?.resetAppTimeoutWhenViewTouchedOrFocused(this, database.loaded)
} }
invalidateOptionsMenu() invalidateOptionsMenu()
@@ -422,6 +422,11 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
sendBroadcast(Intent(LOCK_ACTION)) sendBroadcast(Intent(LOCK_ACTION))
} }
fun resetAppTimeout() {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this,
mDatabase?.loaded ?: false)
}
override fun onBackPressed() { override fun onBackPressed() {
if (mTimeoutEnable) { if (mTimeoutEnable) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this, TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this,
@@ -451,12 +456,12 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
* To reset the app timeout when a view is focused or changed * To reset the app timeout when a view is focused or changed
*/ */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context, databaseLoaded: Boolean?) { fun View.resetAppTimeoutWhenViewTouchedOrFocused(context: Context, databaseLoaded: Boolean?) {
// Log.d(LockingActivity.TAG, "View prepared to reset app timeout") // Log.d(DatabaseLockActivity.TAG, "View prepared to reset app timeout")
setOnTouchListener { _, event -> setOnTouchListener { _, event ->
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> { 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, TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
databaseLoaded ?: false) databaseLoaded ?: false)
} }
@@ -464,13 +469,13 @@ fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context, databaseLoade
false false
} }
setOnFocusChangeListener { _, _ -> 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, TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
databaseLoaded ?: false) databaseLoaded ?: false)
} }
if (this is ViewGroup) { if (this is ViewGroup) {
for (i in 0..childCount) { for (i in 0..childCount) {
getChildAt(i)?.resetAppTimeoutWhenViewFocusedOrChanged(context, databaseLoaded) getChildAt(i)?.resetAppTimeoutWhenViewTouchedOrFocused(context, databaseLoaded)
} }
} }
} }

View File

@@ -44,7 +44,6 @@ class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHist
holder.lastModifiedView.text = entryHistory.lastModificationTime.getDateTimeString(context.resources) holder.lastModifiedView.text = entryHistory.lastModificationTime.getDateTimeString(context.resources)
holder.titleView.text = entryHistory.title holder.titleView.text = entryHistory.title
holder.usernameView.text = entryHistory.username holder.usernameView.text = entryHistory.username
holder.urlView.text = entryHistory.url
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryHistory, position) onItemClickListener?.invoke(entryHistory, position)
@@ -64,6 +63,5 @@ class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHist
var lastModifiedView: TextView = itemView.findViewById(R.id.entry_history_last_modified) var lastModifiedView: TextView = itemView.findViewById(R.id.entry_history_last_modified)
var titleView: TextView = itemView.findViewById(R.id.entry_history_title) var titleView: TextView = itemView.findViewById(R.id.entry_history_title)
var usernameView: TextView = itemView.findViewById(R.id.entry_history_username) var usernameView: TextView = itemView.findViewById(R.id.entry_history_username)
var urlView: TextView = itemView.findViewById(R.id.entry_history_url)
} }
} }

View File

@@ -26,6 +26,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -40,6 +41,8 @@ import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.setTextSize import com.kunzisoft.keepass.view.setTextSize
import com.kunzisoft.keepass.view.strikeOut import com.kunzisoft.keepass.view.strikeOut
@@ -68,6 +71,7 @@ class NodeAdapter (private val context: Context,
private var mShowUserNames: Boolean = true private var mShowUserNames: Boolean = true
private var mShowNumberEntries: Boolean = true private var mShowNumberEntries: Boolean = true
private var mShowOTP: Boolean = false
private var mShowUUID: Boolean = false private var mShowUUID: Boolean = false
private var mEntryFilters = arrayOf<Group.ChildFilter>() private var mEntryFilters = arrayOf<Group.ChildFilter>()
@@ -122,6 +126,7 @@ class NodeAdapter (private val context: Context,
this.mShowUserNames = PreferencesUtil.showUsernamesListEntries(context) this.mShowUserNames = PreferencesUtil.showUsernamesListEntries(context)
this.mShowNumberEntries = PreferencesUtil.showNumberEntries(context) this.mShowNumberEntries = PreferencesUtil.showNumberEntries(context)
this.mShowOTP = PreferencesUtil.showOTPToken(context)
this.mShowUUID = PreferencesUtil.showUUID(context) this.mShowUUID = PreferencesUtil.showUUID(context)
this.mEntryFilters = Group.ChildFilter.getDefaults(context) this.mEntryFilters = Group.ChildFilter.getDefaults(context)
@@ -148,6 +153,7 @@ class NodeAdapter (private val context: Context,
if (oldItem is Entry && newItem is Entry) { if (oldItem is Entry && newItem is Entry) {
typeContentTheSame = oldItem.getVisualTitle() == newItem.getVisualTitle() typeContentTheSame = oldItem.getVisualTitle() == newItem.getVisualTitle()
&& oldItem.username == newItem.username && oldItem.username == newItem.username
&& oldItem.getOtpElement() == newItem.getOtpElement()
&& oldItem.containsAttachment() == newItem.containsAttachment() && oldItem.containsAttachment() == newItem.containsAttachment()
} else if (oldItem is Group && newItem is Group) { } else if (oldItem is Group && newItem is Group) {
typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries
@@ -357,6 +363,25 @@ class NodeAdapter (private val context: Context,
} }
} }
val otpElement = entry.getOtpElement()
holder.otpContainer?.removeCallbacks(holder.otpRunnable)
if (otpElement != null
&& mShowOTP
&& otpElement.token.isNotEmpty()) {
// Execute runnable to show progress
holder.otpRunnable.action = {
populateOtpView(holder, otpElement)
}
if (otpElement.type == OtpType.TOTP) {
holder.otpRunnable.postDelayed()
}
populateOtpView(holder, otpElement)
holder.otpContainer?.visibility = View.VISIBLE
} else {
holder.otpContainer?.visibility = View.GONE
}
holder.attachmentIcon?.visibility = holder.attachmentIcon?.visibility =
if (entry.containsAttachment()) View.VISIBLE else View.GONE if (entry.containsAttachment()) View.VISIBLE else View.GONE
@@ -386,7 +411,41 @@ class NodeAdapter (private val context: Context,
mNodeClickCallback?.onNodeLongClick(database, subNode) ?: false mNodeClickCallback?.onNodeLongClick(database, subNode) ?: false
} }
} }
private fun populateOtpView(holder: NodeViewHolder?, otpElement: OtpElement?) {
when (otpElement?.type) {
OtpType.HOTP -> {
holder?.otpCounter?.text = otpElement.counter.toString()
holder?.otpProgress?.apply {
max = 100
progress = 100
}
}
OtpType.TOTP -> {
holder?.otpCounter?.text = otpElement.secondsRemaining.toString()
holder?.otpProgress?.apply {
max = otpElement.period
progress = otpElement.secondsRemaining
}
}
}
holder?.otpToken?.text = otpElement?.token
}
class OtpRunnable(val view: View?): Runnable {
var action: (() -> Unit)? = null
override fun run() {
action?.invoke()
postDelayed()
}
fun postDelayed() {
view?.postDelayed(this, 1000)
}
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return mNodeSortedList.size() return mNodeSortedList.size()
} }
@@ -413,6 +472,11 @@ class NodeAdapter (private val context: Context,
var text: TextView = itemView.findViewById(R.id.node_text) var text: TextView = itemView.findViewById(R.id.node_text)
var subText: TextView = itemView.findViewById(R.id.node_subtext) var subText: TextView = itemView.findViewById(R.id.node_subtext)
var meta: TextView = itemView.findViewById(R.id.node_meta) 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 otpCounter: TextView? = itemView.findViewById(R.id.node_otp_counter)
var otpToken: TextView? = itemView.findViewById(R.id.node_otp_token)
var otpRunnable: OtpRunnable = OtpRunnable(otpContainer)
var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers) var numberChildren: TextView? = itemView.findViewById(R.id.node_child_numbers)
var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon) var attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon)
} }

View File

@@ -56,7 +56,7 @@ class OtpModel() : Parcelable {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as OtpElement other as OtpModel
if (type != other.type) return false if (type != other.type) return false
// Token type is important only if it's a TOTP // Token type is important only if it's a TOTP

View File

@@ -185,6 +185,19 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
return secondsRemaining == otpModel.period 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 { companion object {
const val MIN_HOTP_COUNTER = 0 const val MIN_HOTP_COUNTER = 0
const val MAX_HOTP_COUNTER = Long.MAX_VALUE const val MAX_HOTP_COUNTER = Long.MAX_VALUE

View File

@@ -454,7 +454,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
} }
override fun onPreferenceTreeClick(preference: Preference?): Boolean { override fun onPreferenceTreeClick(preference: Preference?): Boolean {
// To reload group when appearence settings are modified // To reload group when appearance settings are modified
when (preference?.key) { when (preference?.key) {
getString(R.string.setting_style_key), getString(R.string.setting_style_key),
getString(R.string.setting_style_brightness_key), getString(R.string.setting_style_brightness_key),
@@ -464,6 +464,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
getString(R.string.list_size_key), getString(R.string.list_size_key),
getString(R.string.monospace_font_fields_enable_key), getString(R.string.monospace_font_fields_enable_key),
getString(R.string.hide_expired_entries_key), getString(R.string.hide_expired_entries_key),
getString(R.string.show_otp_token_key),
getString(R.string.show_uuid_key), getString(R.string.show_uuid_key),
getString(R.string.enable_education_screens_key), getString(R.string.enable_education_screens_key),
getString(R.string.reset_education_screens_key) -> { getString(R.string.reset_education_screens_key) -> {

View File

@@ -32,7 +32,7 @@ import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment 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.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
@@ -74,7 +74,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
mDatabaseViewModel.database.observe(viewLifecycleOwner) { database -> mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
mDatabase = database mDatabase = database
view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), database?.loaded) view.resetAppTimeoutWhenViewTouchedOrFocused(requireContext(), database?.loaded)
onDatabaseRetrieved(database) onDatabaseRetrieved(database)
} }

View File

@@ -128,6 +128,12 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.hide_expired_entries_default)) context.resources.getBoolean(R.bool.hide_expired_entries_default))
} }
fun showOTPToken(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.show_otp_token_key),
context.resources.getBoolean(R.bool.show_otp_token_default))
}
fun showUUID(context: Context): Boolean { fun showUUID(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.show_uuid_key), return prefs.getBoolean(context.getString(R.string.show_uuid_key),
@@ -644,6 +650,7 @@ object PreferencesUtil {
context.getString(R.string.list_size_key) -> editor.putString(name, value) context.getString(R.string.list_size_key) -> editor.putString(name, value)
context.getString(R.string.monospace_font_fields_enable_key) -> editor.putBoolean(name, value.toBoolean()) context.getString(R.string.monospace_font_fields_enable_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.hide_expired_entries_key) -> editor.putBoolean(name, value.toBoolean()) context.getString(R.string.hide_expired_entries_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.show_otp_token_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.show_uuid_key) -> editor.putBoolean(name, value.toBoolean()) context.getString(R.string.show_uuid_key) -> editor.putBoolean(name, value.toBoolean())
context.getString(R.string.enable_education_screens_key) -> editor.putBoolean(name, value.toBoolean()) context.getString(R.string.enable_education_screens_key) -> editor.putBoolean(name, value.toBoolean())

View File

@@ -20,7 +20,6 @@
package com.kunzisoft.keepass.timeout package com.kunzisoft.keepass.timeout
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
@@ -30,6 +29,7 @@ import android.text.method.LinkMovementMethod
import android.text.util.Linkify import android.text.util.Linkify
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.exception.ClipboardException import com.kunzisoft.keepass.database.exception.ClipboardException
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil

View File

@@ -239,8 +239,23 @@ object UriUtil {
} }
fun openExternalApp(context: Context, packageName: String) { fun openExternalApp(context: Context, packageName: String) {
var launchIntent: Intent? = null
try { try {
context.startActivity(context.applicationContext.packageManager.getLaunchIntentForPackage(packageName)) launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
} catch (ignored: Exception) {
}
try {
if (launchIntent == null) {
context.startActivity(
Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.parse("https://play.google.com/store/apps/details?id=$packageName"))
)
} else {
context.startActivity(launchIntent)
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "App cannot be open", e) Log.e(TAG, "App cannot be open", e)
} }

View File

@@ -39,7 +39,7 @@ abstract class TemplateAbstractView<
private var mTemplate: Template? = null private var mTemplate: Template? = null
protected var mEntryInfo: EntryInfo? = null protected var mEntryInfo: EntryInfo? = null
protected var mCustomFieldIds = mutableListOf<FieldId>() private var mViewFields = mutableListOf<ViewField>()
protected var mFontInVisibility: Boolean = PreferencesUtil.fieldFontIsInVisibility(context) protected var mFontInVisibility: Boolean = PreferencesUtil.fieldFontIsInVisibility(context)
protected var mHideProtectedValue: Boolean = PreferencesUtil.hideProtectedValue(context) protected var mHideProtectedValue: Boolean = PreferencesUtil.hideProtectedValue(context)
@@ -48,7 +48,7 @@ abstract class TemplateAbstractView<
protected var entryIconView: ImageView protected var entryIconView: ImageView
private var titleContainerView: ViewGroup private var titleContainerView: ViewGroup
protected var templateContainerView: ViewGroup protected var templateContainerView: ViewGroup
protected var customFieldsContainerView: SectionView private var customFieldsContainerView: SectionView
private var notReferencedFieldsContainerView: SectionView private var notReferencedFieldsContainerView: SectionView
init { init {
@@ -95,7 +95,7 @@ abstract class TemplateAbstractView<
} }
} }
fun buildTemplate() { private fun buildTemplate() {
// Retrieve preferences // Retrieve preferences
mHideProtectedValue = PreferencesUtil.hideProtectedValue(context) mHideProtectedValue = PreferencesUtil.hideProtectedValue(context)
@@ -104,7 +104,7 @@ abstract class TemplateAbstractView<
templateContainerView.removeAllViews() templateContainerView.removeAllViews()
customFieldsContainerView.removeAllViews() customFieldsContainerView.removeAllViews()
notReferencedFieldsContainerView.removeAllViews() notReferencedFieldsContainerView.removeAllViews()
mCustomFieldIds.clear() mViewFields.clear()
mTemplate?.let { template -> mTemplate?.let { template ->
@@ -200,7 +200,7 @@ abstract class TemplateAbstractView<
TemplateAttributeType.TEXT, TemplateAttributeType.TEXT,
field.protectedValue.isProtected, field.protectedValue.isProtected,
TemplateAttributeOption().apply { TemplateAttributeOption().apply {
setNumberLines(3) setNumberLines(20)
}, },
TemplateAttributeAction.CUSTOM_EDITION TemplateAttributeAction.CUSTOM_EDITION
).apply { ).apply {
@@ -231,13 +231,13 @@ abstract class TemplateAbstractView<
// Add new custom view id to the custom field list // Add new custom view id to the custom field list
if (fieldTag == FIELD_CUSTOM_TAG) { if (fieldTag == FIELD_CUSTOM_TAG) {
val indexOldItem = indexCustomFieldIdByName(field.name) val indexOldItem = getIndexViewFieldByName(field.name)
if (indexOldItem >= 0) if (indexOldItem >= 0)
mCustomFieldIds.removeAt(indexOldItem) mViewFields.removeAt(indexOldItem)
if (itemView?.id != null) { if (itemView?.id != null) {
mCustomFieldIds.add( mViewFields.add(
FieldId( ViewField(
itemView.id, itemView,
field field
) )
) )
@@ -320,7 +320,7 @@ abstract class TemplateAbstractView<
/** /**
* Return empty custom fields * Return empty custom fields
*/ */
protected open fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<FieldId> { protected open fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<ViewField> {
mEntryInfo?.let { entryInfo -> mEntryInfo?.let { entryInfo ->
populateEntryFieldView(FIELD_TITLE_TAG, populateEntryFieldView(FIELD_TITLE_TAG,
@@ -350,15 +350,14 @@ abstract class TemplateAbstractView<
showEmptyFields) showEmptyFields)
customFieldsContainerView.removeAllViews() customFieldsContainerView.removeAllViews()
val emptyCustomFields = mutableListOf<FieldId>().also { it.addAll(mCustomFieldIds) } val emptyCustomFields = mutableListOf<ViewField>().also { it.addAll(mViewFields) }
entryInfo.customFields.forEach { customField -> entryInfo.customFields.forEach { customField ->
val indexFieldViewId = indexCustomFieldIdByName(customField.name) val indexFieldViewId = getIndexViewFieldByName(customField.name)
if (indexFieldViewId >= 0) { if (indexFieldViewId >= 0) {
// Template contains the custom view // Template contains the custom view
val customFieldId = mCustomFieldIds[indexFieldViewId] val viewField = mViewFields[indexFieldViewId]
emptyCustomFields.remove(customFieldId) emptyCustomFields.remove(viewField)
templateContainerView.findViewById<View>(customFieldId.viewId) viewField.view.let { customView ->
?.let { customView ->
if (customView is GenericTextFieldView) { if (customView is GenericTextFieldView) {
customView.value = customField.protectedValue.stringValue customView.value = customField.protectedValue.stringValue
customView.applyFontVisibility(mFontInVisibility) 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 { private fun isStandardFieldName(name: String): Boolean {
return TemplateField.isStandardFieldName(name) return TemplateField.isStandardFieldName(name)
} }
protected fun customFieldIdByName(name: String): FieldId? { protected fun getViewFieldByName(name: String): ViewField? {
return mCustomFieldIds.find { it.field.name.equals(name, true) } return mViewFields.find { it.field.name.equals(name, true) }
} }
protected fun indexCustomFieldIdByName(name: String): Int { private fun getIndexViewFieldByName(name: String): Int {
return mCustomFieldIds.indexOfFirst { it.field.name.equals(name, true) } return mViewFields.indexOfFirst { it.field.name.equals(name, true) }
} }
private fun retrieveCustomFieldsFromView(templateFieldNotEmpty: Boolean = false) { private fun retrieveCustomFieldsFromView(templateFieldNotEmpty: Boolean = false) {
mEntryInfo?.customFields = mCustomFieldIds.mapNotNull { mEntryInfo?.customFields = mViewFields.mapNotNull {
getCustomField(it.field.name, templateFieldNotEmpty) getCustomField(it.field.name, templateFieldNotEmpty)
}.toMutableList() }.toMutableList()
} }
@@ -484,10 +483,9 @@ abstract class TemplateAbstractView<
?: Field(fieldName, ProtectedString(false)) ?: Field(fieldName, ProtectedString(false))
} }
protected fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field? { private fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field? {
customFieldIdByName(fieldName)?.let { fieldId -> getViewFieldByName(fieldName)?.let { fieldId ->
val editView: View? = templateContainerView.findViewById(fieldId.viewId) val editView: View? = fieldId.view
?: customFieldsContainerView.findViewById(fieldId.viewId)
if (editView is GenericFieldView) { if (editView is GenericFieldView) {
// Do not return field with a default value // Do not return field with a default value
val defaultViewValue = if (editView.value == editView.default) "" else editView.value val defaultViewValue = if (editView.value == editView.default) "" else editView.value
@@ -514,20 +512,22 @@ abstract class TemplateAbstractView<
return if (!isStandardFieldName(customField.name)) { return if (!isStandardFieldName(customField.name)) {
customFieldsContainerView.visibility = View.VISIBLE customFieldsContainerView.visibility = View.VISIBLE
if (indexCustomFieldIdByName(customField.name) >= 0) { if (getIndexViewFieldByName(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 { } else {
val newCustomView = buildViewForCustomField(customField) val newCustomView = buildViewForCustomField(customField)
newCustomView?.let { newCustomView?.let {
customFieldsContainerView.addView(newCustomView) customFieldsContainerView.addView(newCustomView)
val fieldId = FieldId( val fieldId = ViewField(
newCustomView.id, newCustomView,
customField customField
) )
val indexOldItem = indexCustomFieldIdByName(fieldId.field.name) val indexOldItem = getIndexViewFieldByName(fieldId.field.name)
if (indexOldItem >= 0) if (indexOldItem >= 0)
mCustomFieldIds.removeAt(indexOldItem) mViewFields.removeAt(indexOldItem)
mCustomFieldIds.add(indexOldItem, fieldId) mViewFields.add(indexOldItem, fieldId)
if (focus) if (focus)
newCustomView.requestFocus() newCustomView.requestFocus()
} }
@@ -544,58 +544,63 @@ abstract class TemplateAbstractView<
return put return put
} }
/** private fun replaceCustomField(oldField: Field,
* Update a custom field and keep the old value newField: Field,
*/ keepOldValue: Boolean,
private fun replaceCustomField(oldField: Field, newField: Field, focus: Boolean): Boolean { focus: Boolean): Boolean {
if (!isStandardFieldName(newField.name)) { if (!isStandardFieldName(newField.name)) {
customFieldIdByName(oldField.name)?.viewId?.let { viewId -> getViewFieldByName(oldField.name)?.view?.let { viewToReplace ->
customFieldsContainerView.findViewById<View>(viewId)?.let { viewToReplace -> val oldValue = getCustomField(oldField.name).protectedValue.toString()
val oldValue = getCustomField(oldField.name).protectedValue.toString()
val parentGroup = viewToReplace.parent as ViewGroup val parentGroup = viewToReplace.parent as ViewGroup
val indexInParent = parentGroup.indexOfChild(viewToReplace) val indexInParent = parentGroup.indexOfChild(viewToReplace)
parentGroup.removeView(viewToReplace) parentGroup.removeView(viewToReplace)
val newCustomFieldWithValue = Field(newField.name, val newCustomFieldWithValue = if (keepOldValue)
ProtectedString(newField.protectedValue.isProtected, oldValue)) Field(newField.name,
val oldPosition = indexCustomFieldIdByName(oldField.name) ProtectedString(newField.protectedValue.isProtected, oldValue)
if (oldPosition >= 0) )
mCustomFieldIds.removeAt(oldPosition) else
newField
val oldPosition = getIndexViewFieldByName(oldField.name)
if (oldPosition >= 0)
mViewFields.removeAt(oldPosition)
val newCustomView = buildViewForCustomField(newCustomFieldWithValue) val newCustomView = buildViewForCustomField(newCustomFieldWithValue)
newCustomView?.let { newCustomView?.let {
parentGroup.addView(newCustomView, indexInParent) parentGroup.addView(newCustomView, indexInParent)
mCustomFieldIds.add( mViewFields.add(
oldPosition, oldPosition,
FieldId( ViewField(
newCustomView.id, newCustomView,
newCustomFieldWithValue newCustomFieldWithValue
)
) )
if (focus) )
newCustomView.requestFocus() if (focus)
} newCustomView.requestFocus()
return true
} }
return true
} }
} }
return false return false
} }
/**
* Update a custom field and keep the old value
*/
fun replaceCustomField(oldField: Field, newField: Field): Boolean { fun replaceCustomField(oldField: Field, newField: Field): Boolean {
val replace = replaceCustomField(oldField, newField, true) val replace = replaceCustomField(oldField, newField, keepOldValue = true, focus = true)
retrieveCustomFieldsFromView() retrieveCustomFieldsFromView()
return replace return replace
} }
fun removeCustomField(oldCustomField: Field) { fun removeCustomField(oldCustomField: Field) {
val indexOldField = indexCustomFieldIdByName(oldCustomField.name) val indexOldField = getIndexViewFieldByName(oldCustomField.name)
if (indexOldField >= 0) { if (indexOldField >= 0) {
mCustomFieldIds[indexOldField].viewId.let { viewId -> mViewFields[indexOldField].let { fieldView ->
customFieldsContainerView.removeViewById(viewId) customFieldsContainerView.removeViewById(fieldView.view.id)
} }
mCustomFieldIds.removeAt(indexOldField) mViewFields.removeAt(indexOldField)
} }
retrieveCustomFieldsFromView() retrieveCustomFieldsFromView()
} }

View File

@@ -192,7 +192,7 @@ class TemplateEditView @JvmOverloads constructor(context: Context,
} }
} }
override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<FieldId> { override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<ViewField> {
refreshIcon() refreshIcon()
return super.populateViewsWithEntryInfo(showEmptyFields) return super.populateViewsWithEntryInfo(showEmptyFields)
} }

View File

@@ -56,18 +56,20 @@ class TemplateView @JvmOverloads constructor(context: Context,
setMaxLines(templateAttribute.options.getNumberLines()) setMaxLines(templateAttribute.options.getNumberLines())
// TODO Linkify // TODO Linkify
value = field.protectedValue.stringValue value = field.protectedValue.stringValue
// Here the value is often empty
if (field.protectedValue.isProtected) { if (field.protectedValue.isProtected) {
if (mFirstTimeAskAllowCopyProtectedFields) { if (mFirstTimeAskAllowCopyProtectedFields) {
setCopyButtonState(TextFieldView.ButtonState.DEACTIVATE) setCopyButtonState(TextFieldView.ButtonState.DEACTIVATE)
setCopyButtonClickListener { setCopyButtonClickListener { _, _ ->
mOnAskCopySafeClickListener?.invoke() mOnAskCopySafeClickListener?.invoke()
} }
} else { } else {
if (mAllowCopyProtectedFields) { if (mAllowCopyProtectedFields) {
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { setCopyButtonClickListener { label, value ->
mOnCopyActionClickListener?.invoke(field) mOnCopyActionClickListener
?.invoke(Field(label, ProtectedString(false, value)))
} }
} else { } else {
setCopyButtonState(TextFieldView.ButtonState.GONE) setCopyButtonState(TextFieldView.ButtonState.GONE)
@@ -76,8 +78,9 @@ class TemplateView @JvmOverloads constructor(context: Context,
} }
} else { } else {
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { setCopyButtonClickListener { label, value ->
mOnCopyActionClickListener?.invoke(field) mOnCopyActionClickListener
?.invoke(Field(label, ProtectedString(false, value)))
} }
} }
} }
@@ -114,22 +117,22 @@ class TemplateView @JvmOverloads constructor(context: Context,
return findViewWithTag<TextFieldView?>(FIELD_PASSWORD_TAG)?.getCopyButtonView() return findViewWithTag<TextFieldView?>(FIELD_PASSWORD_TAG)?.getCopyButtonView()
} }
override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<FieldId> { override fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<ViewField> {
val emptyCustomFields = super.populateViewsWithEntryInfo(false) val emptyCustomFields = super.populateViewsWithEntryInfo(false)
// Hide empty custom fields // Hide empty custom fields
emptyCustomFields.forEach { customFieldId -> emptyCustomFields.forEach { customFieldId ->
templateContainerView.findViewById<View?>(customFieldId.viewId) customFieldId.view.isVisible = false
?.isVisible = false
} }
removeOtpRunnable()
mEntryInfo?.let { entryInfo -> mEntryInfo?.let { entryInfo ->
// Assign specific OTP dynamic view // Assign specific OTP dynamic view
removeOtpRunnable()
entryInfo.otpModel?.let { entryInfo.otpModel?.let {
assignOtp(it) assignOtp(it)
} }
} }
return emptyCustomFields return emptyCustomFields
} }
@@ -146,11 +149,10 @@ class TemplateView @JvmOverloads constructor(context: Context,
private var mOnOtpElementUpdated: ((OtpElement?) -> Unit)? = null private var mOnOtpElementUpdated: ((OtpElement?) -> Unit)? = null
private fun getOtpTokenView(): TextFieldView? { private fun getOtpTokenView(): TextFieldView? {
val indexFieldViewId = indexCustomFieldIdByName(OTP_TOKEN_FIELD) getViewFieldByName(OTP_TOKEN_FIELD)?.let { viewField ->
if (indexFieldViewId >= 0) { val view = viewField.view
// Template contains the custom view if (view is TextFieldView)
val customFieldId = mCustomFieldIds[indexFieldViewId] return view
return findViewById(customFieldId.viewId)
} }
return null return null
} }
@@ -166,7 +168,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
label = otpElement.type.name label = otpElement.type.name
value = otpElement.token value = otpElement.token
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE) setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
setCopyButtonClickListener { setCopyButtonClickListener { _, _ ->
mOnCopyActionClickListener?.invoke(Field( mOnCopyActionClickListener?.invoke(Field(
otpElement.type.name, otpElement.type.name,
ProtectedString(false, otpElement.token))) ProtectedString(false, otpElement.token)))

View File

@@ -1,6 +1,7 @@
package com.kunzisoft.keepass.view package com.kunzisoft.keepass.view
import android.content.Context import android.content.Context
import android.os.Build
import android.text.InputFilter import android.text.InputFilter
import android.text.InputType import android.text.InputType
import android.util.AttributeSet import android.util.AttributeSet
@@ -41,11 +42,11 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT) LayoutParams.WRAP_CONTENT)
inputType = EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS inputType = EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
imeOptions = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING imeOptions = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO
} }
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
} }
maxLines = 1 maxLines = 1
@@ -61,7 +62,7 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
resources.displayMetrics resources.displayMetrics
).toInt() ).toInt()
it.addRule(ALIGN_PARENT_RIGHT) it.addRule(ALIGN_PARENT_RIGHT)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it.addRule(ALIGN_PARENT_END) it.addRule(ALIGN_PARENT_END)
} }
} }
@@ -82,7 +83,7 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
id = labelViewId id = labelViewId
layoutParams = (layoutParams as LayoutParams?).also { layoutParams = (layoutParams as LayoutParams?).also {
it?.addRule(LEFT_OF, actionImageButtonId) it?.addRule(LEFT_OF, actionImageButtonId)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it?.addRule(START_OF, actionImageButtonId) it?.addRule(START_OF, actionImageButtonId)
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2019 Jeremy Jamet / Kunzisoft. * Copyright 2021 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -20,16 +20,20 @@
package com.kunzisoft.keepass.view package com.kunzisoft.keepass.view
import android.content.Context import android.content.Context
import android.os.Build
import android.text.InputFilter import android.text.InputFilter
import android.text.util.Linkify import android.text.util.Linkify
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.RelativeLayout
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatImageButton
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.ContextCompat
import androidx.core.text.util.LinkifyCompat import androidx.core.text.util.LinkifyCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.EntryInfo.Companion.APPLICATION_ID_FIELD_NAME import com.kunzisoft.keepass.model.EntryInfo.Companion.APPLICATION_ID_FIELD_NAME
@@ -39,22 +43,130 @@ import com.kunzisoft.keepass.utils.UriUtil
class TextFieldView @JvmOverloads constructor(context: Context, class TextFieldView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyle: Int = 0) defStyle: Int = 0)
: LinearLayout(context, attrs, defStyle), GenericTextFieldView { : RelativeLayout(context, attrs, defStyle), GenericTextFieldView {
private val labelView: TextView private var labelViewId = ViewCompat.generateViewId()
private val valueView: TextView private var valueViewId = ViewCompat.generateViewId()
private val showButtonView: ImageView private var showButtonId = ViewCompat.generateViewId()
private val copyButtonView: ImageView private var copyButtonId = ViewCompat.generateViewId()
private val labelView = AppCompatTextView(context).apply {
setTextAppearance(context,
R.style.KeepassDXStyle_TextAppearance_LabelTextStyle)
layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
).also {
it.leftMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
4f,
resources.displayMetrics
).toInt()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it.marginStart = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
4f,
resources.displayMetrics
).toInt()
}
}
}
private val valueView = AppCompatTextView(context).apply {
setTextAppearance(context,
R.style.KeepassDXStyle_TextAppearance_TextEntryItem)
layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT).also {
it.topMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
4f,
resources.displayMetrics
).toInt()
it.leftMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
8f,
resources.displayMetrics
).toInt()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it.marginStart = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
8f,
resources.displayMetrics
).toInt()
}
}
setTextIsSelectable(true)
}
private var showButton = AppCompatImageButton(
ContextThemeWrapper(context, R.style.KeepassDXStyle_ImageButton_Simple), null, 0).apply {
layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_visibility_state))
contentDescription = context.getString(R.string.menu_showpass)
}
private var copyButton = AppCompatImageButton(
ContextThemeWrapper(context, R.style.KeepassDXStyle_ImageButton_Simple), null, 0).apply {
layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT)
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_content_copy_white_24dp))
contentDescription = context.getString(R.string.menu_copy)
}
init { init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater? buildViews()
inflater?.inflate(R.layout.view_entry_field, this) addView(copyButton)
addView(showButton)
addView(labelView)
addView(valueView)
}
labelView = findViewById(R.id.entry_field_label) private fun buildViews() {
valueView = findViewById(R.id.entry_field_value) copyButton.apply {
showButtonView = findViewById(R.id.entry_field_show) id = copyButtonId
copyButtonView = findViewById(R.id.entry_field_copy) layoutParams = (layoutParams as LayoutParams?).also {
copyButtonView.visibility = View.GONE it?.addRule(ALIGN_PARENT_RIGHT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it?.addRule(ALIGN_PARENT_END)
}
}
}
showButton.apply {
id = showButtonId
layoutParams = (layoutParams as LayoutParams?).also {
if (copyButton.isVisible) {
it?.addRule(LEFT_OF, copyButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it?.addRule(START_OF, copyButtonId)
}
} else {
it?.addRule(ALIGN_PARENT_RIGHT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it?.addRule(ALIGN_PARENT_END)
}
}
}
}
labelView.apply {
id = labelViewId
layoutParams = (layoutParams as LayoutParams?).also {
it?.addRule(LEFT_OF, showButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it?.addRule(START_OF, showButtonId)
}
}
}
valueView.apply {
id = valueViewId
layoutParams = (layoutParams as LayoutParams?).also {
it?.addRule(LEFT_OF, showButtonId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it?.addRule(START_OF, showButtonId)
}
it?.addRule(BELOW, labelViewId)
}
}
} }
override fun applyFontVisibility(fontInVisibility: Boolean) { override fun applyFontVisibility(fontInVisibility: Boolean) {
@@ -115,19 +227,20 @@ class TextFieldView @JvmOverloads constructor(context: Context,
} }
fun setProtection(protection: Boolean, hiddenProtectedValue: Boolean = false) { fun setProtection(protection: Boolean, hiddenProtectedValue: Boolean = false) {
showButtonView.isVisible = protection showButton.isVisible = protection
showButtonView.isSelected = hiddenProtectedValue showButton.isSelected = hiddenProtectedValue
showButtonView.setOnClickListener { showButton.setOnClickListener {
showButtonView.isSelected = !showButtonView.isSelected showButton.isSelected = !showButton.isSelected
changeProtectedValueParameters() changeProtectedValueParameters()
} }
changeProtectedValueParameters() changeProtectedValueParameters()
invalidate()
} }
private fun changeProtectedValueParameters() { private fun changeProtectedValueParameters() {
valueView.apply { valueView.apply {
if (showButtonView.isVisible) { if (showButton.isVisible) {
applyHiddenStyle(showButtonView.isSelected) applyHiddenStyle(showButton.isSelected)
} else { } else {
linkify() linkify()
} }
@@ -138,11 +251,11 @@ class TextFieldView @JvmOverloads constructor(context: Context,
when { when {
labelView.text.contains(APPLICATION_ID_FIELD_NAME) -> { labelView.text.contains(APPLICATION_ID_FIELD_NAME) -> {
val packageName = valueView.text.toString() val packageName = valueView.text.toString()
if (UriUtil.isExternalAppInstalled(context, packageName)) { // TODO #996 if (UriUtil.isExternalAppInstalled(context, packageName)) {
valueView.customLink { valueView.customLink {
UriUtil.openExternalApp(context, packageName) UriUtil.openExternalApp(context, packageName)
} }
} //}
} }
else -> { else -> {
LinkifyCompat.addLinks(valueView, Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES) LinkifyCompat.addLinks(valueView, Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES)
@@ -151,8 +264,8 @@ class TextFieldView @JvmOverloads constructor(context: Context,
} }
fun getCopyButtonView(): View? { fun getCopyButtonView(): View? {
if (copyButtonView.isVisible) { if (copyButton.isVisible) {
return copyButtonView return copyButton
} }
return null return null
} }
@@ -160,7 +273,7 @@ class TextFieldView @JvmOverloads constructor(context: Context,
fun setCopyButtonState(buttonState: ButtonState) { fun setCopyButtonState(buttonState: ButtonState) {
when (buttonState) { when (buttonState) {
ButtonState.ACTIVATE -> { ButtonState.ACTIVATE -> {
copyButtonView.apply { copyButton.apply {
visibility = VISIBLE visibility = VISIBLE
isActivated = false isActivated = false
} }
@@ -170,7 +283,7 @@ class TextFieldView @JvmOverloads constructor(context: Context,
} }
} }
ButtonState.DEACTIVATE -> { ButtonState.DEACTIVATE -> {
copyButtonView.apply { copyButton.apply {
visibility = VISIBLE visibility = VISIBLE
// Reverse because isActivated show custom color and allow click // Reverse because isActivated show custom color and allow click
isActivated = true isActivated = true
@@ -181,7 +294,7 @@ class TextFieldView @JvmOverloads constructor(context: Context,
} }
} }
ButtonState.GONE -> { ButtonState.GONE -> {
copyButtonView.apply { copyButton.apply {
visibility = GONE visibility = GONE
setOnClickListener(null) setOnClickListener(null)
} }
@@ -191,18 +304,24 @@ class TextFieldView @JvmOverloads constructor(context: Context,
} }
} }
} }
invalidate()
} }
fun setCopyButtonClickListener(onActionClickListener: OnClickListener?) { fun setCopyButtonClickListener(onActionClickListener: ((label: String, value: String) -> Unit)?) {
setOnActionClickListener(onActionClickListener, null) val clickListener = if (onActionClickListener != null)
OnClickListener { onActionClickListener.invoke(label, value) }
else
null
setOnActionClickListener(clickListener, null)
} }
override fun setOnActionClickListener( override fun setOnActionClickListener(
onActionClickListener: OnClickListener?, onActionClickListener: OnClickListener?,
actionImageId: Int? actionImageId: Int?
) { ) {
copyButtonView.setOnClickListener(onActionClickListener) copyButton.setOnClickListener(onActionClickListener)
copyButtonView.isVisible = onActionClickListener != null copyButton.isVisible = onActionClickListener != null
invalidate()
} }
override var isFieldVisible: Boolean override var isFieldVisible: Boolean
@@ -213,6 +332,11 @@ class TextFieldView @JvmOverloads constructor(context: Context,
isVisible = value isVisible = value
} }
override fun invalidate() {
super.invalidate()
buildViews()
}
enum class ButtonState { enum class ButtonState {
ACTIVATE, DEACTIVATE, GONE ACTIVATE, DEACTIVATE, GONE
} }

View File

@@ -2,21 +2,27 @@ package com.kunzisoft.keepass.view
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.text.InputType
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.* import android.widget.*
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatImageButton
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
class TextSelectFieldView @JvmOverloads constructor(context: Context, class TextSelectFieldView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyle: Int = 0) defStyle: Int = 0)
@@ -28,30 +34,54 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
private var actionImageButtonId = ViewCompat.generateViewId() private var actionImageButtonId = ViewCompat.generateViewId()
private var mDefaultPosition = 0 private var mDefaultPosition = 0
private val labelView = AppCompatTextView(context).apply { private val labelView = TextInputLayout(context).apply {
setTextAppearance(context, R.style.KeepassDXStyle_TextAppearance_LabelTextStyle)
layoutParams = LayoutParams( layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT).also { LayoutParams.WRAP_CONTENT)
val leftMargin = 4f }
it.leftMargin = TypedValue.applyDimension( private val valueView = TextInputEditText(
TypedValue.COMPLEX_UNIT_DIP, ContextThemeWrapper(context,
leftMargin, R.style.KeepassDXStyle_TextInputLayout)
resources.displayMetrics ).apply {
).toInt() layoutParams = LinearLayout.LayoutParams(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { LayoutParams.MATCH_PARENT,
it.marginStart = TypedValue.applyDimension( LayoutParams.WRAP_CONTENT)
TypedValue.COMPLEX_UNIT_DIP, inputType = EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS
leftMargin, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
resources.displayMetrics imeOptions = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
).toInt() importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO
}
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
}
val drawable = ContextCompat.getDrawable(context, R.drawable.ic_arrow_down_white_24dp)
?.apply {
mutate().colorFilter = BlendModeColorFilterCompat
.createBlendModeColorFilterCompat(currentTextColor, BlendModeCompat.SRC_IN)
}
setCompoundDrawablesWithIntrinsicBounds(
null,
null,
drawable,
null
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
setCompoundDrawablesRelativeWithIntrinsicBounds(
null,
null,
drawable,
null
)
}
isFocusable = false
inputType = InputType.TYPE_NULL
maxLines = 1
} }
private val valueSpinnerView = Spinner(context).apply { private val valueSpinnerView = Spinner(context).apply {
layoutParams = LayoutParams( layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT, 0,
LayoutParams.WRAP_CONTENT) 0
)
} }
private var actionImageButton = AppCompatImageButton( private var actionImageButton = AppCompatImageButton(
ContextThemeWrapper(context, R.style.KeepassDXStyle_ImageButton_Simple), null, 0).apply { ContextThemeWrapper(context, R.style.KeepassDXStyle_ImageButton_Simple), null, 0).apply {
@@ -64,7 +94,7 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
resources.displayMetrics resources.displayMetrics
).toInt() ).toInt()
it.addRule(ALIGN_PARENT_RIGHT) it.addRule(ALIGN_PARENT_RIGHT)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it.addRule(ALIGN_PARENT_END) it.addRule(ALIGN_PARENT_END)
} }
} }
@@ -75,15 +105,22 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
init { init {
// Manually write view to avoid view id bugs // Manually write view to avoid view id bugs
buildViews() buildViews()
labelView.addView(valueView)
addView(labelView) addView(labelView)
addView(valueSpinnerView) addView(valueSpinnerView)
addView(actionImageButton) addView(actionImageButton)
valueView.apply {
setOnClickListener {
valueSpinnerView.performClick()
}
}
valueSpinnerView.apply { valueSpinnerView.apply {
adapter = valueSpinnerAdapter adapter = valueSpinnerAdapter
onItemSelectedListener = object : AdapterView.OnItemSelectedListener { onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
valueSpinnerAdapter.getItem(position) val stringValue = valueSpinnerAdapter.getItem(position)
valueView.setText(stringValue)
} }
override fun onNothingSelected(parent: AdapterView<*>?) {} override fun onNothingSelected(parent: AdapterView<*>?) {}
} }
@@ -95,7 +132,7 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
id = labelViewId id = labelViewId
layoutParams = (layoutParams as LayoutParams?).also { layoutParams = (layoutParams as LayoutParams?).also {
it?.addRule(LEFT_OF, actionImageButtonId) it?.addRule(LEFT_OF, actionImageButtonId)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it?.addRule(START_OF, actionImageButtonId) it?.addRule(START_OF, actionImageButtonId)
} }
} }
@@ -104,7 +141,7 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
id = valueViewId id = valueViewId
layoutParams = (layoutParams as LayoutParams?).also { layoutParams = (layoutParams as LayoutParams?).also {
it?.addRule(LEFT_OF, actionImageButtonId) it?.addRule(LEFT_OF, actionImageButtonId)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
it?.addRule(START_OF, actionImageButtonId) it?.addRule(START_OF, actionImageButtonId)
} }
it?.addRule(BELOW, labelViewId) it?.addRule(BELOW, labelViewId)

View File

@@ -5,29 +5,23 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.kunzisoft.keepass.app.database.IOActionTask import com.kunzisoft.keepass.app.database.IOActionTask
import com.kunzisoft.keepass.database.element.* 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.NodeId
import com.kunzisoft.keepass.database.element.template.Template import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.model.* import com.kunzisoft.keepass.model.*
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
import java.util.*
class EntryEditViewModel: NodeEditViewModel() { class EntryEditViewModel: NodeEditViewModel() {
private val mTempAttachments = mutableListOf<EntryAttachmentState>() private val mTempAttachments = mutableListOf<EntryAttachmentState>()
val entryInfo : LiveData<EntryInfo> get() = _entryInfo val templatesEntry : LiveData<TemplatesEntry> get() = _templatesEntry
private val _entryInfo = MutableLiveData<EntryInfo>() private val _templatesEntry = MutableLiveData<TemplatesEntry>()
val requestEntryInfoUpdate : LiveData<EntryUpdate> get() = _requestEntryInfoUpdate val requestEntryInfoUpdate : LiveData<EntryUpdate> get() = _requestEntryInfoUpdate
private val _requestEntryInfoUpdate = SingleLiveEvent<EntryUpdate>() private val _requestEntryInfoUpdate = SingleLiveEvent<EntryUpdate>()
val onEntrySaved : LiveData<EntrySave> get() = _onEntrySaved val onEntrySaved : LiveData<EntrySave> get() = _onEntrySaved
private val _onEntrySaved = SingleLiveEvent<EntrySave>() private val _onEntrySaved = SingleLiveEvent<EntrySave>()
val templates : LiveData<TemplatesLoad> get() = _templates
private val _templates = MutableLiveData<TemplatesLoad>()
val onTemplateChanged : LiveData<Template> get() = _onTemplateChanged val onTemplateChanged : LiveData<Template> get() = _onTemplateChanged
private val _onTemplateChanged = SingleLiveEvent<Template>() private val _onTemplateChanged = SingleLiveEvent<Template>()
@@ -69,38 +63,27 @@ class EntryEditViewModel: NodeEditViewModel() {
{ {
val templates = database.getTemplates(isTemplate) val templates = database.getTemplates(isTemplate)
val entryTemplate = entry?.let { database.getTemplate(it) } ?: Template.STANDARD val entryTemplate = entry?.let { database.getTemplate(it) } ?: Template.STANDARD
TemplatesLoad(templates, entryTemplate) var entryInfo: EntryInfo? = null
}, // Decode the entry / load entry info
{ templatesLoad -> entry?.let {
// Call templates init before populate entry info database.decodeEntryWithTemplateConfiguration(it).let { entry ->
_templates.value = templatesLoad // Load entry info
changeTemplate(templatesLoad!!.defaultTemplate) entry.getEntryInfo(database, true).let { tempEntryInfo ->
// Retrieve data from registration
IOActionTask( (registerInfo?.searchInfo ?: searchInfo)?.let { tempSearchInfo ->
{ tempEntryInfo.saveSearchInfo(database, tempSearchInfo)
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
}
} }
registerInfo?.let { regInfo ->
tempEntryInfo.saveRegisterInfo(database, regInfo)
}
entryInfo = tempEntryInfo
} }
entryInfo
},
{ entryInfo ->
_entryInfo.value = entryInfo
} }
).execute() }
TemplatesEntry(templates, entryTemplate, entryInfo)
},
{ templatesEntry ->
_templatesEntry.value = templatesEntry
} }
).execute() ).execute()
} }
@@ -239,7 +222,7 @@ class EntryEditViewModel: NodeEditViewModel() {
_onBinaryPreviewLoaded.value = AttachmentPosition(entryAttachmentState, viewPosition) _onBinaryPreviewLoaded.value = AttachmentPosition(entryAttachmentState, viewPosition)
} }
data class TemplatesLoad(val templates: List<Template>, val defaultTemplate: Template) data class TemplatesEntry(val templates: List<Template>, val defaultTemplate: Template, val entryInfo: EntryInfo?)
data class EntryUpdate(val database: Database?, val entry: Entry?, val parent: Group?) 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 EntrySave(val oldEntry: Entry, val newEntry: Entry, val parent: Group?)
data class FieldEdition(val oldField: Field?, val newField: Field?) data class FieldEdition(val oldField: Field?, val newField: Field?)

View File

@@ -45,9 +45,6 @@ class EntryViewModel: ViewModel() {
val historyPosition : LiveData<Int> get() = _historyPosition val historyPosition : LiveData<Int> get() = _historyPosition
private val _historyPosition = MutableLiveData<Int>() private val _historyPosition = MutableLiveData<Int>()
val url : LiveData<String?> get() = _url
private val _url = MutableLiveData<String?>()
val entryInfo : LiveData<EntryInfo> get() = _entryInfo val entryInfo : LiveData<EntryInfo> get() = _entryInfo
private val _entryInfo = MutableLiveData<EntryInfo>() private val _entryInfo = MutableLiveData<EntryInfo>()
@@ -66,58 +63,49 @@ class EntryViewModel: ViewModel() {
private val _historySelected = SingleLiveEvent<EntryHistory>() private val _historySelected = SingleLiveEvent<EntryHistory>()
fun loadEntry(database: Database?, entryId: NodeId<UUID>?, historyPosition: Int) { fun loadEntry(database: Database?, entryId: NodeId<UUID>?, historyPosition: Int) {
if (entryId != null) { if (database != null && entryId != null) {
IOActionTask( IOActionTask(
{ {
database?.getEntryById(entryId) val mainEntry = database.getEntryById(entryId)
},
{ mainEntry ->
// Manage current version and history
_mainEntryId.value = mainEntry?.nodeId
_historyPosition.value = historyPosition
val currentEntry = if (historyPosition > -1) { val currentEntry = if (historyPosition > -1) {
mainEntry?.getHistory()?.get(historyPosition) mainEntry?.getHistory()?.get(historyPosition)
} else { } else {
mainEntry mainEntry
} }
_url.value = currentEntry?.url
IOActionTask( val entryTemplate = currentEntry?.let {
{ database.getTemplate(it)
val entryTemplate = currentEntry?.let { } ?: Template.STANDARD
database?.getTemplate(it)
} ?: Template.STANDARD
// To simplify template field visibility // To simplify template field visibility
currentEntry?.let { entry -> currentEntry?.let { entry ->
// Add mainEntry to check the parent and define the template state // Add mainEntry to check the parent and define the template state
database?.decodeEntryWithTemplateConfiguration(entry, mainEntry) database.decodeEntryWithTemplateConfiguration(entry, mainEntry).let {
?.let { // To update current modification time
// To update current modification time it.touch(modified = false, touchParents = false)
it.touch(modified = false, touchParents = false)
// Build history info // Build history info
val entryInfoHistory = it.getHistory().map { entryHistory -> val entryInfoHistory = it.getHistory().map { entryHistory ->
entryHistory.getEntryInfo(database) 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
} }
EntryInfoHistory(
mainEntry!!.nodeId,
entryTemplate,
it.getEntryInfo(database),
entryInfoHistory
)
} }
).execute() }
},
{ entryInfoHistory ->
if (entryInfoHistory != null) {
_mainEntryId.value = entryInfoHistory.mainEntryId
_historyPosition.value = historyPosition
_template.value = entryInfoHistory.template
_entryInfo.value = entryInfoHistory.entryInfo
_entryHistory.value = entryInfoHistory.entryHistory
}
} }
).execute() ).execute()
} }
@@ -143,7 +131,8 @@ class EntryViewModel: ViewModel() {
_historySelected.value = EntryHistory(NodeIdUUID(item.id), null, item, position) _historySelected.value = EntryHistory(NodeIdUUID(item.id), null, item, position)
} }
data class EntryInfoHistory(val template: Template, data class EntryInfoHistory(var mainEntryId: NodeId<UUID>,
val template: Template,
val entryInfo: EntryInfo, val entryInfo: EntryInfo,
val entryHistory: List<EntryInfo>) val entryHistory: List<EntryInfo>)
// Custom data class to manage entry to retrieve and define is it's an history item (!= -1) // Custom data class to manage entry to retrieve and define is it's an history item (!= -1)

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorAccent" />
</selector>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" > <selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_selected="true" android:color="@color/white"/> <item android:state_selected="true" android:color="@color/white"/>
<item android:color="?android:attr/textColorSecondary"/> <item android:color="@color/grey"/>
</selector> </selector>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/white_dark" />
</selector>

View File

@@ -7,6 +7,6 @@
android:innerRadiusRatio="2.5" android:innerRadiusRatio="2.5"
android:thickness="2dp" android:thickness="2dp"
android:useLevel="true"> android:useLevel="true">
<solid android:color="@color/orange" /> <solid android:color="@color/progress_color" />
</shape> </shape>
</rotate> </rotate>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android" <rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="270" android:fromDegrees="270"
android:toDegrees="270"> android:toDegrees="270">
@@ -8,6 +7,6 @@
android:innerRadiusRatio="2.5" android:innerRadiusRatio="2.5"
android:thickness="2dp" android:thickness="2dp"
android:useLevel="true"> android:useLevel="true">
<solid android:color="?attr/colorAccent" /> <solid android:color="@color/list_secondary_color" />
</shape> </shape>
</rotate> </rotate>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M 11 2.0566406 C 6.762335 2.4220229 3.0067094 5.7987155 2.203125 9.9785156 C 1.3601754 13.960549 3.1781148 18.394742 6.7089844 20.480469 C 9.6237318 22.368157 13.514425 22.492178 16.582031 20.892578 C 17.959775 20.180473 19.316015 19.099467 20.087891 17.808594 L 18.402344 16.791016 C 16.277892 19.724364 12.039121 20.844607 8.7519531 19.306641 C 5.4810648 17.911181 3.4461927 14.150571 4.109375 10.648438 C 4.6649663 7.2806973 7.5784749 4.4226117 11 4.0839844 L 11 2.0566406 z M 13 2.0644531 L 13 4.09375 C 16.367309 4.4801388 19.308004 7.2166098 19.861328 10.574219 C 20.123351 12.069186 19.935392 13.632673 19.367188 15.037109 C 19.94644 15.387646 20.527063 15.73602 21.105469 16.087891 C 22.671735 12.714066 22.12099 8.4920873 19.708984 5.6542969 C 18.063396 3.6246553 15.604973 2.2995703 13 2.0644531 z M 11 7.0644531 L 11 12.064453 L 7 12.064453 L 12 17.064453 L 17 12.064453 L 13 12.064453 L 13 7.0644531 L 11 7.0644531 z" />
</vector>

View File

@@ -1,7 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportHeight="24"
android:viewportWidth="24" >
<path android:fillColor="#FFFFFF" android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M 11 2.0566406 C 6.762335 2.4220229 3.0067094 5.7987155 2.203125 9.9785156 C 1.3601753 13.960549 3.1781148 18.394742 6.7089844 20.480469 C 9.6237318 22.368157 13.514425 22.492178 16.582031 20.892578 C 17.959775 20.180473 19.316015 19.099467 20.087891 17.808594 L 18.402344 16.791016 C 16.277892 19.724364 12.039121 20.844605 8.7519531 19.306641 C 5.481064 17.911182 3.4461934 14.150571 4.109375 10.648438 C 4.6649664 7.2806974 7.5784749 4.4226117 11 4.0839844 L 11 2.0566406 z M 13 2.0644531 L 13 4.09375 C 16.367309 4.4801387 19.308002 7.2166101 19.861328 10.574219 C 20.123352 12.069186 19.935393 13.632674 19.367188 15.037109 C 19.94644 15.387646 20.527063 15.73602 21.105469 16.087891 C 22.671737 12.714066 22.120988 8.4920871 19.708984 5.6542969 C 18.063396 3.6246553 15.604973 2.2995704 13 2.0644531 z M 8.1113281 7 C 7.4946611 7 7 7.5002173 7 8.1113281 L 7 15.888672 C 7 16.499784 7.4946611 17 8.1113281 17 L 15.888672 17 C 16.499783 17 17 16.499784 17 15.888672 L 17 9.2226562 L 14.777344 7 L 8.1113281 7 z M 8.1113281 8.1113281 L 13.666016 8.1113281 L 13.666016 10.333984 L 8.1113281 10.333984 L 8.1113281 8.1113281 z M 12 12.554688 C 12.922223 12.554688 13.666016 13.300434 13.666016 14.222656 C 13.666016 15.144878 12.922223 15.888672 12 15.888672 C 11.077779 15.888672 10.333984 15.144878 10.333984 14.222656 C 10.333984 13.300434 11.077779 12.554687 12 12.554688 z" />
</vector>

View File

@@ -70,16 +70,16 @@
tools:targetApi="lollipop"> tools:targetApi="lollipop">
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
<ProgressBar <com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/entry_progress" android:id="@+id/entry_progress"
android:visibility="gone" android:visibility="gone"
style="?android:attr/progressBarStyleHorizontal"
android:indeterminate="false" android:indeterminate="false"
app:indicatorColor="?attr/colorAccent"
android:progress="10" android:progress="10"
android:max="30" android:max="30"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="4dp" /> android:layout_height="wrap_content" />
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--> -->
<androidx.constraintlayout.widget.ConstraintLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -29,15 +29,14 @@
android:id="@+id/special_mode_view" android:id="@+id/special_mode_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="?attr/toolbarSpecialAppearance" android:theme="?attr/toolbarSpecialAppearance" />
app:layout_constraintTop_toTopOf="parent" />
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/group_coordinator" android:id="@+id/group_coordinator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@+id/special_mode_view" android:layout_below="@+id/special_mode_view"
app:layout_constraintBottom_toTopOf="@+id/toolbar_action"> android:layout_above="@+id/toolbar_action">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar" android:id="@+id/app_bar"
@@ -47,99 +46,86 @@
android:elevation="4dp" android:elevation="4dp"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_layout" android:id="@+id/toolbar"
android:title="@string/app_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="?attr/actionBarSize"
app:titleEnabled="false" android:background="?attr/colorPrimary"
app:contentScrim="?attr/colorPrimary" android:theme="?attr/toolbarAppearance"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> android:elevation="4dp"
tools:targetApi="lollipop">
<androidx.appcompat.widget.Toolbar <LinearLayout
android:id="@+id/toolbar" android:id="@+id/group_header"
android:title="@string/app_name" android:layout_width="wrap_content"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="?attr/actionBarSize" android:layout_alignParentLeft="true"
android:background="?attr/colorPrimary" android:layout_alignParentStart="true"
android:theme="?attr/toolbarAppearance" android:layout_below="@+id/toolbar"
android:elevation="4dp" android:orientation="vertical">
tools:targetApi="lollipop"> <TextView android:id="@+id/search_title"
<LinearLayout android:layout_width="match_parent"
android:id="@+id/group_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:text="@string/search_results"
android:layout_alignParentStart="true" android:visibility="gone"
android:layout_below="@+id/toolbar" style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary" />
android:orientation="vertical"> <LinearLayout
<TextView android:id="@+id/search_title" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:baselineAligned="false">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/search_results"
android:visibility="gone"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"> android:gravity="center_vertical">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/group_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end|center_vertical"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:scaleType="fitXY" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_numbers"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical"> tools:text="3"
<androidx.appcompat.widget.AppCompatImageView style="@style/KeepassDXStyle.TextAppearance.Info" />
android:id="@+id/group_icon" </RelativeLayout>
android:layout_width="32dp" <LinearLayout
android:layout_height="32dp" android:layout_width="0dp"
android:layout_gravity="end|center_vertical" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:orientation="vertical"
android:layout_marginEnd="6dp" android:layout_gravity="start|center_vertical"
app:layout_constraintTop_toTopOf="parent" android:layout_marginLeft="14dp"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginStart="14dp"
app:layout_constraintStart_toStartOf="parent" android:layout_weight="1">
android:scaleType="fitXY" /> <androidx.appcompat.widget.AppCompatTextView
<androidx.appcompat.widget.AppCompatTextView android:id="@+id/group_name"
android:id="@+id/group_numbers" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="match_parent"
android:layout_height="wrap_content" android:text="@string/root"
tools:text="3" android:maxLines="2"
style="@style/KeepassDXStyle.TextAppearance.Info" android:ellipsize="end"
app:layout_constraintTop_toTopOf="parent" style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary" />
app:layout_constraintStart_toStartOf="parent" /> <androidx.appcompat.widget.AppCompatTextView
</androidx.constraintlayout.widget.ConstraintLayout> android:id="@+id/group_meta"
<LinearLayout android:layout_height="match_parent"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C"
android:orientation="vertical" android:lines="1"
android:layout_gravity="start|center_vertical" android:singleLine="true"
android:layout_marginLeft="@dimen/image_list_margin_vertical" style="@style/KeepassDXStyle.TextAppearance.Meta.TextOnPrimary" />
android:layout_marginStart="@dimen/image_list_margin_vertical"
android:layout_weight="1">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/root"
android:maxLines="2"
android:ellipsize="end"
style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/group_meta"
android:layout_height="match_parent"
android:layout_width="match_parent"
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C"
android:lines="1"
android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Meta.TextOnPrimary" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.appcompat.widget.Toolbar> </LinearLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<LinearLayout <RelativeLayout
android:id="@+id/node_list_container" android:id="@+id/node_list_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -151,7 +137,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:attr/windowBackground" /> android:background="?android:attr/windowBackground" />
</LinearLayout> </RelativeLayout>
<com.kunzisoft.keepass.view.AddNodeButtonView <com.kunzisoft.keepass.view.AddNodeButtonView
android:id="@+id/add_node_button" android:id="@+id/add_node_button"
@@ -167,8 +153,7 @@
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:visibility="gone" android:visibility="gone"
android:theme="?attr/toolbarActionAppearance" android:theme="?attr/toolbarActionAppearance"
app:layout_constraintStart_toStartOf="parent" android:layout_alignParentBottom="true" />
app:layout_constraintBottom_toBottomOf="parent" />
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -185,7 +170,6 @@
layout="@layout/view_button_lock" layout="@layout/view_button_lock"
android:layout_width="@dimen/lock_button_size" android:layout_width="@dimen/lock_button_size"
android:layout_height="@dimen/lock_button_size" android:layout_height="@dimen/lock_button_size"
app:layout_constraintStart_toStartOf="parent" android:layout_alignParentBottom="true"/>
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </RelativeLayout>

View File

@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--> -->
<androidx.constraintlayout.widget.ConstraintLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -29,17 +29,16 @@
android:id="@+id/special_mode_view" android:id="@+id/special_mode_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="?attr/toolbarSpecialAppearance" android:theme="?attr/toolbarSpecialAppearance" />
app:layout_constraintTop_toTopOf="parent" />
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/activity_password_coordinator_layout" android:id="@+id/activity_password_coordinator_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:background="@drawable/background_repeat" android:background="@drawable/background_repeat"
android:backgroundTint="?android:attr/textColor" android:backgroundTint="?android:attr/textColor"
app:layout_constraintTop_toBottomOf="@+id/special_mode_view" android:layout_below="@+id/special_mode_view"
app:layout_constraintBottom_toTopOf="@+id/activity_password_footer"> android:layout_above="@+id/activity_password_footer">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar" android:id="@+id/app_bar"
@@ -111,12 +110,12 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="4dp" android:elevation="4dp"
android:paddingTop="@dimen/default_margin" android:paddingTop="6dp"
android:paddingLeft="@dimen/default_margin" android:paddingLeft="12dp"
android:paddingStart="@dimen/default_margin" android:paddingStart="12dp"
android:paddingRight="@dimen/default_margin" android:paddingRight="12dp"
android:paddingEnd="@dimen/default_margin" android:paddingEnd="12dp"
android:paddingBottom="@dimen/default_margin" android:paddingBottom="12dp"
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
app:layout_constraintWidth_percent="@dimen/content_percent" app:layout_constraintWidth_percent="@dimen/content_percent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@@ -214,7 +213,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"> android:layout_alignParentBottom="true">
<LinearLayout <LinearLayout
android:id="@+id/activity_password_info_container" android:id="@+id/activity_password_info_container"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -250,4 +249,4 @@
android:text="@string/menu_open" /> android:text="@string/menu_open" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </RelativeLayout>

View File

@@ -17,28 +17,24 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--> -->
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="o" tools:targetApi="o"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:orientation="vertical">
<com.kunzisoft.keepass.view.TemplateEditView <com.kunzisoft.keepass.view.TemplateEditView
android:id="@+id/template_view" android:id="@+id/template_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"/>
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/entry_attachments_container"/>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/entry_attachments_container" android:id="@+id/entry_attachments_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="?attr/cardViewStyle" style="?attr/cardViewStyle">
app:layout_constraintTop_toBottomOf="@+id/template_view"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout <LinearLayout
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -62,4 +58,4 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>

View File

@@ -38,10 +38,11 @@
android:text="@string/entry_history" android:text="@string/entry_history"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" /> style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:weightSum="3"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"> android:layout_marginBottom="4dp">
@@ -49,51 +50,30 @@
android:id="@+id/entry_history_last_modified" android:id="@+id/entry_history_last_modified"
android:text="@string/entry_modified" android:text="@string/entry_modified"
style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle" style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_title"
android:gravity="center" android:gravity="center"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:layout_weight="1" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_title" android:id="@+id/entry_history_title"
android:text="@string/entry_title" android:text="@string/entry_title"
style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle" style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle"
app:layout_constraintStart_toEndOf="@+id/entry_history_last_modified"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_username"
android:gravity="center" android:gravity="center"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:layout_weight="1" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_username" android:id="@+id/entry_history_username"
android:text="@string/entry_user_name" android:text="@string/entry_user_name"
style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle" style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle"
app:layout_constraintStart_toEndOf="@+id/entry_history_title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_url"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_url"
android:text="@string/entry_url"
style="@style/KeepassDXStyle.TextAppearance.LabelTableTextStyle"
app:layout_constraintStart_toEndOf="@+id/entry_history_username"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="center" android:gravity="center"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"/> android:layout_weight="1" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/entry_history_list" android:id="@+id/entry_history_list"

View File

@@ -18,7 +18,9 @@
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView <TextView
@@ -55,9 +57,9 @@
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
style="@style/KeepassDXStyle.TextAppearance.WarningTextStyle"/> style="@style/KeepassDXStyle.TextAppearance.WarningTextStyle"/>
<ProgressBar <com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_dialog_bar" android:id="@+id/progress_dialog_bar"
style="?android:attr/progressBarStyleHorizontal" app:indicatorColor="?attr/colorAccent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"

View File

@@ -38,43 +38,40 @@
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
android:scaleType="fitStart" /> android:scaleType="fitStart" />
<androidx.constraintlayout.widget.ConstraintLayout <RelativeLayout
android:id="@+id/item_attachment_info" android:id="@+id/item_attachment_info"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="48dp"
android:layout_alignBottom="@+id/item_attachment_thumbnail" android:layout_alignBottom="@+id/item_attachment_thumbnail"
android:background="?attr/cardBackgroundTransparentColor"> android:background="?attr/cardBackgroundTransparentColor">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/item_attachment_broken" android:id="@+id/item_attachment_broken"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:contentDescription="@string/entry_attachments" android:contentDescription="@string/entry_attachments"
android:src="@drawable/ic_attach_file_broken_white_24dp" android:src="@drawable/ic_attach_file_broken_white_24dp" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/item_attachment_title" android:id="@+id/item_attachment_title"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent" android:layout_toStartOf="@+id/item_attachment_size_container"
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container" android:layout_toLeftOf="@+id/item_attachment_size_container"
app:layout_constraintStart_toEndOf="@+id/item_attachment_broken" android:layout_toEndOf="@+id/item_attachment_broken"
app:layout_constraintTop_toTopOf="parent" android:layout_toRightOf="@+id/item_attachment_broken"
android:gravity="center_vertical"
tools:text="BinaryFile.attach" /> tools:text="BinaryFile.attach" />
<LinearLayout <LinearLayout
android:id="@+id/item_attachment_size_container" android:id="@+id/item_attachment_size_container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:gravity="end" android:gravity="center_vertical|end"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent" android:layout_toLeftOf="@+id/item_attachment_action_container"
app:layout_constraintEnd_toStartOf="@+id/item_attachment_action_container" android:layout_toStartOf="@+id/item_attachment_action_container">
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/item_attachment_size" android:id="@+id/item_attachment_size"
@@ -101,10 +98,9 @@
<FrameLayout <FrameLayout
android:id="@+id/item_attachment_action_container" android:id="@+id/item_attachment_action_container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent" android:layout_alignParentEnd="true"
app:layout_constraintEnd_toEndOf="parent" android:layout_alignParentRight="true">
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/item_attachment_delete_button" android:id="@+id/item_attachment_delete_button"
@@ -150,5 +146,5 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
</FrameLayout> </FrameLayout>
</FrameLayout> </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>

View File

@@ -17,65 +17,43 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--> -->
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:weightSum="3"
android:background="?android:attr/selectableItemBackground"> android:background="?android:attr/selectableItemBackground">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_last_modified" android:id="@+id/entry_history_last_modified"
tools:text = "Last Modified" tools:text = "Last Modified"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_title"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_title"
tools:text = "Title"
app:layout_constraintStart_toEndOf="@+id/entry_history_last_modified"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_username"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
android:maxLines="6"
android:ellipsize="end"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_username"
tools:text = "Username"
app:layout_constraintStart_toEndOf="@+id/entry_history_title"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_history_url"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
android:maxLines="6"
android:ellipsize="end"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_url"
tools:text = "URL"
app:layout_constraintStart_toEndOf="@+id/entry_history_username"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
android:gravity="center" android:gravity="center"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"/> android:layout_weight="1" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_title"
tools:text = "Title"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
android:maxLines="6"
android:ellipsize="end"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/entry_history_username"
tools:text = "Username"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
android:maxLines="6"
android:ellipsize="end"
android:gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>

View File

@@ -25,15 +25,20 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/KeepassDXStyle.Selectable.Item"> style="@style/KeepassDXStyle.Selectable.Item">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:minHeight="56dp"
android:maxHeight="72dp"
app:layout_constraintWidth_percent="@dimen/content_percent" app:layout_constraintWidth_percent="@dimen/content_percent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:background="?android:attr/selectableItemBackground" > android:background="?android:attr/selectableItemBackground" >
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/node_icon" android:id="@+id/node_icon"
android:layout_width="32dp" android:layout_width="32dp"
@@ -44,67 +49,129 @@
android:layout_marginStart="@dimen/image_list_margin_vertical" android:layout_marginStart="@dimen/image_list_margin_vertical"
android:layout_marginRight="@dimen/image_list_margin_vertical" android:layout_marginRight="@dimen/image_list_margin_vertical"
android:layout_marginEnd="@dimen/image_list_margin_vertical" android:layout_marginEnd="@dimen/image_list_margin_vertical"
android:layout_gravity="center"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/ic_blank_32dp" android:src="@drawable/ic_blank_32dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent" /> app:layout_constraintLeft_toLeftOf="parent" />
<LinearLayout <LinearLayout
android:id="@+id/node_container_info"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:paddingTop="4dp"
android:paddingTop="2dp"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:layout_marginLeft="@dimen/image_list_margin_vertical"
android:layout_marginStart="@dimen/image_list_margin_vertical" android:layout_marginStart="@dimen/image_list_margin_vertical"
android:layout_marginRight="@dimen/image_list_margin_vertical" android:layout_marginLeft="@dimen/image_list_margin_vertical"
android:layout_marginEnd="@dimen/image_list_margin_vertical" android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/node_icon" app:layout_constraintStart_toEndOf="@+id/node_icon"
app:layout_constraintLeft_toRightOf="@+id/node_icon" app:layout_constraintLeft_toRightOf="@+id/node_icon"
app:layout_constraintEnd_toStartOf="@+id/node_attachment_icon" app:layout_constraintEnd_toStartOf="@+id/node_options"
app:layout_constraintRight_toLeftOf="@+id/node_attachment_icon"> app:layout_constraintRight_toLeftOf="@+id/node_options">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_text" android:id="@+id/node_text"
android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.Entry.Title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
tools:text="Node Title" android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end" android:ellipsize="end"
style="@style/KeepassDXStyle.TextAppearance.Entry.Title" /> android:maxLines="2"
tools:text="Node Title" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_subtext" android:id="@+id/node_subtext"
android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-4dp" android:layout_marginTop="-4dp"
tools:text="Node SubTitle"
android:lines="1" android:lines="1"
android:singleLine="true" android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle" /> tools:text="Node SubTitle" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_meta" android:id="@+id/node_meta"
android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.Entry.Meta"
android:layout_width="wrap_content" android:layout_width="wrap_content"
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" android:layout_height="wrap_content"
android:lines="1" android:lines="1"
android:singleLine="true" android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Entry.Meta" /> tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" />
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/node_attachment_icon" <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/node_options"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_marginLeft="@dimen/image_list_margin_vertical" android:layout_marginStart="12dp"
android:layout_marginStart="@dimen/image_list_margin_vertical" android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:src="@drawable/ic_attach_file_white_24dp"
style="@style/KeepassDXStyle.TextAppearance.Entry.Icon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintRight_toRightOf="parent" />
<LinearLayout
android:id="@+id/node_otp_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/node_attachment_icon"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/node_otp_token"
style="@style/KeepassDXStyle.TextAppearance.Entry.Info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="5136" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp">
<ProgressBar
android:id="@+id/node_otp_progress"
style="@style/KeepassDXStyle.ProgressBar.Circle.Secondary"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center"
android:max="100"
android:progress="60" />
<TextView
android:id="@+id/node_otp_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
style="@style/KeepassDXStyle.TextAppearance.Entry.Info"
android:textSize="11sp"
tools:text="70" />
</FrameLayout>
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/node_attachment_icon"
style="@style/KeepassDXStyle.TextAppearance.Entry.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:src="@drawable/ic_attach_file_white_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/node_otp_container" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -25,12 +25,16 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/KeepassDXStyle.Selectable.Item"> style="@style/KeepassDXStyle.Selectable.Item">
<RelativeLayout <RelativeLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:minHeight="56dp"
android:maxHeight="72dp"
app:layout_constraintWidth_percent="@dimen/content_percent" app:layout_constraintWidth_percent="@dimen/content_percent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:background="?android:attr/selectableItemBackground" > android:background="?android:attr/selectableItemBackground" >
@@ -66,14 +70,14 @@
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toEndOf="@+id/node_icon"
android:layout_toRightOf="@+id/node_icon"
android:layout_toStartOf="@+id/node_image_identifier" android:layout_toStartOf="@+id/node_image_identifier"
android:layout_toLeftOf="@+id/node_image_identifier" android:layout_toLeftOf="@+id/node_image_identifier"
android:orientation="vertical" android:layout_toEndOf="@+id/node_icon"
android:paddingTop="2dp" android:layout_toRightOf="@+id/node_icon"
android:paddingBottom="4dp"> android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_text" android:id="@+id/node_text"
@@ -84,25 +88,27 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:maxLines="2" android:maxLines="2"
tools:text="Node Title" /> tools:text="Node Title" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_subtext" android:id="@+id/node_subtext"
style="@style/KeepassDXStyle.TextAppearance.Group.SubTitle" style="@style/KeepassDXStyle.TextAppearance.Group.SubTitle"
android:visibility="gone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="-4dp" android:layout_marginTop="-4dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:lines="1" android:lines="1"
android:singleLine="true" android:singleLine="true"
android:visibility="gone"
tools:text="Node SubTitle" /> tools:text="Node SubTitle" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/node_meta" android:id="@+id/node_meta"
android:layout_height="wrap_content" style="@style/KeepassDXStyle.TextAppearance.Group.Meta"
android:layout_width="wrap_content" android:layout_width="wrap_content"
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" android:layout_height="wrap_content"
android:lines="1" android:lines="1"
android:singleLine="true" android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Group.Meta" /> tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" />
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView

View File

@@ -27,10 +27,10 @@
android:ellipsize="end" android:ellipsize="end"
android:gravity="center_vertical" android:gravity="center_vertical"
android:maxLines="2" android:maxLines="2"
android:paddingStart="6dp" android:paddingStart="@dimen/default_margin"
android:paddingLeft="6dp" android:paddingLeft="@dimen/default_margin"
android:paddingEnd="6dp" android:paddingEnd="@dimen/default_margin"
android:paddingRight="6dp" android:paddingRight="@dimen/default_margin"
style="@style/KeepassDXStyle.TextAppearance.Large" style="@style/KeepassDXStyle.TextAppearance.Large"
tools:text="Test" /> tools:text="Test" />
</FrameLayout> </FrameLayout>

View File

@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="2dp"
android:paddingBottom="2dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/entry_field_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_field_show"
tools:text="title"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
<TextView
android:id="@+id/entry_field_value"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/entry_field_label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_field_show"
tools:text="value"
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/entry_field_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@+id/entry_field_copy"
android:src="@drawable/ic_visibility_state"
android:contentDescription="@string/menu_showpass"
style="@style/KeepassDXStyle.ImageButton.Simple"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/entry_field_copy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:src="@drawable/ic_content_copy_white_24dp"
android:contentDescription="@string/menu_copy"
style="@style/KeepassDXStyle.ImageButton.Simple"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -17,22 +17,20 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--> -->
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="o" tools:targetApi="o"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/template_header_container" android:id="@+id/template_header_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="?attr/cardViewStyle" style="?attr/cardViewStyle"
android:visibility="gone" android:visibility="gone">
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/template_fields_container">
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -61,8 +59,6 @@
android:id="@+id/template_fields_container" android:id="@+id/template_fields_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/template_header_container"
app:layout_constraintBottom_toTopOf="@+id/not_referenced_fields_container"
android:orientation="vertical" /> android:orientation="vertical" />
<com.kunzisoft.keepass.view.SectionView <com.kunzisoft.keepass.view.SectionView
@@ -70,16 +66,12 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="?attr/cardViewStyle" style="?attr/cardViewStyle"
android:layout_marginBottom="@dimen/card_view_margin_vertical" android:layout_marginBottom="@dimen/card_view_margin_vertical"/>
app:layout_constraintTop_toBottomOf="@+id/template_fields_container"
app:layout_constraintBottom_toTopOf="@+id/custom_fields_container"/>
<com.kunzisoft.keepass.view.SectionView <com.kunzisoft.keepass.view.SectionView
android:id="@+id/custom_fields_container" android:id="@+id/custom_fields_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="?attr/cardViewStyle" style="?attr/cardViewStyle"/>
app:layout_constraintTop_toBottomOf="@+id/not_referenced_fields_container"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>

View File

@@ -20,13 +20,13 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_save_database" <item android:id="@+id/menu_save_database"
android:icon="@drawable/ic_save_white_24dp" android:icon="@drawable/ic_saving_white_24dp"
android:title="@string/menu_save_database" android:title="@string/menu_save_database"
android:orderInCategory="95" android:orderInCategory="95"
app:iconTint="?attr/colorControlNormal" app:iconTint="?attr/colorControlNormal"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item android:id="@+id/menu_reload_database" <item android:id="@+id/menu_reload_database"
android:icon="@drawable/ic_reload_white_24dp" android:icon="@drawable/ic_downloading_white_24dp"
android:title="@string/menu_reload_database" android:title="@string/menu_reload_database"
android:orderInCategory="96" android:orderInCategory="96"
app:iconTint="?attr/colorControlNormal" app:iconTint="?attr/colorControlNormal"

View File

@@ -33,5 +33,5 @@
<dimen name="hidden_lock_button_size">0dp</dimen> <dimen name="hidden_lock_button_size">0dp</dimen>
<dimen name="content_percent">1</dimen> <dimen name="content_percent">1</dimen>
<dimen name="toolbar_parallax_height">160dp</dimen> <dimen name="toolbar_parallax_height">160dp</dimen>
<integer name="animation_duration">320</integer> <integer name="animation_duration">260</integer>
</resources> </resources>

View File

@@ -190,6 +190,8 @@
<bool name="monospace_font_fields_enable_default" translatable="false">true</bool> <bool name="monospace_font_fields_enable_default" translatable="false">true</bool>
<string name="hide_expired_entries_key" translatable="false">hide_expired_entries_key</string> <string name="hide_expired_entries_key" translatable="false">hide_expired_entries_key</string>
<bool name="hide_expired_entries_default" translatable="false">false</bool> <bool name="hide_expired_entries_default" translatable="false">false</bool>
<string name="show_otp_token_key" translatable="false">show_otp_token_key</string>
<bool name="show_otp_token_default" translatable="false">true</bool>
<string name="show_uuid_key" translatable="false">show_uuid_key</string> <string name="show_uuid_key" translatable="false">show_uuid_key</string>
<bool name="show_uuid_default" translatable="false">false</bool> <bool name="show_uuid_default" translatable="false">false</bool>
<string name="enable_education_screens_key" translatable="false">enable_education_screens_key</string> <string name="enable_education_screens_key" translatable="false">enable_education_screens_key</string>

View File

@@ -628,5 +628,7 @@
<string name="hide_expired_entries_title">Hide expired entries</string> <string name="hide_expired_entries_title">Hide expired entries</string>
<string name="hide_expired_entries_summary">Expired entries are not shown</string> <string name="hide_expired_entries_summary">Expired entries are not shown</string>
<string name="show_uuid_title">Show UUID</string> <string name="show_uuid_title">Show UUID</string>
<string name="show_uuid_summary">Displays the UUID linked to an entry</string> <string name="show_uuid_summary">Displays the UUID linked to an entry or a group</string>
<string name="show_otp_token_title">Show OTP Token</string>
<string name="show_otp_token_summary">Displays OTP tokens in the list of entries</string>
</resources> </resources>

View File

@@ -245,8 +245,8 @@
<item name="colorControlActivated">?attr/colorAccent</item> <item name="colorControlActivated">?attr/colorAccent</item>
</style> </style>
<style name="KeepassDXStyle.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox"> <style name="KeepassDXStyle.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<item name="android:paddingTop">1dp</item> <item name="android:paddingTop">2dp</item>
<item name="android:paddingBottom">1dp</item> <item name="android:paddingBottom">2dp</item>
<item name="boxStrokeColor">@color/edit_text_stroke_color</item> <item name="boxStrokeColor">@color/edit_text_stroke_color</item>
<item name="hintTextColor">?attr/colorAccent</item> <item name="hintTextColor">?attr/colorAccent</item>
<item name="materialThemeOverlay">@style/ThemeOverlay.KeepassDXTheme.TextInputEditText</item> <item name="materialThemeOverlay">@style/ThemeOverlay.KeepassDXTheme.TextInputEditText</item>
@@ -352,35 +352,41 @@
<!-- Nodes Text Style --> <!-- Nodes Text Style -->
<style name="KeepassDXStyle.TextAppearance.Entry.Title" parent="KeepassDXStyle.TextAppearance"> <style name="KeepassDXStyle.TextAppearance.Entry.Title" parent="KeepassDXStyle.TextAppearance">
<item name="android:textColor">@color/entry_title_color</item> <item name="android:textColor">@color/list_color</item>
<item name="android:tint">@color/entry_title_color</item> <item name="android:tint">@color/list_color</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.Entry.SubTitle" parent="KeepassDXStyle.TextAppearance.Small"> <style name="KeepassDXStyle.TextAppearance.Entry.SubTitle" parent="KeepassDXStyle.TextAppearance.Small">
<item name="android:textColor">@color/entry_subtitle_color</item> <item name="android:textColor">@color/list_secondary_color</item>
<item name="android:tint">@color/entry_subtitle_color</item> <item name="android:tint">@color/list_secondary_color</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.Entry.Meta" parent="KeepassDXStyle.TextAppearance.Tiny"> <style name="KeepassDXStyle.TextAppearance.Entry.Meta" parent="KeepassDXStyle.TextAppearance.Tiny">
<item name="android:textColor">@color/entry_title_color</item> <item name="android:textColor">@color/list_color</item>
<item name="android:tint">@color/entry_title_color</item> <item name="android:tint">@color/list_color</item>
<item name="android:textSize">11sp</item> <item name="android:textSize">11sp</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.Entry.Info" parent="KeepassDXStyle.TextAppearance.Small">
<item name="android:textColor">@color/list_secondary_color</item>
<item name="android:tint">@color/list_secondary_color</item>
<item name="android:textSize">16sp</item>
</style>
<style name="KeepassDXStyle.TextAppearance.Entry.Icon" parent="KeepassDXStyle.TextAppearance.Small"> <style name="KeepassDXStyle.TextAppearance.Entry.Icon" parent="KeepassDXStyle.TextAppearance.Small">
<item name="tint">@color/entry_subtitle_color</item> <item name="tint">@color/list_secondary_color</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.Group.Title" parent="KeepassDXStyle.TextAppearance"> <style name="KeepassDXStyle.TextAppearance.Group.Title" parent="KeepassDXStyle.TextAppearance">
<item name="android:textColor">@color/group_title_color</item> <item name="android:textColor">@color/list_primary_color</item>
<item name="android:tint">@color/group_title_color</item> <item name="android:tint">@color/list_primary_color</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.Group.SubTitle" parent="KeepassDXStyle.TextAppearance.Small"> <style name="KeepassDXStyle.TextAppearance.Group.SubTitle" parent="KeepassDXStyle.TextAppearance.Small">
<item name="android:textColor">@color/group_subtitle_color</item> <item name="android:textColor">@color/list_secondary_color</item>
<item name="android:tint">@color/group_subtitle_color</item> <item name="android:tint">@color/list_secondary_color</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.Group.Meta" parent="KeepassDXStyle.TextAppearance.Tiny"> <style name="KeepassDXStyle.TextAppearance.Group.Meta" parent="KeepassDXStyle.TextAppearance.Tiny">
<item name="android:textColor">@color/group_title_color</item> <item name="android:textColor">@color/list_primary_color</item>
<item name="android:tint">@color/group_title_color</item> <item name="android:tint">@color/list_primary_color</item>
<item name="android:textSize">11sp</item> <item name="android:textSize">11sp</item>
</style> </style>
@@ -463,18 +469,29 @@
<item name="android:textColor">?attr/colorAccent</item> <item name="android:textColor">?attr/colorAccent</item>
<item name="android:textSize">12sp</item> <item name="android:textSize">12sp</item>
<item name="android:paddingLeft">4dp</item> <item name="android:paddingLeft">4dp</item>
<item name="android:paddingStart">4dp</item>
<item name="android:paddingRight">4dp</item> <item name="android:paddingRight">4dp</item>
<item name="android:paddingEnd">4dp</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.LabelTableTextStyle" parent="KeepassDXStyle.TextAppearance"> <style name="KeepassDXStyle.TextAppearance.LabelTableTextStyle" parent="KeepassDXStyle.TextAppearance">
<item name="android:textSize">12sp</item> <item name="android:textSize">12sp</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
</style> </style>
<style name="KeepassDXStyle.TextAppearance.TextEntryItem" parent="KeepassDXStyle.TextAppearance"> <style name="KeepassDXStyle.TextAppearance.TextEntryItem" parent="KeepassDXStyle.TextAppearance">
<item name="android:padding">5sp</item> <item name="android:layout_marginTop">4dp</item>
<item name="android:layout_marginBottom">4dp</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:paddingStart">8dp</item>
<item name="android:paddingRight">8dp</item>
<item name="android:paddingEnd">8dp</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
</style> </style>
<!-- Progress bar --> <!-- Progress bar -->
<style name="KeepassDXStyle.ProgressBar.Circle.Secondary" parent="Widget.AppCompat.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/foreground_progress_circle_secondary</item>
<item name="android:background">@null</item>
</style>
<style name="KeepassDXStyle.ProgressBar.Circle" parent="Widget.AppCompat.ProgressBar.Horizontal"> <style name="KeepassDXStyle.ProgressBar.Circle" parent="Widget.AppCompat.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/foreground_progress_circle</item> <item name="android:progressDrawable">@drawable/foreground_progress_circle</item>
<item name="android:background">@drawable/background_progress_circle</item> <item name="android:background">@drawable/background_progress_circle</item>

View File

@@ -80,6 +80,11 @@
android:title="@string/hide_expired_entries_title" android:title="@string/hide_expired_entries_title"
android:summary="@string/hide_expired_entries_summary" android:summary="@string/hide_expired_entries_summary"
android:defaultValue="@bool/hide_expired_entries_default"/> android:defaultValue="@bool/hide_expired_entries_default"/>
<SwitchPreference
android:key="@string/show_otp_token_key"
android:title="@string/show_otp_token_title"
android:summary="@string/show_otp_token_summary"
android:defaultValue="@bool/show_otp_token_default"/>
<SwitchPreference <SwitchPreference
android:key="@string/show_uuid_key" android:key="@string/show_uuid_key"
android:title="@string/show_uuid_title" android:title="@string/show_uuid_title"

59
art/reload_database.svg Normal file
View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg4"
sodipodi:docname="Save.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#c8c8c8"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0.28235294"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1016"
id="namedview6"
showgrid="true"
inkscape:zoom="11.313709"
inkscape:cx="10.124302"
inkscape:cy="14.525681"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg4">
<inkscape:grid
type="xygrid"
id="grid818" />
</sodipodi:namedview>
<path
style="fill:#ffffff"
d="M 11 2.0566406 C 6.762335 2.4220229 3.0067094 5.7987155 2.203125 9.9785156 C 1.3601754 13.960549 3.1781148 18.394742 6.7089844 20.480469 C 9.6237318 22.368157 13.514425 22.492178 16.582031 20.892578 C 17.959775 20.180473 19.316015 19.099467 20.087891 17.808594 L 18.402344 16.791016 C 16.277892 19.724364 12.039121 20.844607 8.7519531 19.306641 C 5.4810648 17.911181 3.4461927 14.150571 4.109375 10.648438 C 4.6649663 7.2806973 7.5784749 4.4226117 11 4.0839844 L 11 2.0566406 z M 13 2.0644531 L 13 4.09375 C 16.367309 4.4801388 19.308004 7.2166098 19.861328 10.574219 C 20.123351 12.069186 19.935392 13.632673 19.367188 15.037109 C 19.94644 15.387646 20.527063 15.73602 21.105469 16.087891 C 22.671735 12.714066 22.12099 8.4920873 19.708984 5.6542969 C 18.063396 3.6246553 15.604973 2.2995703 13 2.0644531 z M 11 7.0644531 L 11 12.064453 L 7 12.064453 L 12 17.064453 L 17 12.064453 L 13 12.064453 L 13 7.0644531 L 11 7.0644531 z "
id="path868" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

59
art/save_database.svg Normal file
View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
version="1.1"
id="svg4"
sodipodi:docname="Save2.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#c8c8c8"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0.28235294"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1016"
id="namedview6"
showgrid="true"
inkscape:zoom="11.313709"
inkscape:cx="-2.2942603"
inkscape:cy="14.525681"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg4">
<inkscape:grid
type="xygrid"
id="grid818" />
</sodipodi:namedview>
<path
style="fill:#ffffff"
d="M 11 2.0566406 C 6.762335 2.4220229 3.0067094 5.7987155 2.203125 9.9785156 C 1.3601753 13.960549 3.1781148 18.394742 6.7089844 20.480469 C 9.6237318 22.368157 13.514425 22.492178 16.582031 20.892578 C 17.959775 20.180473 19.316015 19.099467 20.087891 17.808594 L 18.402344 16.791016 C 16.277892 19.724364 12.039121 20.844605 8.7519531 19.306641 C 5.481064 17.911182 3.4461934 14.150571 4.109375 10.648438 C 4.6649664 7.2806974 7.5784749 4.4226117 11 4.0839844 L 11 2.0566406 z M 13 2.0644531 L 13 4.09375 C 16.367309 4.4801387 19.308002 7.2166101 19.861328 10.574219 C 20.123352 12.069186 19.935393 13.632674 19.367188 15.037109 C 19.94644 15.387646 20.527063 15.73602 21.105469 16.087891 C 22.671737 12.714066 22.120988 8.4920871 19.708984 5.6542969 C 18.063396 3.6246553 15.604973 2.2995704 13 2.0644531 z M 8.1113281 7 C 7.4946611 7 7 7.5002173 7 8.1113281 L 7 15.888672 C 7 16.499784 7.4946611 17 8.1113281 17 L 15.888672 17 C 16.499783 17 17 16.499784 17 15.888672 L 17 9.2226562 L 14.777344 7 L 8.1113281 7 z M 8.1113281 8.1113281 L 13.666016 8.1113281 L 13.666016 10.333984 L 8.1113281 10.333984 L 8.1113281 8.1113281 z M 12 12.554688 C 12.922223 12.554688 13.666016 13.300434 13.666016 14.222656 C 13.666016 15.144878 12.922223 15.888672 12 15.888672 C 11.077779 15.888672 10.333984 15.144878 10.333984 14.222656 C 10.333984 13.300434 11.077779 12.554687 12 12.554688 z "
id="path868" />
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,5 +1,6 @@
* Add / Manage dynamic templates #191 * Add / Manage dynamic templates #191
* Allow to manually select RecycleBin group and Templates group #191 * Allow to manually select RecycleBin group and Templates group #191
* Setting to display OTP Token in list #655
* Fix timeout in dialogs #716 * Fix timeout in dialogs #716
* Check URI permissions #626 * Check URI permissions #626
* Small changes #1035 #1043 #942 * Improvements #1035 #1043 #942 #1021 #1027

View File

@@ -1,5 +1,6 @@
* Ajout / Gestion dynamique des templates #191 * Ajout / Gestion dynamique des templates #191
* Sélection manuelle des groupes de la corbeille et des templates #191 * Sélection manuelle des groupes de la corbeille et des templates #191
* Paramètres pour afficher les jetons OTP dans la liste #655
* Correction du délai d'expiration dans les dialogues #716 * Correction du délai d'expiration dans les dialogues #716
* Vérification des permissions URI #626 * Vérification des permissions URI #626
* Petits changements #1035 #1043 #942 * Améliorations #1035 #1043 #942 #1021 #1027