mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Add saved instance state in template view
This commit is contained in:
@@ -118,7 +118,7 @@ class EntryEditFragment: DatabaseFragment(), SetOTPDialogFragment.CreateOtpListe
|
||||
}
|
||||
|
||||
mEntryEditViewModel.onTemplateChanged.observe(viewLifecycleOwner) { template ->
|
||||
templateView.buildTemplate(template)
|
||||
templateView.setTemplate(template)
|
||||
}
|
||||
|
||||
mEntryEditViewModel.entryInfoLoaded.observe(viewLifecycleOwner) { entryInfo ->
|
||||
@@ -213,11 +213,6 @@ class EntryEditFragment: DatabaseFragment(), SetOTPDialogFragment.CreateOtpListe
|
||||
// TODO fontInVisibility = PreferencesUtil.fieldFontIsInVisibility(requireContext())
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
mEntryEditViewModel.updateEntryInfo(templateView.getEntryInfo())
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
@@ -348,12 +343,6 @@ class EntryEditFragment: DatabaseFragment(), SetOTPDialogFragment.CreateOtpListe
|
||||
}, 250)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
//mEntryEditViewModel.loadEntryInfo(retrieveEntryInfo())
|
||||
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = EntryEditFragment::class.java.name
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.kunzisoft.keepass.view
|
||||
|
||||
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
|
||||
@@ -21,13 +24,14 @@ import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import org.joda.time.DateTime
|
||||
|
||||
|
||||
class TemplateView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
: FrameLayout(context, attrs, defStyle) {
|
||||
|
||||
private var mTemplate: Template? = null
|
||||
private var mEntryInfo: EntryInfo = EntryInfo()
|
||||
private var mEntryInfo: EntryInfo? = null
|
||||
|
||||
private var mCustomFieldIds = mutableListOf<FieldId>()
|
||||
|
||||
@@ -57,6 +61,8 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
templateContainerView.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
|
||||
}
|
||||
customFieldsContainerView = findViewById(R.id.custom_fields_container)
|
||||
|
||||
buildTemplateAndPopulateInfo()
|
||||
}
|
||||
|
||||
fun setOnIconClickListener(onClickListener: OnClickListener) {
|
||||
@@ -81,67 +87,68 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
// To show icon image
|
||||
var populateIconMethod: ((ImageView, IconImage) -> Unit)? = null
|
||||
|
||||
fun buildTemplate(template: Template) {
|
||||
if (mTemplate != template) {
|
||||
mTemplate = template
|
||||
post {
|
||||
// Save temp modifications
|
||||
getEntryInfo()
|
||||
fun setTemplate(template: Template?) {
|
||||
mTemplate = template
|
||||
buildTemplateAndPopulateInfo()
|
||||
}
|
||||
|
||||
// Retrieve preferences
|
||||
mHideProtectedValue = PreferencesUtil.hideProtectedValue(context)
|
||||
fun buildTemplate() {
|
||||
// Retrieve preferences
|
||||
mHideProtectedValue = PreferencesUtil.hideProtectedValue(context)
|
||||
|
||||
// Build each template section
|
||||
templateContainerView.removeAllViews()
|
||||
customFieldsContainerView.removeAllViews()
|
||||
mCustomFieldIds.clear()
|
||||
// Build each template section
|
||||
templateContainerView.removeAllViews()
|
||||
customFieldsContainerView.removeAllViews()
|
||||
mCustomFieldIds.clear()
|
||||
|
||||
template.sections.forEach { templateSection ->
|
||||
mTemplate?.sections?.forEach { templateSection ->
|
||||
|
||||
val sectionView = SectionView(context, null, R.attr.cardViewStyle)
|
||||
// Add build view to parent
|
||||
templateContainerView.addView(sectionView)
|
||||
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)
|
||||
// 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
|
||||
}
|
||||
}
|
||||
setEntryInfo(mEntryInfo)
|
||||
|
||||
val attributeView = buildViewForTemplateField(
|
||||
templateAttribute,
|
||||
Field(
|
||||
templateAttribute.label,
|
||||
ProtectedString(templateAttribute.protected, "")
|
||||
),
|
||||
fieldTag
|
||||
)
|
||||
// Add created view to this parent
|
||||
sectionView.addView(attributeView)
|
||||
}
|
||||
}
|
||||
showByFading()
|
||||
}
|
||||
|
||||
private fun buildViewForCustomField(field: Field): View? {
|
||||
@@ -249,11 +256,11 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
|
||||
fun getIcon(): IconImage {
|
||||
return mEntryInfo.icon
|
||||
return mEntryInfo?.icon ?: IconImage()
|
||||
}
|
||||
|
||||
fun setIcon(iconImage: IconImage) {
|
||||
mEntryInfo.icon = iconImage
|
||||
mEntryInfo?.icon = iconImage
|
||||
populateIconMethod?.invoke(entryIconView, iconImage)
|
||||
}
|
||||
|
||||
@@ -290,92 +297,116 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
}
|
||||
|
||||
fun setEntryInfo(entryInfo: EntryInfo) {
|
||||
|
||||
fun setEntryInfo(entryInfo: EntryInfo?) {
|
||||
mEntryInfo = entryInfo
|
||||
setIcon(entryInfo.icon)
|
||||
buildTemplateAndPopulateInfo()
|
||||
}
|
||||
|
||||
entryTitleView.value = entryInfo.title
|
||||
private fun populateViewsWithEntryInfo() {
|
||||
mEntryInfo?.let { entryInfo ->
|
||||
setIcon(entryInfo.icon)
|
||||
|
||||
val userNameView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_USERNAME_TAG)
|
||||
userNameView?.value = entryInfo.username
|
||||
entryTitleView.value = entryInfo.title
|
||||
|
||||
val passwordView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
passwordView?.value = entryInfo.password
|
||||
val userNameView: EntryEditFieldView? =
|
||||
templateContainerView.findViewWithTag(FIELD_USERNAME_TAG)
|
||||
userNameView?.value = entryInfo.username
|
||||
|
||||
val urlView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_URL_TAG)
|
||||
urlView?.value = entryInfo.url
|
||||
val passwordView: EntryEditFieldView? =
|
||||
templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
passwordView?.value = entryInfo.password
|
||||
|
||||
val expirationView: DateTimeView? = templateContainerView.findViewWithTag(FIELD_EXPIRES_TAG)
|
||||
expirationView?.activation = entryInfo.expires
|
||||
expirationView?.dateTime = entryInfo.expiryTime
|
||||
val urlView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_URL_TAG)
|
||||
urlView?.value = entryInfo.url
|
||||
|
||||
val notesView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_NOTES_TAG)
|
||||
notesView?.value = entryInfo.notes
|
||||
val expirationView: DateTimeView? =
|
||||
templateContainerView.findViewWithTag(FIELD_EXPIRES_TAG)
|
||||
expirationView?.activation = entryInfo.expires
|
||||
expirationView?.dateTime = entryInfo.expiryTime
|
||||
|
||||
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
|
||||
} else if (customView is DateTimeView) {
|
||||
try {
|
||||
customView.dateTime = DateInstant(customField.protectedValue.stringValue)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "unable to populate date time view", e)
|
||||
val notesView: EntryEditFieldView? =
|
||||
templateContainerView.findViewWithTag(FIELD_NOTES_TAG)
|
||||
notesView?.value = entryInfo.notes
|
||||
|
||||
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
|
||||
} else if (customView is DateTimeView) {
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
// If template view not found, create a new custom view
|
||||
putCustomField(customField, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getEntryInfo(): EntryInfo {
|
||||
populateEntryInfoWithViews()
|
||||
return mEntryInfo ?: EntryInfo()
|
||||
}
|
||||
|
||||
fun populateEntryInfoWithViews() {
|
||||
if (mEntryInfo == null)
|
||||
mEntryInfo = EntryInfo()
|
||||
|
||||
// Icon already populate
|
||||
mEntryInfo.title = entryTitleView.value
|
||||
mEntryInfo?.title = entryTitleView.value
|
||||
|
||||
val userNameView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_USERNAME_TAG)
|
||||
userNameView?.value?.let {
|
||||
mEntryInfo.username = it
|
||||
mEntryInfo?.username = it
|
||||
}
|
||||
|
||||
val passwordView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_PASSWORD_TAG)
|
||||
passwordView?.value?.let {
|
||||
mEntryInfo.password = it
|
||||
mEntryInfo?.password = it
|
||||
}
|
||||
|
||||
val urlView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_URL_TAG)
|
||||
urlView?.value?.let {
|
||||
mEntryInfo.url = it
|
||||
mEntryInfo?.url = it
|
||||
}
|
||||
|
||||
val expirationView: DateTimeView? = templateContainerView.findViewWithTag(FIELD_EXPIRES_TAG)
|
||||
expirationView?.activation?.let {
|
||||
mEntryInfo.expires = it
|
||||
mEntryInfo?.expires = it
|
||||
}
|
||||
expirationView?.dateTime?.let {
|
||||
mEntryInfo.expiryTime = it
|
||||
mEntryInfo?.expiryTime = it
|
||||
}
|
||||
|
||||
val notesView: EntryEditFieldView? = templateContainerView.findViewWithTag(FIELD_NOTES_TAG)
|
||||
notesView?.value?.let {
|
||||
mEntryInfo.notes = it
|
||||
mEntryInfo?.notes = it
|
||||
}
|
||||
|
||||
retrieveCustomFieldsFromView(true)
|
||||
|
||||
mEntryInfo.otpModel = OtpEntryFields.parseFields { key ->
|
||||
mEntryInfo?.otpModel = OtpEntryFields.parseFields { key ->
|
||||
getCustomField(key).protectedValue.toString()
|
||||
}?.otpModel
|
||||
}
|
||||
|
||||
return mEntryInfo
|
||||
private fun buildTemplateAndPopulateInfo() {
|
||||
if (mTemplate != null && mEntryInfo != null) {
|
||||
buildTemplate()
|
||||
populateViewsWithEntryInfo()
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------
|
||||
@@ -419,7 +450,7 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
|
||||
private fun retrieveCustomFieldsFromView(templateFieldNotEmpty: Boolean = false) {
|
||||
mEntryInfo.customFields = mCustomFieldIds.mapNotNull {
|
||||
mEntryInfo?.customFields = mCustomFieldIds.mapNotNull {
|
||||
getCustomField(it.label, templateFieldNotEmpty)
|
||||
}.toMutableList()
|
||||
}
|
||||
@@ -533,9 +564,63 @@ class TemplateView @JvmOverloads constructor(context: Context,
|
||||
retrieveCustomFieldsFromView()
|
||||
}
|
||||
|
||||
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
|
||||
buildTemplateAndPopulateInfo()
|
||||
super.onRestoreInstanceState(state.superState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(): Parcelable {
|
||||
val superSave = super.onSaveInstanceState()
|
||||
val saveState = SavedState(superSave)
|
||||
saveState.template = this.mTemplate
|
||||
saveState.entryInfo = this.mEntryInfo
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
super.writeToParcel(out, flags)
|
||||
out.writeParcelable(template, flags)
|
||||
out.writeParcelable(entryInfo, flags)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import com.kunzisoft.keepass.view.TemplateView
|
||||
|
||||
class EntryEditViewModel: ViewModel() {
|
||||
|
||||
private var mEntryInfo = EntryInfo()
|
||||
private var mTempEntryInfo = EntryInfo()
|
||||
private var mLoaded = false
|
||||
val entryInfoLoaded : LiveData<EntryInfo> get() = _entryInfoLoaded
|
||||
private val _entryInfoLoaded = SingleLiveEvent<EntryInfo>()
|
||||
@@ -69,13 +69,13 @@ class EntryEditViewModel: ViewModel() {
|
||||
fun loadEntryInfo(entryInfo: EntryInfo) {
|
||||
if (!mLoaded) {
|
||||
mLoaded = true
|
||||
updateEntryInfo(entryInfo)
|
||||
_entryInfoLoaded.value = entryInfo
|
||||
internalUpdateEntryInfo(entryInfo)
|
||||
_entryInfoLoaded.value = mTempEntryInfo
|
||||
}
|
||||
}
|
||||
|
||||
fun updateEntryInfo(entryInfo: EntryInfo) {
|
||||
mEntryInfo = entryInfo
|
||||
private fun internalUpdateEntryInfo(entryInfo: EntryInfo) {
|
||||
mTempEntryInfo = entryInfo
|
||||
// Do not save entry in upload progression
|
||||
mTempAttachments.forEach { attachmentState ->
|
||||
if (attachmentState.streamDirection == StreamDirection.UPLOAD) {
|
||||
@@ -85,7 +85,7 @@ class EntryEditViewModel: ViewModel() {
|
||||
AttachmentState.CANCELED,
|
||||
AttachmentState.ERROR -> {
|
||||
// Remove attachment not finished from info
|
||||
entryInfo.attachments = entryInfo.attachments.toMutableList().apply {
|
||||
mTempEntryInfo.attachments = mTempEntryInfo.attachments.toMutableList().apply {
|
||||
remove(attachmentState.attachment)
|
||||
}
|
||||
}
|
||||
@@ -97,8 +97,8 @@ class EntryEditViewModel: ViewModel() {
|
||||
}
|
||||
|
||||
fun saveEntryInfo(entryInfo: EntryInfo) {
|
||||
updateEntryInfo(entryInfo)
|
||||
_onEntryInfoSaved.value = EntryInfoTempAttachments(entryInfo, mTempAttachments)
|
||||
internalUpdateEntryInfo(entryInfo)
|
||||
_onEntryInfoSaved.value = EntryInfoTempAttachments(mTempEntryInfo, mTempAttachments)
|
||||
}
|
||||
|
||||
fun assignTemplate(template: Template) {
|
||||
|
||||
Reference in New Issue
Block a user