From 322702471f6f5c389f86afc63cbc2e963c4bf5f5 Mon Sep 17 00:00:00 2001 From: Aurelien Hubert Date: Thu, 9 May 2019 10:46:20 +0200 Subject: [PATCH 1/8] Fix bug during deletion --- .../android/ui/fragment/DataListFragment.kt | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt index d9fea442..71c47e4a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt @@ -46,7 +46,7 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep private var deletedItem: RealmObject? = null private var lastDeletedItemPosition: Int = 0 - private var lastItemClickedPosition: Int = 0 + private var lastItemClickedId: String = "" override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) @@ -61,11 +61,12 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_DETAILS && resultCode == Activity.RESULT_OK) { - val needToDeleteItem = data?.getBooleanExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, false) ?: false + val needToDeleteItem = + data?.getBooleanExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, false) ?: false if (needToDeleteItem) { GlobalScope.launch(Dispatchers.Main) { delay(300) - deleteItem(lastItemClickedPosition) + deleteItem(lastItemClickedId) } } } @@ -95,20 +96,15 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { this.dataType?.let { - if (it == LiveData.FILTER) { - val intent = Intent() - intent.putExtra(FiltersActivity.IntentKey.FILTER_ID.keyName, (row as Filter).id) - activity?.setResult(Activity.RESULT_OK, intent) - activity?.finish() - } else { - lastItemClickedPosition = position - EditableDataActivity.newInstanceForResult( - this, - it.ordinal, - (row as Identifiable).id, - REQUEST_CODE_DETAILS - ) - } + if (it == LiveData.FILTER) { + val intent = Intent() + intent.putExtra(FiltersActivity.IntentKey.FILTER_ID.keyName, (row as Filter).id) + activity?.setResult(Activity.RESULT_OK, intent) + activity?.finish() + } else { + lastItemClickedId = (row as Identifiable).id + EditableDataActivity.newInstanceForResult(this, it.ordinal, lastItemClickedId, REQUEST_CODE_DETAILS) + } } } @@ -130,7 +126,7 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep dataListAdapter = RowRepresentableAdapter(this, this) val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position -> - deleteItem(position) + deleteItem((this.items[position] as Identifiable).id) } val itemTouchHelper = ItemTouchHelper(swipeToDelete) @@ -156,30 +152,31 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep /** * Delete item */ - private fun deleteItem(position: Int) { + private fun deleteItem(itemId: String) { if (isDetached || activity == null) { return } // Save the delete position & create a copy of the object - val mRecentlyDeletedItem = rowRepresentableForPosition(position) - lastDeletedItemPosition = position + val itemPosition = this.items.indexOfFirst { (it as Identifiable).id == itemId } + val itemToDelete = this.items.find { (it as Identifiable).id == itemId } - if (mRecentlyDeletedItem is RealmObject) { + if (itemToDelete is RealmObject && itemPosition != -1) { // Check if the object is valid for the deletion - if ((mRecentlyDeletedItem as Deletable).isValidForDelete(this.getRealm())) { - deletedItem = getRealm().copyFromRealm(mRecentlyDeletedItem) + if ((itemToDelete as Deletable).isValidForDelete(this.getRealm())) { + deletedItem = getRealm().copyFromRealm(itemToDelete) + lastDeletedItemPosition = itemPosition getRealm().executeTransaction { - mRecentlyDeletedItem.deleteFromRealm() + itemToDelete.deleteFromRealm() } - dataListAdapter.notifyItemRemoved(position) + dataListAdapter.notifyItemRemoved(itemPosition) showUndoSnackBar() } else { - dataListAdapter.notifyItemChanged(position) + dataListAdapter.notifyItemChanged(itemPosition) val builder = AlertDialog.Builder(requireContext()) - .setMessage((mRecentlyDeletedItem as Deletable).getFailedDeleteMessage()) + .setMessage((itemToDelete as Deletable).getFailedDeleteMessage()) .setNegativeButton(R.string.ok, null) builder.show() } From 51bf2481a9b0eae294e8be0a9d169411d1b219b4 Mon Sep 17 00:00:00 2001 From: Aurelien Hubert Date: Thu, 9 May 2019 11:57:41 +0200 Subject: [PATCH 2/8] Add transaction row (additive) --- .../android/model/realm/TransactionType.kt | 8 +++++ .../rowrepresentable/TransactionTypeRow.kt | 29 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt index 0a55ef46..756bc899 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt @@ -69,6 +69,13 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab } } + override fun boolForRow(row: RowRepresentable): Boolean { + return when (row) { + TransactionTypeRow.TRANSACTION_ADDITIVE -> this.additive + else -> super.boolForRow(row) + } + } + override fun editDescriptors(row: RowRepresentable): ArrayList? { return row.editingDescriptors(mapOf("defaultValue" to this.name)) } @@ -76,6 +83,7 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab override fun updateValue(value: Any?, row: RowRepresentable) { when (row) { SimpleRow.NAME -> this.name = value as String? ?: "" + TransactionTypeRow.TRANSACTION_ADDITIVE -> this.additive = value as Boolean? ?: false } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionTypeRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionTypeRow.kt index 83707b5d..2a98cfd7 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionTypeRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionTypeRow.kt @@ -1,6 +1,33 @@ package net.pokeranalytics.android.ui.view.rowrepresentable +import net.pokeranalytics.android.R +import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType import net.pokeranalytics.android.ui.view.DefaultEditDataSource import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType -enum class TransactionTypeRow : RowRepresentable, DefaultEditDataSource +enum class TransactionTypeRow : RowRepresentable, DefaultEditDataSource { + TRANSACTION_ADDITIVE; + + override val resId: Int? + get() { + return when (this) { + TRANSACTION_ADDITIVE -> R.string.additive + } + } + + override val viewType: Int + get() { + return when (this) { + TRANSACTION_ADDITIVE -> RowViewType.TITLE_SWITCH.ordinal + } + } + + override val bottomSheetType: BottomSheetType + get() { + return when (this) { + TRANSACTION_ADDITIVE -> BottomSheetType.NONE + } + } + +} From 6b057b70a43b49efa4fd63b432ec46af3ac9ac4a Mon Sep 17 00:00:00 2001 From: Aurelien Hubert Date: Thu, 9 May 2019 12:11:21 +0200 Subject: [PATCH 3/8] Improve timer for Session --- .../android/ui/fragment/SessionFragment.kt | 6 +++--- .../android/util/extensions/DateExtension.kt | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt index b5e23f51..978c83e2 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt @@ -27,6 +27,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentableDiffCallback import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow +import net.pokeranalytics.android.util.extensions.getNextMinuteInMilliseconds import java.util.* @@ -47,7 +48,6 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate { private val refreshTimer: Runnable = object : Runnable { override fun run() { - // Refresh header each 30 seconds refreshTimer() handler.postDelayed(this, TIMER_DELAY) } @@ -194,7 +194,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate { floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f) .setDuration(animationDuration) .setInterpolator(OvershootInterpolator()).start() - handler.postDelayed(refreshTimer, TIMER_DELAY) + handler.postDelayed(refreshTimer, Date().getNextMinuteInMilliseconds()) } SessionState.STARTED -> { sessionMenu?.findItem(R.id.restart)?.isVisible = true @@ -203,7 +203,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate { floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f) .setDuration(animationDuration) .setInterpolator(OvershootInterpolator()).start() - handler.postDelayed(refreshTimer, TIMER_DELAY) + handler.postDelayed(refreshTimer, Date().getNextMinuteInMilliseconds()) } SessionState.PAUSED -> { sessionMenu?.findItem(R.id.restart)?.isVisible = true diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt index 8efb3739..7c905504 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt @@ -140,4 +140,18 @@ fun Date.startOfYear() : Date { calendar.time = this.startOfMonth() calendar.set(Calendar.MONTH, 0) return calendar.time +} + +// Return the number of seconds until the next minute +fun Date.getNextMinuteInseconds() : Int { + return (getNextMinuteInMilliseconds() / 1000).toInt() +} + +// Return the number of milliseconds until the next minute +fun Date.getNextMinuteInMilliseconds() : Long { + val calendar = Calendar.getInstance() + calendar.add(Calendar.MINUTE, 1) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + return calendar.time.time - this.time } \ No newline at end of file From 6355a611e39817fffcf92305817f47cd413fe6b2 Mon Sep 17 00:00:00 2001 From: Aurelien Hubert Date: Thu, 9 May 2019 12:24:14 +0200 Subject: [PATCH 4/8] Add tabs instead of chips --- .../android/ui/fragment/FeedFragment.kt | 24 ++- app/src/main/res/layout/fragment_feed.xml | 193 ++++++++---------- 2 files changed, 101 insertions(+), 116 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt index f277b9bd..e5fe9e29 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt @@ -10,7 +10,7 @@ import android.widget.Toast import androidx.core.app.ActivityOptionsCompat import androidx.core.view.isVisible import androidx.interpolator.view.animation.FastOutSlowInInterpolator -import com.google.android.material.chip.ChipGroup +import com.google.android.material.tabs.TabLayout import io.realm.RealmResults import io.realm.Sort import io.realm.kotlin.where @@ -27,7 +27,6 @@ import net.pokeranalytics.android.ui.activity.SessionActivity import net.pokeranalytics.android.ui.adapter.FeedSessionRowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.FeedTransactionRowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate -import net.pokeranalytics.android.ui.extensions.ChipGroupExtension import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager @@ -128,14 +127,19 @@ class FeedFragment : RealmFragment(), RowRepresentableDelegate { } } - filters.setOnCheckedChangeListener(object : ChipGroupExtension.SingleSelectionOnCheckedListener() { - override fun onCheckedChanged(group: ChipGroup, checkedId: Int) { - super.onCheckedChanged(group, checkedId) - when (checkedId) { - R.id.filterSessions -> recyclerView.adapter = feedSessionAdapter - R.id.filterTransactions -> recyclerView.adapter = feedTransactionAdapter + tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + when (tab.position) { + 0 -> recyclerView.adapter = feedSessionAdapter + 1 -> recyclerView.adapter = feedTransactionAdapter } } + + override fun onTabUnselected(tab: TabLayout.Tab) { + } + + override fun onTabReselected(tab: TabLayout.Tab) { + } }) } @@ -182,9 +186,9 @@ class FeedFragment : RealmFragment(), RowRepresentableDelegate { */ private fun createNewSession(isTournament: Boolean) { - val sessionCount = this.feedSessionAdapter.realmResults.size +// val sessionCount = this.feedSessionAdapter.realmResults.size // if (!AppGuard.isProUser && sessionCount >= AppGuard.MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION) { // && !BuildConfig.DEBUG -//// Toast.makeText(context, "Please subscribe!", Toast.LENGTH_LONG).show() +// Toast.makeText(context, "Please subscribe!", Toast.LENGTH_LONG).show() // BillingActivity.newInstance(requireContext()) // return // } diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index 4ba53686..736dbfc7 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -1,137 +1,118 @@ - + - - - - + + - - + app:tabMode="fixed"> - + android:text="@string/sessions"/> - + android:text="@string/operations"/> - - - + + android:id="@+id/recyclerView" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/appBar" + tools:listitem="@layout/row_feed_session"/> - - - - - - + android:text="@string/no_sessions" + android:textSize="24sp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.5" + tools:visibility="visible"/> - + android:layout_marginBottom="16dp" + android:src="@drawable/ic_add" + android:tint="@color/black" + android:transitionName="floating_action_button" + app:fabSize="normal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent"/> + + + + + + From cef6cc966cfbd5a4d13fe37d303ce399343c11e4 Mon Sep 17 00:00:00 2001 From: Aurelien Hubert Date: Thu, 9 May 2019 14:10:40 +0200 Subject: [PATCH 5/8] Add strings --- app/src/main/res/values-fr/strings.xml | 3 ++- app/src/main/res/values/strings.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index ffc7f535..a6b25db7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -14,11 +14,12 @@ Devenez membre Pro Passer Pro d\'essai gratuit + Cette bankroll contient au moins une transaction empêchant la suppression. Adresse Suggestions de noms - %s effacés + Élément effacé La date de fin doit être après la date de début Sauvegarder Nom du tournoi diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d94aecac..a2c7222b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,7 +18,7 @@ Address Naming suggestions - %s deleted + Data deleted The end date should be after the start date Save Tournament name @@ -30,6 +30,7 @@ A tournament feature with the same name already exists. A tournament name can not be empty. This name already exists. + One or more transactions are associated with this bankroll, please delete the linked transaction(s) first. From 91c9973ee06cfad0e2123e9936a0f186d27b7426 Mon Sep 17 00:00:00 2001 From: Aurelien Hubert Date: Thu, 9 May 2019 14:11:34 +0200 Subject: [PATCH 6/8] Refactor data deletion, work in progress --- .../android/ui/fragment/DataListFragment.kt | 67 ++--------------- .../ui/fragment/DeletableItemFragment.kt | 73 +++++++++++++++++++ 2 files changed, 78 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/net/pokeranalytics/android/ui/fragment/DeletableItemFragment.kt diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt index 71c47e4a..6ebaf518 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt @@ -6,11 +6,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.snackbar.Snackbar -import io.realm.RealmObject import io.realm.RealmResults import kotlinx.android.synthetic.main.fragment_data_list.* import kotlinx.coroutines.Dispatchers @@ -19,7 +16,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.pokeranalytics.android.R import net.pokeranalytics.android.model.LiveData -import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.ui.activity.DataListActivity @@ -29,12 +25,11 @@ import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate -import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate { +class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate { companion object { const val REQUEST_CODE_DETAILS = 1000 @@ -43,11 +38,9 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep private lateinit var dataType: LiveData private lateinit var items: RealmResults<*> private lateinit var dataListAdapter: RowRepresentableAdapter - - private var deletedItem: RealmObject? = null - private var lastDeletedItemPosition: Int = 0 private var lastItemClickedId: String = "" + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) return inflater.inflate(R.layout.fragment_data_list, container, false) @@ -66,7 +59,7 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep if (needToDeleteItem) { GlobalScope.launch(Dispatchers.Main) { delay(300) - deleteItem(lastItemClickedId) + deleteItem(dataListAdapter, items, lastItemClickedId) } } } @@ -126,7 +119,8 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep dataListAdapter = RowRepresentableAdapter(this, this) val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position -> - deleteItem((this.items[position] as Identifiable).id) + val itemId = (this.items[position] as Identifiable).id + deleteItem(dataListAdapter, items, itemId) } val itemTouchHelper = ItemTouchHelper(swipeToDelete) @@ -149,57 +143,6 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep } } - /** - * Delete item - */ - private fun deleteItem(itemId: String) { - - if (isDetached || activity == null) { - return - } - - // Save the delete position & create a copy of the object - val itemPosition = this.items.indexOfFirst { (it as Identifiable).id == itemId } - val itemToDelete = this.items.find { (it as Identifiable).id == itemId } - - if (itemToDelete is RealmObject && itemPosition != -1) { - - // Check if the object is valid for the deletion - if ((itemToDelete as Deletable).isValidForDelete(this.getRealm())) { - deletedItem = getRealm().copyFromRealm(itemToDelete) - lastDeletedItemPosition = itemPosition - getRealm().executeTransaction { - itemToDelete.deleteFromRealm() - } - dataListAdapter.notifyItemRemoved(itemPosition) - showUndoSnackBar() - } else { - dataListAdapter.notifyItemChanged(itemPosition) - val builder = AlertDialog.Builder(requireContext()) - .setMessage((itemToDelete as Deletable).getFailedDeleteMessage()) - .setNegativeButton(R.string.ok, null) - builder.show() - } - } - } - - /** - * Show undo snack bar - */ - private fun showUndoSnackBar() { - val message = String.format(getString(R.string.data_deleted), this.dataType.localizedTitle(requireContext())) - val snackBar = Snackbar.make(constraintLayout, message, Snackbar.LENGTH_INDEFINITE) - snackBar.setAction(R.string.cancel) { - getRealm().executeTransaction { realm -> - deletedItem?.let { - realm.copyToRealmOrUpdate(it) - dataListAdapter.notifyItemInserted(lastDeletedItemPosition) - } - } - } - snackBar.show() - } - /** * Set fragment data */ diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DeletableItemFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DeletableItemFragment.kt new file mode 100644 index 00000000..365109c6 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DeletableItemFragment.kt @@ -0,0 +1,73 @@ +package net.pokeranalytics.android.ui.fragment + +import androidx.appcompat.app.AlertDialog +import com.google.android.material.snackbar.Snackbar +import io.realm.RealmObject +import kotlinx.android.synthetic.main.fragment_data_list.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.interfaces.Deletable +import net.pokeranalytics.android.model.interfaces.Identifiable +import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter +import net.pokeranalytics.android.ui.fragment.components.RealmFragment + +open class DeletableItemFragment : RealmFragment() { + + private var deletedItem: RealmObject? = null + private var lastDeletedItemPosition: Int = 0 + + /** + * Delete item + */ + fun deleteItem(dataListAdapter: RowRepresentableAdapter, items: List<*>, itemId: String) { + + if (isDetached || activity == null) { + return + } + + // Save the delete position & create a copy of the object + val itemPosition = items.indexOfFirst { (it as Identifiable).id == itemId } + val itemToDelete = items.find { (it as Identifiable).id == itemId } + + if (itemToDelete is RealmObject && itemPosition != -1) { + + val deletableItem = (itemToDelete as Deletable) + + // Check if the object is valid for the deletion + if (deletableItem.isValidForDelete(this.getRealm())) { + deletedItem = getRealm().copyFromRealm(itemToDelete) + lastDeletedItemPosition = itemPosition + getRealm().executeTransaction { + itemToDelete.deleteFromRealm() + } + dataListAdapter.notifyItemRemoved(itemPosition) + showUndoSnackBar(dataListAdapter) + } else { + dataListAdapter.notifyItemChanged(itemPosition) + val status = deletableItem.getDeleteStatus(this.getRealm()) + val message = deletableItem.getFailedDeleteMessage(status) + val builder = AlertDialog.Builder(requireContext()) + .setMessage(message) + .setNegativeButton(R.string.ok, null) + builder.show() + } + } + } + + /** + * Show undo snack bar + */ + private fun showUndoSnackBar(dataListAdapter: RowRepresentableAdapter) { + val message = String.format(getString(R.string.data_deleted)) + val snackBar = Snackbar.make(constraintLayout, message, Snackbar.LENGTH_INDEFINITE) + snackBar.setAction(R.string.cancel) { + getRealm().executeTransaction { realm -> + deletedItem?.let { + realm.copyToRealmOrUpdate(it) + dataListAdapter.notifyItemInserted(lastDeletedItemPosition) + } + } + } + snackBar.show() + } + +} \ No newline at end of file From 0bc502e468fa95581f1d3952bf21fffce9fa05a2 Mon Sep 17 00:00:00 2001 From: Aurelien Hubert Date: Thu, 9 May 2019 14:11:56 +0200 Subject: [PATCH 7/8] Improve deletion system and add deletion for bankroll --- .../android/model/interfaces/Manageable.kt | 32 +++-- .../android/model/realm/Bankroll.kt | 118 ++++++++++-------- .../android/model/realm/Transaction.kt | 9 +- .../android/model/realm/TransactionType.kt | 9 +- .../android/ui/activity/BankrollActivity.kt | 6 +- .../ui/fragment/BankrollEditDataFragment.kt | 7 -- .../android/ui/fragment/BankrollFragment.kt | 15 ++- .../ui/fragment/EditableDataFragment.kt | 3 +- 8 files changed, 116 insertions(+), 83 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 6ea3ecca..2620928c 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 @@ -12,12 +12,19 @@ enum class SaveValidityStatus { DATA_INVALID; } +enum class DeleteValidityStatus { + VALID, + INVALID, + SESSIONS_LINKED, + TRANSACTIONS_LINKED; +} + /** * An interface to grouped object which are managed by the database */ interface Manageable : Savable, Deletable, Editable -interface NameManageable: Manageable { +interface NameManageable : Manageable { var name: String override fun isValidForSave(): Boolean { @@ -25,16 +32,16 @@ interface NameManageable: Manageable { } override fun alreadyExists(realm: Realm): Boolean { - return realm.where(this::class.java).equalTo("name", this.name).and().notEqualTo("id", this.id).findAll().isNotEmpty() + return realm.where(this::class.java).equalTo("name", this.name).and().notEqualTo("id", this.id).findAll().isNotEmpty() } override fun getFailedSaveMessage(status: SaveValidityStatus): Int { - throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled") + throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled") } - override fun getFailedDeleteMessage(): Int { - return R.string.relationship_error - } + override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { + return R.string.relationship_error + } } @@ -87,7 +94,7 @@ interface Savable : Identifiable { /** * A method to get the reason why the object can't be saved */ - fun getFailedSaveMessage(status:SaveValidityStatus): Int + fun getFailedSaveMessage(status: SaveValidityStatus): Int } @@ -101,8 +108,17 @@ interface Deletable : Identifiable { */ fun isValidForDelete(realm: Realm): Boolean + + fun getDeleteStatus(realm: Realm): DeleteValidityStatus { + if (!isValidForDelete(realm)) { + return DeleteValidityStatus.INVALID + } + return DeleteValidityStatus.VALID + } + /** * A method to get the reason why the object can't be deleted */ - fun getFailedDeleteMessage(): Int + fun getFailedDeleteMessage(status: DeleteValidityStatus): Int + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt index f4d6dd88..7d69c508 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt @@ -7,6 +7,7 @@ import io.realm.annotations.LinkingObjects 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 import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.ui.view.RowRepresentable @@ -16,69 +17,82 @@ import java.util.* open class Bankroll : RealmObject(), NameManageable, RowRepresentable { - @PrimaryKey - override var id = UUID.randomUUID().toString() + @PrimaryKey + override var id = UUID.randomUUID().toString() - override var name: String = "" + override var name: String = "" - // Indicates whether the bankroll is live or online - var live: Boolean = true + // Indicates whether the bankroll is live or online + var live: Boolean = true - /** - * The list of transactions of the bankroll - */ - @LinkingObjects("bankroll") - val transactions: RealmResults? = null + /** + * The list of transactions of the bankroll + */ + @LinkingObjects("bankroll") + val transactions: RealmResults? = null - // The currency of the bankroll - var currency: Currency? = null + // The currency of the bankroll + var currency: Currency? = null - // The initial value of the bankroll - var initialValue: Double = 0.0 + // The initial value of the bankroll + var initialValue: Double = 0.0 - val rate: Double - get() { - return this.currency?.rate ?: 1.0 - } + val rate: Double + get() { + return this.currency?.rate ?: 1.0 + } - override fun getDisplayName(): String { - return this.name - } + override fun getDisplayName(): String { + return this.name + } - override fun updateValue(value: Any?, row: RowRepresentable) { - when (row) { - SimpleRow.NAME -> this.name = value as String? ?: "" - BankrollRow.LIVE -> { - this.live = if (value is Boolean) !value else false - } - BankrollRow.INITIAL_VALUE -> { - this.initialValue = value as Double? ?: 0.0 - } - BankrollRow.CURRENCY -> { - //TODO handle a use default currency option - this.currency?.code = value as String? - } - BankrollRow.RATE -> { - this.currency?.rate = value as Double? - } - } - } + override fun updateValue(value: Any?, row: RowRepresentable) { + when (row) { + SimpleRow.NAME -> this.name = value as String? ?: "" + BankrollRow.LIVE -> { + this.live = if (value is Boolean) !value else false + } + BankrollRow.INITIAL_VALUE -> { + this.initialValue = value as Double? ?: 0.0 + } + BankrollRow.CURRENCY -> { + //TODO handle a use default currency option + this.currency?.code = value as String? + } + BankrollRow.RATE -> { + this.currency?.rate = value as Double? + } + } + } - override fun isValidForDelete(realm: Realm): Boolean { + override fun isValidForDelete(realm: Realm): Boolean { return realm.where().equalTo("bankroll.id", id).findAll().isEmpty() - //TODO: Check transactions - //&& realm.where().equalTo("bankroll.id", id).findAll().isEmpty() + && realm.where().equalTo("bankroll.id", id).findAll().isEmpty() + } + + override fun getDeleteStatus(realm: Realm): DeleteValidityStatus { + return if (!realm.where().equalTo("bankroll.id", id).findAll().isEmpty()) { + DeleteValidityStatus.SESSIONS_LINKED + } else if (!realm.where().equalTo("bankroll.id", id).findAll().isEmpty()) { + DeleteValidityStatus.TRANSACTIONS_LINKED + } else { + DeleteValidityStatus.VALID + } } - override fun getFailedDeleteMessage(): Int { - return R.string.bankroll_relationship_error - } + override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { + return when (status) { + DeleteValidityStatus.SESSIONS_LINKED -> R.string.bankroll_relationship_error + DeleteValidityStatus.TRANSACTIONS_LINKED -> R.string.bankroll_relationship_error_transactions + else -> super.getFailedDeleteMessage(status) + } + } - override fun getFailedSaveMessage(status: SaveValidityStatus): Int { - return when (status) { - SaveValidityStatus.DATA_INVALID -> R.string.empty_name_for_br_error - SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_bankroll_name_error - else -> super.getFailedSaveMessage(status) - } - } + override fun getFailedSaveMessage(status: SaveValidityStatus): Int { + return when (status) { + SaveValidityStatus.DATA_INVALID -> R.string.empty_name_for_br_error + SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_bankroll_name_error + else -> super.getFailedSaveMessage(status) + } + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt index c6c3f4f9..d45b3b51 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt @@ -10,10 +10,7 @@ import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.TextFormat import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.QueryCondition -import net.pokeranalytics.android.model.interfaces.DatedValue -import net.pokeranalytics.android.model.interfaces.Manageable -import net.pokeranalytics.android.model.interfaces.SaveValidityStatus -import net.pokeranalytics.android.model.interfaces.TimeFilterable +import net.pokeranalytics.android.model.interfaces.* import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry import net.pokeranalytics.android.ui.view.RowRepresentable @@ -115,8 +112,8 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo return true } - override fun getFailedDeleteMessage(): Int { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { + return R.string.relationship_error } override fun getSaveValidityStatus(realm: Realm): SaveValidityStatus { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt index 756bc899..8705eb16 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt @@ -3,6 +3,8 @@ package net.pokeranalytics.android.model.realm import io.realm.Realm import io.realm.RealmObject import io.realm.annotations.PrimaryKey +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.view.RowRepresentable @@ -88,11 +90,12 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab } override fun isValidForDelete(realm: Realm): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + return true } - override fun getFailedDeleteMessage(): Int { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { + return R.string.relationship_error } + } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt index 5fb6250d..d114029c 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt @@ -69,8 +69,10 @@ class BankrollActivity : PokerAnalyticsActivity() { indexes.addAll(changeSet.insertions.toList()) indexes.addAll(changeSet.deletions.toList()) indexes.forEach { index -> - t[index]?.bankroll?.let { br -> - bankrolls.add(br) + if (t.isNotEmpty()) { + t[index]?.bankroll?.let { br -> + bankrolls.add(br) + } } } this.computeBankrollReports(bankrolls) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollEditDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollEditDataFragment.kt index 67947540..bdecaa46 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollEditDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollEditDataFragment.kt @@ -3,8 +3,6 @@ package net.pokeranalytics.android.ui.fragment import android.app.Activity.RESULT_OK import android.content.Intent import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater import android.view.View import net.pokeranalytics.android.R import net.pokeranalytics.android.api.CurrencyConverterApi @@ -56,11 +54,6 @@ class BankrollEditDataFragment : EditableDataFragment(), StaticRowRepresentableD } ?: false } - override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { - super.onCreateOptionsMenu(menu, inflater) - menu?.findItem(R.id.delete)?.isVisible = false - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt index 459e3123..3e0eed9f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt @@ -9,6 +9,7 @@ import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import com.github.mikephil.charting.data.LineDataSet +import io.realm.Realm import io.realm.RealmObject import io.realm.RealmResults import kotlinx.android.synthetic.main.fragment_bankroll.* @@ -34,7 +35,6 @@ import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource -import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable @@ -43,7 +43,7 @@ import timber.log.Timber import java.util.* import kotlin.collections.ArrayList -class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { +class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { companion object { @@ -153,6 +153,9 @@ class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowR val bankrolls = LiveData.BANKROLL.items(getRealm()) as RealmResults bankrolls.forEach { + + Timber.d("Bankroll: ${it.name} => isValidForDelete: ${it.isValidForDelete(Realm.getDefaultInstance())}") + val bankrollReportSetup = BankrollReportSetup(it) val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup) val computedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total) @@ -217,8 +220,10 @@ class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowR if (mRecentlyDeletedItem is Bankroll) { + val deletableItem = (mRecentlyDeletedItem as Deletable) + // Check if the object is valid for the deletion - if ((mRecentlyDeletedItem as Deletable).isValidForDelete(this.getRealm())) { + if (deletableItem.isValidForDelete(this.getRealm())) { deletedItem = getRealm().copyFromRealm(mRecentlyDeletedItem) getRealm().executeTransaction { mRecentlyDeletedItem.deleteFromRealm() @@ -231,8 +236,10 @@ class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowR } else { bankrollAdapter.notifyItemChanged(position) + val status = deletableItem.getDeleteStatus(this.getRealm()) + val errorMessage = deletableItem.getFailedDeleteMessage(status) val builder = AlertDialog.Builder(requireContext()) - .setMessage((mRecentlyDeletedItem as Deletable).getFailedDeleteMessage()) + .setMessage(errorMessage) .setNegativeButton(R.string.ok, null) builder.show() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt index 0460b9d8..11fef313 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt @@ -192,7 +192,8 @@ open class EditableDataFragment : RealmFragment(), RowRepresentableDelegate { activity?.setResult(RESULT_OK, intent) activity?.finish() } else { - val message = deletable.getFailedDeleteMessage() + val status = deletable.getDeleteStatus(realm) + val message = deletable.getFailedDeleteMessage(status) val builder = AlertDialog.Builder(requireContext()) .setMessage(message) .setNegativeButton(R.string.ok, null) From 955206f00db49e3a6c1ffe4dfec6cd99a23e4491 Mon Sep 17 00:00:00 2001 From: Aurelien Hubert Date: Thu, 9 May 2019 15:45:10 +0200 Subject: [PATCH 8/8] Improve DeletableItemFragment and bankroll deletion --- .../android/ui/fragment/BankrollFragment.kt | 97 ++++++------------- .../android/ui/fragment/DataListFragment.kt | 1 - .../ui/fragment/DeletableItemFragment.kt | 63 +++++++++--- .../CustomizableRowRepresentable.kt | 4 +- .../main/res/layout/fragment_data_list.xml | 2 +- 5 files changed, 84 insertions(+), 83 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt index 3e0eed9f..d2315be1 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt @@ -6,10 +6,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager import com.github.mikephil.charting.data.LineDataSet -import io.realm.Realm import io.realm.RealmObject import io.realm.RealmResults import kotlinx.android.synthetic.main.fragment_bankroll.* @@ -25,7 +23,7 @@ import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator import net.pokeranalytics.android.calculus.bankroll.BankrollReport import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup import net.pokeranalytics.android.model.LiveData -import net.pokeranalytics.android.model.interfaces.Deletable +import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity import net.pokeranalytics.android.ui.activity.DataListActivity @@ -66,9 +64,9 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour private var rows: ArrayList = ArrayList() private var bankrollReportForRow: HashMap = HashMap() - private var deletedItem: RealmObject? = null - private var lastDeletedItemPosition: Int = 0 private var lastItemClickedPosition: Int = 0 + private var lastItemClickedId: String = "" + private var deletedRow: RowRepresentable? = null // Life Cycle @@ -91,7 +89,7 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour if (needToDeleteItem) { GlobalScope.launch(Dispatchers.Main) { delay(300) - deleteItem(lastItemClickedPosition) + deleteItem(bankrollAdapter, LiveData.BANKROLL.items(getRealm()), lastItemClickedId) } } } else if (requestCode == REQUEST_CODE_CREATE && resultCode == Activity.RESULT_OK) { @@ -115,6 +113,7 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour if (bankrollReportForRow.containsKey(row)) { bankrollReportForRow[row]?.let { bankrollReport -> lastItemClickedPosition = position + lastItemClickedId = (row as Identifiable).id BankrollDetailsActivity.newInstanceForResult(this, bankrollReport, REQUEST_CODE_DETAILS) } } @@ -136,6 +135,9 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour launch(Dispatchers.Main) { + // TODO: Improve that + // We are in the main thread... + val startDate = Date() // Graph @@ -152,14 +154,13 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour val bankrolls = LiveData.BANKROLL.items(getRealm()) as RealmResults - bankrolls.forEach { - - Timber.d("Bankroll: ${it.name} => isValidForDelete: ${it.isValidForDelete(Realm.getDefaultInstance())}") - - val bankrollReportSetup = BankrollReportSetup(it) + bankrolls.forEach { bankroll -> + val bankrollReportSetup = BankrollReportSetup(bankroll) val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup) val computedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total) - val row = CustomizableRowRepresentable(RowViewType.TITLE_VALUE_ARROW, title = it.name, computedStat = computedStat, isSelectable = true) + val row = + CustomizableRowRepresentable(RowViewType.TITLE_VALUE_ARROW, title = bankroll.name, computedStat = computedStat, isSelectable = true) + row.id = bankroll.id rows.add(row) bankrollReportForRow[row] = bankrollReport @@ -201,68 +202,26 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour } } + override fun updateUIAfterDeletion(itemPosition: Int) { + lastItemClickedPosition = rows.indexOfFirst { if (it is Identifiable) it.id == lastItemClickedId else false } + deletedRow = rows.find { if (it is Identifiable) it.id == lastItemClickedId else false } + rows.removeAt(lastItemClickedPosition) + bankrollAdapter.notifyItemRemoved(lastItemClickedPosition) + } - /** - * Delete item - */ - private fun deleteItem(position: Int) { - - //TODO: Get bankroll from bankrollReport and delete it - - if (isDetached || activity == null) { - return - } - - // Save the delete position & create a copy of the object - val bankrollReport = bankrollReportForRow[rowRepresentableForPosition(position)] - val mRecentlyDeletedItem = bankrollReport?.setup?.bankroll - lastDeletedItemPosition = position - - if (mRecentlyDeletedItem is Bankroll) { + override fun updateUIAfterUndoDeletion(newItem: RealmObject) { - val deletableItem = (mRecentlyDeletedItem as Deletable) + // TODO: Improve that + // We are recreating a Bankroll report because the last one if invalid => the bankroll of the setup has been deleted - // Check if the object is valid for the deletion - if (deletableItem.isValidForDelete(this.getRealm())) { - deletedItem = getRealm().copyFromRealm(mRecentlyDeletedItem) - getRealm().executeTransaction { - mRecentlyDeletedItem.deleteFromRealm() - } - //bankrollAdapter.notifyItemRemoved(position) - //showUndoSnackBar() - - //TODO: Refresh bankrolls - initData() - - } else { - bankrollAdapter.notifyItemChanged(position) - val status = deletableItem.getDeleteStatus(this.getRealm()) - val errorMessage = deletableItem.getFailedDeleteMessage(status) - val builder = AlertDialog.Builder(requireContext()) - .setMessage(errorMessage) - .setNegativeButton(R.string.ok, null) - builder.show() - } - } - } + deletedRow?.let { row -> + val bankrollReportSetup = BankrollReportSetup(newItem as Bankroll) + val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup) + bankrollReportForRow[row] = bankrollReport - /** - * Show undo snack bar - */ - private fun showUndoSnackBar() { - /* - val message = String.format(getString(R.string.data_deleted), getString(R.string.bankroll)) - val snackBar = Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_INDEFINITE) - snackBar.setAction(R.string.cancel) { - getRealm().executeTransaction { realm -> - deletedItem?.let { - realm.copyToRealmOrUpdate(it) - bankrollAdapter.notifyItemInserted(lastDeletedItemPosition) - } - } + rows.add(lastItemClickedPosition, row) + bankrollAdapter.notifyItemInserted(lastItemClickedPosition) } - snackBar.show() - */ } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt index 6ebaf518..95c7b5fe 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt @@ -40,7 +40,6 @@ class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataSource private lateinit var dataListAdapter: RowRepresentableAdapter private var lastItemClickedId: String = "" - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) return inflater.inflate(R.layout.fragment_data_list, container, false) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DeletableItemFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DeletableItemFragment.kt index 365109c6..ec02f805 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DeletableItemFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DeletableItemFragment.kt @@ -1,22 +1,45 @@ package net.pokeranalytics.android.ui.fragment +import android.os.Bundle +import android.view.View import androidx.appcompat.app.AlertDialog +import androidx.coordinatorlayout.widget.CoordinatorLayout import com.google.android.material.snackbar.Snackbar import io.realm.RealmObject -import kotlinx.android.synthetic.main.fragment_data_list.* import net.pokeranalytics.android.R import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.fragment.components.RealmFragment +/** + * Deletable Item Fragment + * Don't forget to add a CoordinatorLayout at the top of your XML if you want to display correctly the snack bar + */ open class DeletableItemFragment : RealmFragment() { private var deletedItem: RealmObject? = null private var lastDeletedItemPosition: Int = 0 + private var dataListAdapter: RowRepresentableAdapter? = null + private var coordinatorLayout: CoordinatorLayout? = null + private var snackBar: Snackbar? = null + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + this.coordinatorLayout = view.findViewById(R.id.coordinatorLayout) + } + + override fun onPause() { + super.onPause() + snackBar?.dismiss() + } /** * Delete item + * [dataListAdapter]: Adapter to update + * [items]: List of items which contains the element to delete + * [itemId]: Id of the item to delete + * [container]: View to display the Snackbar */ fun deleteItem(dataListAdapter: RowRepresentableAdapter, items: List<*>, itemId: String) { @@ -24,6 +47,8 @@ open class DeletableItemFragment : RealmFragment() { return } + this.dataListAdapter = dataListAdapter + // Save the delete position & create a copy of the object val itemPosition = items.indexOfFirst { (it as Identifiable).id == itemId } val itemToDelete = items.find { (it as Identifiable).id == itemId } @@ -39,8 +64,8 @@ open class DeletableItemFragment : RealmFragment() { getRealm().executeTransaction { itemToDelete.deleteFromRealm() } - dataListAdapter.notifyItemRemoved(itemPosition) - showUndoSnackBar(dataListAdapter) + updateUIAfterDeletion(itemPosition) + showUndoSnackBar() } else { dataListAdapter.notifyItemChanged(itemPosition) val status = deletableItem.getDeleteStatus(this.getRealm()) @@ -56,18 +81,34 @@ open class DeletableItemFragment : RealmFragment() { /** * Show undo snack bar */ - private fun showUndoSnackBar(dataListAdapter: RowRepresentableAdapter) { + private fun showUndoSnackBar() { val message = String.format(getString(R.string.data_deleted)) - val snackBar = Snackbar.make(constraintLayout, message, Snackbar.LENGTH_INDEFINITE) - snackBar.setAction(R.string.cancel) { - getRealm().executeTransaction { realm -> - deletedItem?.let { - realm.copyToRealmOrUpdate(it) - dataListAdapter.notifyItemInserted(lastDeletedItemPosition) + this.coordinatorLayout?.let { view -> + snackBar = Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE) + snackBar?.setAction(R.string.cancel) { + getRealm().executeTransaction { realm -> + deletedItem?.let { + val item = realm.copyToRealmOrUpdate(it) + updateUIAfterUndoDeletion(item) + } } } + snackBar?.show() } - snackBar.show() + } + + /** + * Called once the object has been deleted + */ + open fun updateUIAfterDeletion(itemPosition: Int) { + dataListAdapter?.notifyItemRemoved(itemPosition) + } + + /** + * Called once the object has been restored + */ + open fun updateUIAfterUndoDeletion(newItem: RealmObject) { + dataListAdapter?.notifyItemInserted(lastDeletedItemPosition) } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomizableRowRepresentable.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomizableRowRepresentable.kt index 50a23ee9..3b6da0ce 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomizableRowRepresentable.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomizableRowRepresentable.kt @@ -2,6 +2,7 @@ package net.pokeranalytics.android.ui.view.rowrepresentable import android.content.Context import net.pokeranalytics.android.calculus.ComputedStat +import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType @@ -15,7 +16,7 @@ class CustomizableRowRepresentable( var value: String? = null, var computedStat: ComputedStat? = null, var isSelectable: Boolean? = false - ) : RowRepresentable { + ) : RowRepresentable, Identifiable { override fun localizedTitle(context: Context): String { @@ -30,4 +31,5 @@ class CustomizableRowRepresentable( override val viewType: Int = customViewType?.ordinal ?: RowViewType.HEADER_TITLE.ordinal + override var id: String = "" } diff --git a/app/src/main/res/layout/fragment_data_list.xml b/app/src/main/res/layout/fragment_data_list.xml index eae259fb..03650077 100644 --- a/app/src/main/res/layout/fragment_data_list.xml +++ b/app/src/main/res/layout/fragment_data_list.xml @@ -2,7 +2,7 @@