mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Add template / edit / view
This commit is contained in:
@@ -22,11 +22,11 @@ package com.kunzisoft.keepass.activities.dialogs
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Field
|
||||
import com.kunzisoft.keepass.password.PasswordGenerator
|
||||
@@ -80,7 +80,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
||||
passwordView = root?.findViewById(R.id.password)
|
||||
passwordView?.applyFontVisibility()
|
||||
val passwordCopyView: ImageView? = root?.findViewById(R.id.password_copy_button)
|
||||
passwordCopyView?.visibility = if(PreferencesUtil.allowCopyPasswordAndProtectedFields(activity))
|
||||
passwordCopyView?.visibility = if(PreferencesUtil.allowCopyProtectedFields(activity))
|
||||
View.VISIBLE else View.GONE
|
||||
val clipboardHelper = ClipboardHelper(activity)
|
||||
passwordCopyView?.setOnClickListener {
|
||||
|
||||
@@ -38,7 +38,7 @@ import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.model.GroupInfo
|
||||
import com.kunzisoft.keepass.view.DateTimeView
|
||||
import com.kunzisoft.keepass.view.DateTimeEditView
|
||||
import org.joda.time.DateTime
|
||||
|
||||
class GroupEditDialogFragment : DialogFragment() {
|
||||
@@ -56,7 +56,7 @@ class GroupEditDialogFragment : DialogFragment() {
|
||||
private lateinit var nameTextView: TextView
|
||||
private lateinit var notesTextLayoutView: TextInputLayout
|
||||
private lateinit var notesTextView: TextView
|
||||
private lateinit var expirationView: DateTimeView
|
||||
private lateinit var expirationView: DateTimeEditView
|
||||
|
||||
enum class EditGroupDialogAction {
|
||||
CREATION, UPDATE, NONE;
|
||||
|
||||
@@ -35,14 +35,13 @@ import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
||||
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.StreamDirection
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.TemplateView
|
||||
import com.kunzisoft.keepass.view.TemplateEditView
|
||||
import com.kunzisoft.keepass.view.collapse
|
||||
import com.kunzisoft.keepass.view.expand
|
||||
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
|
||||
@@ -55,7 +54,7 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
|
||||
private val mEntryEditViewModel: EntryEditViewModel by activityViewModels()
|
||||
|
||||
private lateinit var templateView: TemplateView
|
||||
private lateinit var templateView: TemplateEditView
|
||||
private lateinit var attachmentsContainerView: ViewGroup
|
||||
private lateinit var attachmentsListView: RecyclerView
|
||||
private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
|
||||
@@ -67,9 +66,6 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
.inflate(R.layout.fragment_entry_edit, container, false)
|
||||
|
||||
templateView = rootView.findViewById(R.id.template_view)
|
||||
templateView.populateIconMethod = { imageView, icon ->
|
||||
drawFactory?.assignDatabaseIcon(imageView, icon, iconColor)
|
||||
}
|
||||
attachmentsContainerView = rootView.findViewById(R.id.entry_attachments_container)
|
||||
attachmentsListView = rootView.findViewById(R.id.entry_attachments_list)
|
||||
|
||||
@@ -87,6 +83,9 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
taIconColor?.recycle()
|
||||
|
||||
templateView.apply {
|
||||
populateIconMethod = { imageView, icon ->
|
||||
drawFactory?.assignDatabaseIcon(imageView, icon, iconColor)
|
||||
}
|
||||
setOnIconClickListener {
|
||||
mEntryEditViewModel.requestIconSelection(templateView.getIcon())
|
||||
}
|
||||
@@ -242,6 +241,7 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
|
||||
context?.let { context ->
|
||||
templateView.setFontInVisibility(PreferencesUtil.fieldFontIsInVisibility(context))
|
||||
templateView.setHideProtectedValue(PreferencesUtil.hideProtectedValue(context))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package com.kunzisoft.keepass.activities.fragments
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -16,37 +15,25 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateField
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.StreamDirection
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||
import com.kunzisoft.keepass.utils.UuidUtil
|
||||
import com.kunzisoft.keepass.view.EntryFieldView
|
||||
import com.kunzisoft.keepass.view.TemplateView
|
||||
import com.kunzisoft.keepass.viewmodels.EntryViewModel
|
||||
import java.util.*
|
||||
|
||||
class EntryFragment: DatabaseFragment() {
|
||||
|
||||
private lateinit var entryFieldsContainerView: View
|
||||
private lateinit var templateView: TemplateView
|
||||
|
||||
private lateinit var userNameFieldView: EntryFieldView
|
||||
private lateinit var passwordFieldView: EntryFieldView
|
||||
private lateinit var otpFieldView: EntryFieldView
|
||||
private lateinit var urlFieldView: EntryFieldView
|
||||
private lateinit var notesFieldView: EntryFieldView
|
||||
|
||||
private lateinit var extraFieldsContainerView: View
|
||||
private lateinit var extraFieldsListView: ViewGroup
|
||||
|
||||
private lateinit var expiresDateView: TextView
|
||||
private lateinit var creationDateView: TextView
|
||||
private lateinit var modificationDateView: TextView
|
||||
private lateinit var expiresImageView: ImageView
|
||||
|
||||
private lateinit var attachmentsContainerView: View
|
||||
private lateinit var attachmentsListView: RecyclerView
|
||||
@@ -56,11 +43,6 @@ class EntryFragment: DatabaseFragment() {
|
||||
private lateinit var uuidView: TextView
|
||||
private lateinit var uuidReferenceView: TextView
|
||||
|
||||
private var mFontInVisibility: Boolean = false
|
||||
private var mHideProtectedValue: Boolean = false
|
||||
private var mIsFirstTimeAskAllowCopyPasswordAndProtectedFields: Boolean = false
|
||||
private var mAllowCopyPasswordAndProtectedFields: Boolean = false
|
||||
|
||||
private var mOtpRunnable: Runnable? = null
|
||||
private var mClipboardHelper: ClipboardHelper? = null
|
||||
|
||||
@@ -86,28 +68,7 @@ class EntryFragment: DatabaseFragment() {
|
||||
attachmentsAdapter?.database = mDatabase
|
||||
}
|
||||
|
||||
entryFieldsContainerView = view.findViewById(R.id.entry_fields_container)
|
||||
entryFieldsContainerView.visibility = View.GONE
|
||||
|
||||
userNameFieldView = view.findViewById(R.id.entry_user_name_field)
|
||||
userNameFieldView.setLabel(R.string.entry_user_name)
|
||||
|
||||
passwordFieldView = view.findViewById(R.id.entry_password_field)
|
||||
passwordFieldView.setLabel(R.string.entry_password)
|
||||
|
||||
otpFieldView = view.findViewById(R.id.entry_otp_field)
|
||||
otpFieldView.setLabel(R.string.entry_otp)
|
||||
|
||||
urlFieldView = view.findViewById(R.id.entry_url_field)
|
||||
urlFieldView.setLabel(R.string.entry_url)
|
||||
urlFieldView.setLinkAll()
|
||||
|
||||
notesFieldView = view.findViewById(R.id.entry_notes_field)
|
||||
notesFieldView.setLabel(R.string.entry_notes)
|
||||
notesFieldView.setAutoLink()
|
||||
|
||||
extraFieldsContainerView = view.findViewById(R.id.extra_fields_container)
|
||||
extraFieldsListView = view.findViewById(R.id.extra_fields_list)
|
||||
templateView = view.findViewById(R.id.entry_template)
|
||||
|
||||
attachmentsContainerView = view.findViewById(R.id.entry_attachments_container)
|
||||
attachmentsListView = view.findViewById(R.id.entry_attachments_list)
|
||||
@@ -117,10 +78,8 @@ class EntryFragment: DatabaseFragment() {
|
||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
}
|
||||
|
||||
expiresDateView = view.findViewById(R.id.entry_expires_date)
|
||||
creationDateView = view.findViewById(R.id.entry_created)
|
||||
modificationDateView = view.findViewById(R.id.entry_modified)
|
||||
expiresImageView = view.findViewById(R.id.entry_expires_image)
|
||||
|
||||
uuidContainerView = view.findViewById(R.id.entry_UUID_container)
|
||||
uuidContainerView.apply {
|
||||
@@ -129,6 +88,10 @@ class EntryFragment: DatabaseFragment() {
|
||||
uuidView = view.findViewById(R.id.entry_UUID)
|
||||
uuidReferenceView = view.findViewById(R.id.entry_UUID_reference)
|
||||
|
||||
mEntryViewModel.template.observe(viewLifecycleOwner) { template ->
|
||||
templateView.setTemplate(template)
|
||||
}
|
||||
|
||||
mEntryViewModel.entryInfo.observe(viewLifecycleOwner) { entryInfo ->
|
||||
assignEntryInfo(entryInfo)
|
||||
}
|
||||
@@ -145,94 +108,42 @@ class EntryFragment: DatabaseFragment() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
loadTemplateSettings()
|
||||
}
|
||||
|
||||
private fun loadTemplateSettings() {
|
||||
context?.let { context ->
|
||||
mFontInVisibility = PreferencesUtil.fieldFontIsInVisibility(context)
|
||||
mHideProtectedValue = PreferencesUtil.hideProtectedValue(context)
|
||||
mIsFirstTimeAskAllowCopyPasswordAndProtectedFields =
|
||||
PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields(context)
|
||||
mAllowCopyPasswordAndProtectedFields =
|
||||
PreferencesUtil.allowCopyPasswordAndProtectedFields(context)
|
||||
templateView.setFontInVisibility(PreferencesUtil.fieldFontIsInVisibility(context))
|
||||
templateView.setHideProtectedValue(PreferencesUtil.hideProtectedValue(context))
|
||||
templateView.setFirstTimeAskAllowCopyProtectedFields(PreferencesUtil.isFirstTimeAskAllowCopyProtectedFields(context))
|
||||
templateView.setAllowCopyProtectedFields(PreferencesUtil.allowCopyProtectedFields(context))
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignEntryInfo(entryInfo: EntryInfo?) {
|
||||
context?.let { context ->
|
||||
|
||||
entryInfo?.username?.let { userName ->
|
||||
assignUserName(userName) {
|
||||
mClipboardHelper?.timeoutCopyToClipboard(userName,
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_user_name)))
|
||||
// Set copy buttons
|
||||
templateView.apply {
|
||||
setOnAskCopySafeClickListener {
|
||||
showClipboardDialog()
|
||||
}
|
||||
|
||||
setOnCopyActionClickListener { field ->
|
||||
mClipboardHelper?.timeoutCopyToClipboard(
|
||||
field.protectedValue.stringValue,
|
||||
getString(
|
||||
R.string.copy_field,
|
||||
TemplateField.getLocalizedName(context, field.name)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val showWarningClipboardDialogOnClickListener = View.OnClickListener {
|
||||
showClipboardDialog(entryInfo)
|
||||
}
|
||||
val onPasswordCopyClickListener: View.OnClickListener? = if (mAllowCopyPasswordAndProtectedFields) {
|
||||
View.OnClickListener {
|
||||
entryInfo?.password?.let { password ->
|
||||
mClipboardHelper?.timeoutCopyToClipboard(password,
|
||||
getString(R.string.copy_field,
|
||||
getString(R.string.entry_password)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If dialog not already shown
|
||||
if (mIsFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||
showWarningClipboardDialogOnClickListener
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
assignPassword(entryInfo?.password,
|
||||
mAllowCopyPasswordAndProtectedFields,
|
||||
onPasswordCopyClickListener)
|
||||
// Populate entry views
|
||||
templateView.setEntryInfo(entryInfo)
|
||||
|
||||
//Assign OTP field
|
||||
entryInfo?.otpModel?.let { otpModel ->
|
||||
val otpElement = OtpElement(otpModel)
|
||||
assignOtp(otpElement) {
|
||||
mClipboardHelper?.timeoutCopyToClipboard(
|
||||
otpElement.token,
|
||||
getString(R.string.copy_field, getString(R.string.entry_otp))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
assignURL(entryInfo?.url)
|
||||
assignNotes(entryInfo?.notes)
|
||||
|
||||
// Assign custom fields
|
||||
if (mDatabase?.allowEntryCustomFields() == true) {
|
||||
clearExtraFields()
|
||||
entryInfo?.customFields?.forEach { field ->
|
||||
val label = field.name
|
||||
// OTP field is already managed in dedicated view
|
||||
if (label != OtpEntryFields.OTP_TOKEN_FIELD) {
|
||||
val value = field.protectedValue
|
||||
val allowCopyProtectedField = !value.isProtected || mAllowCopyPasswordAndProtectedFields
|
||||
if (allowCopyProtectedField) {
|
||||
addExtraField(label, value, allowCopyProtectedField) {
|
||||
mClipboardHelper?.timeoutCopyToClipboard(
|
||||
value.toString(),
|
||||
getString(R.string.copy_field,
|
||||
TemplateField.getLocalizedName(context, field.name))
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// If dialog not already shown
|
||||
if (mIsFirstTimeAskAllowCopyPasswordAndProtectedFields) {
|
||||
addExtraField(label, value, allowCopyProtectedField, showWarningClipboardDialogOnClickListener)
|
||||
} else {
|
||||
addExtraField(label, value, allowCopyProtectedField, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setHiddenProtectedValue(mHideProtectedValue)
|
||||
assignOtp(entryInfo)
|
||||
|
||||
// Manage attachments
|
||||
entryInfo?.attachments?.toSet()?.let { attachments ->
|
||||
@@ -242,14 +153,12 @@ class EntryFragment: DatabaseFragment() {
|
||||
// Assign dates
|
||||
assignCreationDate(entryInfo?.creationTime)
|
||||
assignModificationDate(entryInfo?.lastModificationTime)
|
||||
setExpires(entryInfo?.expires ?: false, entryInfo?.expiryTime)
|
||||
|
||||
// Assign special data
|
||||
assignUUID(entryInfo?.id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showClipboardDialog(entryInfo: EntryInfo?) {
|
||||
private fun showClipboardDialog() {
|
||||
context?.let {
|
||||
AlertDialog.Builder(it)
|
||||
.setMessage(
|
||||
@@ -260,70 +169,45 @@ class EntryFragment: DatabaseFragment() {
|
||||
.create().apply {
|
||||
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(context, true)
|
||||
dialog.dismiss()
|
||||
assignEntryInfo(entryInfo)
|
||||
finishDialog(dialog)
|
||||
}
|
||||
setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable)) { dialog, _ ->
|
||||
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(context, false)
|
||||
dialog.dismiss()
|
||||
assignEntryInfo(entryInfo)
|
||||
finishDialog(dialog)
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignUserName(userName: String?,
|
||||
onClickListener: View.OnClickListener?) {
|
||||
userNameFieldView.apply {
|
||||
if (userName != null && userName.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
setValue(userName)
|
||||
applyFontVisibility(mFontInVisibility)
|
||||
showOrHideEntryFieldsContainer(false)
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
assignCopyButtonClickListener(onClickListener)
|
||||
}
|
||||
private fun finishDialog(dialog: DialogInterface) {
|
||||
dialog.dismiss()
|
||||
loadTemplateSettings()
|
||||
templateView.reload()
|
||||
}
|
||||
|
||||
private fun assignPassword(password: String?,
|
||||
allowCopyPassword: Boolean,
|
||||
onClickListener: View.OnClickListener?) {
|
||||
passwordFieldView.apply {
|
||||
if (password != null && password.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
setValue(password, true)
|
||||
applyFontVisibility(mFontInVisibility)
|
||||
activateCopyButton(allowCopyPassword)
|
||||
showOrHideEntryFieldsContainer(false)
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
assignCopyButtonClickListener(onClickListener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignOtp(otpElement: OtpElement?,
|
||||
onClickListener: View.OnClickListener) {
|
||||
private fun assignOtp(entryInfo: EntryInfo?) {
|
||||
entryInfo?.otpModel?.let { otpModel ->
|
||||
val otpElement = OtpElement(otpModel)
|
||||
templateView.getOtpTokenView()?.let { otpFieldView ->
|
||||
otpFieldView.removeCallbacks(mOtpRunnable)
|
||||
|
||||
if (otpElement != null) {
|
||||
otpFieldView.visibility = View.VISIBLE
|
||||
|
||||
if (otpElement.token.isEmpty()) {
|
||||
otpFieldView.setLabel(R.string.entry_otp)
|
||||
otpFieldView.setValue(R.string.error_invalid_OTP)
|
||||
otpFieldView.activateCopyButton(false)
|
||||
otpFieldView.assignCopyButtonClickListener(null)
|
||||
otpFieldView.setCopyButtonState(EntryFieldView.ButtonState.GONE)
|
||||
} else {
|
||||
otpFieldView.setLabel(otpElement.type.name)
|
||||
otpFieldView.setValue(otpElement.token)
|
||||
otpFieldView.assignCopyButtonClickListener(onClickListener)
|
||||
|
||||
otpFieldView.label = otpElement.type.name
|
||||
otpFieldView.value = otpElement.token
|
||||
otpFieldView.setCopyButtonState(EntryFieldView.ButtonState.ACTIVATE)
|
||||
otpFieldView.setCopyButtonClickListener {
|
||||
mClipboardHelper?.timeoutCopyToClipboard(
|
||||
otpElement.token,
|
||||
getString(R.string.copy_field, getString(R.string.entry_otp))
|
||||
)
|
||||
}
|
||||
mOtpRunnable = Runnable {
|
||||
if (otpElement.shouldRefreshToken()) {
|
||||
otpFieldView.setValue(otpElement.token)
|
||||
otpFieldView.value = otpElement.token
|
||||
}
|
||||
mEntryViewModel.onOtpElementUpdated(otpElement)
|
||||
otpFieldView.postDelayed(mOtpRunnable, 1000)
|
||||
@@ -331,44 +215,8 @@ class EntryFragment: DatabaseFragment() {
|
||||
mEntryViewModel.onOtpElementUpdated(otpElement)
|
||||
otpFieldView.post(mOtpRunnable)
|
||||
}
|
||||
showOrHideEntryFieldsContainer(false)
|
||||
} else {
|
||||
otpFieldView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignURL(url: String?) {
|
||||
urlFieldView.apply {
|
||||
if (url != null && url.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
setValue(url)
|
||||
showOrHideEntryFieldsContainer(false)
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignNotes(notes: String?) {
|
||||
notesFieldView.apply {
|
||||
if (notes != null && notes.isNotEmpty()) {
|
||||
visibility = View.VISIBLE
|
||||
setValue(notes)
|
||||
applyFontVisibility(mFontInVisibility)
|
||||
showOrHideEntryFieldsContainer(false)
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setExpires(isExpires: Boolean, expiryTime: DateInstant?) {
|
||||
expiresImageView.visibility = if (isExpires) View.VISIBLE else View.GONE
|
||||
expiresDateView.text = if (isExpires) {
|
||||
expiryTime?.getDateTimeString(resources)
|
||||
} else {
|
||||
resources.getString(R.string.never)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignCreationDate(date: DateInstant?) {
|
||||
@@ -384,54 +232,6 @@ class EntryFragment: DatabaseFragment() {
|
||||
uuidReferenceView.text = UuidUtil.toHexString(uuid)
|
||||
}
|
||||
|
||||
private fun setHiddenProtectedValue(hiddenProtectedValue: Boolean) {
|
||||
passwordFieldView.hiddenProtectedValue = hiddenProtectedValue
|
||||
// Hidden style for custom fields
|
||||
extraFieldsListView.let {
|
||||
for (i in 0 until it.childCount) {
|
||||
val childCustomView = it.getChildAt(i)
|
||||
if (childCustomView is EntryFieldView)
|
||||
childCustomView.hiddenProtectedValue = hiddenProtectedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOrHideEntryFieldsContainer(hide: Boolean) {
|
||||
entryFieldsContainerView.visibility = if (hide) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
/* -------------
|
||||
* Extra Fields
|
||||
* -------------
|
||||
*/
|
||||
|
||||
private fun showOrHideExtraFieldsContainer(hide: Boolean) {
|
||||
extraFieldsContainerView.visibility = if (hide) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
private fun addExtraField(title: String,
|
||||
value: ProtectedString,
|
||||
allowCopy: Boolean,
|
||||
onCopyButtonClickListener: View.OnClickListener?) {
|
||||
context?.let { context ->
|
||||
extraFieldsListView.addView(EntryFieldView(context).apply {
|
||||
setLabel(TemplateField.getLocalizedName(context, title))
|
||||
setValue(value.toString(), value.isProtected)
|
||||
setAutoLink()
|
||||
activateCopyButton(allowCopy)
|
||||
assignCopyButtonClickListener(onCopyButtonClickListener)
|
||||
applyFontVisibility(mFontInVisibility)
|
||||
})
|
||||
|
||||
showOrHideExtraFieldsContainer(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearExtraFields() {
|
||||
extraFieldsListView.removeAllViews()
|
||||
showOrHideExtraFieldsContainer(true)
|
||||
}
|
||||
|
||||
/* -------------
|
||||
* Attachments
|
||||
* -------------
|
||||
@@ -460,14 +260,7 @@ class EntryFragment: DatabaseFragment() {
|
||||
|
||||
fun firstEntryFieldCopyView(): View? {
|
||||
return try {
|
||||
when {
|
||||
userNameFieldView.isVisible && userNameFieldView.copyButtonView.isVisible -> userNameFieldView.copyButtonView
|
||||
passwordFieldView.isVisible && passwordFieldView.copyButtonView.isVisible -> passwordFieldView.copyButtonView
|
||||
otpFieldView.isVisible && otpFieldView.copyButtonView.isVisible -> otpFieldView.copyButtonView
|
||||
urlFieldView.isVisible && urlFieldView.copyButtonView.isVisible -> urlFieldView.copyButtonView
|
||||
notesFieldView.isVisible && notesFieldView.copyButtonView.isVisible -> notesFieldView.copyButtonView
|
||||
else -> null
|
||||
}
|
||||
templateView.getActionImageView()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -226,12 +226,12 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
||||
|
||||
val containsUsernameToCopy = entry.username.isNotEmpty()
|
||||
val containsPasswordToCopy = entry.password.isNotEmpty()
|
||||
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(context)
|
||||
&& PreferencesUtil.allowCopyProtectedFields(context)
|
||||
val containsOTPToCopy = entry.containsCustomField(OTP_TOKEN_FIELD)
|
||||
val containsExtraFieldToCopy = entry.customFields.isNotEmpty()
|
||||
&& (entry.containsCustomFieldsNotProtected()
|
||||
||
|
||||
(entry.containsCustomFieldsProtected() && PreferencesUtil.allowCopyPasswordAndProtectedFields(context))
|
||||
(entry.containsCustomFieldsProtected() && PreferencesUtil.allowCopyProtectedFields(context))
|
||||
)
|
||||
|
||||
var startService = false
|
||||
@@ -277,7 +277,7 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
||||
entry.customFields.forEach { field ->
|
||||
//If value is not protected or allowed
|
||||
if ((!field.protectedValue.isProtected
|
||||
|| PreferencesUtil.allowCopyPasswordAndProtectedFields(context))
|
||||
|| PreferencesUtil.allowCopyProtectedFields(context))
|
||||
&& field.name != OTP_TOKEN_FIELD) {
|
||||
notificationFields.add(
|
||||
ClipboardEntryNotificationField(
|
||||
|
||||
@@ -352,13 +352,13 @@ object PreferencesUtil {
|
||||
context.resources.getBoolean(R.bool.monospace_font_fields_enable_default))
|
||||
}
|
||||
|
||||
fun isFirstTimeAskAllowCopyPasswordAndProtectedFields(context: Context): Boolean {
|
||||
fun isFirstTimeAskAllowCopyProtectedFields(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.allow_copy_password_first_time_key),
|
||||
context.resources.getBoolean(R.bool.allow_copy_password_first_time_default))
|
||||
}
|
||||
|
||||
fun allowCopyPasswordAndProtectedFields(context: Context): Boolean {
|
||||
fun allowCopyProtectedFields(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.allow_copy_password_key),
|
||||
context.resources.getBoolean(R.bool.allow_copy_password_default))
|
||||
|
||||
3
app/src/main/java/com/kunzisoft/keepass/view/DataDate.kt
Normal file
3
app/src/main/java/com/kunzisoft/keepass/view/DataDate.kt
Normal file
@@ -0,0 +1,3 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
data class DataDate(val year: Int, val month: Int, val day: Int)
|
||||
3
app/src/main/java/com/kunzisoft/keepass/view/DataTime.kt
Normal file
3
app/src/main/java/com/kunzisoft/keepass/view/DataTime.kt
Normal file
@@ -0,0 +1,3 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
data class DataTime(val hours: Int, val minutes: Int)
|
||||
105
app/src/main/java/com/kunzisoft/keepass/view/DateTimeEditView.kt
Normal file
105
app/src/main/java/com/kunzisoft/keepass/view/DateTimeEditView.kt
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2021 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
|
||||
class DateTimeEditView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: FrameLayout(context, attrs, defStyle) {
|
||||
|
||||
private var entryExpiresLabelView: TextInputLayout
|
||||
private var entryExpiresTextView: TextView
|
||||
private var entryExpiresCheckBox: CompoundButton
|
||||
|
||||
private var mDateTime: DateInstant = DateInstant.IN_ONE_MONTH_DATE_TIME
|
||||
|
||||
var setOnDateClickListener: ((DateInstant) -> Unit)? = null
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_edit_date_time, this)
|
||||
|
||||
entryExpiresLabelView = findViewById(R.id.expiration_label)
|
||||
entryExpiresTextView = findViewById(R.id.expiration_text)
|
||||
entryExpiresCheckBox = findViewById(R.id.expiration_checkbox)
|
||||
|
||||
entryExpiresTextView.setOnClickListener {
|
||||
if (entryExpiresCheckBox.isChecked)
|
||||
setOnDateClickListener?.invoke(dateTime)
|
||||
}
|
||||
entryExpiresCheckBox.setOnCheckedChangeListener { _, _ ->
|
||||
assignExpiresDateText()
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignExpiresDateText() {
|
||||
entryExpiresTextView.text = if (entryExpiresCheckBox.isChecked) {
|
||||
mDateTime.getDateTimeString(resources)
|
||||
} else {
|
||||
resources.getString(R.string.never)
|
||||
}
|
||||
}
|
||||
|
||||
var label: String
|
||||
get() {
|
||||
return entryExpiresLabelView.hint.toString()
|
||||
}
|
||||
set(value) {
|
||||
entryExpiresLabelView.hint = value
|
||||
}
|
||||
|
||||
var activation: Boolean
|
||||
get() {
|
||||
return entryExpiresCheckBox.isChecked
|
||||
}
|
||||
set(value) {
|
||||
if (!value) {
|
||||
mDateTime = when (mDateTime.type) {
|
||||
DateInstant.Type.DATE_TIME -> DateInstant.IN_ONE_MONTH_DATE_TIME
|
||||
DateInstant.Type.DATE -> DateInstant.IN_ONE_MONTH_DATE
|
||||
DateInstant.Type.TIME -> DateInstant.IN_ONE_HOUR_TIME
|
||||
}
|
||||
}
|
||||
entryExpiresCheckBox.isChecked = value
|
||||
assignExpiresDateText()
|
||||
}
|
||||
|
||||
var dateTime: DateInstant
|
||||
get() {
|
||||
return if (activation)
|
||||
mDateTime
|
||||
else
|
||||
DateInstant.NEVER_EXPIRES
|
||||
}
|
||||
set(value) {
|
||||
mDateTime = value
|
||||
assignExpiresDateText()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
* Copyright 2021 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
@@ -22,69 +22,58 @@ package com.kunzisoft.keepass.view
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import androidx.core.view.isVisible
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import java.util.*
|
||||
|
||||
class DateTimeView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: ConstraintLayout(context, attrs, defStyle) {
|
||||
: FrameLayout(context, attrs, defStyle) {
|
||||
|
||||
private var entryExpiresLabelView: TextInputLayout
|
||||
private var entryExpiresTextView: TextView
|
||||
private var entryExpiresCheckBox: CompoundButton
|
||||
private var dateTimeLabelView: TextView
|
||||
private var dateTimeValueView: TextView
|
||||
private var expiresImage: ImageView
|
||||
|
||||
private var mActivated: Boolean = false
|
||||
private var mDateTime: DateInstant = DateInstant.IN_ONE_MONTH_DATE_TIME
|
||||
|
||||
private var fontInVisibility: Boolean = false
|
||||
|
||||
var setOnDateClickListener: ((DateInstant) -> Unit)? = null
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_expiration, this)
|
||||
inflater?.inflate(R.layout.view_date_time, this)
|
||||
|
||||
entryExpiresLabelView = findViewById(R.id.expiration_label)
|
||||
entryExpiresTextView = findViewById(R.id.expiration_text)
|
||||
entryExpiresCheckBox = findViewById(R.id.expiration_checkbox)
|
||||
|
||||
entryExpiresTextView.setOnClickListener {
|
||||
if (entryExpiresCheckBox.isChecked)
|
||||
setOnDateClickListener?.invoke(dateTime)
|
||||
}
|
||||
entryExpiresCheckBox.setOnCheckedChangeListener { _, _ ->
|
||||
assignExpiresDateText()
|
||||
}
|
||||
|
||||
fontInVisibility = PreferencesUtil.fieldFontIsInVisibility(context)
|
||||
dateTimeLabelView = findViewById(R.id.date_time_label)
|
||||
dateTimeValueView = findViewById(R.id.date_time_value)
|
||||
expiresImage = findViewById(R.id.expires_image)
|
||||
}
|
||||
|
||||
private fun assignExpiresDateText() {
|
||||
entryExpiresTextView.text = if (entryExpiresCheckBox.isChecked) {
|
||||
dateTimeValueView.text = if (mActivated) {
|
||||
expiresImage.isVisible = mDateTime.date.before(Date())
|
||||
mDateTime.getDateTimeString(resources)
|
||||
} else {
|
||||
expiresImage.isVisible = false
|
||||
resources.getString(R.string.never)
|
||||
}
|
||||
if (fontInVisibility)
|
||||
entryExpiresTextView.applyFontVisibility()
|
||||
}
|
||||
|
||||
var label: String
|
||||
get() {
|
||||
return entryExpiresLabelView.hint.toString()
|
||||
return dateTimeLabelView.text.toString()
|
||||
}
|
||||
set(value) {
|
||||
entryExpiresLabelView.hint = value
|
||||
dateTimeLabelView.text = value
|
||||
}
|
||||
|
||||
var activation: Boolean
|
||||
get() {
|
||||
return entryExpiresCheckBox.isChecked
|
||||
return mActivated
|
||||
}
|
||||
set(value) {
|
||||
if (!value) {
|
||||
@@ -94,7 +83,7 @@ class DateTimeView @JvmOverloads constructor(context: Context,
|
||||
DateInstant.Type.TIME -> DateInstant.IN_ONE_HOUR_TIME
|
||||
}
|
||||
}
|
||||
entryExpiresCheckBox.isChecked = value
|
||||
mActivated = value
|
||||
assignExpiresDateText()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.text.InputType
|
||||
import android.text.method.PasswordTransformationMethod
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.ContextThemeWrapper
|
||||
@@ -144,13 +143,10 @@ class EntryEditFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
|
||||
fun setProtection(protection: Boolean, hiddenProtectedValue: Boolean) {
|
||||
// hiddenProtectedValue don't work with TextInputLayout
|
||||
if (protection) {
|
||||
labelView.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
|
||||
valueView.inputType = valueView.inputType or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
labelView.editText?.transformationMethod = if (hiddenProtectedValue)
|
||||
PasswordTransformationMethod.getInstance()
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +159,4 @@ class EntryEditFieldView @JvmOverloads constructor(context: Context,
|
||||
actionImageButton.visibility = if (onActionClickListener == null) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
enum class TextType {
|
||||
NORMAL, SMALL_MULTI_LINE, MULTI_LINE
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.text.InputType
|
||||
import android.text.util.Linkify
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
@@ -29,10 +30,12 @@ import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.text.util.LinkifyCompat
|
||||
import androidx.core.view.isVisible
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.model.EntryInfo.Companion.APPLICATION_ID_FIELD_NAME
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
|
||||
class EntryFieldView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
@@ -41,17 +44,7 @@ class EntryFieldView @JvmOverloads constructor(context: Context,
|
||||
private val labelView: TextView
|
||||
private val valueView: TextView
|
||||
private val showButtonView: ImageView
|
||||
val copyButtonView: ImageView
|
||||
private var isProtected = false
|
||||
|
||||
var hiddenProtectedValue: Boolean
|
||||
get() {
|
||||
return !showButtonView.isSelected
|
||||
}
|
||||
set(value) {
|
||||
showButtonView.isSelected = value
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
private val copyButtonView: ImageView
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
@@ -69,19 +62,58 @@ class EntryFieldView @JvmOverloads constructor(context: Context,
|
||||
valueView.applyFontVisibility()
|
||||
}
|
||||
|
||||
fun setLabel(label: String?) {
|
||||
labelView.text = label ?: ""
|
||||
var label: String
|
||||
get() {
|
||||
return labelView.text.toString()
|
||||
}
|
||||
set(value) {
|
||||
labelView.text = value
|
||||
}
|
||||
|
||||
fun setLabel(@StringRes labelId: Int) {
|
||||
labelView.setText(labelId)
|
||||
}
|
||||
|
||||
fun setValue(value: String?,
|
||||
isProtected: Boolean = false) {
|
||||
valueView.text = value ?: ""
|
||||
this.isProtected = isProtected
|
||||
showButtonView.visibility = if (isProtected) View.VISIBLE else View.GONE
|
||||
var value: String
|
||||
get() {
|
||||
return valueView.text.toString()
|
||||
}
|
||||
set(value) {
|
||||
valueView.text = value
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
|
||||
fun setValue(@StringRes valueId: Int) {
|
||||
value = resources.getString(valueId)
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
|
||||
fun setType(valueType: TextType) {
|
||||
valueView.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
when (valueType) {
|
||||
TextType.NORMAL -> {
|
||||
valueView.inputType = valueView.inputType or
|
||||
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_NORMAL
|
||||
valueView.maxLines = 1
|
||||
}
|
||||
TextType.SMALL_MULTI_LINE -> {
|
||||
valueView.inputType = valueView.inputType or
|
||||
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
valueView.maxEms = 3
|
||||
valueView.maxLines = 3
|
||||
}
|
||||
TextType.MULTI_LINE -> {
|
||||
valueView.inputType = valueView.inputType or
|
||||
InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
valueView.maxEms = 40
|
||||
valueView.maxLines = 40
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setProtection(protection: Boolean, hiddenProtectedValue: Boolean = false) {
|
||||
showButtonView.isVisible = protection
|
||||
showButtonView.isSelected = hiddenProtectedValue
|
||||
showButtonView.setOnClickListener {
|
||||
showButtonView.isSelected = !showButtonView.isSelected
|
||||
changeProtectedValueParameters()
|
||||
@@ -89,26 +121,18 @@ class EntryFieldView @JvmOverloads constructor(context: Context,
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
|
||||
fun setValue(@StringRes valueId: Int,
|
||||
isProtected: Boolean = false) {
|
||||
setValue(resources.getString(valueId), isProtected)
|
||||
}
|
||||
|
||||
private fun changeProtectedValueParameters() {
|
||||
valueView.apply {
|
||||
if (isProtected) {
|
||||
if (showButtonView.isVisible) {
|
||||
isFocusable = false
|
||||
setTextIsSelectable(false)
|
||||
applyHiddenStyle(showButtonView.isSelected)
|
||||
} else {
|
||||
isFocusable = true
|
||||
setTextIsSelectable(true)
|
||||
}
|
||||
applyHiddenStyle(isProtected && showButtonView.isSelected)
|
||||
if (!isProtected) linkify()
|
||||
linkify()
|
||||
}
|
||||
}
|
||||
|
||||
fun setAutoLink() {
|
||||
if (!isProtected) linkify()
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
|
||||
private fun linkify() {
|
||||
@@ -127,17 +151,37 @@ class EntryFieldView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
|
||||
fun setLinkAll() {
|
||||
LinkifyCompat.addLinks(valueView, Linkify.ALL)
|
||||
fun getCopyButtonView(): View? {
|
||||
if (copyButtonView.isVisible) {
|
||||
return copyButtonView
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun activateCopyButton(enable: Boolean) {
|
||||
fun setCopyButtonState(buttonState: ButtonState) {
|
||||
when (buttonState) {
|
||||
ButtonState.ACTIVATE -> {
|
||||
copyButtonView.visibility = VISIBLE
|
||||
copyButtonView.isActivated = false
|
||||
}
|
||||
ButtonState.DEACTIVATE -> {
|
||||
copyButtonView.visibility = VISIBLE
|
||||
// Reverse because isActivated show custom color and allow click
|
||||
copyButtonView.isActivated = !enable
|
||||
copyButtonView.isActivated = true
|
||||
}
|
||||
ButtonState.GONE -> {
|
||||
copyButtonView.visibility = GONE
|
||||
copyButtonView.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun assignCopyButtonClickListener(onClickActionListener: OnClickListener?) {
|
||||
fun setCopyButtonClickListener(onClickActionListener: OnClickListener?) {
|
||||
copyButtonView.setOnClickListener(onClickActionListener)
|
||||
copyButtonView.visibility = if (onClickActionListener == null) GONE else VISIBLE
|
||||
copyButtonView.isVisible = onClickActionListener != null
|
||||
}
|
||||
|
||||
enum class ButtonState {
|
||||
ACTIVATE, DEACTIVATE, GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.Parcelable.Creator
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.IdRes
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Field
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.element.template.*
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
|
||||
|
||||
abstract class TemplateAbstractView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: FrameLayout(context, attrs, defStyle) {
|
||||
|
||||
private var mTemplate: Template? = null
|
||||
protected var mEntryInfo: EntryInfo? = null
|
||||
|
||||
protected var mCustomFieldIds = mutableListOf<FieldId>()
|
||||
|
||||
protected var mFontInVisibility: Boolean = false
|
||||
protected var mHideProtectedValue: Boolean = false
|
||||
|
||||
protected var headerContainerView: ViewGroup
|
||||
protected var entryIconView: ImageView
|
||||
protected var templateContainerView: ViewGroup
|
||||
protected var customFieldsContainerView: SectionView
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_template, this)
|
||||
|
||||
headerContainerView = findViewById(R.id.entry_edit_header_container)
|
||||
entryIconView = findViewById(R.id.entry_edit_icon_button)
|
||||
templateContainerView = findViewById(R.id.template_fields_container)
|
||||
// To fix card view margin in KitKat-
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
val paddingVertical = resources.getDimensionPixelSize(R.dimen.card_view_margin_vertical)
|
||||
val paddingHorizontal = resources.getDimensionPixelSize(R.dimen.card_view_margin_horizontal)
|
||||
templateContainerView.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
|
||||
}
|
||||
customFieldsContainerView = findViewById(R.id.custom_fields_container)
|
||||
}
|
||||
|
||||
// To show icon image
|
||||
var populateIconMethod: ((ImageView, IconImage) -> Unit)? = null
|
||||
|
||||
fun setTemplate(template: Template?) {
|
||||
if (mTemplate != template) {
|
||||
mTemplate = template
|
||||
if (mEntryInfo != null) {
|
||||
populateEntryInfoWithViews(true)
|
||||
}
|
||||
buildTemplateAndPopulateInfo()
|
||||
clearFocus()
|
||||
(context.getSystemService(Activity.INPUT_METHOD_SERVICE) as? InputMethodManager?)
|
||||
?.hideSoftInputFromWindow(windowToken, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun buildTemplate() {
|
||||
// Retrieve preferences
|
||||
mHideProtectedValue = PreferencesUtil.hideProtectedValue(context)
|
||||
|
||||
// Build each template section
|
||||
templateContainerView.removeAllViews()
|
||||
customFieldsContainerView.removeAllViews()
|
||||
mCustomFieldIds.clear()
|
||||
|
||||
mTemplate?.let { template ->
|
||||
|
||||
buildHeader()
|
||||
|
||||
template.sections.forEach { templateSection ->
|
||||
|
||||
val sectionView = SectionView(context, null, R.attr.cardViewStyle)
|
||||
// Add build view to parent
|
||||
templateContainerView.addView(sectionView)
|
||||
|
||||
// Build each attribute
|
||||
templateSection.attributes.forEach { templateAttribute ->
|
||||
val fieldTag: String
|
||||
when {
|
||||
templateAttribute.label.equals(TemplateField.LABEL_TITLE, true) -> {
|
||||
throw Exception("title cannot be in template attribute")
|
||||
}
|
||||
templateAttribute.label.equals(TemplateField.LABEL_USERNAME, true) -> {
|
||||
fieldTag = FIELD_USERNAME_TAG
|
||||
}
|
||||
templateAttribute.label.equals(TemplateField.LABEL_PASSWORD, true) -> {
|
||||
fieldTag = FIELD_PASSWORD_TAG
|
||||
}
|
||||
templateAttribute.label.equals(TemplateField.LABEL_URL, true) -> {
|
||||
fieldTag = FIELD_URL_TAG
|
||||
}
|
||||
templateAttribute.label.equals(
|
||||
TemplateField.LABEL_EXPIRATION,
|
||||
true
|
||||
) -> {
|
||||
fieldTag = FIELD_EXPIRES_TAG
|
||||
}
|
||||
templateAttribute.label.equals(TemplateField.LABEL_NOTES, true) -> {
|
||||
fieldTag = FIELD_NOTES_TAG
|
||||
}
|
||||
else -> {
|
||||
fieldTag = FIELD_CUSTOM_TAG
|
||||
}
|
||||
}
|
||||
|
||||
val attributeView = buildViewForTemplateField(
|
||||
templateAttribute,
|
||||
Field(
|
||||
templateAttribute.label,
|
||||
ProtectedString(templateAttribute.protected, "")
|
||||
),
|
||||
fieldTag
|
||||
)
|
||||
// Add created view to this parent
|
||||
sectionView.addView(attributeView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun buildHeader()
|
||||
|
||||
private fun buildViewForCustomField(field: Field): View? {
|
||||
val customFieldTemplateAttribute = TemplateAttribute(
|
||||
field.name,
|
||||
TemplateAttributeType.MULTILINE,
|
||||
field.protectedValue.isProtected,
|
||||
field.protectedValue.stringValue,
|
||||
TemplateAttributeAction.CUSTOM_EDITION)
|
||||
return buildViewForTemplateField(customFieldTemplateAttribute, field, FIELD_CUSTOM_TAG)
|
||||
}
|
||||
|
||||
private fun buildViewForTemplateField(templateAttribute: TemplateAttribute,
|
||||
field: Field,
|
||||
fieldTag: String): View? {
|
||||
// Build main view depending on type
|
||||
val itemView: View? = when (templateAttribute.type) {
|
||||
TemplateAttributeType.INLINE,
|
||||
TemplateAttributeType.SMALL_MULTILINE,
|
||||
TemplateAttributeType.MULTILINE -> {
|
||||
buildLinearTextView(templateAttribute, field)
|
||||
}
|
||||
TemplateAttributeType.DATE,
|
||||
TemplateAttributeType.TIME,
|
||||
TemplateAttributeType.DATETIME -> {
|
||||
buildDataTimeView(templateAttribute, field)
|
||||
}
|
||||
}
|
||||
// Custom id defined by field name, use getViewByField(field: Field) to retrieve it
|
||||
itemView?.id = field.name.hashCode()
|
||||
itemView?.tag = fieldTag
|
||||
|
||||
// Add new custom view id to the custom field list
|
||||
if (fieldTag == FIELD_CUSTOM_TAG) {
|
||||
val indexOldItem = indexCustomFieldIdByName(field.name)
|
||||
if (indexOldItem >= 0)
|
||||
mCustomFieldIds.removeAt(indexOldItem)
|
||||
mCustomFieldIds.add(FieldId(field.name, itemView!!.id, field.protectedValue.isProtected))
|
||||
}
|
||||
return itemView
|
||||
}
|
||||
|
||||
protected abstract fun buildLinearTextView(templateAttribute: TemplateAttribute,
|
||||
field: Field): View?
|
||||
|
||||
protected abstract fun buildDataTimeView(templateAttribute: TemplateAttribute,
|
||||
field: Field): View?
|
||||
|
||||
abstract fun getActionImageView(): View?
|
||||
|
||||
fun setFontInVisibility(fontInVisibility: Boolean) {
|
||||
this.mFontInVisibility = fontInVisibility
|
||||
}
|
||||
|
||||
fun setHideProtectedValue(hideProtectedValue: Boolean) {
|
||||
this.mHideProtectedValue = hideProtectedValue
|
||||
}
|
||||
|
||||
fun setEntryInfo(entryInfo: EntryInfo?) {
|
||||
mEntryInfo = entryInfo
|
||||
buildTemplateAndPopulateInfo()
|
||||
}
|
||||
|
||||
protected abstract fun populateViewsWithEntryInfo()
|
||||
|
||||
fun getEntryInfo(): EntryInfo {
|
||||
populateEntryInfoWithViews(true)
|
||||
return mEntryInfo ?: EntryInfo()
|
||||
}
|
||||
|
||||
abstract fun populateEntryInfoWithViews(templateFieldNotEmpty: Boolean)
|
||||
|
||||
fun reload() {
|
||||
buildTemplateAndPopulateInfo()
|
||||
}
|
||||
|
||||
private fun buildTemplateAndPopulateInfo() {
|
||||
if (mTemplate != null && mEntryInfo != null) {
|
||||
buildTemplate()
|
||||
populateViewsWithEntryInfo()
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------
|
||||
* External value update
|
||||
* -------------
|
||||
*/
|
||||
|
||||
protected fun getFieldViewById(@IdRes viewId: Int): View? {
|
||||
return templateContainerView.findViewById(viewId)
|
||||
?: customFieldsContainerView.findViewById(viewId)
|
||||
}
|
||||
|
||||
/* -------------
|
||||
* Custom Fields
|
||||
* -------------
|
||||
*/
|
||||
|
||||
protected data class FieldId(var label: String, var viewId: Int, var protected: Boolean)
|
||||
|
||||
private fun isStandardFieldName(name: String): Boolean {
|
||||
return TemplateField.isStandardFieldName(name)
|
||||
}
|
||||
|
||||
protected fun customFieldIdByName(name: String): FieldId? {
|
||||
return mCustomFieldIds.find { it.label.equals(name, true) }
|
||||
}
|
||||
|
||||
protected fun indexCustomFieldIdByName(name: String): Int {
|
||||
return mCustomFieldIds.indexOfFirst { it.label.equals(name, true) }
|
||||
}
|
||||
|
||||
protected fun retrieveCustomFieldsFromView(templateFieldNotEmpty: Boolean = false) {
|
||||
mEntryInfo?.customFields = mCustomFieldIds.mapNotNull {
|
||||
getCustomField(it.label, templateFieldNotEmpty)
|
||||
}.toMutableList()
|
||||
}
|
||||
|
||||
protected fun getCustomField(fieldName: String): Field {
|
||||
return getCustomField(fieldName, false)
|
||||
?: Field(fieldName, ProtectedString(false, ""))
|
||||
}
|
||||
|
||||
protected abstract fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field?
|
||||
|
||||
/**
|
||||
* Update a custom field or create a new one if doesn't exists, the old value is lost
|
||||
*/
|
||||
protected fun putCustomField(customField: Field, focus: Boolean): Boolean {
|
||||
return if (!isStandardFieldName(customField.name)) {
|
||||
customFieldsContainerView.visibility = View.VISIBLE
|
||||
if (indexCustomFieldIdByName(customField.name) >= 0) {
|
||||
replaceCustomField(customField, customField, focus)
|
||||
} else {
|
||||
val newCustomView = buildViewForCustomField(customField)
|
||||
customFieldsContainerView.addView(newCustomView)
|
||||
val fieldId = FieldId(customField.name,
|
||||
newCustomView!!.id,
|
||||
customField.protectedValue.isProtected)
|
||||
val indexOldItem = indexCustomFieldIdByName(fieldId.label)
|
||||
if (indexOldItem >= 0)
|
||||
mCustomFieldIds.removeAt(indexOldItem)
|
||||
mCustomFieldIds.add(indexOldItem, fieldId)
|
||||
if (focus)
|
||||
newCustomView.requestFocus()
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun putCustomField(customField: Field): Boolean {
|
||||
val put = putCustomField(customField, true)
|
||||
retrieveCustomFieldsFromView()
|
||||
return put
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a custom field and keep the old value
|
||||
*/
|
||||
private fun replaceCustomField(oldField: Field, newField: Field, 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()
|
||||
|
||||
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 newCustomView = buildViewForCustomField(newCustomFieldWithValue)
|
||||
parentGroup.addView(newCustomView, indexInParent)
|
||||
mCustomFieldIds.add(oldPosition, FieldId(newCustomFieldWithValue.name,
|
||||
newCustomView!!.id,
|
||||
newCustomFieldWithValue.protectedValue.isProtected))
|
||||
if (focus)
|
||||
newCustomView.requestFocus()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun replaceCustomField(oldField: Field, newField: Field): Boolean {
|
||||
val replace = replaceCustomField(oldField, newField, true)
|
||||
retrieveCustomFieldsFromView()
|
||||
return replace
|
||||
}
|
||||
|
||||
fun removeCustomField(oldCustomField: Field) {
|
||||
val indexOldField = indexCustomFieldIdByName(oldCustomField.name)
|
||||
if (indexOldField >= 0) {
|
||||
mCustomFieldIds[indexOldField].viewId.let { viewId ->
|
||||
customFieldsContainerView.removeViewById(viewId)
|
||||
}
|
||||
mCustomFieldIds.removeAt(indexOldField)
|
||||
}
|
||||
retrieveCustomFieldsFromView()
|
||||
}
|
||||
|
||||
fun putOtpElement(otpElement: OtpElement) {
|
||||
val otpField = OtpEntryFields.buildOtpField(otpElement,
|
||||
mEntryInfo?.title, mEntryInfo?.username)
|
||||
putCustomField(Field(otpField.name, otpField.protectedValue))
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable?) {
|
||||
//begin boilerplate code so parent classes can restore state
|
||||
if (state !is SavedState) {
|
||||
super.onRestoreInstanceState(state)
|
||||
return
|
||||
} else {
|
||||
mTemplate = state.template
|
||||
mEntryInfo = state.entryInfo
|
||||
onRestoreEntryInstanceState(state)
|
||||
buildTemplateAndPopulateInfo()
|
||||
super.onRestoreInstanceState(state.superState)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onRestoreEntryInstanceState(state: SavedState) {}
|
||||
|
||||
override fun onSaveInstanceState(): Parcelable {
|
||||
val superSave = super.onSaveInstanceState()
|
||||
val saveState = SavedState(superSave)
|
||||
populateEntryInfoWithViews(false)
|
||||
saveState.template = this.mTemplate
|
||||
saveState.entryInfo = this.mEntryInfo
|
||||
onSaveEntryInstanceState(saveState)
|
||||
return saveState
|
||||
}
|
||||
|
||||
protected open fun onSaveEntryInstanceState(savedState: SavedState) {}
|
||||
|
||||
protected class SavedState : BaseSavedState {
|
||||
var template: Template? = null
|
||||
var entryInfo: EntryInfo? = null
|
||||
// TODO Move
|
||||
var tempDateTimeViewId: Int? = null
|
||||
|
||||
constructor(superState: Parcelable?) : super(superState)
|
||||
|
||||
private constructor(parcel: Parcel) : super(parcel) {
|
||||
template = parcel.readParcelable(Template::class.java.classLoader)
|
||||
?: template
|
||||
entryInfo = parcel.readParcelable(EntryInfo::class.java.classLoader)
|
||||
?: entryInfo
|
||||
val dateTimeViewId = parcel.readInt()
|
||||
if (dateTimeViewId != -1)
|
||||
tempDateTimeViewId = dateTimeViewId
|
||||
}
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
super.writeToParcel(out, flags)
|
||||
out.writeParcelable(template, flags)
|
||||
out.writeParcelable(entryInfo, flags)
|
||||
out.writeInt(tempDateTimeViewId ?: -1)
|
||||
}
|
||||
|
||||
companion object {
|
||||
//required field that makes Parcelables from a Parcel
|
||||
@JvmField val CREATOR = object : Creator<SavedState?> {
|
||||
override fun createFromParcel(parcel: Parcel): SavedState {
|
||||
return SavedState(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<SavedState?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FIELD_TITLE_TAG = "FIELD_TITLE_TAG"
|
||||
const val FIELD_USERNAME_TAG = "FIELD_USERNAME_TAG"
|
||||
const val FIELD_PASSWORD_TAG = "FIELD_PASSWORD_TAG"
|
||||
const val FIELD_URL_TAG = "FIELD_URL_TAG"
|
||||
const val FIELD_EXPIRES_TAG = "FIELD_EXPIRES_TAG"
|
||||
const val FIELD_NOTES_TAG = "FIELD_NOTES_TAG"
|
||||
const val FIELD_CUSTOM_TAG = "FIELD_CUSTOM_TAG"
|
||||
}
|
||||
}
|
||||
332
app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt
Normal file
332
app/src/main/java/com/kunzisoft/keepass/view/TemplateEditView.kt
Normal file
@@ -0,0 +1,332 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.core.view.isVisible
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.Field
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateAttribute
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateAttributeAction
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateAttributeType
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateField
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateField.LABEL_TITLE
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import org.joda.time.DateTime
|
||||
|
||||
|
||||
class TemplateEditView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: TemplateAbstractView(context, attrs, defStyle) {
|
||||
|
||||
// Current date time selection
|
||||
@IdRes
|
||||
private var mTempDateTimeViewId: Int? = null
|
||||
|
||||
private var mOnCustomEditionActionClickListener: ((Field) -> Unit)? = null
|
||||
fun setOnCustomEditionActionClickListener(listener: ((Field) -> Unit)?) {
|
||||
this.mOnCustomEditionActionClickListener = listener
|
||||
}
|
||||
|
||||
private var mOnPasswordGenerationActionClickListener: ((Field) -> Unit)? = null
|
||||
fun setOnPasswordGenerationActionClickListener(listener: ((Field) -> Unit)?) {
|
||||
this.mOnPasswordGenerationActionClickListener = listener
|
||||
}
|
||||
|
||||
private var mOnDateInstantClickListener: ((DateInstant) -> Unit)? = null
|
||||
fun setOnDateInstantClickListener(listener: ((DateInstant) -> Unit)?) {
|
||||
this.mOnDateInstantClickListener = listener
|
||||
}
|
||||
|
||||
fun setOnIconClickListener(onClickListener: OnClickListener) {
|
||||
entryIconView.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
fun getIcon(): IconImage {
|
||||
return mEntryInfo?.icon ?: IconImage()
|
||||
}
|
||||
|
||||
fun setIcon(iconImage: IconImage) {
|
||||
mEntryInfo?.icon = iconImage
|
||||
populateIconMethod?.invoke(entryIconView, iconImage)
|
||||
}
|
||||
|
||||
override fun buildHeader() {
|
||||
headerContainerView.isVisible = true
|
||||
findViewById<EntryEditFieldView?>(R.id.entry_edit_title)?.apply {
|
||||
tag = FIELD_TITLE_TAG
|
||||
id = LABEL_TITLE.hashCode()
|
||||
label = TemplateField.getLocalizedName(context, LABEL_TITLE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildLinearTextView(templateAttribute: TemplateAttribute,
|
||||
field: Field): View? {
|
||||
// Add an action icon if needed
|
||||
return context?.let {
|
||||
EntryEditFieldView(it).apply {
|
||||
applyFontVisibility(mFontInVisibility)
|
||||
setProtection(field.protectedValue.isProtected, mHideProtectedValue)
|
||||
label = TemplateField.getLocalizedName(context, field.name)
|
||||
setType(when (templateAttribute.type) {
|
||||
TemplateAttributeType.SMALL_MULTILINE -> TextType.SMALL_MULTI_LINE
|
||||
TemplateAttributeType.MULTILINE -> TextType.MULTI_LINE
|
||||
else -> TextType.NORMAL
|
||||
})
|
||||
value = field.protectedValue.stringValue
|
||||
when (templateAttribute.action) {
|
||||
TemplateAttributeAction.NONE -> {
|
||||
setOnActionClickListener(null)
|
||||
}
|
||||
TemplateAttributeAction.CUSTOM_EDITION -> {
|
||||
setOnActionClickListener({
|
||||
mOnCustomEditionActionClickListener?.invoke(field)
|
||||
}, R.drawable.ic_more_white_24dp)
|
||||
}
|
||||
TemplateAttributeAction.PASSWORD_GENERATION -> {
|
||||
setOnActionClickListener({
|
||||
mOnPasswordGenerationActionClickListener?.invoke(field)
|
||||
}, R.drawable.ic_generate_password_white_24dp)
|
||||
}
|
||||
}
|
||||
templateAttribute.options.forEach { option ->
|
||||
// TODO options
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildDataTimeView(templateAttribute: TemplateAttribute,
|
||||
field: Field): View? {
|
||||
return context?.let {
|
||||
DateTimeEditView(it).apply {
|
||||
label = TemplateField.getLocalizedName(context, field.name)
|
||||
try {
|
||||
val value = field.protectedValue.toString()
|
||||
activation = value.trim().isNotEmpty()
|
||||
dateTime = DateInstant(value,
|
||||
when (templateAttribute.type) {
|
||||
TemplateAttributeType.DATE -> DateInstant.Type.DATE
|
||||
TemplateAttributeType.TIME -> DateInstant.Type.TIME
|
||||
else -> DateInstant.Type.DATE_TIME
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
activation = false
|
||||
dateTime = when (templateAttribute.type) {
|
||||
TemplateAttributeType.DATE -> DateInstant.IN_ONE_MONTH_DATE
|
||||
TemplateAttributeType.TIME -> DateInstant.IN_ONE_HOUR_TIME
|
||||
else -> DateInstant.IN_ONE_MONTH_DATE_TIME
|
||||
}
|
||||
}
|
||||
setOnDateClickListener = { dateInstant ->
|
||||
mTempDateTimeViewId = id
|
||||
mOnDateInstantClickListener?.invoke(dateInstant)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getActionImageView(): View? {
|
||||
return findViewWithTag<EntryEditFieldView?>(FIELD_PASSWORD_TAG)?.getActionImageView()
|
||||
}
|
||||
|
||||
fun setPasswordField(passwordField: Field) {
|
||||
val passwordView = getFieldViewById(passwordField.name.hashCode())
|
||||
if (passwordView is EntryEditFieldView?) {
|
||||
passwordView?.value = passwordField.protectedValue.stringValue
|
||||
}
|
||||
}
|
||||
|
||||
fun getPasswordField(): Field {
|
||||
val passwordView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
return Field(TemplateField.LABEL_PASSWORD, ProtectedString(true, passwordView?.value ?: ""))
|
||||
}
|
||||
|
||||
private fun setCurrentDateTimeSelection(action: (dateInstant: DateInstant) -> DateInstant) {
|
||||
mTempDateTimeViewId?.let { viewId ->
|
||||
val dateTimeView = getFieldViewById(viewId)
|
||||
if (dateTimeView is DateTimeEditView) {
|
||||
dateTimeView.dateTime = DateInstant(
|
||||
action.invoke(dateTimeView.dateTime).date,
|
||||
dateTimeView.dateTime.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setCurrentDateTimeValue(date: DataDate) {
|
||||
// Save the date
|
||||
setCurrentDateTimeSelection { instant ->
|
||||
val newDateInstant = DateInstant(
|
||||
DateTime(instant.date)
|
||||
.withYear(date.year)
|
||||
.withMonthOfYear(date.month + 1)
|
||||
.withDayOfMonth(date.day)
|
||||
.toDate(), instant.type)
|
||||
if (instant.type == DateInstant.Type.DATE_TIME) {
|
||||
val instantTime = DateInstant(instant.date, DateInstant.Type.TIME)
|
||||
// Trick to recall selection with time
|
||||
mOnDateInstantClickListener?.invoke(instantTime)
|
||||
}
|
||||
newDateInstant
|
||||
}
|
||||
}
|
||||
|
||||
fun setCurrentTimeValue(time: DataTime) {
|
||||
setCurrentDateTimeSelection { instant ->
|
||||
DateInstant(
|
||||
DateTime(instant.date)
|
||||
.withHourOfDay(time.hours)
|
||||
.withMinuteOfHour(time.minutes)
|
||||
.toDate(), instant.type)
|
||||
}
|
||||
}
|
||||
|
||||
override fun populateViewsWithEntryInfo() {
|
||||
mEntryInfo?.let { entryInfo ->
|
||||
setIcon(entryInfo.icon)
|
||||
|
||||
val titleView: EntryEditFieldView? =
|
||||
findViewWithTag(FIELD_TITLE_TAG)
|
||||
titleView?.value = entryInfo.title
|
||||
titleView?.applyFontVisibility(mFontInVisibility)
|
||||
|
||||
val userNameView: EntryEditFieldView? =
|
||||
templateContainerView.findViewWithTag(FIELD_USERNAME_TAG)
|
||||
userNameView?.value = entryInfo.username
|
||||
userNameView?.applyFontVisibility(mFontInVisibility)
|
||||
|
||||
val passwordView: EntryEditFieldView? =
|
||||
templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
passwordView?.value = entryInfo.password
|
||||
passwordView?.applyFontVisibility(mFontInVisibility)
|
||||
|
||||
val urlView: EntryEditFieldView? = templateContainerView.findViewWithTag(
|
||||
FIELD_URL_TAG
|
||||
)
|
||||
urlView?.value = entryInfo.url
|
||||
urlView?.applyFontVisibility(mFontInVisibility)
|
||||
|
||||
val expirationView: DateTimeEditView? =
|
||||
templateContainerView.findViewWithTag(FIELD_EXPIRES_TAG)
|
||||
expirationView?.activation = entryInfo.expires
|
||||
expirationView?.dateTime = entryInfo.expiryTime
|
||||
|
||||
val notesView: EntryEditFieldView? =
|
||||
templateContainerView.findViewWithTag(FIELD_NOTES_TAG)
|
||||
notesView?.value = entryInfo.notes
|
||||
notesView?.applyFontVisibility(mFontInVisibility)
|
||||
|
||||
customFieldsContainerView.removeAllViews()
|
||||
entryInfo.customFields.forEach { customField ->
|
||||
val indexFieldViewId = indexCustomFieldIdByName(customField.name)
|
||||
if (indexFieldViewId >= 0) {
|
||||
// Template contains the custom view
|
||||
val customFieldId = mCustomFieldIds[indexFieldViewId]
|
||||
templateContainerView.findViewById<View>(customFieldId.viewId)
|
||||
?.let { customView ->
|
||||
if (customView is EntryEditFieldView) {
|
||||
customView.value = customField.protectedValue.stringValue
|
||||
customView.applyFontVisibility(mFontInVisibility)
|
||||
} else if (customView is DateTimeEditView) {
|
||||
try {
|
||||
customView.dateTime =
|
||||
DateInstant(customField.protectedValue.stringValue)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "unable to populate date time view", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If template view not found, create a new custom view
|
||||
putCustomField(customField, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun populateEntryInfoWithViews(templateFieldNotEmpty: Boolean) {
|
||||
if (mEntryInfo == null)
|
||||
mEntryInfo = EntryInfo()
|
||||
|
||||
// Icon already populate
|
||||
|
||||
val titleView: EntryEditFieldView? = findViewWithTag(FIELD_TITLE_TAG)
|
||||
titleView?.value?.let {
|
||||
mEntryInfo?.title = it
|
||||
}
|
||||
|
||||
val userNameView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_USERNAME_TAG)
|
||||
userNameView?.value?.let {
|
||||
mEntryInfo?.username = it
|
||||
}
|
||||
|
||||
val passwordView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
passwordView?.value?.let {
|
||||
mEntryInfo?.password = it
|
||||
}
|
||||
|
||||
val urlView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_URL_TAG)
|
||||
urlView?.value?.let {
|
||||
mEntryInfo?.url = it
|
||||
}
|
||||
|
||||
val expirationView: DateTimeEditView? = templateContainerView.findViewWithTag(FIELD_EXPIRES_TAG)
|
||||
expirationView?.activation?.let {
|
||||
mEntryInfo?.expires = it
|
||||
}
|
||||
expirationView?.dateTime?.let {
|
||||
mEntryInfo?.expiryTime = it
|
||||
}
|
||||
|
||||
val notesView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_NOTES_TAG)
|
||||
notesView?.value?.let {
|
||||
mEntryInfo?.notes = it
|
||||
}
|
||||
|
||||
retrieveCustomFieldsFromView(templateFieldNotEmpty)
|
||||
|
||||
mEntryInfo?.otpModel = OtpEntryFields.parseFields { key ->
|
||||
getCustomField(key).protectedValue.toString()
|
||||
}?.otpModel
|
||||
}
|
||||
|
||||
override fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field? {
|
||||
customFieldIdByName(fieldName)?.let { fieldId ->
|
||||
val editView: View? = templateContainerView.findViewById(fieldId.viewId)
|
||||
?: customFieldsContainerView.findViewById(fieldId.viewId)
|
||||
if (editView is EntryEditFieldView) {
|
||||
if (!templateFieldNotEmpty ||
|
||||
(editView.tag == FIELD_CUSTOM_TAG
|
||||
&& editView.value.isNotEmpty()))
|
||||
return Field(fieldName, ProtectedString(fieldId.protected, editView.value))
|
||||
}
|
||||
if (editView is DateTimeEditView) {
|
||||
val value = if (editView.activation) editView.dateTime.toString() else ""
|
||||
if (!templateFieldNotEmpty ||
|
||||
(editView.tag == FIELD_CUSTOM_TAG
|
||||
&& value.isNotEmpty()))
|
||||
return Field(fieldName, ProtectedString(fieldId.protected, value))
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onRestoreEntryInstanceState(state: SavedState) {
|
||||
mTempDateTimeViewId = state.tempDateTimeViewId
|
||||
}
|
||||
|
||||
override fun onSaveEntryInstanceState(savedState: SavedState) {
|
||||
savedState.tempDateTimeViewId = this.mTempDateTimeViewId
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = TemplateEditView::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -1,257 +1,104 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.Parcelable.Creator
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.IdRes
|
||||
import com.kunzisoft.keepass.R
|
||||
import androidx.core.view.isVisible
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.Field
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.element.template.*
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateAttribute
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateAttributeType
|
||||
import com.kunzisoft.keepass.database.element.template.TemplateField
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import org.joda.time.DateTime
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
||||
|
||||
|
||||
class TemplateView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: FrameLayout(context, attrs, defStyle) {
|
||||
: TemplateAbstractView(context, attrs, defStyle) {
|
||||
|
||||
private var mTemplate: Template? = null
|
||||
private var mEntryInfo: EntryInfo? = null
|
||||
|
||||
private var mCustomFieldIds = mutableListOf<FieldId>()
|
||||
|
||||
private var mHideProtectedValue: Boolean = false
|
||||
private var mFontInVisibility: Boolean = false
|
||||
|
||||
private var entryIconView: ImageView
|
||||
private var templateContainerView: ViewGroup
|
||||
private var customFieldsContainerView: SectionView
|
||||
|
||||
// Current date time selection
|
||||
@IdRes
|
||||
private var mTempDateTimeViewId: Int? = null
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_template, this)
|
||||
|
||||
entryIconView = findViewById(R.id.entry_edit_icon_button)
|
||||
templateContainerView = findViewById(R.id.template_fields_container)
|
||||
// To fix card view margin in KitKat-
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
val paddingVertical = resources.getDimensionPixelSize(R.dimen.card_view_margin_vertical)
|
||||
val paddingHorizontal = resources.getDimensionPixelSize(R.dimen.card_view_margin_horizontal)
|
||||
templateContainerView.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
|
||||
private var mOnAskCopySafeClickListener: (() -> Unit)? = null
|
||||
fun setOnAskCopySafeClickListener(listener: (() -> Unit)? = null) {
|
||||
this.mOnAskCopySafeClickListener = listener
|
||||
}
|
||||
customFieldsContainerView = findViewById(R.id.custom_fields_container)
|
||||
private var mOnCopyActionClickListener: ((Field) -> Unit)? = null
|
||||
fun setOnCopyActionClickListener(listener: ((Field) -> Unit)? = null) {
|
||||
this.mOnCopyActionClickListener = listener
|
||||
}
|
||||
|
||||
fun setOnIconClickListener(onClickListener: OnClickListener) {
|
||||
entryIconView.setOnClickListener(onClickListener)
|
||||
private var mFirstTimeAskAllowCopyProtectedFields: Boolean = false
|
||||
fun setFirstTimeAskAllowCopyProtectedFields(firstTimeAskAllowCopyProtectedFields : Boolean) {
|
||||
this.mFirstTimeAskAllowCopyProtectedFields = firstTimeAskAllowCopyProtectedFields
|
||||
}
|
||||
|
||||
private var mOnCustomEditionActionClickListener: ((Field) -> Unit)? = null
|
||||
fun setOnCustomEditionActionClickListener(listener: ((Field) -> Unit)?) {
|
||||
this.mOnCustomEditionActionClickListener = listener
|
||||
private var mAllowCopyProtectedFields: Boolean = false
|
||||
fun setAllowCopyProtectedFields(allowCopyProtectedFields : Boolean) {
|
||||
this.mAllowCopyProtectedFields = allowCopyProtectedFields
|
||||
}
|
||||
|
||||
private var mOnPasswordGenerationActionClickListener: ((Field) -> Unit)? = null
|
||||
fun setOnPasswordGenerationActionClickListener(listener: ((Field) -> Unit)?) {
|
||||
this.mOnPasswordGenerationActionClickListener = listener
|
||||
override fun buildHeader() {
|
||||
headerContainerView.isVisible = false
|
||||
}
|
||||
|
||||
private var mOnDateInstantClickListener: ((DateInstant) -> Unit)? = null
|
||||
fun setOnDateInstantClickListener(listener: ((DateInstant) -> Unit)?) {
|
||||
this.mOnDateInstantClickListener = listener
|
||||
}
|
||||
|
||||
// To show icon image
|
||||
var populateIconMethod: ((ImageView, IconImage) -> Unit)? = null
|
||||
|
||||
fun setTemplate(template: Template?) {
|
||||
if (mTemplate != template) {
|
||||
mTemplate = template
|
||||
if (mEntryInfo != null) {
|
||||
populateEntryInfoWithViews()
|
||||
}
|
||||
buildTemplateAndPopulateInfo()
|
||||
clearFocus()
|
||||
(context.getSystemService(Activity.INPUT_METHOD_SERVICE) as? InputMethodManager?)
|
||||
?.hideSoftInputFromWindow(windowToken, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun buildTemplate() {
|
||||
// Retrieve preferences
|
||||
mHideProtectedValue = PreferencesUtil.hideProtectedValue(context)
|
||||
|
||||
// Build each template section
|
||||
templateContainerView.removeAllViews()
|
||||
customFieldsContainerView.removeAllViews()
|
||||
mCustomFieldIds.clear()
|
||||
|
||||
mTemplate?.let { template ->
|
||||
|
||||
findViewById<EntryEditFieldView?>(R.id.entry_edit_title)?.apply {
|
||||
tag = FIELD_TITLE_TAG
|
||||
id = TemplateField.LABEL_TITLE.hashCode()
|
||||
label = TemplateField.getLocalizedName(context, TemplateField.LABEL_TITLE)
|
||||
}
|
||||
|
||||
template.sections.forEach { templateSection ->
|
||||
|
||||
val sectionView = SectionView(context, null, R.attr.cardViewStyle)
|
||||
// Add build view to parent
|
||||
templateContainerView.addView(sectionView)
|
||||
|
||||
// Build each attribute
|
||||
templateSection.attributes.forEach { templateAttribute ->
|
||||
val fieldTag: String
|
||||
when {
|
||||
templateAttribute.label.equals(TemplateField.LABEL_TITLE, true) -> {
|
||||
throw Exception("title cannot be in template attribute")
|
||||
}
|
||||
templateAttribute.label.equals(TemplateField.LABEL_USERNAME, true) -> {
|
||||
fieldTag = FIELD_USERNAME_TAG
|
||||
}
|
||||
templateAttribute.label.equals(TemplateField.LABEL_PASSWORD, true) -> {
|
||||
fieldTag = FIELD_PASSWORD_TAG
|
||||
}
|
||||
templateAttribute.label.equals(TemplateField.LABEL_URL, true) -> {
|
||||
fieldTag = FIELD_URL_TAG
|
||||
}
|
||||
templateAttribute.label.equals(
|
||||
TemplateField.LABEL_EXPIRATION,
|
||||
true
|
||||
) -> {
|
||||
fieldTag = FIELD_EXPIRES_TAG
|
||||
}
|
||||
templateAttribute.label.equals(TemplateField.LABEL_NOTES, true) -> {
|
||||
fieldTag = FIELD_NOTES_TAG
|
||||
}
|
||||
else -> {
|
||||
fieldTag = FIELD_CUSTOM_TAG
|
||||
}
|
||||
}
|
||||
|
||||
val attributeView = buildViewForTemplateField(
|
||||
templateAttribute,
|
||||
Field(
|
||||
templateAttribute.label,
|
||||
ProtectedString(templateAttribute.protected, "")
|
||||
),
|
||||
fieldTag
|
||||
)
|
||||
// Add created view to this parent
|
||||
sectionView.addView(attributeView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildViewForCustomField(field: Field): View? {
|
||||
val customFieldTemplateAttribute = TemplateAttribute(
|
||||
field.name,
|
||||
TemplateAttributeType.MULTILINE,
|
||||
field.protectedValue.isProtected,
|
||||
field.protectedValue.stringValue,
|
||||
TemplateAttributeAction.CUSTOM_EDITION)
|
||||
return buildViewForTemplateField(customFieldTemplateAttribute, field, FIELD_CUSTOM_TAG)
|
||||
}
|
||||
|
||||
private fun buildViewForTemplateField(templateAttribute: TemplateAttribute,
|
||||
field: Field,
|
||||
fieldTag: String): View? {
|
||||
// Build main view depending on type
|
||||
val itemView: View? = when (templateAttribute.type) {
|
||||
TemplateAttributeType.INLINE,
|
||||
TemplateAttributeType.SMALL_MULTILINE,
|
||||
TemplateAttributeType.MULTILINE -> {
|
||||
buildLinearTextView(templateAttribute, field)
|
||||
}
|
||||
TemplateAttributeType.DATE,
|
||||
TemplateAttributeType.TIME,
|
||||
TemplateAttributeType.DATETIME -> {
|
||||
buildDataTimeView(templateAttribute, field)
|
||||
}
|
||||
}
|
||||
// Custom id defined by field name, use getViewByField(field: Field) to retrieve it
|
||||
itemView?.id = field.name.hashCode()
|
||||
itemView?.tag = fieldTag
|
||||
|
||||
// Add new custom view id to the custom field list
|
||||
if (fieldTag == FIELD_CUSTOM_TAG) {
|
||||
val indexOldItem = indexCustomFieldIdByName(field.name)
|
||||
if (indexOldItem >= 0)
|
||||
mCustomFieldIds.removeAt(indexOldItem)
|
||||
mCustomFieldIds.add(FieldId(field.name, itemView!!.id, field.protectedValue.isProtected))
|
||||
}
|
||||
return itemView
|
||||
}
|
||||
|
||||
private fun buildLinearTextView(templateAttribute: TemplateAttribute,
|
||||
override fun buildLinearTextView(templateAttribute: TemplateAttribute,
|
||||
field: Field): View? {
|
||||
// Add an action icon if needed
|
||||
return context?.let {
|
||||
EntryEditFieldView(it).apply {
|
||||
label = TemplateField.getLocalizedName(context, field.name)
|
||||
EntryFieldView(it).apply {
|
||||
applyFontVisibility(mFontInVisibility)
|
||||
setProtection(field.protectedValue.isProtected, mHideProtectedValue)
|
||||
label = TemplateField.getLocalizedName(context, field.name)
|
||||
setType(when (templateAttribute.type) {
|
||||
TemplateAttributeType.SMALL_MULTILINE -> EntryEditFieldView.TextType.SMALL_MULTI_LINE
|
||||
TemplateAttributeType.MULTILINE -> EntryEditFieldView.TextType.MULTI_LINE
|
||||
else -> EntryEditFieldView.TextType.NORMAL
|
||||
TemplateAttributeType.SMALL_MULTILINE -> TextType.SMALL_MULTI_LINE
|
||||
TemplateAttributeType.MULTILINE -> TextType.MULTI_LINE
|
||||
else -> TextType.NORMAL
|
||||
})
|
||||
value = field.protectedValue.stringValue
|
||||
when (templateAttribute.action) {
|
||||
TemplateAttributeAction.NONE -> {
|
||||
setOnActionClickListener(null)
|
||||
|
||||
if (field.protectedValue.isProtected) {
|
||||
if (mFirstTimeAskAllowCopyProtectedFields) {
|
||||
setCopyButtonState(EntryFieldView.ButtonState.DEACTIVATE)
|
||||
setCopyButtonClickListener {
|
||||
mOnAskCopySafeClickListener?.invoke()
|
||||
}
|
||||
TemplateAttributeAction.CUSTOM_EDITION -> {
|
||||
setOnActionClickListener({
|
||||
mOnCustomEditionActionClickListener?.invoke(field)
|
||||
}, R.drawable.ic_more_white_24dp)
|
||||
} else {
|
||||
if (mAllowCopyProtectedFields) {
|
||||
setCopyButtonState(EntryFieldView.ButtonState.ACTIVATE)
|
||||
setCopyButtonClickListener {
|
||||
mOnCopyActionClickListener?.invoke(field)
|
||||
}
|
||||
TemplateAttributeAction.PASSWORD_GENERATION -> {
|
||||
setOnActionClickListener({
|
||||
mOnPasswordGenerationActionClickListener?.invoke(field)
|
||||
}, R.drawable.ic_generate_password_white_24dp)
|
||||
} else {
|
||||
setCopyButtonState(EntryFieldView.ButtonState.GONE)
|
||||
setCopyButtonClickListener(null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setCopyButtonState(EntryFieldView.ButtonState.ACTIVATE)
|
||||
setCopyButtonClickListener {
|
||||
mOnCopyActionClickListener?.invoke(field)
|
||||
}
|
||||
}
|
||||
|
||||
templateAttribute.options.forEach { option ->
|
||||
// TODO options
|
||||
}
|
||||
applyFontVisibility(mFontInVisibility)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDataTimeView(templateAttribute: TemplateAttribute,
|
||||
override fun buildDataTimeView(templateAttribute: TemplateAttribute,
|
||||
field: Field): View? {
|
||||
return context?.let {
|
||||
DateTimeView(it).apply {
|
||||
label = TemplateField.getLocalizedName(context, field.name)
|
||||
try {
|
||||
dateTime = try {
|
||||
val value = field.protectedValue.toString()
|
||||
activation = value.trim().isNotEmpty()
|
||||
dateTime = DateInstant(value,
|
||||
DateInstant(value,
|
||||
when (templateAttribute.type) {
|
||||
TemplateAttributeType.DATE -> DateInstant.Type.DATE
|
||||
TemplateAttributeType.TIME -> DateInstant.Type.TIME
|
||||
@@ -259,122 +106,83 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
activation = false
|
||||
dateTime = when (templateAttribute.type) {
|
||||
when (templateAttribute.type) {
|
||||
TemplateAttributeType.DATE -> DateInstant.IN_ONE_MONTH_DATE
|
||||
TemplateAttributeType.TIME -> DateInstant.IN_ONE_HOUR_TIME
|
||||
else -> DateInstant.IN_ONE_MONTH_DATE_TIME
|
||||
}
|
||||
}
|
||||
setOnDateClickListener = { dateInstant ->
|
||||
mTempDateTimeViewId = id
|
||||
mOnDateInstantClickListener?.invoke(dateInstant)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getActionImageView(): View? {
|
||||
return findViewWithTag<EntryEditFieldView?>(FIELD_PASSWORD_TAG)?.getActionImageView()
|
||||
override fun getActionImageView(): View? {
|
||||
return findViewWithTag<EntryFieldView?>(FIELD_PASSWORD_TAG)?.getCopyButtonView()
|
||||
}
|
||||
|
||||
fun setFontInVisibility(fontInVisibility: Boolean) {
|
||||
this.mFontInVisibility = fontInVisibility
|
||||
}
|
||||
|
||||
fun getIcon(): IconImage {
|
||||
return mEntryInfo?.icon ?: IconImage()
|
||||
}
|
||||
|
||||
fun setIcon(iconImage: IconImage) {
|
||||
mEntryInfo?.icon = iconImage
|
||||
populateIconMethod?.invoke(entryIconView, iconImage)
|
||||
}
|
||||
|
||||
fun setPasswordField(passwordField: Field) {
|
||||
val passwordView = getFieldViewById(passwordField.name.hashCode())
|
||||
if (passwordView is EntryEditFieldView?) {
|
||||
passwordView?.value = passwordField.protectedValue.stringValue
|
||||
}
|
||||
}
|
||||
|
||||
fun getPasswordField(): Field {
|
||||
val passwordView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
return Field(TemplateField.LABEL_PASSWORD, ProtectedString(true, passwordView?.value ?: ""))
|
||||
}
|
||||
|
||||
fun setCurrentDateTimeValue(date: Date) {
|
||||
// Save the date
|
||||
setCurrentDateTimeSelection { instant ->
|
||||
val newDateInstant = DateInstant(DateTime(instant.date)
|
||||
.withYear(date.year)
|
||||
.withMonthOfYear(date.month + 1)
|
||||
.withDayOfMonth(date.day)
|
||||
.toDate(), instant.type)
|
||||
if (instant.type == DateInstant.Type.DATE_TIME) {
|
||||
val instantTime = DateInstant(instant.date, DateInstant.Type.TIME)
|
||||
// Trick to recall selection with time
|
||||
mOnDateInstantClickListener?.invoke(instantTime)
|
||||
}
|
||||
newDateInstant
|
||||
}
|
||||
}
|
||||
|
||||
fun setCurrentTimeValue(time: Time) {
|
||||
setCurrentDateTimeSelection { instant ->
|
||||
DateInstant(DateTime(instant.date)
|
||||
.withHourOfDay(time.hours)
|
||||
.withMinuteOfHour(time.minutes)
|
||||
.toDate(), instant.type)
|
||||
}
|
||||
}
|
||||
|
||||
fun setEntryInfo(entryInfo: EntryInfo?) {
|
||||
mEntryInfo = entryInfo
|
||||
buildTemplateAndPopulateInfo()
|
||||
}
|
||||
|
||||
private fun populateViewsWithEntryInfo() {
|
||||
override fun populateViewsWithEntryInfo() {
|
||||
mEntryInfo?.let { entryInfo ->
|
||||
setIcon(entryInfo.icon)
|
||||
|
||||
val titleView: EntryEditFieldView? =
|
||||
val titleView: EntryFieldView? =
|
||||
findViewWithTag(FIELD_TITLE_TAG)
|
||||
titleView?.value = entryInfo.title
|
||||
titleView?.applyFontVisibility(mFontInVisibility)
|
||||
if (entryInfo.title.isEmpty()) {
|
||||
titleView?.isVisible = false
|
||||
}
|
||||
|
||||
val userNameView: EntryEditFieldView? =
|
||||
val userNameView: EntryFieldView? =
|
||||
templateContainerView.findViewWithTag(FIELD_USERNAME_TAG)
|
||||
userNameView?.value = entryInfo.username
|
||||
userNameView?.applyFontVisibility(mFontInVisibility)
|
||||
if (entryInfo.username.isEmpty()) {
|
||||
userNameView?.isVisible = false
|
||||
}
|
||||
|
||||
val passwordView: EntryEditFieldView? =
|
||||
val passwordView: EntryFieldView? =
|
||||
templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
passwordView?.value = entryInfo.password
|
||||
passwordView?.applyFontVisibility(mFontInVisibility)
|
||||
if (entryInfo.password.isEmpty()) {
|
||||
passwordView?.isVisible = false
|
||||
}
|
||||
|
||||
val urlView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_URL_TAG)
|
||||
val urlView: EntryFieldView? = templateContainerView.findViewWithTag(
|
||||
FIELD_URL_TAG
|
||||
)
|
||||
urlView?.value = entryInfo.url
|
||||
urlView?.applyFontVisibility(mFontInVisibility)
|
||||
if (entryInfo.url.isEmpty()) {
|
||||
urlView?.isVisible = false
|
||||
}
|
||||
|
||||
val expirationView: DateTimeView? =
|
||||
templateContainerView.findViewWithTag(FIELD_EXPIRES_TAG)
|
||||
expirationView?.activation = entryInfo.expires
|
||||
expirationView?.dateTime = entryInfo.expiryTime
|
||||
if (!entryInfo.expires) {
|
||||
expirationView?.isVisible = false
|
||||
}
|
||||
|
||||
val notesView: EntryEditFieldView? =
|
||||
val notesView: EntryFieldView? =
|
||||
templateContainerView.findViewWithTag(FIELD_NOTES_TAG)
|
||||
notesView?.value = entryInfo.notes
|
||||
notesView?.applyFontVisibility(mFontInVisibility)
|
||||
if (entryInfo.notes.isEmpty()) {
|
||||
notesView?.isVisible = false
|
||||
}
|
||||
|
||||
customFieldsContainerView.removeAllViews()
|
||||
val emptyCustomFields = mutableListOf<FieldId>().also { it.addAll(mCustomFieldIds) }
|
||||
entryInfo.customFields.forEach { customField ->
|
||||
val indexFieldViewId = indexCustomFieldIdByName(customField.name)
|
||||
if (indexFieldViewId >= 0) {
|
||||
// Template contains the custom view
|
||||
val customFieldId = mCustomFieldIds[indexFieldViewId]
|
||||
emptyCustomFields.remove(customFieldId)
|
||||
templateContainerView.findViewById<View>(customFieldId.viewId)
|
||||
?.let { customView ->
|
||||
if (customView is EntryEditFieldView) {
|
||||
if (customView is EntryFieldView) {
|
||||
customView.value = customField.protectedValue.stringValue
|
||||
customView.applyFontVisibility(mFontInVisibility)
|
||||
} else if (customView is DateTimeView) {
|
||||
@@ -391,36 +199,37 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
putCustomField(customField, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Hide empty custom fields
|
||||
emptyCustomFields.forEach { customFieldId ->
|
||||
templateContainerView.findViewById<View>(customFieldId.viewId)
|
||||
.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getEntryInfo(): EntryInfo {
|
||||
populateEntryInfoWithViews()
|
||||
return mEntryInfo ?: EntryInfo()
|
||||
}
|
||||
|
||||
fun populateEntryInfoWithViews(templateFieldNotEmpty: Boolean = true) {
|
||||
override fun populateEntryInfoWithViews(templateFieldNotEmpty: Boolean) {
|
||||
if (mEntryInfo == null)
|
||||
mEntryInfo = EntryInfo()
|
||||
|
||||
// Icon already populate
|
||||
|
||||
val titleView: EntryEditFieldView? = findViewWithTag(FIELD_TITLE_TAG)
|
||||
val titleView: EntryFieldView? = findViewWithTag(FIELD_TITLE_TAG)
|
||||
titleView?.value?.let {
|
||||
mEntryInfo?.title = it
|
||||
}
|
||||
|
||||
val userNameView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_USERNAME_TAG)
|
||||
val userNameView: EntryFieldView? = templateContainerView.findViewWithTag(FIELD_USERNAME_TAG)
|
||||
userNameView?.value?.let {
|
||||
mEntryInfo?.username = it
|
||||
}
|
||||
|
||||
val passwordView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
val passwordView: EntryFieldView? = templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
passwordView?.value?.let {
|
||||
mEntryInfo?.password = it
|
||||
}
|
||||
|
||||
val urlView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_URL_TAG)
|
||||
val urlView: EntryFieldView? = templateContainerView.findViewWithTag(FIELD_URL_TAG)
|
||||
urlView?.value?.let {
|
||||
mEntryInfo?.url = it
|
||||
}
|
||||
@@ -433,7 +242,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
mEntryInfo?.expiryTime = it
|
||||
}
|
||||
|
||||
val notesView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_NOTES_TAG)
|
||||
val notesView: EntryFieldView? = templateContainerView.findViewWithTag(FIELD_NOTES_TAG)
|
||||
notesView?.value?.let {
|
||||
mEntryInfo?.notes = it
|
||||
}
|
||||
@@ -445,69 +254,21 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
}?.otpModel
|
||||
}
|
||||
|
||||
private fun buildTemplateAndPopulateInfo() {
|
||||
if (mTemplate != null && mEntryInfo != null) {
|
||||
buildTemplate()
|
||||
populateViewsWithEntryInfo()
|
||||
fun getOtpTokenView(): EntryFieldView? {
|
||||
val indexFieldViewId = indexCustomFieldIdByName(OTP_TOKEN_FIELD)
|
||||
if (indexFieldViewId >= 0) {
|
||||
// Template contains the custom view
|
||||
val customFieldId = mCustomFieldIds[indexFieldViewId]
|
||||
return findViewById(customFieldId.viewId)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/* -------------
|
||||
* External value update
|
||||
* -------------
|
||||
*/
|
||||
|
||||
private fun getFieldViewById(@IdRes viewId: Int): View? {
|
||||
return templateContainerView.findViewById(viewId)
|
||||
?: customFieldsContainerView.findViewById(viewId)
|
||||
}
|
||||
|
||||
private fun setCurrentDateTimeSelection(action: (dateInstant: DateInstant) -> DateInstant) {
|
||||
mTempDateTimeViewId?.let { viewId ->
|
||||
val dateTimeView = getFieldViewById(viewId)
|
||||
if (dateTimeView is DateTimeView) {
|
||||
dateTimeView.dateTime = DateInstant(
|
||||
action.invoke(dateTimeView.dateTime).date,
|
||||
dateTimeView.dateTime.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------
|
||||
* Custom Fields
|
||||
* -------------
|
||||
*/
|
||||
|
||||
private data class FieldId(var label: String, var viewId: Int, var protected: Boolean)
|
||||
|
||||
private fun isStandardFieldName(name: String): Boolean {
|
||||
return TemplateField.isStandardFieldName(name)
|
||||
}
|
||||
|
||||
private fun customFieldIdByName(name: String): FieldId? {
|
||||
return mCustomFieldIds.find { it.label.equals(name, true) }
|
||||
}
|
||||
|
||||
private fun indexCustomFieldIdByName(name: String): Int {
|
||||
return mCustomFieldIds.indexOfFirst { it.label.equals(name, true) }
|
||||
}
|
||||
|
||||
private fun retrieveCustomFieldsFromView(templateFieldNotEmpty: Boolean = false) {
|
||||
mEntryInfo?.customFields = mCustomFieldIds.mapNotNull {
|
||||
getCustomField(it.label, templateFieldNotEmpty)
|
||||
}.toMutableList()
|
||||
}
|
||||
|
||||
private fun getCustomField(fieldName: String): Field {
|
||||
return getCustomField(fieldName, false)
|
||||
?: Field(fieldName, ProtectedString(false, ""))
|
||||
}
|
||||
|
||||
private fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field? {
|
||||
override fun getCustomField(fieldName: String, templateFieldNotEmpty: Boolean): Field? {
|
||||
customFieldIdByName(fieldName)?.let { fieldId ->
|
||||
val editView: View? = templateContainerView.findViewById(fieldId.viewId)
|
||||
?: customFieldsContainerView.findViewById(fieldId.viewId)
|
||||
if (editView is EntryEditFieldView) {
|
||||
if (editView is EntryFieldView) {
|
||||
if (!templateFieldNotEmpty ||
|
||||
(editView.tag == FIELD_CUSTOM_TAG
|
||||
&& editView.value.isNotEmpty()))
|
||||
@@ -524,169 +285,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a custom field or create a new one if doesn't exists, the old value is lost
|
||||
*/
|
||||
private fun putCustomField(customField: Field, focus: Boolean): Boolean {
|
||||
return if (!isStandardFieldName(customField.name)) {
|
||||
customFieldsContainerView.visibility = View.VISIBLE
|
||||
if (indexCustomFieldIdByName(customField.name) >= 0) {
|
||||
replaceCustomField(customField, customField, focus)
|
||||
} else {
|
||||
val newCustomView = buildViewForCustomField(customField)
|
||||
customFieldsContainerView.addView(newCustomView)
|
||||
val fieldId = FieldId(customField.name,
|
||||
newCustomView!!.id,
|
||||
customField.protectedValue.isProtected)
|
||||
val indexOldItem = indexCustomFieldIdByName(fieldId.label)
|
||||
if (indexOldItem >= 0)
|
||||
mCustomFieldIds.removeAt(indexOldItem)
|
||||
mCustomFieldIds.add(indexOldItem, fieldId)
|
||||
if (focus)
|
||||
newCustomView.requestFocus()
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun putCustomField(customField: Field): Boolean {
|
||||
val put = putCustomField(customField, true)
|
||||
retrieveCustomFieldsFromView()
|
||||
return put
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a custom field and keep the old value
|
||||
*/
|
||||
private fun replaceCustomField(oldField: Field, newField: Field, 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()
|
||||
|
||||
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 newCustomView = buildViewForCustomField(newCustomFieldWithValue)
|
||||
parentGroup.addView(newCustomView, indexInParent)
|
||||
mCustomFieldIds.add(oldPosition, FieldId(newCustomFieldWithValue.name,
|
||||
newCustomView!!.id,
|
||||
newCustomFieldWithValue.protectedValue.isProtected))
|
||||
if (focus)
|
||||
newCustomView.requestFocus()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun replaceCustomField(oldField: Field, newField: Field): Boolean {
|
||||
val replace = replaceCustomField(oldField, newField, true)
|
||||
retrieveCustomFieldsFromView()
|
||||
return replace
|
||||
}
|
||||
|
||||
fun removeCustomField(oldCustomField: Field) {
|
||||
val indexOldField = indexCustomFieldIdByName(oldCustomField.name)
|
||||
if (indexOldField >= 0) {
|
||||
mCustomFieldIds[indexOldField].viewId.let { viewId ->
|
||||
customFieldsContainerView.removeViewById(viewId)
|
||||
}
|
||||
mCustomFieldIds.removeAt(indexOldField)
|
||||
}
|
||||
retrieveCustomFieldsFromView()
|
||||
}
|
||||
|
||||
fun putOtpElement(otpElement: OtpElement) {
|
||||
val otpField = OtpEntryFields.buildOtpField(otpElement,
|
||||
mEntryInfo?.title, mEntryInfo?.username)
|
||||
putCustomField(Field(otpField.name, otpField.protectedValue))
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable?) {
|
||||
//begin boilerplate code so parent classes can restore state
|
||||
if (state !is SavedState) {
|
||||
super.onRestoreInstanceState(state)
|
||||
return
|
||||
} else {
|
||||
mTemplate = state.template
|
||||
mEntryInfo = state.entryInfo
|
||||
mTempDateTimeViewId = state.tempDateTimeViewId
|
||||
buildTemplateAndPopulateInfo()
|
||||
super.onRestoreInstanceState(state.superState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(): Parcelable {
|
||||
val superSave = super.onSaveInstanceState()
|
||||
val saveState = SavedState(superSave)
|
||||
populateEntryInfoWithViews(false)
|
||||
saveState.template = this.mTemplate
|
||||
saveState.entryInfo = this.mEntryInfo
|
||||
saveState.tempDateTimeViewId = this.mTempDateTimeViewId
|
||||
return saveState
|
||||
}
|
||||
|
||||
data class Date(val year: Int, val month: Int, val day: Int)
|
||||
data class Time(val hours: Int, val minutes: Int)
|
||||
|
||||
internal class SavedState : BaseSavedState {
|
||||
var template: Template? = null
|
||||
var entryInfo: EntryInfo? = null
|
||||
var tempDateTimeViewId: Int? = null
|
||||
|
||||
constructor(superState: Parcelable?) : super(superState)
|
||||
|
||||
private constructor(parcel: Parcel) : super(parcel) {
|
||||
template = parcel.readParcelable(Template::class.java.classLoader)
|
||||
?: template
|
||||
entryInfo = parcel.readParcelable(EntryInfo::class.java.classLoader)
|
||||
?: entryInfo
|
||||
val dateTimeViewId = parcel.readInt()
|
||||
if (dateTimeViewId != -1)
|
||||
tempDateTimeViewId = dateTimeViewId
|
||||
}
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
super.writeToParcel(out, flags)
|
||||
out.writeParcelable(template, flags)
|
||||
out.writeParcelable(entryInfo, flags)
|
||||
out.writeInt(tempDateTimeViewId ?: -1)
|
||||
}
|
||||
|
||||
companion object {
|
||||
//required field that makes Parcelables from a Parcel
|
||||
@JvmField val CREATOR = object : Creator<SavedState?> {
|
||||
override fun createFromParcel(parcel: Parcel): SavedState {
|
||||
return SavedState(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<SavedState?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = TemplateView::class.java.name
|
||||
|
||||
private const val FIELD_TITLE_TAG = "FIELD_TITLE_TAG"
|
||||
private const val FIELD_USERNAME_TAG = "FIELD_USERNAME_TAG"
|
||||
private const val FIELD_PASSWORD_TAG = "FIELD_PASSWORD_TAG"
|
||||
private const val FIELD_URL_TAG = "FIELD_URL_TAG"
|
||||
private const val FIELD_EXPIRES_TAG = "FIELD_EXPIRES_TAG"
|
||||
private const val FIELD_NOTES_TAG = "FIELD_NOTES_TAG"
|
||||
private const val FIELD_CUSTOM_TAG = "FIELD_CUSTOM_TAG"
|
||||
private val TAG = TemplateEditView::class.java.name
|
||||
}
|
||||
}
|
||||
5
app/src/main/java/com/kunzisoft/keepass/view/TextType.kt
Normal file
5
app/src/main/java/com/kunzisoft/keepass/view/TextType.kt
Normal file
@@ -0,0 +1,5 @@
|
||||
package com.kunzisoft.keepass.view
|
||||
|
||||
enum class TextType {
|
||||
NORMAL, SMALL_MULTI_LINE, MULTI_LINE
|
||||
}
|
||||
@@ -12,7 +12,8 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.template.Template
|
||||
import com.kunzisoft.keepass.model.*
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.view.TemplateView
|
||||
import com.kunzisoft.keepass.view.DataDate
|
||||
import com.kunzisoft.keepass.view.DataTime
|
||||
import java.util.*
|
||||
|
||||
|
||||
@@ -58,10 +59,10 @@ class EntryEditViewModel: ViewModel() {
|
||||
|
||||
val requestDateTimeSelection : LiveData<DateInstant> get() = _requestDateTimeSelection
|
||||
private val _requestDateTimeSelection = SingleLiveEvent<DateInstant>()
|
||||
val onDateSelected : LiveData<TemplateView.Date> get() = _onDateSelected
|
||||
private val _onDateSelected = SingleLiveEvent<TemplateView.Date>()
|
||||
val onTimeSelected : LiveData<TemplateView.Time> get() = _onTimeSelected
|
||||
private val _onTimeSelected = SingleLiveEvent<TemplateView.Time>()
|
||||
val onDateSelected : LiveData<DataDate> get() = _onDateSelected
|
||||
private val _onDateSelected = SingleLiveEvent<DataDate>()
|
||||
val onTimeSelected : LiveData<DataTime> get() = _onTimeSelected
|
||||
private val _onTimeSelected = SingleLiveEvent<DataTime>()
|
||||
|
||||
val requestSetupOtp : LiveData<Void?> get() = _requestSetupOtp
|
||||
private val _requestSetupOtp = SingleLiveEvent<Void?>()
|
||||
@@ -274,11 +275,11 @@ class EntryEditViewModel: ViewModel() {
|
||||
}
|
||||
|
||||
fun selectDate(year: Int, month: Int, day: Int) {
|
||||
_onDateSelected.value = TemplateView.Date(year, month, day)
|
||||
_onDateSelected.value = DataDate(year, month, day)
|
||||
}
|
||||
|
||||
fun selectTime(hours: Int, minutes: Int) {
|
||||
_onTimeSelected.value = TemplateView.Time(hours, minutes)
|
||||
_onTimeSelected.value = DataTime(hours, minutes)
|
||||
}
|
||||
|
||||
fun setupOtp() {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.template.Template
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
@@ -18,10 +19,14 @@ class EntryViewModel: ViewModel() {
|
||||
|
||||
private val mDatabase: Database? = Database.getInstance()
|
||||
|
||||
private var mEntryTemplate: Template? = null
|
||||
private var mEntry: Entry? = null
|
||||
private var mLastEntryVersion: Entry? = null
|
||||
private var mHistoryPosition: Int = -1
|
||||
|
||||
val template : LiveData<Template> get() = _template
|
||||
private val _template = MutableLiveData<Template>()
|
||||
|
||||
val entryInfo : LiveData<EntryInfo> get() = _entryInfo
|
||||
private val _entryInfo = MutableLiveData<EntryInfo>()
|
||||
|
||||
@@ -52,11 +57,27 @@ class EntryViewModel: ViewModel() {
|
||||
} else {
|
||||
mLastEntryVersion
|
||||
}
|
||||
mEntryTemplate = mEntry?.let {
|
||||
mDatabase?.getTemplate(it)
|
||||
} ?: Template.STANDARD
|
||||
mHistoryPosition = historyPosition
|
||||
createEntryInfoHistory(mEntry)
|
||||
|
||||
// To simplify template field visibility
|
||||
mEntry?.let { entry ->
|
||||
mDatabase?.decodeEntryWithTemplateConfiguration(entry)?.let {
|
||||
// To update current modification time
|
||||
it.touch(modified = false, touchParents = false)
|
||||
EntryInfoHistory(
|
||||
mEntryTemplate ?: Template.STANDARD,
|
||||
it.getEntryInfo(mDatabase),
|
||||
it.getHistory()
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ entryInfoHistory ->
|
||||
if (entryInfoHistory != null) {
|
||||
_template.value = entryInfoHistory.template
|
||||
_entryInfo.value = entryInfoHistory.entryInfo
|
||||
_entryIsHistory.value = mHistoryPosition != -1
|
||||
_entryHistory.value = entryInfoHistory.entryHistory
|
||||
@@ -71,18 +92,6 @@ class EntryViewModel: ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEntryInfoHistory(entry: Entry?): EntryInfoHistory? {
|
||||
if (entry != null) {
|
||||
// To simplify template field visibility
|
||||
mDatabase?.decodeEntryWithTemplateConfiguration(entry)?.let {
|
||||
// To update current modification time
|
||||
it.touch(modified = false, touchParents = false)
|
||||
return EntryInfoHistory(it.getEntryInfo(mDatabase), it.getHistory())
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// TODO Remove
|
||||
fun getEntry(): Entry? {
|
||||
return mEntry
|
||||
@@ -116,13 +125,15 @@ class EntryViewModel: ViewModel() {
|
||||
}
|
||||
|
||||
fun onHistorySelected(item: Entry, position: Int) {
|
||||
_historySelected.value = EntryHistory(item.nodeId, item, null, position)
|
||||
_historySelected.value = EntryHistory(item.nodeId, null, item, null, position)
|
||||
}
|
||||
|
||||
data class EntryInfoHistory(val entryInfo: EntryInfo,
|
||||
data class EntryInfoHistory(val template: Template,
|
||||
val entryInfo: EntryInfo,
|
||||
val entryHistory: List<Entry>)
|
||||
// Custom data class to manage entry to retrieve and define is it's an history item (!= -1)
|
||||
data class EntryHistory(var nodeIdUUID: NodeId<UUID>?,
|
||||
var template: Template?,
|
||||
var entry: Entry?,
|
||||
var lastEntryVersion: Entry?,
|
||||
var historyPosition: Int = -1)
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/entry_table"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -26,69 +25,10 @@
|
||||
android:paddingTop="@dimen/card_view_margin_vertical"
|
||||
android:paddingBottom="@dimen/card_view_margin_vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/entry_fields_container"
|
||||
<com.kunzisoft.keepass.view.TemplateView
|
||||
android:id="@+id/entry_template"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/cardViewStyle">
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_margin="@dimen/card_view_padding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Username -->
|
||||
<com.kunzisoft.keepass.view.EntryFieldView
|
||||
android:id="@+id/entry_user_name_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Password -->
|
||||
<com.kunzisoft.keepass.view.EntryFieldView
|
||||
android:id="@+id/entry_password_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- OTP -->
|
||||
<com.kunzisoft.keepass.view.EntryFieldView
|
||||
android:id="@+id/entry_otp_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- URL -->
|
||||
<com.kunzisoft.keepass.view.EntryFieldView
|
||||
android:id="@+id/entry_url_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Notes -->
|
||||
<com.kunzisoft.keepass.view.EntryFieldView
|
||||
android:id="@+id/entry_notes_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/extra_fields_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
style="?attr/cardViewStyle">
|
||||
<LinearLayout
|
||||
android:id="@+id/extra_fields_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/card_view_padding"
|
||||
android:orientation="vertical" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/entry_attachments_container"
|
||||
@@ -127,32 +67,6 @@
|
||||
android:layout_margin="@dimen/card_view_padding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Expires -->
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/entry_expires_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/entry_expires"
|
||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/entry_expires_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_info_white_24dp"
|
||||
android:contentDescription="@string/content_description_file_information"
|
||||
android:tint="@color/red"/>
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/entry_expires_date"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Created -->
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/entry_created_label"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.kunzisoft.keepass.view.TemplateView
|
||||
<com.kunzisoft.keepass.view.TemplateEditView
|
||||
android:id="@+id/template_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
android:maxLines="3"
|
||||
android:hint="@string/entry_notes"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.kunzisoft.keepass.view.DateTimeView
|
||||
<com.kunzisoft.keepass.view.DateTimeEditView
|
||||
android:id="@+id/group_edit_expiration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
30
app/src/main/res/layout/view_date_time.xml
Normal file
30
app/src/main/res/layout/view_date_time.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/date_time_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/entry_expires"
|
||||
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/expires_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_info_white_24dp"
|
||||
android:contentDescription="@string/content_description_file_information"
|
||||
android:tint="@color/red"/>
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/date_time_value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -30,6 +30,7 @@
|
||||
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">
|
||||
<LinearLayout
|
||||
|
||||
Reference in New Issue
Block a user