mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'Kunzisoft:develop' into develop
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
KeePassDX(3.0.0)
|
||||
* Add / Manage dynamic templates #191
|
||||
* Manually select RecycleBin group and Templates group #191
|
||||
* Setting to display OTP Token in list #655
|
||||
* Fix timeout in dialogs #716
|
||||
* Check URI permissions #626
|
||||
* Small changes #1035 #1043 #942
|
||||
* Improvements #1035 #1043 #942 #1021 #1027
|
||||
|
||||
KeePassDX(2.10.5)
|
||||
* Increase the saving speed of database #1028
|
||||
|
||||
@@ -30,6 +30,7 @@ package com.igreenwood.loupe
|
||||
import android.animation.Animator
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
@@ -108,6 +109,8 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
|
||||
var viewDragFriction = DEFAULT_VIEW_DRAG_FRICTION
|
||||
// drag distance threshold in dp for swipe to dismiss
|
||||
var dragDismissDistanceInDp = DEFAULT_DRAG_DISMISS_DISTANCE_IN_DP
|
||||
// on view touched
|
||||
var onViewTouchedListener: View.OnTouchListener? = null
|
||||
// on view translate listener
|
||||
var onViewTranslateListener: OnViewTranslateListener? = null
|
||||
// on scale changed
|
||||
@@ -272,7 +275,10 @@ class Loupe(imageView: ImageView, container: ViewGroup) : View.OnTouchListener,
|
||||
private var imageViewRef: WeakReference<ImageView> = WeakReference(imageView)
|
||||
private var containerRef: WeakReference<ViewGroup> = WeakReference(container)
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouch(view: View?, event: MotionEvent?): Boolean {
|
||||
onViewTouchedListener?.onTouch(view, event)
|
||||
|
||||
event ?: return false
|
||||
val imageView = imageViewRef.get() ?: return false
|
||||
val container = containerRef.get() ?: return false
|
||||
|
||||
@@ -157,11 +157,6 @@ class EntryActivity : DatabaseLockActivity() {
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
mEntryViewModel.url.observe(this) { url ->
|
||||
this.mUrl = url
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
mEntryViewModel.entryInfo.observe(this) { entryInfo ->
|
||||
// Manage entry copy to start notification if allowed (at the first start)
|
||||
if (savedInstanceState == null) {
|
||||
@@ -184,6 +179,8 @@ class EntryActivity : DatabaseLockActivity() {
|
||||
collapsingToolbarLayout?.title = entryTitle
|
||||
toolbar?.title = entryTitle
|
||||
|
||||
mUrl = entryInfo.url
|
||||
|
||||
// Refresh Menu
|
||||
invalidateOptionsMenu()
|
||||
|
||||
|
||||
@@ -143,13 +143,13 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
|
||||
// Entry is retrieve, it's an entry to update
|
||||
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let { entryToUpdate ->
|
||||
intent.removeExtra(KEY_ENTRY)
|
||||
//intent.removeExtra(KEY_ENTRY)
|
||||
mEntryId = entryToUpdate
|
||||
}
|
||||
|
||||
// Parent is retrieve, it's a new entry to create
|
||||
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let { parent ->
|
||||
intent.removeExtra(KEY_PARENT)
|
||||
//intent.removeExtra(KEY_PARENT)
|
||||
mParentId = parent
|
||||
}
|
||||
|
||||
@@ -166,7 +166,37 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
// Save button
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -238,27 +268,6 @@ class EntryEditActivity : DatabaseLockActivity(),
|
||||
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
|
||||
mEntryEditViewModel.onEntrySaved.observe(this) { entrySave ->
|
||||
// Open a progress dialog and save entry
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
@@ -43,6 +44,7 @@ class ImageViewerActivity : DatabaseLockActivity() {
|
||||
private lateinit var imageView: ImageView
|
||||
private lateinit var progressView: View
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -52,12 +54,21 @@ class ImageViewerActivity : DatabaseLockActivity() {
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
toolbar.setOnTouchListener { _, _ ->
|
||||
resetAppTimeout()
|
||||
false
|
||||
}
|
||||
|
||||
imageContainerView = findViewById(R.id.image_viewer_container)
|
||||
imageView = findViewById(R.id.image_viewer_image)
|
||||
progressView = findViewById(R.id.image_viewer_progress)
|
||||
|
||||
Loupe.create(imageView, imageContainerView!!) {
|
||||
onViewTouchedListener = View.OnTouchListener { _, _ ->
|
||||
// to reset timeout when Loupe image view touched
|
||||
resetAppTimeout()
|
||||
false
|
||||
}
|
||||
onViewTranslateListener = object : Loupe.OnViewTranslateListener {
|
||||
|
||||
override fun onStart(view: ImageView) {
|
||||
@@ -81,7 +92,8 @@ class ImageViewerActivity : DatabaseLockActivity() {
|
||||
}
|
||||
|
||||
override fun viewToInvalidateTimeout(): View? {
|
||||
return imageContainerView
|
||||
// Null to manually manage events
|
||||
return null
|
||||
}
|
||||
|
||||
override fun finishActivityIfReloadRequested(): Boolean {
|
||||
|
||||
@@ -4,9 +4,10 @@ import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseViewModel
|
||||
|
||||
abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
|
||||
@@ -19,7 +20,7 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
|
||||
|
||||
mDatabaseViewModel.database.observe(this) { database ->
|
||||
this.mDatabase = database
|
||||
resetAppTimeoutWhenViewFocusedOrChanged()
|
||||
resetAppTimeoutOnTouchOrFocus()
|
||||
onDatabaseRetrieved(database)
|
||||
}
|
||||
|
||||
@@ -31,7 +32,7 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
resetAppTimeoutWhenViewFocusedOrChanged()
|
||||
resetAppTimeoutOnTouchOrFocus()
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: Database?) {
|
||||
@@ -46,9 +47,25 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
|
||||
// Can be overridden by a subclass
|
||||
}
|
||||
|
||||
private fun resetAppTimeoutWhenViewFocusedOrChanged() {
|
||||
fun resetAppTimeout() {
|
||||
context?.let {
|
||||
dialog?.window?.decorView?.resetAppTimeoutWhenViewFocusedOrChanged(it, mDatabase?.loaded)
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(it,
|
||||
mDatabase?.loaded ?: false)
|
||||
}
|
||||
}
|
||||
|
||||
open fun overrideTimeoutTouchAndFocusEvents(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun resetAppTimeoutOnTouchOrFocus() {
|
||||
if (!overrideTimeoutTouchAndFocusEvents()) {
|
||||
context?.let {
|
||||
dialog?.window?.decorView?.resetAppTimeoutWhenViewTouchedOrFocused(
|
||||
it,
|
||||
mDatabase?.loaded
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,10 @@ import android.app.DatePickerDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
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 mDefaultMonth: Int = 1
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.dialogs
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
|
||||
@@ -79,11 +79,15 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
|
||||
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
|
||||
if (!isFocus)
|
||||
mManualEvent = true
|
||||
else
|
||||
resetAppTimeout()
|
||||
}
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private var mOnTouchListener = View.OnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
mManualEvent = true
|
||||
resetAppTimeout()
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -94,6 +98,10 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
|
||||
private var mPeriodWellFormed = false
|
||||
private var mDigitsWellFormed = false
|
||||
|
||||
override fun overrideTimeoutTouchAndFocusEvents(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
// Verify that the host activity implements the callback interface
|
||||
@@ -224,8 +232,11 @@ class SetOTPDialogFragment : DatabaseDialogFragment() {
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
builder.apply {
|
||||
setView(root)
|
||||
.setPositiveButton(android.R.string.ok) {_, _ -> }
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
resetAppTimeout()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
resetAppTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@ import android.app.TimePickerDialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
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 defaultMinute: Int = 0
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||
@@ -33,7 +33,7 @@ abstract class DatabaseFragment : StylishFragment(), DatabaseRetrieval {
|
||||
|
||||
protected fun resetAppTimeoutWhenViewFocusedOrChanged(view: View?) {
|
||||
context?.let {
|
||||
view?.resetAppTimeoutWhenViewFocusedOrChanged(it, mDatabase?.loaded)
|
||||
view?.resetAppTimeoutWhenViewTouchedOrFocused(it, mDatabase?.loaded)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,10 +118,11 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
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
|
||||
if (savedInstanceState == null) {
|
||||
assignEntryInfo(entryInfo)
|
||||
assignEntryInfo(templateEntry.entryInfo)
|
||||
}
|
||||
// To prevent flickering
|
||||
rootView.showByFading()
|
||||
|
||||
@@ -219,6 +219,8 @@ class GroupFragment : DatabaseFragment(), SortDialogFragment.SortSelectionListen
|
||||
activity?.intent?.let {
|
||||
specialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(it)
|
||||
}
|
||||
|
||||
rebuildList()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
||||
@@ -169,7 +169,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
// Focus view to reinitialize timeout,
|
||||
// view is not necessary loaded so retry later in resume
|
||||
viewToInvalidateTimeout()
|
||||
?.resetAppTimeoutWhenViewFocusedOrChanged(this, database?.loaded)
|
||||
?.resetAppTimeoutWhenViewTouchedOrFocused(this, database?.loaded)
|
||||
|
||||
database?.let {
|
||||
// check timeout
|
||||
@@ -383,7 +383,7 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
// Invalidate timeout by touch
|
||||
mDatabase?.let { database ->
|
||||
viewToInvalidateTimeout()
|
||||
?.resetAppTimeoutWhenViewFocusedOrChanged(this, database.loaded)
|
||||
?.resetAppTimeoutWhenViewTouchedOrFocused(this, database.loaded)
|
||||
}
|
||||
|
||||
invalidateOptionsMenu()
|
||||
@@ -422,6 +422,11 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
sendBroadcast(Intent(LOCK_ACTION))
|
||||
}
|
||||
|
||||
fun resetAppTimeout() {
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this,
|
||||
mDatabase?.loaded ?: false)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (mTimeoutEnable) {
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this,
|
||||
@@ -451,12 +456,12 @@ abstract class DatabaseLockActivity : DatabaseModeActivity(),
|
||||
* To reset the app timeout when a view is focused or changed
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context, databaseLoaded: Boolean?) {
|
||||
// Log.d(LockingActivity.TAG, "View prepared to reset app timeout")
|
||||
fun View.resetAppTimeoutWhenViewTouchedOrFocused(context: Context, databaseLoaded: Boolean?) {
|
||||
// Log.d(DatabaseLockActivity.TAG, "View prepared to reset app timeout")
|
||||
setOnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
// Log.d(LockingActivity.TAG, "View touched, try to reset app timeout")
|
||||
// Log.d(DatabaseLockActivity.TAG, "View touched, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
|
||||
databaseLoaded ?: false)
|
||||
}
|
||||
@@ -464,13 +469,13 @@ fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context, databaseLoade
|
||||
false
|
||||
}
|
||||
setOnFocusChangeListener { _, _ ->
|
||||
// Log.d(LockingActivity.TAG, "View focused, try to reset app timeout")
|
||||
// Log.d(DatabaseLockActivity.TAG, "View focused, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context,
|
||||
databaseLoaded ?: false)
|
||||
}
|
||||
if (this is ViewGroup) {
|
||||
for (i in 0..childCount) {
|
||||
getChildAt(i)?.resetAppTimeoutWhenViewFocusedOrChanged(context, databaseLoaded)
|
||||
getChildAt(i)?.resetAppTimeoutWhenViewTouchedOrFocused(context, databaseLoaded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHist
|
||||
holder.lastModifiedView.text = entryHistory.lastModificationTime.getDateTimeString(context.resources)
|
||||
holder.titleView.text = entryHistory.title
|
||||
holder.usernameView.text = entryHistory.username
|
||||
holder.urlView.text = entryHistory.url
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
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 titleView: TextView = itemView.findViewById(R.id.entry_history_title)
|
||||
var usernameView: TextView = itemView.findViewById(R.id.entry_history_username)
|
||||
var urlView: TextView = itemView.findViewById(R.id.entry_history_url)
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
@@ -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.NodeVersionedInterface
|
||||
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.view.setTextSize
|
||||
import com.kunzisoft.keepass.view.strikeOut
|
||||
@@ -68,6 +71,7 @@ class NodeAdapter (private val context: Context,
|
||||
|
||||
private var mShowUserNames: Boolean = true
|
||||
private var mShowNumberEntries: Boolean = true
|
||||
private var mShowOTP: Boolean = false
|
||||
private var mShowUUID: Boolean = false
|
||||
private var mEntryFilters = arrayOf<Group.ChildFilter>()
|
||||
|
||||
@@ -122,6 +126,7 @@ class NodeAdapter (private val context: Context,
|
||||
|
||||
this.mShowUserNames = PreferencesUtil.showUsernamesListEntries(context)
|
||||
this.mShowNumberEntries = PreferencesUtil.showNumberEntries(context)
|
||||
this.mShowOTP = PreferencesUtil.showOTPToken(context)
|
||||
this.mShowUUID = PreferencesUtil.showUUID(context)
|
||||
|
||||
this.mEntryFilters = Group.ChildFilter.getDefaults(context)
|
||||
@@ -148,6 +153,7 @@ class NodeAdapter (private val context: Context,
|
||||
if (oldItem is Entry && newItem is Entry) {
|
||||
typeContentTheSame = oldItem.getVisualTitle() == newItem.getVisualTitle()
|
||||
&& oldItem.username == newItem.username
|
||||
&& oldItem.getOtpElement() == newItem.getOtpElement()
|
||||
&& oldItem.containsAttachment() == newItem.containsAttachment()
|
||||
} else if (oldItem is Group && newItem is Group) {
|
||||
typeContentTheSame = oldItem.numberOfChildEntries == newItem.numberOfChildEntries
|
||||
@@ -357,6 +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 =
|
||||
if (entry.containsAttachment()) View.VISIBLE else View.GONE
|
||||
|
||||
@@ -386,7 +411,41 @@ class NodeAdapter (private val context: Context,
|
||||
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 {
|
||||
return mNodeSortedList.size()
|
||||
}
|
||||
@@ -413,6 +472,11 @@ class NodeAdapter (private val context: Context,
|
||||
var text: TextView = itemView.findViewById(R.id.node_text)
|
||||
var subText: TextView = itemView.findViewById(R.id.node_subtext)
|
||||
var meta: TextView = itemView.findViewById(R.id.node_meta)
|
||||
var otpContainer: ViewGroup? = itemView.findViewById(R.id.node_otp_container)
|
||||
var otpProgress: ProgressBar? = itemView.findViewById(R.id.node_otp_progress)
|
||||
var 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 attachmentIcon: ImageView? = itemView.findViewById(R.id.node_attachment_icon)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class OtpModel() : Parcelable {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as OtpElement
|
||||
other as OtpModel
|
||||
|
||||
if (type != other.type) return false
|
||||
// Token type is important only if it's a TOTP
|
||||
|
||||
@@ -185,6 +185,19 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
|
||||
return secondsRemaining == otpModel.period
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is OtpElement) return false
|
||||
|
||||
if (otpModel != other.otpModel) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return otpModel.hashCode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MIN_HOTP_COUNTER = 0
|
||||
const val MAX_HOTP_COUNTER = Long.MAX_VALUE
|
||||
|
||||
@@ -454,7 +454,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
}
|
||||
|
||||
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) {
|
||||
getString(R.string.setting_style_key),
|
||||
getString(R.string.setting_style_brightness_key),
|
||||
@@ -464,6 +464,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
getString(R.string.list_size_key),
|
||||
getString(R.string.monospace_font_fields_enable_key),
|
||||
getString(R.string.hide_expired_entries_key),
|
||||
getString(R.string.show_otp_token_key),
|
||||
getString(R.string.show_uuid_key),
|
||||
getString(R.string.enable_education_screens_key),
|
||||
getString(R.string.reset_education_screens_key) -> {
|
||||
|
||||
@@ -32,7 +32,7 @@ import com.kunzisoft.androidclearchroma.ChromaUtil
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseRetrieval
|
||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.activities.legacy.resetAppTimeoutWhenViewTouchedOrFocused
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
@@ -74,7 +74,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment(), DatabaseRetriev
|
||||
|
||||
mDatabaseViewModel.database.observe(viewLifecycleOwner) { database ->
|
||||
mDatabase = database
|
||||
view.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), database?.loaded)
|
||||
view.resetAppTimeoutWhenViewTouchedOrFocused(requireContext(), database?.loaded)
|
||||
onDatabaseRetrieved(database)
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,12 @@ object PreferencesUtil {
|
||||
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 {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
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.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.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.enable_education_screens_key) -> editor.putBoolean(name, value.toBoolean())
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
package com.kunzisoft.keepass.timeout
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
@@ -30,6 +29,7 @@ import android.text.method.LinkMovementMethod
|
||||
import android.text.util.Linkify
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.exception.ClipboardException
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
|
||||
@@ -239,8 +239,23 @@ object UriUtil {
|
||||
}
|
||||
|
||||
fun openExternalApp(context: Context, packageName: String) {
|
||||
var launchIntent: Intent? = null
|
||||
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) {
|
||||
Log.e(TAG, "App cannot be open", e)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ abstract class TemplateAbstractView<
|
||||
private var mTemplate: Template? = 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 mHideProtectedValue: Boolean = PreferencesUtil.hideProtectedValue(context)
|
||||
@@ -48,7 +48,7 @@ abstract class TemplateAbstractView<
|
||||
protected var entryIconView: ImageView
|
||||
private var titleContainerView: ViewGroup
|
||||
protected var templateContainerView: ViewGroup
|
||||
protected var customFieldsContainerView: SectionView
|
||||
private var customFieldsContainerView: SectionView
|
||||
private var notReferencedFieldsContainerView: SectionView
|
||||
|
||||
init {
|
||||
@@ -95,7 +95,7 @@ abstract class TemplateAbstractView<
|
||||
}
|
||||
}
|
||||
|
||||
fun buildTemplate() {
|
||||
private fun buildTemplate() {
|
||||
// Retrieve preferences
|
||||
mHideProtectedValue = PreferencesUtil.hideProtectedValue(context)
|
||||
|
||||
@@ -104,7 +104,7 @@ abstract class TemplateAbstractView<
|
||||
templateContainerView.removeAllViews()
|
||||
customFieldsContainerView.removeAllViews()
|
||||
notReferencedFieldsContainerView.removeAllViews()
|
||||
mCustomFieldIds.clear()
|
||||
mViewFields.clear()
|
||||
|
||||
mTemplate?.let { template ->
|
||||
|
||||
@@ -200,7 +200,7 @@ abstract class TemplateAbstractView<
|
||||
TemplateAttributeType.TEXT,
|
||||
field.protectedValue.isProtected,
|
||||
TemplateAttributeOption().apply {
|
||||
setNumberLines(3)
|
||||
setNumberLines(20)
|
||||
},
|
||||
TemplateAttributeAction.CUSTOM_EDITION
|
||||
).apply {
|
||||
@@ -231,13 +231,13 @@ abstract class TemplateAbstractView<
|
||||
|
||||
// Add new custom view id to the custom field list
|
||||
if (fieldTag == FIELD_CUSTOM_TAG) {
|
||||
val indexOldItem = indexCustomFieldIdByName(field.name)
|
||||
val indexOldItem = getIndexViewFieldByName(field.name)
|
||||
if (indexOldItem >= 0)
|
||||
mCustomFieldIds.removeAt(indexOldItem)
|
||||
mViewFields.removeAt(indexOldItem)
|
||||
if (itemView?.id != null) {
|
||||
mCustomFieldIds.add(
|
||||
FieldId(
|
||||
itemView.id,
|
||||
mViewFields.add(
|
||||
ViewField(
|
||||
itemView,
|
||||
field
|
||||
)
|
||||
)
|
||||
@@ -320,7 +320,7 @@ abstract class TemplateAbstractView<
|
||||
/**
|
||||
* Return empty custom fields
|
||||
*/
|
||||
protected open fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<FieldId> {
|
||||
protected open fun populateViewsWithEntryInfo(showEmptyFields: Boolean): List<ViewField> {
|
||||
mEntryInfo?.let { entryInfo ->
|
||||
|
||||
populateEntryFieldView(FIELD_TITLE_TAG,
|
||||
@@ -350,15 +350,14 @@ abstract class TemplateAbstractView<
|
||||
showEmptyFields)
|
||||
|
||||
customFieldsContainerView.removeAllViews()
|
||||
val emptyCustomFields = mutableListOf<FieldId>().also { it.addAll(mCustomFieldIds) }
|
||||
val emptyCustomFields = mutableListOf<ViewField>().also { it.addAll(mViewFields) }
|
||||
entryInfo.customFields.forEach { customField ->
|
||||
val indexFieldViewId = indexCustomFieldIdByName(customField.name)
|
||||
val indexFieldViewId = getIndexViewFieldByName(customField.name)
|
||||
if (indexFieldViewId >= 0) {
|
||||
// Template contains the custom view
|
||||
val customFieldId = mCustomFieldIds[indexFieldViewId]
|
||||
emptyCustomFields.remove(customFieldId)
|
||||
templateContainerView.findViewById<View>(customFieldId.viewId)
|
||||
?.let { customView ->
|
||||
val viewField = mViewFields[indexFieldViewId]
|
||||
emptyCustomFields.remove(viewField)
|
||||
viewField.view.let { customView ->
|
||||
if (customView is GenericTextFieldView) {
|
||||
customView.value = customField.protectedValue.stringValue
|
||||
customView.applyFontVisibility(mFontInVisibility)
|
||||
@@ -459,22 +458,22 @@ abstract class TemplateAbstractView<
|
||||
* -------------
|
||||
*/
|
||||
|
||||
protected data class FieldId(var viewId: Int, var field: Field)
|
||||
protected data class ViewField(var view: View, var field: Field)
|
||||
|
||||
private fun isStandardFieldName(name: String): Boolean {
|
||||
return TemplateField.isStandardFieldName(name)
|
||||
}
|
||||
|
||||
protected fun customFieldIdByName(name: String): FieldId? {
|
||||
return mCustomFieldIds.find { it.field.name.equals(name, true) }
|
||||
protected fun getViewFieldByName(name: String): ViewField? {
|
||||
return mViewFields.find { it.field.name.equals(name, true) }
|
||||
}
|
||||
|
||||
protected fun indexCustomFieldIdByName(name: String): Int {
|
||||
return mCustomFieldIds.indexOfFirst { it.field.name.equals(name, true) }
|
||||
private fun getIndexViewFieldByName(name: String): Int {
|
||||
return mViewFields.indexOfFirst { it.field.name.equals(name, true) }
|
||||
}
|
||||
|
||||
private fun retrieveCustomFieldsFromView(templateFieldNotEmpty: Boolean = false) {
|
||||
mEntryInfo?.customFields = mCustomFieldIds.mapNotNull {
|
||||
mEntryInfo?.customFields = mViewFields.mapNotNull {
|
||||
getCustomField(it.field.name, templateFieldNotEmpty)
|
||||
}.toMutableList()
|
||||
}
|
||||
@@ -484,10 +483,9 @@ abstract class TemplateAbstractView<
|
||||
?: Field(fieldName, ProtectedString(false))
|
||||
}
|
||||
|
||||
protected fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field? {
|
||||
customFieldIdByName(fieldName)?.let { fieldId ->
|
||||
val editView: View? = templateContainerView.findViewById(fieldId.viewId)
|
||||
?: customFieldsContainerView.findViewById(fieldId.viewId)
|
||||
private fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field? {
|
||||
getViewFieldByName(fieldName)?.let { fieldId ->
|
||||
val editView: View? = fieldId.view
|
||||
if (editView is GenericFieldView) {
|
||||
// Do not return field with a default value
|
||||
val defaultViewValue = if (editView.value == editView.default) "" else editView.value
|
||||
@@ -514,20 +512,22 @@ abstract class TemplateAbstractView<
|
||||
|
||||
return if (!isStandardFieldName(customField.name)) {
|
||||
customFieldsContainerView.visibility = View.VISIBLE
|
||||
if (indexCustomFieldIdByName(customField.name) >= 0) {
|
||||
replaceCustomField(customField, customField, focus)
|
||||
if (getIndexViewFieldByName(customField.name) >= 0) {
|
||||
// Update a custom field with a new value,
|
||||
// new field name must be the same as old field name
|
||||
replaceCustomField(customField, customField, false, focus)
|
||||
} else {
|
||||
val newCustomView = buildViewForCustomField(customField)
|
||||
newCustomView?.let {
|
||||
customFieldsContainerView.addView(newCustomView)
|
||||
val fieldId = FieldId(
|
||||
newCustomView.id,
|
||||
val fieldId = ViewField(
|
||||
newCustomView,
|
||||
customField
|
||||
)
|
||||
val indexOldItem = indexCustomFieldIdByName(fieldId.field.name)
|
||||
val indexOldItem = getIndexViewFieldByName(fieldId.field.name)
|
||||
if (indexOldItem >= 0)
|
||||
mCustomFieldIds.removeAt(indexOldItem)
|
||||
mCustomFieldIds.add(indexOldItem, fieldId)
|
||||
mViewFields.removeAt(indexOldItem)
|
||||
mViewFields.add(indexOldItem, fieldId)
|
||||
if (focus)
|
||||
newCustomView.requestFocus()
|
||||
}
|
||||
@@ -544,58 +544,63 @@ abstract class TemplateAbstractView<
|
||||
return put
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a custom field and keep the old value
|
||||
*/
|
||||
private fun replaceCustomField(oldField: Field, newField: Field, focus: Boolean): Boolean {
|
||||
private fun replaceCustomField(oldField: Field,
|
||||
newField: Field,
|
||||
keepOldValue: Boolean,
|
||||
focus: Boolean): Boolean {
|
||||
if (!isStandardFieldName(newField.name)) {
|
||||
customFieldIdByName(oldField.name)?.viewId?.let { viewId ->
|
||||
customFieldsContainerView.findViewById<View>(viewId)?.let { viewToReplace ->
|
||||
val oldValue = getCustomField(oldField.name).protectedValue.toString()
|
||||
getViewFieldByName(oldField.name)?.view?.let { viewToReplace ->
|
||||
val oldValue = getCustomField(oldField.name).protectedValue.toString()
|
||||
|
||||
val parentGroup = viewToReplace.parent as ViewGroup
|
||||
val indexInParent = parentGroup.indexOfChild(viewToReplace)
|
||||
parentGroup.removeView(viewToReplace)
|
||||
val parentGroup = viewToReplace.parent as ViewGroup
|
||||
val indexInParent = parentGroup.indexOfChild(viewToReplace)
|
||||
parentGroup.removeView(viewToReplace)
|
||||
|
||||
val newCustomFieldWithValue = Field(newField.name,
|
||||
ProtectedString(newField.protectedValue.isProtected, oldValue))
|
||||
val oldPosition = indexCustomFieldIdByName(oldField.name)
|
||||
if (oldPosition >= 0)
|
||||
mCustomFieldIds.removeAt(oldPosition)
|
||||
val newCustomFieldWithValue = if (keepOldValue)
|
||||
Field(newField.name,
|
||||
ProtectedString(newField.protectedValue.isProtected, oldValue)
|
||||
)
|
||||
else
|
||||
newField
|
||||
val oldPosition = getIndexViewFieldByName(oldField.name)
|
||||
if (oldPosition >= 0)
|
||||
mViewFields.removeAt(oldPosition)
|
||||
|
||||
val newCustomView = buildViewForCustomField(newCustomFieldWithValue)
|
||||
newCustomView?.let {
|
||||
parentGroup.addView(newCustomView, indexInParent)
|
||||
mCustomFieldIds.add(
|
||||
oldPosition,
|
||||
FieldId(
|
||||
newCustomView.id,
|
||||
newCustomFieldWithValue
|
||||
)
|
||||
val newCustomView = buildViewForCustomField(newCustomFieldWithValue)
|
||||
newCustomView?.let {
|
||||
parentGroup.addView(newCustomView, indexInParent)
|
||||
mViewFields.add(
|
||||
oldPosition,
|
||||
ViewField(
|
||||
newCustomView,
|
||||
newCustomFieldWithValue
|
||||
)
|
||||
if (focus)
|
||||
newCustomView.requestFocus()
|
||||
}
|
||||
return true
|
||||
)
|
||||
if (focus)
|
||||
newCustomView.requestFocus()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a custom field and keep the old value
|
||||
*/
|
||||
fun replaceCustomField(oldField: Field, newField: Field): Boolean {
|
||||
val replace = replaceCustomField(oldField, newField, true)
|
||||
val replace = replaceCustomField(oldField, newField, keepOldValue = true, focus = true)
|
||||
retrieveCustomFieldsFromView()
|
||||
return replace
|
||||
}
|
||||
|
||||
fun removeCustomField(oldCustomField: Field) {
|
||||
val indexOldField = indexCustomFieldIdByName(oldCustomField.name)
|
||||
val indexOldField = getIndexViewFieldByName(oldCustomField.name)
|
||||
if (indexOldField >= 0) {
|
||||
mCustomFieldIds[indexOldField].viewId.let { viewId ->
|
||||
customFieldsContainerView.removeViewById(viewId)
|
||||
mViewFields[indexOldField].let { fieldView ->
|
||||
customFieldsContainerView.removeViewById(fieldView.view.id)
|
||||
}
|
||||
mCustomFieldIds.removeAt(indexOldField)
|
||||
mViewFields.removeAt(indexOldField)
|
||||
}
|
||||
retrieveCustomFieldsFromView()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
return super.populateViewsWithEntryInfo(showEmptyFields)
|
||||
}
|
||||
|
||||
@@ -56,18 +56,20 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
setMaxLines(templateAttribute.options.getNumberLines())
|
||||
// TODO Linkify
|
||||
value = field.protectedValue.stringValue
|
||||
// Here the value is often empty
|
||||
|
||||
if (field.protectedValue.isProtected) {
|
||||
if (mFirstTimeAskAllowCopyProtectedFields) {
|
||||
setCopyButtonState(TextFieldView.ButtonState.DEACTIVATE)
|
||||
setCopyButtonClickListener {
|
||||
setCopyButtonClickListener { _, _ ->
|
||||
mOnAskCopySafeClickListener?.invoke()
|
||||
}
|
||||
} else {
|
||||
if (mAllowCopyProtectedFields) {
|
||||
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
|
||||
setCopyButtonClickListener {
|
||||
mOnCopyActionClickListener?.invoke(field)
|
||||
setCopyButtonClickListener { label, value ->
|
||||
mOnCopyActionClickListener
|
||||
?.invoke(Field(label, ProtectedString(false, value)))
|
||||
}
|
||||
} else {
|
||||
setCopyButtonState(TextFieldView.ButtonState.GONE)
|
||||
@@ -76,8 +78,9 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
} else {
|
||||
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
|
||||
setCopyButtonClickListener {
|
||||
mOnCopyActionClickListener?.invoke(field)
|
||||
setCopyButtonClickListener { label, value ->
|
||||
mOnCopyActionClickListener
|
||||
?.invoke(Field(label, ProtectedString(false, value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,22 +117,22 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
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)
|
||||
|
||||
// Hide empty custom fields
|
||||
emptyCustomFields.forEach { customFieldId ->
|
||||
templateContainerView.findViewById<View?>(customFieldId.viewId)
|
||||
?.isVisible = false
|
||||
customFieldId.view.isVisible = false
|
||||
}
|
||||
|
||||
removeOtpRunnable()
|
||||
mEntryInfo?.let { entryInfo ->
|
||||
// Assign specific OTP dynamic view
|
||||
removeOtpRunnable()
|
||||
entryInfo.otpModel?.let {
|
||||
assignOtp(it)
|
||||
}
|
||||
}
|
||||
|
||||
return emptyCustomFields
|
||||
}
|
||||
|
||||
@@ -146,11 +149,10 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
private var mOnOtpElementUpdated: ((OtpElement?) -> Unit)? = null
|
||||
|
||||
private fun getOtpTokenView(): TextFieldView? {
|
||||
val indexFieldViewId = indexCustomFieldIdByName(OTP_TOKEN_FIELD)
|
||||
if (indexFieldViewId >= 0) {
|
||||
// Template contains the custom view
|
||||
val customFieldId = mCustomFieldIds[indexFieldViewId]
|
||||
return findViewById(customFieldId.viewId)
|
||||
getViewFieldByName(OTP_TOKEN_FIELD)?.let { viewField ->
|
||||
val view = viewField.view
|
||||
if (view is TextFieldView)
|
||||
return view
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -166,7 +168,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
label = otpElement.type.name
|
||||
value = otpElement.token
|
||||
setCopyButtonState(TextFieldView.ButtonState.ACTIVATE)
|
||||
setCopyButtonClickListener {
|
||||
setCopyButtonClickListener { _, _ ->
|
||||
mOnCopyActionClickListener?.invoke(Field(
|
||||
otpElement.type.name,
|
||||
ProtectedString(false, otpElement.token)))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.text.InputFilter
|
||||
import android.text.InputType
|
||||
import android.util.AttributeSet
|
||||
@@ -41,11 +42,11 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.WRAP_CONTENT)
|
||||
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
|
||||
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
|
||||
}
|
||||
maxLines = 1
|
||||
@@ -61,7 +62,7 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ class TextEditFieldView @JvmOverloads constructor(context: Context,
|
||||
id = labelViewId
|
||||
layoutParams = (layoutParams as LayoutParams?).also {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
* Copyright 2021 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
@@ -20,16 +20,20 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.text.InputFilter
|
||||
import android.text.util.Linkify
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.util.TypedValue
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.RelativeLayout
|
||||
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.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.kunzisoft.keepass.R
|
||||
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,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyle), GenericTextFieldView {
|
||||
: RelativeLayout(context, attrs, defStyle), GenericTextFieldView {
|
||||
|
||||
private val labelView: TextView
|
||||
private val valueView: TextView
|
||||
private val showButtonView: ImageView
|
||||
private val copyButtonView: ImageView
|
||||
private var labelViewId = ViewCompat.generateViewId()
|
||||
private var valueViewId = ViewCompat.generateViewId()
|
||||
private var showButtonId = ViewCompat.generateViewId()
|
||||
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 {
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_entry_field, this)
|
||||
buildViews()
|
||||
addView(copyButton)
|
||||
addView(showButton)
|
||||
addView(labelView)
|
||||
addView(valueView)
|
||||
}
|
||||
|
||||
labelView = findViewById(R.id.entry_field_label)
|
||||
valueView = findViewById(R.id.entry_field_value)
|
||||
showButtonView = findViewById(R.id.entry_field_show)
|
||||
copyButtonView = findViewById(R.id.entry_field_copy)
|
||||
copyButtonView.visibility = View.GONE
|
||||
private fun buildViews() {
|
||||
copyButton.apply {
|
||||
id = copyButtonId
|
||||
layoutParams = (layoutParams as LayoutParams?).also {
|
||||
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) {
|
||||
@@ -115,19 +227,20 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
|
||||
fun setProtection(protection: Boolean, hiddenProtectedValue: Boolean = false) {
|
||||
showButtonView.isVisible = protection
|
||||
showButtonView.isSelected = hiddenProtectedValue
|
||||
showButtonView.setOnClickListener {
|
||||
showButtonView.isSelected = !showButtonView.isSelected
|
||||
showButton.isVisible = protection
|
||||
showButton.isSelected = hiddenProtectedValue
|
||||
showButton.setOnClickListener {
|
||||
showButton.isSelected = !showButton.isSelected
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
changeProtectedValueParameters()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun changeProtectedValueParameters() {
|
||||
valueView.apply {
|
||||
if (showButtonView.isVisible) {
|
||||
applyHiddenStyle(showButtonView.isSelected)
|
||||
if (showButton.isVisible) {
|
||||
applyHiddenStyle(showButton.isSelected)
|
||||
} else {
|
||||
linkify()
|
||||
}
|
||||
@@ -138,11 +251,11 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
when {
|
||||
labelView.text.contains(APPLICATION_ID_FIELD_NAME) -> {
|
||||
val packageName = valueView.text.toString()
|
||||
if (UriUtil.isExternalAppInstalled(context, packageName)) {
|
||||
// TODO #996 if (UriUtil.isExternalAppInstalled(context, packageName)) {
|
||||
valueView.customLink {
|
||||
UriUtil.openExternalApp(context, packageName)
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
else -> {
|
||||
LinkifyCompat.addLinks(valueView, Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES)
|
||||
@@ -151,8 +264,8 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
|
||||
fun getCopyButtonView(): View? {
|
||||
if (copyButtonView.isVisible) {
|
||||
return copyButtonView
|
||||
if (copyButton.isVisible) {
|
||||
return copyButton
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -160,7 +273,7 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
fun setCopyButtonState(buttonState: ButtonState) {
|
||||
when (buttonState) {
|
||||
ButtonState.ACTIVATE -> {
|
||||
copyButtonView.apply {
|
||||
copyButton.apply {
|
||||
visibility = VISIBLE
|
||||
isActivated = false
|
||||
}
|
||||
@@ -170,7 +283,7 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
ButtonState.DEACTIVATE -> {
|
||||
copyButtonView.apply {
|
||||
copyButton.apply {
|
||||
visibility = VISIBLE
|
||||
// Reverse because isActivated show custom color and allow click
|
||||
isActivated = true
|
||||
@@ -181,7 +294,7 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
ButtonState.GONE -> {
|
||||
copyButtonView.apply {
|
||||
copyButton.apply {
|
||||
visibility = GONE
|
||||
setOnClickListener(null)
|
||||
}
|
||||
@@ -191,18 +304,24 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setCopyButtonClickListener(onActionClickListener: OnClickListener?) {
|
||||
setOnActionClickListener(onActionClickListener, null)
|
||||
fun setCopyButtonClickListener(onActionClickListener: ((label: String, value: String) -> Unit)?) {
|
||||
val clickListener = if (onActionClickListener != null)
|
||||
OnClickListener { onActionClickListener.invoke(label, value) }
|
||||
else
|
||||
null
|
||||
setOnActionClickListener(clickListener, null)
|
||||
}
|
||||
|
||||
override fun setOnActionClickListener(
|
||||
onActionClickListener: OnClickListener?,
|
||||
actionImageId: Int?
|
||||
) {
|
||||
copyButtonView.setOnClickListener(onActionClickListener)
|
||||
copyButtonView.isVisible = onActionClickListener != null
|
||||
copyButton.setOnClickListener(onActionClickListener)
|
||||
copyButton.isVisible = onActionClickListener != null
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override var isFieldVisible: Boolean
|
||||
@@ -213,6 +332,11 @@ class TextFieldView @JvmOverloads constructor(context: Context,
|
||||
isVisible = value
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
super.invalidate()
|
||||
buildViews()
|
||||
}
|
||||
|
||||
enum class ButtonState {
|
||||
ACTIVATE, DEACTIVATE, GONE
|
||||
}
|
||||
|
||||
@@ -2,21 +2,27 @@ package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.text.InputType
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.*
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.widget.AppCompatImageButton
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
|
||||
|
||||
class TextSelectFieldView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
@@ -28,30 +34,54 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
|
||||
private var actionImageButtonId = ViewCompat.generateViewId()
|
||||
private var mDefaultPosition = 0
|
||||
|
||||
private val labelView = AppCompatTextView(context).apply {
|
||||
setTextAppearance(context, R.style.KeepassDXStyle_TextAppearance_LabelTextStyle)
|
||||
private val labelView = TextInputLayout(context).apply {
|
||||
layoutParams = LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.WRAP_CONTENT).also {
|
||||
val leftMargin = 4f
|
||||
it.leftMargin = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
leftMargin,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
it.marginStart = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
leftMargin,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
}
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
private val valueView = TextInputEditText(
|
||||
ContextThemeWrapper(context,
|
||||
R.style.KeepassDXStyle_TextInputLayout)
|
||||
).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.WRAP_CONTENT)
|
||||
inputType = EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
imeOptions = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
|
||||
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 {
|
||||
layoutParams = LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.WRAP_CONTENT)
|
||||
0,
|
||||
0
|
||||
)
|
||||
}
|
||||
private var actionImageButton = AppCompatImageButton(
|
||||
ContextThemeWrapper(context, R.style.KeepassDXStyle_ImageButton_Simple), null, 0).apply {
|
||||
@@ -64,7 +94,7 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -75,15 +105,22 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
|
||||
init {
|
||||
// Manually write view to avoid view id bugs
|
||||
buildViews()
|
||||
labelView.addView(valueView)
|
||||
addView(labelView)
|
||||
addView(valueSpinnerView)
|
||||
addView(actionImageButton)
|
||||
|
||||
valueView.apply {
|
||||
setOnClickListener {
|
||||
valueSpinnerView.performClick()
|
||||
}
|
||||
}
|
||||
valueSpinnerView.apply {
|
||||
adapter = valueSpinnerAdapter
|
||||
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
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<*>?) {}
|
||||
}
|
||||
@@ -95,7 +132,7 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
|
||||
id = labelViewId
|
||||
layoutParams = (layoutParams as LayoutParams?).also {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -104,7 +141,7 @@ class TextSelectFieldView @JvmOverloads constructor(context: Context,
|
||||
id = valueViewId
|
||||
layoutParams = (layoutParams as LayoutParams?).also {
|
||||
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(BELOW, labelViewId)
|
||||
|
||||
@@ -5,29 +5,23 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.kunzisoft.keepass.app.database.IOActionTask
|
||||
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.model.*
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import java.util.*
|
||||
|
||||
|
||||
class EntryEditViewModel: NodeEditViewModel() {
|
||||
|
||||
private val mTempAttachments = mutableListOf<EntryAttachmentState>()
|
||||
|
||||
val entryInfo : LiveData<EntryInfo> get() = _entryInfo
|
||||
private val _entryInfo = MutableLiveData<EntryInfo>()
|
||||
val templatesEntry : LiveData<TemplatesEntry> get() = _templatesEntry
|
||||
private val _templatesEntry = MutableLiveData<TemplatesEntry>()
|
||||
|
||||
val requestEntryInfoUpdate : LiveData<EntryUpdate> get() = _requestEntryInfoUpdate
|
||||
private val _requestEntryInfoUpdate = SingleLiveEvent<EntryUpdate>()
|
||||
val onEntrySaved : LiveData<EntrySave> get() = _onEntrySaved
|
||||
private val _onEntrySaved = SingleLiveEvent<EntrySave>()
|
||||
|
||||
val templates : LiveData<TemplatesLoad> get() = _templates
|
||||
private val _templates = MutableLiveData<TemplatesLoad>()
|
||||
val onTemplateChanged : LiveData<Template> get() = _onTemplateChanged
|
||||
private val _onTemplateChanged = SingleLiveEvent<Template>()
|
||||
|
||||
@@ -69,38 +63,27 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
{
|
||||
val templates = database.getTemplates(isTemplate)
|
||||
val entryTemplate = entry?.let { database.getTemplate(it) } ?: Template.STANDARD
|
||||
TemplatesLoad(templates, entryTemplate)
|
||||
},
|
||||
{ templatesLoad ->
|
||||
// Call templates init before populate entry info
|
||||
_templates.value = templatesLoad
|
||||
changeTemplate(templatesLoad!!.defaultTemplate)
|
||||
|
||||
IOActionTask(
|
||||
{
|
||||
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
|
||||
}
|
||||
var entryInfo: EntryInfo? = null
|
||||
// Decode the entry / load entry info
|
||||
entry?.let {
|
||||
database.decodeEntryWithTemplateConfiguration(it).let { entry ->
|
||||
// Load entry info
|
||||
entry.getEntryInfo(database, true).let { tempEntryInfo ->
|
||||
// Retrieve data from registration
|
||||
(registerInfo?.searchInfo ?: searchInfo)?.let { tempSearchInfo ->
|
||||
tempEntryInfo.saveSearchInfo(database, tempSearchInfo)
|
||||
}
|
||||
registerInfo?.let { regInfo ->
|
||||
tempEntryInfo.saveRegisterInfo(database, regInfo)
|
||||
}
|
||||
entryInfo = tempEntryInfo
|
||||
}
|
||||
entryInfo
|
||||
},
|
||||
{ entryInfo ->
|
||||
_entryInfo.value = entryInfo
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
TemplatesEntry(templates, entryTemplate, entryInfo)
|
||||
},
|
||||
{ templatesEntry ->
|
||||
_templatesEntry.value = templatesEntry
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
@@ -239,7 +222,7 @@ class EntryEditViewModel: NodeEditViewModel() {
|
||||
_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 EntrySave(val oldEntry: Entry, val newEntry: Entry, val parent: Group?)
|
||||
data class FieldEdition(val oldField: Field?, val newField: Field?)
|
||||
|
||||
@@ -45,9 +45,6 @@ class EntryViewModel: ViewModel() {
|
||||
val historyPosition : LiveData<Int> get() = _historyPosition
|
||||
private val _historyPosition = MutableLiveData<Int>()
|
||||
|
||||
val url : LiveData<String?> get() = _url
|
||||
private val _url = MutableLiveData<String?>()
|
||||
|
||||
val entryInfo : LiveData<EntryInfo> get() = _entryInfo
|
||||
private val _entryInfo = MutableLiveData<EntryInfo>()
|
||||
|
||||
@@ -66,58 +63,49 @@ class EntryViewModel: ViewModel() {
|
||||
private val _historySelected = SingleLiveEvent<EntryHistory>()
|
||||
|
||||
fun loadEntry(database: Database?, entryId: NodeId<UUID>?, historyPosition: Int) {
|
||||
if (entryId != null) {
|
||||
if (database != null && entryId != null) {
|
||||
IOActionTask(
|
||||
{
|
||||
database?.getEntryById(entryId)
|
||||
},
|
||||
{ mainEntry ->
|
||||
// Manage current version and history
|
||||
_mainEntryId.value = mainEntry?.nodeId
|
||||
_historyPosition.value = historyPosition
|
||||
|
||||
val mainEntry = database.getEntryById(entryId)
|
||||
val currentEntry = if (historyPosition > -1) {
|
||||
mainEntry?.getHistory()?.get(historyPosition)
|
||||
} else {
|
||||
mainEntry
|
||||
}
|
||||
_url.value = currentEntry?.url
|
||||
|
||||
IOActionTask(
|
||||
{
|
||||
val entryTemplate = currentEntry?.let {
|
||||
database?.getTemplate(it)
|
||||
} ?: Template.STANDARD
|
||||
val entryTemplate = currentEntry?.let {
|
||||
database.getTemplate(it)
|
||||
} ?: Template.STANDARD
|
||||
|
||||
// To simplify template field visibility
|
||||
currentEntry?.let { entry ->
|
||||
// Add mainEntry to check the parent and define the template state
|
||||
database?.decodeEntryWithTemplateConfiguration(entry, mainEntry)
|
||||
?.let {
|
||||
// To update current modification time
|
||||
it.touch(modified = false, touchParents = false)
|
||||
// To simplify template field visibility
|
||||
currentEntry?.let { entry ->
|
||||
// Add mainEntry to check the parent and define the template state
|
||||
database.decodeEntryWithTemplateConfiguration(entry, mainEntry).let {
|
||||
// To update current modification time
|
||||
it.touch(modified = false, touchParents = false)
|
||||
|
||||
// Build history info
|
||||
val entryInfoHistory = it.getHistory().map { entryHistory ->
|
||||
entryHistory.getEntryInfo(database)
|
||||
}
|
||||
|
||||
EntryInfoHistory(
|
||||
entryTemplate,
|
||||
it.getEntryInfo(database),
|
||||
entryInfoHistory
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ entryInfoHistory ->
|
||||
if (entryInfoHistory != null) {
|
||||
_template.value = entryInfoHistory.template
|
||||
_entryInfo.value = entryInfoHistory.entryInfo
|
||||
_entryHistory.value = entryInfoHistory.entryHistory
|
||||
// Build history info
|
||||
val entryInfoHistory = it.getHistory().map { entryHistory ->
|
||||
entryHistory.getEntryInfo(database)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -143,7 +131,8 @@ class EntryViewModel: ViewModel() {
|
||||
_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 entryHistory: List<EntryInfo>)
|
||||
// Custom data class to manage entry to retrieve and define is it's an history item (!= -1)
|
||||
|
||||
4
app/src/main/res/color-v21/progress_color.xml
Normal file
4
app/src/main/res/color-v21/progress_color.xml
Normal 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>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:state_selected="true" android:color="@color/white"/>
|
||||
<item android:color="?android:attr/textColorSecondary"/>
|
||||
</selector>
|
||||
<item android:color="@color/grey"/>
|
||||
</selector>
|
||||
4
app/src/main/res/color/progress_color.xml
Normal file
4
app/src/main/res/color/progress_color.xml
Normal 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>
|
||||
@@ -7,6 +7,6 @@
|
||||
android:innerRadiusRatio="2.5"
|
||||
android:thickness="2dp"
|
||||
android:useLevel="true">
|
||||
<solid android:color="@color/orange" />
|
||||
<solid android:color="@color/progress_color" />
|
||||
</shape>
|
||||
</rotate>
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromDegrees="270"
|
||||
android:toDegrees="270">
|
||||
@@ -8,6 +7,6 @@
|
||||
android:innerRadiusRatio="2.5"
|
||||
android:thickness="2dp"
|
||||
android:useLevel="true">
|
||||
<solid android:color="?attr/colorAccent" />
|
||||
<solid android:color="@color/list_secondary_color" />
|
||||
</shape>
|
||||
</rotate>
|
||||
10
app/src/main/res/drawable/ic_downloading_white_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_downloading_white_24dp.xml
Normal 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>
|
||||
@@ -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>
|
||||
10
app/src/main/res/drawable/ic_saving_white_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_saving_white_24dp.xml
Normal 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>
|
||||
@@ -70,16 +70,16 @@
|
||||
tools:targetApi="lollipop">
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<ProgressBar
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/entry_progress"
|
||||
android:visibility="gone"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:indeterminate="false"
|
||||
app:indicatorColor="?attr/colorAccent"
|
||||
android:progress="10"
|
||||
android:max="30"
|
||||
android:layout_gravity="bottom"
|
||||
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.AppBarLayout>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
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
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
@@ -29,15 +29,14 @@
|
||||
android:id="@+id/special_mode_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="?attr/toolbarSpecialAppearance"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:theme="?attr/toolbarSpecialAppearance" />
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/group_coordinator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/special_mode_view"
|
||||
app:layout_constraintBottom_toTopOf="@+id/toolbar_action">
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@+id/special_mode_view"
|
||||
android:layout_above="@+id/toolbar_action">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
@@ -47,99 +46,86 @@
|
||||
android:elevation="4dp"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/toolbar_layout"
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:title="@string/app_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:titleEnabled="false"
|
||||
app:contentScrim="?attr/colorPrimary"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:title="@string/app_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="?attr/toolbarAppearance"
|
||||
android:elevation="4dp"
|
||||
tools:targetApi="lollipop">
|
||||
<LinearLayout
|
||||
android:id="@+id/group_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="?attr/toolbarAppearance"
|
||||
android:elevation="4dp"
|
||||
tools:targetApi="lollipop">
|
||||
<LinearLayout
|
||||
android:id="@+id/group_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/toolbar"
|
||||
android:orientation="vertical">
|
||||
<TextView android:id="@+id/search_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/toolbar"
|
||||
android:orientation="vertical">
|
||||
<TextView android:id="@+id/search_title"
|
||||
android:layout_width="match_parent"
|
||||
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:baselineAligned="false">
|
||||
<RelativeLayout
|
||||
android:layout_width="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">
|
||||
<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_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
<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"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:scaleType="fitXY" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/group_numbers"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="3"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Info"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginLeft="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginStart="@dimen/image_list_margin_vertical"
|
||||
android:layout_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>
|
||||
tools:text="3"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Info" />
|
||||
</RelativeLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginLeft="14dp"
|
||||
android:layout_marginStart="14dp"
|
||||
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>
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
</LinearLayout>
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<LinearLayout
|
||||
<RelativeLayout
|
||||
android:id="@+id/node_list_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -151,7 +137,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/windowBackground" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<com.kunzisoft.keepass.view.AddNodeButtonView
|
||||
android:id="@+id/add_node_button"
|
||||
@@ -167,8 +153,7 @@
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:visibility="gone"
|
||||
android:theme="?attr/toolbarActionAppearance"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -185,7 +170,6 @@
|
||||
layout="@layout/view_button_lock"
|
||||
android:layout_width="@dimen/lock_button_size"
|
||||
android:layout_height="@dimen/lock_button_size"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
android:layout_alignParentBottom="true"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</RelativeLayout>
|
||||
@@ -17,7 +17,7 @@
|
||||
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
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
@@ -29,17 +29,16 @@
|
||||
android:id="@+id/special_mode_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="?attr/toolbarSpecialAppearance"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:theme="?attr/toolbarSpecialAppearance" />
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/activity_password_coordinator_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/background_repeat"
|
||||
android:backgroundTint="?android:attr/textColor"
|
||||
app:layout_constraintTop_toBottomOf="@+id/special_mode_view"
|
||||
app:layout_constraintBottom_toTopOf="@+id/activity_password_footer">
|
||||
android:layout_below="@+id/special_mode_view"
|
||||
android:layout_above="@+id/activity_password_footer">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
@@ -111,12 +110,12 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="4dp"
|
||||
android:paddingTop="@dimen/default_margin"
|
||||
android:paddingLeft="@dimen/default_margin"
|
||||
android:paddingStart="@dimen/default_margin"
|
||||
android:paddingRight="@dimen/default_margin"
|
||||
android:paddingEnd="@dimen/default_margin"
|
||||
android:paddingBottom="@dimen/default_margin"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:background="?android:attr/windowBackground"
|
||||
app:layout_constraintWidth_percent="@dimen/content_percent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
@@ -214,7 +213,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
android:layout_alignParentBottom="true">
|
||||
<LinearLayout
|
||||
android:id="@+id/activity_password_info_container"
|
||||
android:layout_width="match_parent"
|
||||
@@ -250,4 +249,4 @@
|
||||
android:text="@string/menu_open" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</RelativeLayout>
|
||||
@@ -17,28 +17,24 @@
|
||||
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
|
||||
<LinearLayout
|
||||
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"
|
||||
tools:targetApi="o"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.kunzisoft.keepass.view.TemplateEditView
|
||||
android:id="@+id/template_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/entry_attachments_container"/>
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/entry_attachments_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/cardViewStyle"
|
||||
app:layout_constraintTop_toBottomOf="@+id/template_view"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
style="?attr/cardViewStyle">
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
@@ -62,4 +58,4 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -38,10 +38,11 @@
|
||||
android:text="@string/entry_history"
|
||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:weightSum="3"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
|
||||
@@ -49,51 +50,30 @@
|
||||
android:id="@+id/entry_history_last_modified"
|
||||
android:text="@string/entry_modified"
|
||||
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:layout_width="0dp"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/entry_history_title"
|
||||
android:text="@string/entry_title"
|
||||
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:layout_width="0dp"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/entry_history_username"
|
||||
android:text="@string/entry_user_name"
|
||||
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:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/entry_history_list"
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<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">
|
||||
|
||||
<TextView
|
||||
@@ -55,9 +57,9 @@
|
||||
android:layout_marginEnd="20dp"
|
||||
style="@style/KeepassDXStyle.TextAppearance.WarningTextStyle"/>
|
||||
|
||||
<ProgressBar
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress_dialog_bar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
app:indicatorColor="?attr/colorAccent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
|
||||
@@ -38,43 +38,40 @@
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:scaleType="fitStart" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<RelativeLayout
|
||||
android:id="@+id/item_attachment_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignBottom="@+id/item_attachment_thumbnail"
|
||||
android:background="?attr/cardBackgroundTransparentColor">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/item_attachment_broken"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/entry_attachments"
|
||||
android:src="@drawable/ic_attach_file_broken_white_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:src="@drawable/ic_attach_file_broken_white_24dp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/item_attachment_title"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_attachment_size_container"
|
||||
app:layout_constraintStart_toEndOf="@+id/item_attachment_broken"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toStartOf="@+id/item_attachment_size_container"
|
||||
android:layout_toLeftOf="@+id/item_attachment_size_container"
|
||||
android:layout_toEndOf="@+id/item_attachment_broken"
|
||||
android:layout_toRightOf="@+id/item_attachment_broken"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="BinaryFile.attach" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/item_attachment_size_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical|end"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_attachment_action_container"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
android:layout_toLeftOf="@+id/item_attachment_action_container"
|
||||
android:layout_toStartOf="@+id/item_attachment_action_container">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/item_attachment_size"
|
||||
@@ -101,10 +98,9 @@
|
||||
<FrameLayout
|
||||
android:id="@+id/item_attachment_action_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/item_attachment_delete_button"
|
||||
@@ -150,5 +146,5 @@
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
@@ -17,65 +17,43 @@
|
||||
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
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:weightSum="3"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/entry_history_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"
|
||||
android:gravity="center"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
android:layout_weight="1" />
|
||||
|
||||
<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>
|
||||
@@ -25,15 +25,20 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.Selectable.Item">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:minHeight="56dp"
|
||||
android:maxHeight="72dp"
|
||||
app:layout_constraintWidth_percent="@dimen/content_percent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:background="?android:attr/selectableItemBackground" >
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/node_icon"
|
||||
android:layout_width="32dp"
|
||||
@@ -44,67 +49,129 @@
|
||||
android:layout_marginStart="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginRight="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginEnd="@dimen/image_list_margin_vertical"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_blank_32dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/node_container_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:layout_marginLeft="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginStart="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginRight="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginEnd="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginLeft="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/node_icon"
|
||||
app:layout_constraintLeft_toRightOf="@+id/node_icon"
|
||||
app:layout_constraintEnd_toStartOf="@+id/node_attachment_icon"
|
||||
app:layout_constraintRight_toLeftOf="@+id/node_attachment_icon">
|
||||
app:layout_constraintEnd_toStartOf="@+id/node_options"
|
||||
app:layout_constraintRight_toLeftOf="@+id/node_options">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_text"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Entry.Title"
|
||||
android:layout_width="wrap_content"
|
||||
tools:text="Node Title"
|
||||
android:maxLines="2"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Entry.Title" />
|
||||
android:maxLines="2"
|
||||
tools:text="Node Title" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_subtext"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-4dp"
|
||||
tools:text="Node SubTitle"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Entry.SubTitle" />
|
||||
tools:text="Node SubTitle" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_meta"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Entry.Meta"
|
||||
android:layout_width="wrap_content"
|
||||
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C"
|
||||
android:layout_height="wrap_content"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Entry.Meta" />
|
||||
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" />
|
||||
</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_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginStart="@dimen/image_list_margin_vertical"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:src="@drawable/ic_attach_file_white_24dp"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Entry.Icon"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
app:layout_constraintEnd_toEndOf="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>
|
||||
@@ -25,12 +25,16 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.Selectable.Item">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:minHeight="56dp"
|
||||
android:maxHeight="72dp"
|
||||
app:layout_constraintWidth_percent="@dimen/content_percent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:background="?android:attr/selectableItemBackground" >
|
||||
@@ -66,14 +70,14 @@
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
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_toLeftOf="@+id/node_image_identifier"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="4dp">
|
||||
android:layout_toEndOf="@+id/node_icon"
|
||||
android:layout_toRightOf="@+id/node_icon"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_text"
|
||||
@@ -84,25 +88,27 @@
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="2"
|
||||
tools:text="Node Title" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_subtext"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Group.SubTitle"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
android:visibility="gone"
|
||||
tools:text="Node SubTitle" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_meta"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Group.Meta"
|
||||
android:layout_width="wrap_content"
|
||||
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C"
|
||||
android:layout_height="wrap_content"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Group.Meta" />
|
||||
tools:text="7543A7EAB2EA7CFD1394F1615EBEB08C" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingLeft="6dp"
|
||||
android:paddingEnd="6dp"
|
||||
android:paddingRight="6dp"
|
||||
android:paddingStart="@dimen/default_margin"
|
||||
android:paddingLeft="@dimen/default_margin"
|
||||
android:paddingEnd="@dimen/default_margin"
|
||||
android:paddingRight="@dimen/default_margin"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Large"
|
||||
tools:text="Test" />
|
||||
</FrameLayout>
|
||||
|
||||
@@ -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>
|
||||
@@ -17,22 +17,20 @@
|
||||
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
|
||||
<LinearLayout
|
||||
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"
|
||||
tools:targetApi="o"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/template_header_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/cardViewStyle"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/template_fields_container">
|
||||
android:visibility="gone">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
@@ -61,8 +59,6 @@
|
||||
android:id="@+id/template_fields_container"
|
||||
android:layout_width="match_parent"
|
||||
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" />
|
||||
|
||||
<com.kunzisoft.keepass.view.SectionView
|
||||
@@ -70,16 +66,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/cardViewStyle"
|
||||
android:layout_marginBottom="@dimen/card_view_margin_vertical"
|
||||
app:layout_constraintTop_toBottomOf="@+id/template_fields_container"
|
||||
app:layout_constraintBottom_toTopOf="@+id/custom_fields_container"/>
|
||||
android:layout_marginBottom="@dimen/card_view_margin_vertical"/>
|
||||
|
||||
<com.kunzisoft.keepass.view.SectionView
|
||||
android:id="@+id/custom_fields_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/cardViewStyle"
|
||||
app:layout_constraintTop_toBottomOf="@+id/not_referenced_fields_container"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
style="?attr/cardViewStyle"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<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:orderInCategory="95"
|
||||
app:iconTint="?attr/colorControlNormal"
|
||||
app:showAsAction="ifRoom" />
|
||||
<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:orderInCategory="96"
|
||||
app:iconTint="?attr/colorControlNormal"
|
||||
|
||||
@@ -33,5 +33,5 @@
|
||||
<dimen name="hidden_lock_button_size">0dp</dimen>
|
||||
<dimen name="content_percent">1</dimen>
|
||||
<dimen name="toolbar_parallax_height">160dp</dimen>
|
||||
<integer name="animation_duration">320</integer>
|
||||
<integer name="animation_duration">260</integer>
|
||||
</resources>
|
||||
@@ -190,6 +190,8 @@
|
||||
<bool name="monospace_font_fields_enable_default" translatable="false">true</bool>
|
||||
<string name="hide_expired_entries_key" translatable="false">hide_expired_entries_key</string>
|
||||
<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>
|
||||
<bool name="show_uuid_default" translatable="false">false</bool>
|
||||
<string name="enable_education_screens_key" translatable="false">enable_education_screens_key</string>
|
||||
|
||||
@@ -628,5 +628,7 @@
|
||||
<string name="hide_expired_entries_title">Hide expired entries</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_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>
|
||||
@@ -245,8 +245,8 @@
|
||||
<item name="colorControlActivated">?attr/colorAccent</item>
|
||||
</style>
|
||||
<style name="KeepassDXStyle.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
<item name="android:paddingTop">1dp</item>
|
||||
<item name="android:paddingBottom">1dp</item>
|
||||
<item name="android:paddingTop">2dp</item>
|
||||
<item name="android:paddingBottom">2dp</item>
|
||||
<item name="boxStrokeColor">@color/edit_text_stroke_color</item>
|
||||
<item name="hintTextColor">?attr/colorAccent</item>
|
||||
<item name="materialThemeOverlay">@style/ThemeOverlay.KeepassDXTheme.TextInputEditText</item>
|
||||
@@ -352,35 +352,41 @@
|
||||
|
||||
<!-- Nodes Text Style -->
|
||||
<style name="KeepassDXStyle.TextAppearance.Entry.Title" parent="KeepassDXStyle.TextAppearance">
|
||||
<item name="android:textColor">@color/entry_title_color</item>
|
||||
<item name="android:tint">@color/entry_title_color</item>
|
||||
<item name="android:textColor">@color/list_color</item>
|
||||
<item name="android:tint">@color/list_color</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
<style name="KeepassDXStyle.TextAppearance.Entry.SubTitle" parent="KeepassDXStyle.TextAppearance.Small">
|
||||
<item name="android:textColor">@color/entry_subtitle_color</item>
|
||||
<item name="android:tint">@color/entry_subtitle_color</item>
|
||||
<item name="android:textColor">@color/list_secondary_color</item>
|
||||
<item name="android:tint">@color/list_secondary_color</item>
|
||||
</style>
|
||||
<style name="KeepassDXStyle.TextAppearance.Entry.Meta" parent="KeepassDXStyle.TextAppearance.Tiny">
|
||||
<item name="android:textColor">@color/entry_title_color</item>
|
||||
<item name="android:tint">@color/entry_title_color</item>
|
||||
<item name="android:textColor">@color/list_color</item>
|
||||
<item name="android:tint">@color/list_color</item>
|
||||
<item name="android:textSize">11sp</item>
|
||||
</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">
|
||||
<item name="tint">@color/entry_subtitle_color</item>
|
||||
<item name="tint">@color/list_secondary_color</item>
|
||||
</style>
|
||||
<style name="KeepassDXStyle.TextAppearance.Group.Title" parent="KeepassDXStyle.TextAppearance">
|
||||
<item name="android:textColor">@color/group_title_color</item>
|
||||
<item name="android:tint">@color/group_title_color</item>
|
||||
<item name="android:textColor">@color/list_primary_color</item>
|
||||
<item name="android:tint">@color/list_primary_color</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
<style name="KeepassDXStyle.TextAppearance.Group.SubTitle" parent="KeepassDXStyle.TextAppearance.Small">
|
||||
<item name="android:textColor">@color/group_subtitle_color</item>
|
||||
<item name="android:tint">@color/group_subtitle_color</item>
|
||||
<item name="android:textColor">@color/list_secondary_color</item>
|
||||
<item name="android:tint">@color/list_secondary_color</item>
|
||||
</style>
|
||||
<style name="KeepassDXStyle.TextAppearance.Group.Meta" parent="KeepassDXStyle.TextAppearance.Tiny">
|
||||
<item name="android:textColor">@color/group_title_color</item>
|
||||
<item name="android:tint">@color/group_title_color</item>
|
||||
<item name="android:textColor">@color/list_primary_color</item>
|
||||
<item name="android:tint">@color/list_primary_color</item>
|
||||
<item name="android:textSize">11sp</item>
|
||||
</style>
|
||||
|
||||
@@ -463,18 +469,29 @@
|
||||
<item name="android:textColor">?attr/colorAccent</item>
|
||||
<item name="android:textSize">12sp</item>
|
||||
<item name="android:paddingLeft">4dp</item>
|
||||
<item name="android:paddingStart">4dp</item>
|
||||
<item name="android:paddingRight">4dp</item>
|
||||
<item name="android:paddingEnd">4dp</item>
|
||||
</style>
|
||||
<style name="KeepassDXStyle.TextAppearance.LabelTableTextStyle" parent="KeepassDXStyle.TextAppearance">
|
||||
<item name="android:textSize">12sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
<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>
|
||||
</style>
|
||||
|
||||
<!-- 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">
|
||||
<item name="android:progressDrawable">@drawable/foreground_progress_circle</item>
|
||||
<item name="android:background">@drawable/background_progress_circle</item>
|
||||
|
||||
@@ -80,6 +80,11 @@
|
||||
android:title="@string/hide_expired_entries_title"
|
||||
android:summary="@string/hide_expired_entries_summary"
|
||||
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
|
||||
android:key="@string/show_uuid_key"
|
||||
android:title="@string/show_uuid_title"
|
||||
|
||||
59
art/reload_database.svg
Normal file
59
art/reload_database.svg
Normal 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
59
art/save_database.svg
Normal 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 |
@@ -1,5 +1,6 @@
|
||||
* Add / Manage dynamic templates #191
|
||||
* Allow to manually select RecycleBin group and Templates group #191
|
||||
* Setting to display OTP Token in list #655
|
||||
* Fix timeout in dialogs #716
|
||||
* Check URI permissions #626
|
||||
* Small changes #1035 #1043 #942
|
||||
* Improvements #1035 #1043 #942 #1021 #1027
|
||||
@@ -1,5 +1,6 @@
|
||||
* Ajout / Gestion dynamique 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
|
||||
* Vérification des permissions URI #626
|
||||
* Petits changements #1035 #1043 #942
|
||||
* Améliorations #1035 #1043 #942 #1021 #1027
|
||||
Reference in New Issue
Block a user