mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
First pass to entry edit as ViewModel
This commit is contained in:
@@ -61,7 +61,6 @@ import com.kunzisoft.keepass.database.element.template.*
|
||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
||||
import com.kunzisoft.keepass.model.*
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.services.AttachmentFileNotificationService
|
||||
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
|
||||
@@ -88,12 +87,6 @@ class EntryEditActivity : LockingActivity(),
|
||||
FileTooBigDialogFragment.ActionChooseListener,
|
||||
ReplaceFileDialogFragment.ActionChooseListener {
|
||||
|
||||
// Refs of an entry and group in database, are not modifiable
|
||||
private var mEntry: Entry? = null
|
||||
private var mParent: Group? = null
|
||||
private var mIsTemplate: Boolean = false
|
||||
private var mEntryTemplate: Template? = null
|
||||
|
||||
// Views
|
||||
private var coordinatorLayout: CoordinatorLayout? = null
|
||||
private var scrollView: NestedScrollView? = null
|
||||
@@ -104,14 +97,17 @@ class EntryEditActivity : LockingActivity(),
|
||||
private var lockView: View? = null
|
||||
private var loadingView: ProgressBar? = null
|
||||
|
||||
private var mEntryInfo: EntryInfo? = null
|
||||
private var mParent : Group? = null
|
||||
private var mEntry : Entry? = null
|
||||
private var mIsTemplate: Boolean = false
|
||||
private var mEntryTemplate: Template = Template.STANDARD
|
||||
private var mTempAttachments = mutableListOf<EntryAttachmentState>()
|
||||
private val mEntryEditViewModel: EntryEditViewModel by viewModels()
|
||||
|
||||
// To manage attachments
|
||||
private var mExternalFileHelper: ExternalFileHelper? = null
|
||||
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||
private var mAllowMultipleAttachments: Boolean = false
|
||||
private var mTempAttachments = ArrayList<EntryAttachmentState>()
|
||||
|
||||
// Education
|
||||
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
||||
@@ -184,10 +180,12 @@ class EntryEditActivity : LockingActivity(),
|
||||
|
||||
// Define is current entry is a template (in direct template group)
|
||||
mIsTemplate = mDatabase?.entryIsTemplate(mEntry) ?: false
|
||||
val templates = mDatabase?.getTemplates(mIsTemplate)
|
||||
|
||||
// Default template
|
||||
mEntryTemplate = mEntry?.let {
|
||||
mDatabase?.getTemplate(it)
|
||||
} ?: if (templates?.isNotEmpty() == true) Template.STANDARD else null
|
||||
} ?: Template.STANDARD
|
||||
|
||||
// Decode the entry
|
||||
mEntry?.let {
|
||||
mEntry = mDatabase?.decodeEntryWithTemplateConfiguration(it)
|
||||
@@ -205,38 +203,42 @@ class EntryEditActivity : LockingActivity(),
|
||||
tempEntryInfo?.saveRegisterInfo(mDatabase, regInfo)
|
||||
}
|
||||
|
||||
mEntryEditViewModel.setEntryInfo(tempEntryInfo)
|
||||
|
||||
// Build fragment to manage entry modification
|
||||
entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment?
|
||||
if (entryEditFragment == null) {
|
||||
entryEditFragment = EntryEditFragment.getInstance()
|
||||
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo, mEntryTemplate)
|
||||
}
|
||||
|
||||
mEntryEditViewModel.requestIconSelection.observe(this) { iconImage ->
|
||||
IconPickerActivity.launch(this@EntryEditActivity, iconImage)
|
||||
}
|
||||
mEntryEditViewModel.requestDateTimeSelection.observe(this) { dateInstant ->
|
||||
if (dateInstant.type == DateInstant.Type.TIME) {
|
||||
selectTime(dateInstant)
|
||||
} else {
|
||||
selectDate(dateInstant)
|
||||
}
|
||||
}
|
||||
mEntryEditViewModel.requestPasswordSelection.observe(this) { passwordField ->
|
||||
GeneratePasswordDialogFragment
|
||||
.getInstance(passwordField)
|
||||
.show(supportFragmentManager, "PasswordGeneratorFragment")
|
||||
}
|
||||
mEntryEditViewModel.requestCustomFieldEdition.observe(this) { field ->
|
||||
editCustomField(field)
|
||||
}
|
||||
mEntryEditViewModel.onCustomFieldError.observe(this) {
|
||||
coordinatorLayout?.let {
|
||||
Snackbar.make(it, R.string.error_field_name_already_exists, Snackbar.LENGTH_LONG)
|
||||
.asError()
|
||||
.show()
|
||||
}
|
||||
}
|
||||
entryEditFragment?.apply {
|
||||
drawFactory = mDatabase?.iconDrawableFactory
|
||||
onDateTimeClickListener = { dateInstant ->
|
||||
if (dateInstant.type == DateInstant.Type.TIME) {
|
||||
selectTime(dateInstant)
|
||||
} else {
|
||||
selectDate(dateInstant)
|
||||
}
|
||||
}
|
||||
onPasswordGeneratorClickListener = { field ->
|
||||
GeneratePasswordDialogFragment
|
||||
.getInstance(field)
|
||||
.show(supportFragmentManager, "PasswordGeneratorFragment")
|
||||
}
|
||||
// Add listener to the icon
|
||||
onIconClickListener = { iconImage ->
|
||||
IconPickerActivity.launch(this@EntryEditActivity, iconImage)
|
||||
}
|
||||
onRemoveAttachment = { attachment ->
|
||||
mAttachmentFileBinderManager?.removeBinaryAttachment(attachment)
|
||||
removeAttachment(EntryAttachmentState(attachment, StreamDirection.DOWNLOAD))
|
||||
}
|
||||
onEditCustomFieldClickListener = { field ->
|
||||
editCustomField(field)
|
||||
}
|
||||
}
|
||||
|
||||
// To show Fragment asynchronously
|
||||
@@ -252,15 +254,16 @@ class EntryEditActivity : LockingActivity(),
|
||||
// Change template dynamically
|
||||
templateSelectorSpinner = findViewById(R.id.entry_edit_template_selector)
|
||||
templateSelectorSpinner?.apply {
|
||||
val templates = mDatabase?.getTemplates(mIsTemplate) ?: listOf()
|
||||
// Build template selector
|
||||
if (templates != null && templates.isNotEmpty()) {
|
||||
if (templates.isNotEmpty()) {
|
||||
adapter = TemplatesSelectorAdapter(this@EntryEditActivity, mDatabase, templates)
|
||||
setSelection(templates.indexOf(mEntryTemplate))
|
||||
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
val newTemplate = templates[position]
|
||||
mEntryTemplate = templates[position]
|
||||
entryEditFragment?.apply {
|
||||
mEntryEditViewModel.assignTemplate(newTemplate)
|
||||
mEntryEditViewModel.assignTemplate(mEntryTemplate)
|
||||
}
|
||||
}
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
@@ -270,80 +273,64 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
mEntryEditViewModel.entryInfoLoaded.observe(this) { entryInfo ->
|
||||
mEntryInfo = entryInfo
|
||||
}
|
||||
// Build new entry from the entry info retrieved
|
||||
mEntryEditViewModel.onEntryInfoUpdated.observe(this) { entryInfo ->
|
||||
|
||||
mEntryEditViewModel.saveEntryResponded.observe(this) { entryInfoSaved ->
|
||||
// Get the temp entry
|
||||
entryInfoSaved?.let { newEntryInfo ->
|
||||
mEntry?.let {
|
||||
// Create a clone
|
||||
var newEntry = Entry(it)
|
||||
mEntry?.let { oldEntry ->
|
||||
// Create a clone
|
||||
var newEntry = Entry(oldEntry)
|
||||
|
||||
// Do not save entry in upload progression
|
||||
mTempAttachments.forEach { attachmentState ->
|
||||
if (attachmentState.streamDirection == StreamDirection.UPLOAD) {
|
||||
when (attachmentState.downloadState) {
|
||||
AttachmentState.START,
|
||||
AttachmentState.IN_PROGRESS,
|
||||
AttachmentState.CANCELED,
|
||||
AttachmentState.ERROR -> {
|
||||
// Remove attachment not finished from info
|
||||
newEntryInfo.attachments = newEntryInfo.attachments.toMutableList().apply {
|
||||
remove(attachmentState.attachment)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Do not save entry in upload progression
|
||||
mTempAttachments.forEach { attachmentState ->
|
||||
if (attachmentState.streamDirection == StreamDirection.UPLOAD) {
|
||||
when (attachmentState.downloadState) {
|
||||
AttachmentState.START,
|
||||
AttachmentState.IN_PROGRESS,
|
||||
AttachmentState.CANCELED,
|
||||
AttachmentState.ERROR -> {
|
||||
// Remove attachment not finished from info
|
||||
entryInfo.attachments = entryInfo.attachments.toMutableList().apply {
|
||||
remove(attachmentState.attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build info
|
||||
newEntry.setEntryInfo(mDatabase, newEntryInfo)
|
||||
|
||||
// Encode entry properties for template
|
||||
mEntryTemplate?.let { template ->
|
||||
newEntry = mDatabase?.encodeEntryWithTemplateConfiguration(newEntry, template)
|
||||
?: newEntry
|
||||
}
|
||||
|
||||
// Delete temp attachment if not used
|
||||
mTempAttachments.forEach { tempAttachmentState ->
|
||||
val tempAttachment = tempAttachmentState.attachment
|
||||
mDatabase?.attachmentPool?.let { binaryPool ->
|
||||
if (!newEntry.getAttachments(binaryPool).contains(tempAttachment)) {
|
||||
mDatabase?.removeAttachmentIfNotUsed(tempAttachment)
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open a progress dialog and save entry
|
||||
if (isEntryCreation()) {
|
||||
mParent?.let { parent ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseCreateEntry(
|
||||
newEntry,
|
||||
parent,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
} else {
|
||||
mEntry?.let { oldEntry ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseUpdateEntry(
|
||||
oldEntry,
|
||||
newEntry,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve temp attachments in case of deletion
|
||||
if (savedInstanceState?.containsKey(TEMP_ATTACHMENTS) == true) {
|
||||
mTempAttachments = savedInstanceState.getParcelableArrayList(TEMP_ATTACHMENTS) ?: mTempAttachments
|
||||
// Build info
|
||||
newEntry.setEntryInfo(mDatabase, entryInfo)
|
||||
|
||||
// Encode entry properties for template
|
||||
newEntry = mDatabase?.encodeEntryWithTemplateConfiguration(newEntry, mEntryTemplate)
|
||||
?: newEntry
|
||||
|
||||
// Delete temp attachment if not used
|
||||
mTempAttachments.forEach { tempAttachmentState ->
|
||||
val tempAttachment = tempAttachmentState.attachment
|
||||
mDatabase?.attachmentPool?.let { binaryPool ->
|
||||
if (!newEntry.getAttachments(binaryPool).contains(tempAttachment)) {
|
||||
mDatabase?.removeAttachmentIfNotUsed(tempAttachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open a progress dialog and save entry
|
||||
mParent?.let { parent ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseCreateEntry(
|
||||
newEntry,
|
||||
parent,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
} ?: run {
|
||||
mProgressDatabaseTaskProvider?.startDatabaseUpdateEntry(
|
||||
oldEntry,
|
||||
newEntry,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To retrieve attachment
|
||||
@@ -372,26 +359,25 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
if (newNodes.size == 1) {
|
||||
(newNodes[0] as? Entry?)?.let { entry ->
|
||||
mEntry = entry
|
||||
EntrySelectionHelper.doSpecialAction(intent,
|
||||
{
|
||||
// Finish naturally
|
||||
finishForEntryResult()
|
||||
finishForEntryResult(actionTask, entry)
|
||||
},
|
||||
{
|
||||
// Nothing when search retrieved
|
||||
},
|
||||
{
|
||||
entryValidatedForSave()
|
||||
entryValidatedForSave(actionTask, entry)
|
||||
},
|
||||
{
|
||||
entryValidatedForKeyboardSelection(entry)
|
||||
entryValidatedForKeyboardSelection(actionTask, entry)
|
||||
},
|
||||
{ _, _ ->
|
||||
entryValidatedForAutofillSelection(entry)
|
||||
},
|
||||
{
|
||||
entryValidatedForAutofillRegistration()
|
||||
entryValidatedForAutofillRegistration(actionTask, entry)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -411,16 +397,12 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEntryCreation() : Boolean {
|
||||
return mParent != null
|
||||
}
|
||||
|
||||
private fun entryValidatedForSave() {
|
||||
private fun entryValidatedForSave(actionTask: String, entry: Entry) {
|
||||
onValidateSpecialMode()
|
||||
finishForEntryResult()
|
||||
finishForEntryResult(actionTask, entry)
|
||||
}
|
||||
|
||||
private fun entryValidatedForKeyboardSelection(entry: Entry) {
|
||||
private fun entryValidatedForKeyboardSelection(actionTask: String, entry: Entry) {
|
||||
// Populate Magikeyboard with entry
|
||||
mDatabase?.let { database ->
|
||||
populateKeyboardAndMoveAppToBackground(this,
|
||||
@@ -429,7 +411,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
onValidateSpecialMode()
|
||||
// Don't keep activity history for entry edition
|
||||
finishForEntryResult()
|
||||
finishForEntryResult(actionTask, entry)
|
||||
}
|
||||
|
||||
private fun entryValidatedForAutofillSelection(entry: Entry) {
|
||||
@@ -444,9 +426,9 @@ class EntryEditActivity : LockingActivity(),
|
||||
onValidateSpecialMode()
|
||||
}
|
||||
|
||||
private fun entryValidatedForAutofillRegistration() {
|
||||
private fun entryValidatedForAutofillRegistration(actionTask: String, entry: Entry) {
|
||||
onValidateSpecialMode()
|
||||
finishForEntryResult()
|
||||
finishForEntryResult(actionTask, entry)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -474,7 +456,8 @@ class EntryEditActivity : LockingActivity(),
|
||||
getAttachmentViewPosition(entryAttachmentState) {
|
||||
scrollView?.smoothScrollTo(0, it.toInt())
|
||||
}
|
||||
} // Add in temp list
|
||||
}
|
||||
// Add in temp list
|
||||
mTempAttachments.add(entryAttachmentState)
|
||||
}
|
||||
AttachmentState.IN_PROGRESS -> {
|
||||
@@ -528,25 +511,16 @@ class EntryEditActivity : LockingActivity(),
|
||||
//}
|
||||
}
|
||||
|
||||
private fun showAddCustomFieldError() {
|
||||
coordinatorLayout?.let {
|
||||
Snackbar.make(it, R.string.error_field_name_already_exists, Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewCustomFieldApproved(newField: Field) {
|
||||
if (entryEditFragment?.putCustomField(newField) != true)
|
||||
showAddCustomFieldError()
|
||||
mEntryEditViewModel.addCustomField(newField)
|
||||
}
|
||||
|
||||
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
|
||||
if (entryEditFragment?.replaceCustomField(oldField, newField) != true) {
|
||||
showAddCustomFieldError()
|
||||
}
|
||||
mEntryEditViewModel.editCustomField(oldField, newField)
|
||||
}
|
||||
|
||||
override fun onDeleteCustomFieldApproved(oldField: Field) {
|
||||
entryEditFragment?.removeCustomField(oldField)
|
||||
mEntryEditViewModel.removeCustomField(oldField)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -596,7 +570,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
IconPickerActivity.onActivityResult(requestCode, resultCode, data) { icon ->
|
||||
entryEditFragment?.setIcon(icon)
|
||||
mEntryEditViewModel.selectIcon(icon)
|
||||
}
|
||||
|
||||
mExternalFileHelper?.onOpenDocumentResult(requestCode, resultCode, data) { uri ->
|
||||
@@ -618,11 +592,8 @@ class EntryEditActivity : LockingActivity(),
|
||||
/**
|
||||
* Set up OTP (HOTP or TOTP) and add it as extra field
|
||||
*/
|
||||
private fun setupOTP() {
|
||||
// Retrieve the current otpElement if exists
|
||||
// and open the dialog to set up the OTP
|
||||
SetOTPDialogFragment.build(mEntryInfo?.otpModel)
|
||||
.show(supportFragmentManager, "addOTPDialog")
|
||||
private fun setupOtp() {
|
||||
entryEditFragment?.setupOtp()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -630,7 +601,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
*/
|
||||
private fun saveEntry() {
|
||||
mAttachmentFileBinderManager?.stopUploadAllAttachments()
|
||||
mEntryEditViewModel.sendRequestSaveEntry()
|
||||
mEntryEditViewModel.requestEntryInfoUpdate()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
@@ -705,7 +676,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
|
||||
setupOtpView,
|
||||
{
|
||||
setupOTP()
|
||||
setupOtp()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -724,7 +695,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
return true
|
||||
}
|
||||
R.id.menu_add_otp -> {
|
||||
setupOTP()
|
||||
setupOtp()
|
||||
return true
|
||||
}
|
||||
android.R.id.home -> {
|
||||
@@ -736,23 +707,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
override fun onOtpCreated(otpElement: OtpElement) {
|
||||
var titleOTP: String? = null
|
||||
var usernameOTP: String? = null
|
||||
// Build a temp entry to get title and username (by ref)
|
||||
mEntryInfo?.let { entryInfo ->
|
||||
val entryTemp = mDatabase?.createEntry()
|
||||
entryTemp?.setEntryInfo(mDatabase, entryInfo)
|
||||
mDatabase?.startManageEntry(entryTemp)
|
||||
titleOTP = entryTemp?.title
|
||||
usernameOTP = entryTemp?.username
|
||||
mDatabase?.stopManageEntry(mEntry)
|
||||
}
|
||||
// Update the otp field with otpauth:// url
|
||||
val otpField = OtpEntryFields.buildOtpField(otpElement, titleOTP, usernameOTP)
|
||||
mEntry?.putExtraField(Field(otpField.name, otpField.protectedValue))
|
||||
entryEditFragment?.apply {
|
||||
putCustomField(otpField)
|
||||
}
|
||||
entryEditFragment?.onOtpCreated(otpElement)
|
||||
}
|
||||
|
||||
// Launch the date picker
|
||||
@@ -778,23 +733,16 @@ class EntryEditActivity : LockingActivity(),
|
||||
// To fix android 4.4 issue
|
||||
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
|
||||
if (datePicker?.isShown == true) {
|
||||
entryEditFragment?.setDate(year, month, day)
|
||||
mEntryEditViewModel.selectDate(year, month, day)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
|
||||
entryEditFragment?.setTime(hours, minutes)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
||||
outState.putParcelableArrayList(TEMP_ATTACHMENTS, mTempAttachments)
|
||||
|
||||
super.onSaveInstanceState(outState)
|
||||
mEntryEditViewModel.selectTime(hours, minutes)
|
||||
}
|
||||
|
||||
override fun acceptPassword(passwordField: Field) {
|
||||
entryEditFragment?.setPassword(passwordField)
|
||||
mEntryEditViewModel.selectPassword(passwordField)
|
||||
entryEditActivityEducation?.let {
|
||||
Handler(Looper.getMainLooper()).post { performedNextEducation(it) }
|
||||
}
|
||||
@@ -832,17 +780,18 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishForEntryResult() {
|
||||
private fun finishForEntryResult(actionTask: String, entry: Entry) {
|
||||
// Assign entry callback as a result
|
||||
try {
|
||||
mEntry?.let { entry ->
|
||||
val bundle = Bundle()
|
||||
val intentEntry = Intent()
|
||||
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry)
|
||||
intentEntry.putExtras(bundle)
|
||||
if (isEntryCreation()) {
|
||||
val bundle = Bundle()
|
||||
val intentEntry = Intent()
|
||||
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, entry)
|
||||
intentEntry.putExtras(bundle)
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_CREATE_ENTRY_TASK -> {
|
||||
setResult(ADD_ENTRY_RESULT_CODE, intentEntry)
|
||||
} else {
|
||||
}
|
||||
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
|
||||
setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry)
|
||||
}
|
||||
}
|
||||
@@ -861,9 +810,6 @@ class EntryEditActivity : LockingActivity(),
|
||||
const val KEY_ENTRY = "entry"
|
||||
const val KEY_PARENT = "parent"
|
||||
|
||||
// SaveInstanceState
|
||||
const val TEMP_ATTACHMENTS = "TEMP_ATTACHMENTS"
|
||||
|
||||
// Keys for callback
|
||||
const val ADD_ENTRY_RESULT_CODE = 31
|
||||
const val UPDATE_ENTRY_RESULT_CODE = 32
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -34,6 +35,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.EntryEditActivity
|
||||
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
|
||||
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
|
||||
@@ -53,13 +55,14 @@ import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||
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.view.*
|
||||
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
|
||||
import org.joda.time.DateTime
|
||||
|
||||
class EntryEditFragment: DatabaseFragment() {
|
||||
class EntryEditFragment: DatabaseFragment(), SetOTPDialogFragment.CreateOtpListener {
|
||||
|
||||
private lateinit var rootView: View
|
||||
private lateinit var entryIconView: ImageView
|
||||
@@ -76,10 +79,6 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
private var iconColor: Int = 0
|
||||
|
||||
var drawFactory: IconDrawableFactory? = null
|
||||
var onIconClickListener: ((IconImage) -> Unit)? = null
|
||||
var onPasswordGeneratorClickListener: ((Field) -> Unit)? = null
|
||||
var onDateTimeClickListener: ((DateInstant) -> Unit)? = null
|
||||
var onEditCustomFieldClickListener: ((Field) -> Unit)? = null
|
||||
var onRemoveAttachment: ((Attachment) -> Unit)? = null
|
||||
|
||||
private val mEntryEditViewModel: EntryEditViewModel by activityViewModels()
|
||||
@@ -102,7 +101,7 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
|
||||
entryIconView = rootView.findViewById(R.id.entry_edit_icon_button)
|
||||
entryIconView.setOnClickListener {
|
||||
onIconClickListener?.invoke(mEntryInfo.icon)
|
||||
mEntryEditViewModel.requestIconSelection(mEntryInfo.icon)
|
||||
}
|
||||
entryTitleView = rootView.findViewById(R.id.entry_edit_title)
|
||||
templateContainerView = rootView.findViewById(R.id.template_fields_container)
|
||||
@@ -139,24 +138,94 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
|
||||
rootView.resetAppTimeoutWhenViewFocusedOrChanged(requireContext(), mDatabase)
|
||||
|
||||
// Retrieve the new entry after an orientation change
|
||||
if (arguments?.containsKey(KEY_ENTRY_INFO) == true)
|
||||
mEntryInfo = arguments?.getParcelable(KEY_ENTRY_INFO) ?: mEntryInfo
|
||||
else if (savedInstanceState?.containsKey(KEY_ENTRY_INFO) == true) {
|
||||
mEntryInfo = savedInstanceState.getParcelable(KEY_ENTRY_INFO) ?: mEntryInfo
|
||||
}
|
||||
|
||||
if (arguments?.containsKey(KEY_TEMPLATE) == true)
|
||||
mTemplate = arguments?.getParcelable(KEY_TEMPLATE) ?: mTemplate
|
||||
else if (savedInstanceState?.containsKey(KEY_TEMPLATE) == true) {
|
||||
mTemplate = savedInstanceState.getParcelable(KEY_TEMPLATE) ?: mTemplate
|
||||
}
|
||||
|
||||
if (savedInstanceState?.containsKey(KEY_SELECTION_DATE_TIME_ID) == true) {
|
||||
mTempDateTimeViewId = savedInstanceState.getInt(KEY_SELECTION_DATE_TIME_ID)
|
||||
}
|
||||
|
||||
mEntryEditViewModel.entryInfoLoaded.observe(viewLifecycleOwner) { entryInfo ->
|
||||
mEntryInfo = entryInfo
|
||||
populateViewsWithEntry()
|
||||
}
|
||||
populateViewsWithEntry()
|
||||
|
||||
mEntryEditViewModel.templateChanged.observe(viewLifecycleOwner) { template ->
|
||||
mEntryEditViewModel.onTemplateChanged.observe(viewLifecycleOwner) { template ->
|
||||
mTemplate = template
|
||||
populateViewsWithEntry()
|
||||
rootView.showByFading()
|
||||
}
|
||||
|
||||
mEntryEditViewModel.saveEntryRequested.observe(viewLifecycleOwner) {
|
||||
mEntryEditViewModel.requestEntryInfoUpdate.observe(viewLifecycleOwner) {
|
||||
populateEntryWithViews()
|
||||
mEntryEditViewModel.setResponseSaveEntry(mEntryInfo)
|
||||
mEntryEditViewModel.updateEntryInfo(mEntryInfo)
|
||||
}
|
||||
|
||||
mEntryEditViewModel.onIconSelected.observe(viewLifecycleOwner) {
|
||||
setIcon(it)
|
||||
}
|
||||
|
||||
mEntryEditViewModel.onPasswordSelected.observe(viewLifecycleOwner) {
|
||||
setPassword(it)
|
||||
}
|
||||
|
||||
mEntryEditViewModel.onDateSelected.observe(viewLifecycleOwner) {
|
||||
// Save the date
|
||||
setCurrentDateTimeSelection { instant ->
|
||||
val newDateInstant = DateInstant(DateTime(instant.date)
|
||||
.withYear(it.year)
|
||||
.withMonthOfYear(it.month + 1)
|
||||
.withDayOfMonth(it.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
|
||||
mEntryEditViewModel.requestDateTimeSelection(instantTime)
|
||||
}
|
||||
newDateInstant
|
||||
}
|
||||
}
|
||||
|
||||
mEntryEditViewModel.onTimeSelected.observe(viewLifecycleOwner) {
|
||||
// Save the time
|
||||
setCurrentDateTimeSelection { instant ->
|
||||
DateInstant(DateTime(instant.date)
|
||||
.withHourOfDay(it.hours)
|
||||
.withMinuteOfHour(it.minutes)
|
||||
.toDate(), instant.type)
|
||||
}
|
||||
}
|
||||
|
||||
mEntryEditViewModel.onCustomFieldEdited.observe(viewLifecycleOwner) { fieldAction ->
|
||||
// Field to add
|
||||
if (fieldAction.oldField == null) {
|
||||
fieldAction.newField?.let {
|
||||
if (!putCustomField(it)) {
|
||||
mEntryEditViewModel.showCustomFieldEditionError()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Field to replace
|
||||
fieldAction.oldField?.let {
|
||||
fieldAction.newField?.let {
|
||||
if (!replaceCustomField(fieldAction.oldField, fieldAction.newField)) {
|
||||
mEntryEditViewModel.showCustomFieldEditionError()
|
||||
}
|
||||
}
|
||||
}
|
||||
// Field to remove
|
||||
if (fieldAction.newField == null) {
|
||||
fieldAction.oldField?.let {
|
||||
removeCustomField(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assignAttachments(mEntryInfo.attachments, StreamDirection.UPLOAD) { attachment ->
|
||||
@@ -167,15 +236,17 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
return rootView
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
drawFactory = mDatabase?.iconDrawableFactory
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
super.onDetach()
|
||||
|
||||
drawFactory = null
|
||||
onDateTimeClickListener = null
|
||||
onPasswordGeneratorClickListener = null
|
||||
onIconClickListener = null
|
||||
onRemoveAttachment = null
|
||||
onEditCustomFieldClickListener = null
|
||||
}
|
||||
|
||||
fun generatePasswordEducationPerformed(entryEditActivityEducation: EntryEditActivityEducation): Boolean {
|
||||
@@ -346,12 +417,12 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
}
|
||||
TemplateAttributeAction.CUSTOM_EDITION -> {
|
||||
setOnActionClickListener({
|
||||
onEditCustomFieldClickListener?.invoke(field)
|
||||
mEntryEditViewModel.requestCustomFieldEdition(field)
|
||||
}, R.drawable.ic_more_white_24dp)
|
||||
}
|
||||
TemplateAttributeAction.PASSWORD_GENERATION -> {
|
||||
setOnActionClickListener({
|
||||
onPasswordGeneratorClickListener?.invoke(field)
|
||||
mEntryEditViewModel.requestPasswordSelection(field)
|
||||
}, R.drawable.ic_generate_password_white_24dp)
|
||||
}
|
||||
}
|
||||
@@ -387,7 +458,7 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
}
|
||||
setOnDateClickListener = { dateInstant ->
|
||||
mTempDateTimeViewId = id
|
||||
onDateTimeClickListener?.invoke(dateInstant)
|
||||
mEntryEditViewModel.requestDateTimeSelection(dateInstant)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -450,12 +521,12 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
?: customFieldsContainerView.findViewById(viewId)
|
||||
}
|
||||
|
||||
fun setIcon(iconImage: IconImage) {
|
||||
private fun setIcon(iconImage: IconImage) {
|
||||
mEntryInfo.icon = iconImage
|
||||
drawFactory?.assignDatabaseIcon(entryIconView, iconImage, iconColor)
|
||||
}
|
||||
|
||||
fun setPassword(passwordField: Field) {
|
||||
private fun setPassword(passwordField: Field) {
|
||||
val passwordValue = passwordField.protectedValue.stringValue
|
||||
mEntryInfo.password = passwordValue
|
||||
val passwordView = getFieldViewByField(passwordField)
|
||||
@@ -475,33 +546,6 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
fun setDate(year: Int, month: Int, day: Int) {
|
||||
// Save the date
|
||||
setCurrentDateTimeSelection { instant ->
|
||||
val newDateInstant = DateInstant(DateTime(instant.date)
|
||||
.withYear(year)
|
||||
.withMonthOfYear(month + 1)
|
||||
.withDayOfMonth(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
|
||||
onDateTimeClickListener?.invoke(instantTime)
|
||||
}
|
||||
newDateInstant
|
||||
}
|
||||
}
|
||||
|
||||
fun setTime(hours: Int, minutes: Int) {
|
||||
// Save the time
|
||||
setCurrentDateTimeSelection { instant ->
|
||||
DateInstant(DateTime(instant.date)
|
||||
.withHourOfDay(hours)
|
||||
.withMinuteOfHour(minutes)
|
||||
.toDate(), instant.type)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------
|
||||
* Custom Fields
|
||||
* -------------
|
||||
@@ -539,7 +583,7 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
/**
|
||||
* Update a custom field or create a new one if doesn't exists, the old value is lost
|
||||
*/
|
||||
fun putCustomField(customField: Field): Boolean {
|
||||
private fun putCustomField(customField: Field): Boolean {
|
||||
return if (!isStandardFieldName(customField.name)) {
|
||||
customFieldsContainerView.visibility = View.VISIBLE
|
||||
if (indexCustomFieldIdByName(customField.name) >= 0) {
|
||||
@@ -561,7 +605,7 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
/**
|
||||
* Update a custom field and keep the old value
|
||||
*/
|
||||
fun replaceCustomField(oldField: Field, newField: Field): Boolean {
|
||||
private fun replaceCustomField(oldField: Field, newField: Field): Boolean {
|
||||
if (!isStandardFieldName(newField.name)) {
|
||||
customFieldIdByName(oldField.name)?.viewId?.let { viewId ->
|
||||
customFieldsContainerView.findViewById<View>(viewId)?.let { viewToReplace ->
|
||||
@@ -590,7 +634,7 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
return false
|
||||
}
|
||||
|
||||
fun removeCustomField(oldCustomField: Field) {
|
||||
private fun removeCustomField(oldCustomField: Field) {
|
||||
val indexOldField = indexCustomFieldIdByName(oldCustomField.name)
|
||||
if (indexOldField > 0) {
|
||||
mCustomFieldIds[indexOldField].viewId.let { viewId ->
|
||||
@@ -600,6 +644,19 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
fun setupOtp() {
|
||||
// Retrieve the current otpElement if exists
|
||||
// and open the dialog to set up the OTP
|
||||
SetOTPDialogFragment.build(mEntryInfo.otpModel)
|
||||
.show(parentFragmentManager, "addOTPDialog")
|
||||
}
|
||||
|
||||
override fun onOtpCreated(otpElement: OtpElement) {
|
||||
// Update the otp field with otpauth:// url
|
||||
val otpField = OtpEntryFields.buildOtpField(otpElement, mEntryInfo.title, mEntryInfo.username)
|
||||
putCustomField(Field(otpField.name, otpField.protectedValue))
|
||||
}
|
||||
|
||||
/* -------------
|
||||
* Attachments
|
||||
* -------------
|
||||
@@ -658,6 +715,8 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
populateEntryWithViews()
|
||||
outState.putParcelable(KEY_ENTRY_INFO, mEntryInfo)
|
||||
outState.putParcelable(KEY_TEMPLATE, mTemplate)
|
||||
mTempDateTimeViewId?.let {
|
||||
outState.putInt(KEY_SELECTION_DATE_TIME_ID, it)
|
||||
}
|
||||
@@ -666,6 +725,8 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_ENTRY_INFO = "KEY_ENTRY_INFO"
|
||||
private const val KEY_TEMPLATE = "KEY_TEMPLATE"
|
||||
private const val KEY_SELECTION_DATE_TIME_ID = "KEY_SELECTION_DATE_TIME_ID"
|
||||
|
||||
private const val FIELD_USERNAME_TAG = "FIELD_USERNAME_TAG"
|
||||
@@ -675,9 +736,13 @@ class EntryEditFragment: DatabaseFragment() {
|
||||
private const val FIELD_NOTES_TAG = "FIELD_NOTES_TAG"
|
||||
private const val FIELD_CUSTOM_TAG = "FIELD_CUSTOM_TAG"
|
||||
|
||||
fun getInstance(): EntryEditFragment {
|
||||
fun getInstance(entryInfo: EntryInfo?,
|
||||
template: Template?): EntryEditFragment {
|
||||
return EntryEditFragment().apply {
|
||||
arguments = Bundle().apply {}
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(KEY_ENTRY_INFO, entryInfo)
|
||||
putParcelable(KEY_TEMPLATE, template)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class EntryInfo : NodeInfo {
|
||||
var customFields: List<Field> = listOf()
|
||||
var attachments: List<Attachment> = listOf()
|
||||
var otpModel: OtpModel? = null
|
||||
var isTemplate: Boolean = false
|
||||
|
||||
constructor() : super()
|
||||
|
||||
@@ -52,6 +53,7 @@ class EntryInfo : NodeInfo {
|
||||
parcel.readList(customFields, Field::class.java.classLoader)
|
||||
parcel.readList(attachments, Attachment::class.java.classLoader)
|
||||
otpModel = parcel.readParcelable(OtpModel::class.java.classLoader) ?: otpModel
|
||||
isTemplate = parcel.readByte().toInt() != 0
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
@@ -68,6 +70,7 @@ class EntryInfo : NodeInfo {
|
||||
parcel.writeArray(customFields.toTypedArray())
|
||||
parcel.writeArray(attachments.toTypedArray())
|
||||
parcel.writeParcelable(otpModel, flags)
|
||||
parcel.writeByte((if (isTemplate) 1 else 0).toByte())
|
||||
}
|
||||
|
||||
fun containsCustomFieldsProtected(): Boolean {
|
||||
|
||||
@@ -1,43 +1,114 @@
|
||||
package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.template.Template
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
|
||||
|
||||
class EntryEditViewModel: ViewModel() {
|
||||
|
||||
val entryInfoLoaded : LiveData<EntryInfo> get() = _entryInfo
|
||||
private val _entryInfo = MutableLiveData<EntryInfo>()
|
||||
|
||||
val saveEntryRequested : LiveData<EntryInfo> get() = _requestSaveEntry
|
||||
private val _requestSaveEntry = SingleLiveEvent<EntryInfo>()
|
||||
val saveEntryResponded : LiveData<EntryInfo> get() = _responseSaveEntry
|
||||
private val _responseSaveEntry = SingleLiveEvent<EntryInfo>()
|
||||
val requestEntryInfoUpdate : LiveData<Void?> get() = _requestEntryInfoUpdate
|
||||
private val _requestEntryInfoUpdate = SingleLiveEvent<Void?>()
|
||||
val onEntryInfoUpdated : LiveData<EntryInfo> get() = _onEntryInfoUpdated
|
||||
private val _onEntryInfoUpdated = SingleLiveEvent<EntryInfo>()
|
||||
|
||||
val templateChanged : LiveData<Template> get() = _template
|
||||
private val _template = SingleLiveEvent<Template>()
|
||||
val onTemplateChanged : LiveData<Template> get() = _onTemplateChanged
|
||||
private val _onTemplateChanged = SingleLiveEvent<Template>()
|
||||
|
||||
fun setEntryInfo(entryInfo: EntryInfo?) {
|
||||
_entryInfo.value = entryInfo
|
||||
val requestIconSelection : LiveData<IconImage> get() = _requestIconSelection
|
||||
private val _requestIconSelection = SingleLiveEvent<IconImage>()
|
||||
val onIconSelected : LiveData<IconImage> get() = _onIconSelected
|
||||
private val _onIconSelected = SingleLiveEvent<IconImage>()
|
||||
|
||||
val requestPasswordSelection : LiveData<Field> get() = _requestPasswordSelection
|
||||
private val _requestPasswordSelection = SingleLiveEvent<Field>()
|
||||
val onPasswordSelected : LiveData<Field> get() = _onPasswordSelected
|
||||
private val _onPasswordSelected = SingleLiveEvent<Field>()
|
||||
|
||||
val requestCustomFieldEdition : LiveData<Field> get() = _requestCustomFieldEdition
|
||||
private val _requestCustomFieldEdition = SingleLiveEvent<Field>()
|
||||
val onCustomFieldEdited : LiveData<FieldEdition> get() = _onCustomFieldEdited
|
||||
private val _onCustomFieldEdited = SingleLiveEvent<FieldEdition>()
|
||||
val onCustomFieldError : LiveData<Void?> get() = _onCustomFieldError
|
||||
private val _onCustomFieldError = SingleLiveEvent<Void?>()
|
||||
|
||||
val requestDateTimeSelection : LiveData<DateInstant> get() = _requestDateTimeSelection
|
||||
private val _requestDateTimeSelection = SingleLiveEvent<DateInstant>()
|
||||
val onDateSelected : LiveData<Date> get() = _onDateSelected
|
||||
private val _onDateSelected = SingleLiveEvent<Date>()
|
||||
val onTimeSelected : LiveData<Time> get() = _onTimeSelected
|
||||
private val _onTimeSelected = SingleLiveEvent<Time>()
|
||||
|
||||
fun requestEntryInfoUpdate() {
|
||||
_requestEntryInfoUpdate.call()
|
||||
}
|
||||
|
||||
fun sendRequestSaveEntry() {
|
||||
_requestSaveEntry.value = entryInfoLoaded.value
|
||||
}
|
||||
|
||||
fun setResponseSaveEntry(entryInfo: EntryInfo?) {
|
||||
_responseSaveEntry.value = entryInfo
|
||||
fun updateEntryInfo(entryInfo: EntryInfo) {
|
||||
_onEntryInfoUpdated.value = entryInfo
|
||||
}
|
||||
|
||||
fun assignTemplate(template: Template) {
|
||||
if (this.templateChanged.value != template) {
|
||||
_template.value = template
|
||||
if (this.onTemplateChanged.value != template) {
|
||||
_onTemplateChanged.value = template
|
||||
}
|
||||
}
|
||||
|
||||
fun requestIconSelection(oldIconImage: IconImage) {
|
||||
_requestIconSelection.value = oldIconImage
|
||||
}
|
||||
|
||||
fun selectIcon(iconImage: IconImage) {
|
||||
_onIconSelected.value = iconImage
|
||||
}
|
||||
|
||||
fun requestPasswordSelection(passwordField: Field) {
|
||||
_requestPasswordSelection.value = passwordField
|
||||
}
|
||||
|
||||
fun selectPassword(passwordField: Field) {
|
||||
_onPasswordSelected.value = passwordField
|
||||
}
|
||||
|
||||
fun requestCustomFieldEdition(customField: Field) {
|
||||
_requestCustomFieldEdition.value = customField
|
||||
}
|
||||
|
||||
fun addCustomField(newField: Field) {
|
||||
_onCustomFieldEdited.value = FieldEdition(null, newField)
|
||||
}
|
||||
|
||||
fun editCustomField(oldField: Field, newField: Field) {
|
||||
_onCustomFieldEdited.value = FieldEdition(oldField, newField)
|
||||
}
|
||||
|
||||
fun removeCustomField(oldField: Field) {
|
||||
_onCustomFieldEdited.value = FieldEdition(oldField, null)
|
||||
}
|
||||
|
||||
fun showCustomFieldEditionError() {
|
||||
_onCustomFieldError.call()
|
||||
}
|
||||
|
||||
fun requestDateTimeSelection(dateInstant: DateInstant) {
|
||||
_requestDateTimeSelection.value = dateInstant
|
||||
}
|
||||
|
||||
fun selectDate(year: Int, month: Int, day: Int) {
|
||||
_onDateSelected.value = Date(year, month, day)
|
||||
}
|
||||
|
||||
fun selectTime(hours: Int, minutes: Int) {
|
||||
_onTimeSelected.value = Time(hours, minutes)
|
||||
}
|
||||
|
||||
data class Date(val year: Int, val month: Int, val day: Int)
|
||||
data class Time(val hours: Int, val minutes: Int)
|
||||
data class FieldEdition(val oldField: Field?, val newField: Field?)
|
||||
|
||||
companion object {
|
||||
private val TAG = EntryEditViewModel::class.java.name
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user