From 13e6fed951e2f30c5916fe3c3a36e952ee2a10f7 Mon Sep 17 00:00:00 2001 From: Aurelien Hubert Date: Fri, 17 May 2019 10:37:14 +0200 Subject: [PATCH] Add clean deletion for CustomField --- .../android/model/interfaces/Manageable.kt | 5 ++ .../android/model/realm/CustomField.kt | 38 ++++++++++++--- .../android/model/realm/CustomFieldEntry.kt | 44 ++++++++++++++++- .../android/ui/extensions/UIExtensions.kt | 24 ++++++---- .../components/DeletableItemFragment.kt | 1 + .../fragment/data/CustomFieldDataFragment.kt | 47 ++++++++++++++++--- .../ui/fragment/data/EditableDataFragment.kt | 4 +- 7 files changed, 140 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt index 3841e81c..7dc3068b 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt @@ -121,4 +121,9 @@ interface Deletable : Identifiable { */ fun getFailedDeleteMessage(status: DeleteValidityStatus): Int + /** + * A method to override if we need to delete linked objects or other stuff + */ + fun deleteDependencies() {} + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt index d9faa987..8afcef6e 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt @@ -7,6 +7,7 @@ import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey +import io.realm.kotlin.where import net.pokeranalytics.android.R import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus import net.pokeranalytics.android.model.interfaces.NameManageable @@ -60,13 +61,9 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa updateRowRepresentation() } - override fun localizedTitle(context: Context): String { - return this.name - } - override fun getDisplayName(context: Context): String { - return this.name - } + @Ignore + private var entriesToDelete: ArrayList = ArrayList() @Ignore override var viewType: Int = RowViewType.TITLE_VALUE_ARROW.ordinal @@ -74,6 +71,15 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa @Ignore private var rowRepresentation: List = mutableListOf() + + override fun localizedTitle(context: Context): String { + return this.name + } + + override fun getDisplayName(context: Context): String { + return this.name + } + override fun adapterRows(): List? { return rowRepresentation } @@ -119,6 +125,13 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa } } + override fun deleteDependencies() { + if (isValid) { + val entries = realm.where().equalTo("customField.id", id).findAll() + entries.deleteAllFromRealm() + } + } + override fun editDescriptors(row: RowRepresentable): ArrayList? { return when (row) { is CustomFieldEntry -> row.editingDescriptors( @@ -208,8 +221,21 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa */ fun deleteEntry(entry: CustomFieldEntry) { entries.remove(entry) + entriesToDelete.add(entry) sortEntries() updateRowRepresentation() } + /** + * Remove the deleted entries from realm + */ + fun cleanDeletedEntries(realm: Realm) { + realm.executeTransaction { + entriesToDelete.forEach { + realm.where().equalTo("id", it.id).findFirst()?.deleteFromRealm() + } + entriesToDelete.clear() + } + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt index 314f2002..0303eb2c 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt @@ -2,10 +2,16 @@ package net.pokeranalytics.android.model.realm import android.content.Context import android.text.InputType +import io.realm.Realm import io.realm.RealmObject import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey +import io.realm.kotlin.where import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.ModelException +import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus +import net.pokeranalytics.android.model.interfaces.Manageable +import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor @@ -16,10 +22,10 @@ import java.util.* import java.util.Currency -open class CustomFieldEntry : RealmObject(), RowRepresentable { +open class CustomFieldEntry : RealmObject(), Manageable, RowRepresentable { @PrimaryKey - var id = UUID.randomUUID().toString() + override var id = UUID.randomUUID().toString() var order: Int = 0 var customField: CustomField? = null @@ -60,6 +66,40 @@ open class CustomFieldEntry : RealmObject(), RowRepresentable { ) } + override fun isValidForSave(): Boolean { + return true + } + + override fun alreadyExists(realm: Realm): Boolean { + return realm.where(this::class.java).notEqualTo("id", this.id).findAll().isNotEmpty() + } + + override fun getFailedSaveMessage(status: SaveValidityStatus): Int { + throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled") + } + + override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { + return R.string.cf_entry_delete_popup_message + } + + override fun deleteDependencies() { + if (isValid) { + val entries = realm.where().equalTo("customField.id", id).findAll() + entries.deleteAllFromRealm() + } + } + + override fun updateValue(value: Any?, row: RowRepresentable) { + this.value = value as String? ?: "" + } + + override fun isValidForDelete(realm: Realm): Boolean { + if (realm.where().contains("customFieldEntries.id", id).findAll().isNotEmpty()) { + return false + } + return true + } + /** * Return the amount */ diff --git a/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt b/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt index e0c1e576..ce769da5 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt @@ -24,10 +24,6 @@ import net.pokeranalytics.android.util.billing.AppGuard import java.io.File - - - - // Sizes val Int.dp: Int get() = (this / Resources.getSystem().displayMetrics.density).toInt() @@ -70,8 +66,9 @@ fun PokerAnalyticsActivity.openPlayStorePage() { } // Open email for "Contact us" -fun PokerAnalyticsActivity.openContactMail(subjectStringRes: Int, filePath: String?= null) { - val info = "v${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE}) - ${AppGuard.isProUser}, Android ${android.os.Build.VERSION.SDK_INT}, ${DeviceUtils.getDeviceName()}" +fun PokerAnalyticsActivity.openContactMail(subjectStringRes: Int, filePath: String? = null) { + val info = + "v${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE}) - ${AppGuard.isProUser}, Android ${android.os.Build.VERSION.SDK_INT}, ${DeviceUtils.getDeviceName()}" val emailIntent = Intent(Intent.ACTION_SEND) @@ -105,6 +102,7 @@ fun PokerAnalyticsActivity.openUrl(url: String) { fun PokerAnalyticsActivity.showAlertDialog(title: Int? = null, message: Int? = null) { showAlertDialog(this, title, message) } + fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = null) { context?.let { showAlertDialog(it, title, message) @@ -114,7 +112,10 @@ fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = n /** * Create and show an alert dialog */ -fun showAlertDialog(context: Context, title: Int? = null, message: Int? = null) { +fun showAlertDialog( + context: Context, title: Int? = null, message: Int? = null, showCancelButton: Boolean = false, + positiveAction: (() -> Unit)? = null, negativeAction: (() -> Unit)? = null +) { val builder = AlertDialog.Builder(context) title?.let { builder.setTitle(title) @@ -122,7 +123,14 @@ fun showAlertDialog(context: Context, title: Int? = null, message: Int? = null) message?.let { builder.setMessage(message) } - builder.setPositiveButton(net.pokeranalytics.android.R.string.ok, null) + builder.setPositiveButton(net.pokeranalytics.android.R.string.ok) { _, _ -> + positiveAction?.invoke() + } + if (showCancelButton) { + builder.setNegativeButton(R.string.cancel) { _, _ -> + negativeAction?.invoke() + } + } builder.show() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt index 29c89ad5..69e2cfd6 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt @@ -62,6 +62,7 @@ open class DeletableItemFragment : RealmFragment() { deletedItem = getRealm().copyFromRealm(itemToDelete) lastDeletedItemPosition = itemPosition getRealm().executeTransaction { + itemToDelete.deleteDependencies() itemToDelete.deleteFromRealm() } itemHasBeenReInserted = false diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt index c5d18e73..65f0f9bb 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt @@ -8,20 +8,24 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.google.android.material.chip.ChipGroup +import io.realm.kotlin.where import kotlinx.android.synthetic.main.fragment_custom_view.* import net.pokeranalytics.android.R import net.pokeranalytics.android.model.realm.CustomField import net.pokeranalytics.android.model.realm.CustomFieldEntry +import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.extensions.ChipGroupExtension import net.pokeranalytics.android.ui.extensions.px +import net.pokeranalytics.android.ui.extensions.showAlertDialog import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.rowrepresentable.CustomFieldRow import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.util.NULL_TEXT +import timber.log.Timber import java.util.* import kotlin.collections.ArrayList @@ -36,8 +40,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa return this.item as CustomField } - private val oldRows: ArrayList = ArrayList() - private val currentEntriesOrder: ArrayList = ArrayList() + private val deletedCustomFieldEntries: ArrayList = ArrayList() private val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() { @@ -74,7 +77,15 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa return true } - override fun onMoved(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, fromPos: Int, target: RecyclerView.ViewHolder, toPos: Int, x: Int, y: Int) { + override fun onMoved( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + fromPos: Int, + target: RecyclerView.ViewHolder, + toPos: Int, + x: Int, + y: Int + ) { super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y) Collections.swap(customField.entries, fromPos - (CustomFieldRow.values().size + 1), toPos - (CustomFieldRow.values().size + 1)) @@ -157,7 +168,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa override fun onRowValueChanged(value: Any?, row: RowRepresentable) { when (row) { is CustomFieldEntry -> { - row.value = value as String? ?: "" + row.updateValue(value, row) customField.updateRowRepresentation() rowRepresentableAdapter.notifyDataSetChanged() } @@ -174,12 +185,26 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa super.onRowDeleted(row) when (row) { is CustomFieldEntry -> { + if (!row.isValidForDelete(getRealm())) { + val status = row.getDeleteStatus(getRealm()) + val message = row.getFailedDeleteMessage(status) + showAlertDialog(requireContext(), R.string.cf_entry_delete_popup_title, message, showCancelButton = true, positiveAction = { + customField.deleteEntry(row) + rowRepresentableAdapter.notifyDataSetChanged() + }) + return + } customField.deleteEntry(row) rowRepresentableAdapter.notifyDataSetChanged() } } } + override fun onDataSaved() { + super.onDataSaved() + customField.cleanDeletedEntries(getRealm()) + } + /** * Init UI */ @@ -194,7 +219,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa itemTouchHelper.attachToRecyclerView(null) } - when(customField.sortCondition) { + when (customField.sortCondition) { CustomField.Sort.DEFAULT.uniqueIdentifier -> sortDefault.isChecked = true CustomField.Sort.ASCENDING.uniqueIdentifier -> sortAscending.isChecked = true CustomField.Sort.DESCENDING.uniqueIdentifier -> sortDescending.isChecked = true @@ -215,7 +240,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa return } - when(checkedId) { + when (checkedId) { R.id.sortDefault -> customField.sortCondition = CustomField.Sort.DEFAULT.uniqueIdentifier R.id.sortAscending -> customField.sortCondition = CustomField.Sort.ASCENDING.uniqueIdentifier R.id.sortDescending -> customField.sortCondition = CustomField.Sort.DESCENDING.uniqueIdentifier @@ -238,8 +263,18 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa onRowSelected(0, it) } } + + + val entries = getRealm().where().equalTo("customField.id", customField.id).findAll() + Timber.d("delete customField: entries: ${entries.size}") + entries.forEach { + val sessions = getRealm().where().contains("customFieldEntries.id", it.id).findAll() + Timber.d("Sessions: ${sessions.size} with entry value: ${it.value}") + } + } + /** * Update UI */ diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt index d9a131a8..bbb837f9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt @@ -161,8 +161,8 @@ open class EditableDataFragment : RealmFragment(), RowRepresentableDelegate { val uniqueIdentifier = (managedItem as Savable).id finishActivityWithResult(uniqueIdentifier) } - } + onDataSaved() } else -> { val message = savable.getFailedSaveMessage(status) @@ -223,4 +223,6 @@ open class EditableDataFragment : RealmFragment(), RowRepresentableDelegate { this.primaryKey = primaryKey } + open fun onDataSaved() {} + } \ No newline at end of file