diff --git a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt index 67725c76..606df167 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt @@ -2,167 +2,176 @@ package net.pokeranalytics.android.model.migrations import io.realm.DynamicRealm import io.realm.RealmMigration +import net.pokeranalytics.android.model.realm.Comment import timber.log.Timber +import java.util.* class PokerAnalyticsMigration : RealmMigration { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { - // DynamicRealm exposes an editable schema - val schema = realm.schema - - var currentVersion = oldVersion.toInt() - Timber.d("*** migrate from $oldVersion to $newVersion") - - // Migrate to version 1 - if (currentVersion == 0) { - Timber.d("*** Running migration 1") - - schema.get("Filter")?.addField("entityType", Int::class.java)?.setNullable("entityType", true) - schema.get("FilterElement")?.let { - it.setNullable("filterName", true) - it.setNullable("sectionName", true) - } - schema.get("FilterElementBlind")?.renameField("code", "currencyCode") - currentVersion++ - } - - // Migrate to version 2 - if (currentVersion == 1) { - Timber.d("*** Running migration ${currentVersion + 1}") - schema.rename("FilterElement", "FilterCondition") - - schema.get("Filter")?.renameField("filterElements", "filterConditions") - - schema.get("SessionSet")?.let { - it.addField("id", String::class.java).setRequired("id", true) - it.addPrimaryKey("id") - } - currentVersion++ - } - - // Migrate to version 3 - if (currentVersion == 2) { - Timber.d("*** Running migration ${currentVersion + 1}") - schema.rename("Report", "ReportSetup") - - schema.get("Filter")?.removeField("entityType") - - schema.get("Session")?.let { - it.addField("blinds", String::class.java).transform { - - } - } - - schema.get("FilterCondition")?.let { - it.removeField("blindValues") - it.removeField("numericValues") - - it.addField("operator", Integer::class.java) - it.addField("intValue", Integer::class.java) - it.addRealmListField("intValues", Integer::class.java) - it.addField("doubleValue", Double::class.java).setNullable("doubleValue", true) - it.addRealmListField("doubleValues", Double::class.java) - if (it.isRequired("doubleValues")) { - it.setRequired("doubleValues", false) - } - it.addField("stringValue", String::class.java) - } - - schema.get("ComputableResult")?.removeField("sessionSet") - - schema.get("Bankroll")?.addField("initialValue", Double::class.java) - - currentVersion++ - } - - // Migrate to version 4 - if (currentVersion == 3) { - Timber.d("*** Running migration ${currentVersion + 1}") - - schema.get("Result")?.addField("numberOfRebuy", Double::class.java)?.setNullable("numberOfRebuy", true) - currentVersion++ - } - - // Migrate to version 5 - if (currentVersion == 4) { - Timber.d("*** Running migration ${currentVersion + 1}") - schema.get("Bankroll")?.removeField("transactions") - currentVersion++ - } - - // Migrate to version 6 - if (currentVersion == 5) { - Timber.d("*** Running migration ${currentVersion + 1}") - schema.get("Transaction")?.let { - it.addField("dayOfWeek", Integer::class.java) - it.addField("month", Integer::class.java) - it.addField("year", Integer::class.java) - it.addField("dayOfMonth", Integer::class.java) - } - - val cfEntry = schema.create("CustomFieldEntry")?.let { - it.addField("id", String::class.java).setRequired("id", true) - it.addPrimaryKey("id") - it.addField("value", String::class.java).setNullable("value", false) - it.addField("order", Integer::class.java).setNullable("order", false) + // DynamicRealm exposes an editable schema + val schema = realm.schema + + var currentVersion = oldVersion.toInt() + Timber.d("*** migrate from $oldVersion to $newVersion") + + // Migrate to version 1 + if (currentVersion == 0) { + Timber.d("*** Running migration 1") + + schema.get("Filter")?.addField("entityType", Int::class.java)?.setNullable("entityType", true) + schema.get("FilterElement")?.let { + it.setNullable("filterName", true) + it.setNullable("sectionName", true) + } + schema.get("FilterElementBlind")?.renameField("code", "currencyCode") + currentVersion++ + } + + // Migrate to version 2 + if (currentVersion == 1) { + Timber.d("*** Running migration ${currentVersion + 1}") + schema.rename("FilterElement", "FilterCondition") + + schema.get("Filter")?.renameField("filterElements", "filterConditions") + + schema.get("SessionSet")?.let { + it.addField("id", String::class.java).setRequired("id", true) + it.addPrimaryKey("id") + } + currentVersion++ + } + + // Migrate to version 3 + if (currentVersion == 2) { + Timber.d("*** Running migration ${currentVersion + 1}") + schema.rename("Report", "ReportSetup") + + schema.get("Filter")?.removeField("entityType") + + schema.get("Session")?.let { + it.addField("blinds", String::class.java).transform { + + } + } + + schema.get("FilterCondition")?.let { + it.removeField("blindValues") + it.removeField("numericValues") + + it.addField("operator", Integer::class.java) + it.addField("intValue", Integer::class.java) + it.addRealmListField("intValues", Integer::class.java) + it.addField("doubleValue", Double::class.java).setNullable("doubleValue", true) + it.addRealmListField("doubleValues", Double::class.java) + if (it.isRequired("doubleValues")) { + it.setRequired("doubleValues", false) + } + it.addField("stringValue", String::class.java) + } + + schema.get("ComputableResult")?.removeField("sessionSet") + + schema.get("Bankroll")?.addField("initialValue", Double::class.java) + + currentVersion++ + } + + // Migrate to version 4 + if (currentVersion == 3) { + Timber.d("*** Running migration ${currentVersion + 1}") + + schema.get("Result")?.addField("numberOfRebuy", Double::class.java)?.setNullable("numberOfRebuy", true) + currentVersion++ + } + + // Migrate to version 5 + if (currentVersion == 4) { + Timber.d("*** Running migration ${currentVersion + 1}") + schema.get("Bankroll")?.removeField("transactions") + currentVersion++ + } + + // Migrate to version 6 + if (currentVersion == 5) { + Timber.d("*** Running migration ${currentVersion + 1}") + schema.get("Transaction")?.let { + it.addField("dayOfWeek", Integer::class.java) + it.addField("month", Integer::class.java) + it.addField("year", Integer::class.java) + it.addField("dayOfMonth", Integer::class.java) + } + + val cfEntry = schema.create("CustomFieldEntry")?.let { + it.addField("id", String::class.java).setRequired("id", true) + it.addPrimaryKey("id") + it.addField("value", String::class.java).setNullable("value", false) + it.addField("order", Integer::class.java).setNullable("order", false) // it.addRealmObjectField("customField", it).setNullable("customField", false) - it.addField("numericValue", Double::class.java).setNullable("numericValue", true) - } - - cfEntry?.let { customFieldEntrySchema -> - schema.get("CustomField")?.let { - it.addField("type", Integer::class.java).setNullable("type", false) - it.addField("duplicateValue", Boolean::class.java) - it.addField("sortCondition", Integer::class.java).setRequired("sortCondition", true) - it.addRealmListField("entries", customFieldEntrySchema) - } - - schema.get("Session")?.let { - it.addField("startDateHourMinuteComponent", Double::class.java) - .setNullable("startDateHourMinuteComponent", true) - it.addField("endDateHourMinuteComponent", Double::class.java) - .setNullable("endDateHourMinuteComponent", true) - it.addRealmListField("customFieldEntries", customFieldEntrySchema) - } - - } - - schema.get("ReportSetup")?.let { - it.addRealmListField("statIds", Int::class.java).setNullable("statIds", true) - it.addRealmListField("criteriaCustomFieldIds", String::class.java) - it.addRealmListField("criteriaIds", Int::class.java).setNullable("criteriaIds", true) - it.removeField("filters") - schema.get("Filter")?.let { filterSchema -> - it.addRealmObjectField("filter", filterSchema) - } - } - - schema.get("Filter")?.addField("filterableTypeUniqueIdentifier", Integer::class.java) - schema.get("Filter")?.addField("useCount", Int::class.java) - schema.get("Filter")?.removeField("usageCount") - - currentVersion++ - } - - // Migrate to version 7 - if (currentVersion == 6) { - Timber.d("*** Running migration ${currentVersion + 1}") - schema.get("TransactionType")?.addField("useCount", Int::class.java) - currentVersion++ - } + it.addField("numericValue", Double::class.java).setNullable("numericValue", true) + } + + cfEntry?.let { customFieldEntrySchema -> + schema.get("CustomField")?.let { + it.addField("type", Integer::class.java).setNullable("type", false) + it.addField("duplicateValue", Boolean::class.java) + it.addField("sortCondition", Integer::class.java).setRequired("sortCondition", true) + it.addRealmListField("entries", customFieldEntrySchema) + } + + schema.get("Session")?.let { + it.addField("startDateHourMinuteComponent", Double::class.java) + .setNullable("startDateHourMinuteComponent", true) + it.addField("endDateHourMinuteComponent", Double::class.java) + .setNullable("endDateHourMinuteComponent", true) + it.addRealmListField("customFieldEntries", customFieldEntrySchema) + } + + } + + schema.get("ReportSetup")?.let { + it.addRealmListField("statIds", Int::class.java).setNullable("statIds", true) + it.addRealmListField("criteriaCustomFieldIds", String::class.java) + it.addRealmListField("criteriaIds", Int::class.java).setNullable("criteriaIds", true) + it.removeField("filters") + schema.get("Filter")?.let { filterSchema -> + it.addRealmObjectField("filter", filterSchema) + } + } + + schema.get("Filter")?.addField("filterableTypeUniqueIdentifier", Integer::class.java) + schema.get("Filter")?.addField("useCount", Int::class.java) + schema.get("Filter")?.removeField("usageCount") + + currentVersion++ + } + + // Migrate to version 7 + if (currentVersion == 6) { + Timber.d("*** Running migration ${currentVersion + 1}") + schema.get("TransactionType")?.addField("useCount", Int::class.java) + currentVersion++ + } // Migrate to version 8 if (currentVersion == 7) { + schema.create("Comment")?.let { + it.addField("id", String::class.java).setRequired("id", true) + it.addField("content", String::class.java) + it.addField("date", Date::class.java) + } + schema.get("Player")?.let { it.addField("summary", String::class.java).setRequired("summary", true) it.addField("color", Int::class.java).setNullable("color", true) it.addField("picture", String::class.java) + it.addRealmListField("comments", Comment::class.java) } currentVersion++ } - } + } override fun equals(other: Any?): Boolean { return other is RealmMigration diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Comment.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Comment.kt new file mode 100644 index 00000000..9111abb2 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Comment.kt @@ -0,0 +1,79 @@ +package net.pokeranalytics.android.model.realm + +import android.content.Context +import androidx.fragment.app.Fragment +import io.realm.Realm +import io.realm.RealmObject +import io.realm.annotations.Ignore +import io.realm.annotations.PrimaryKey +import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.ModelException +import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus +import net.pokeranalytics.android.model.interfaces.Identifiable +import net.pokeranalytics.android.model.interfaces.Manageable +import net.pokeranalytics.android.model.interfaces.SaveValidityStatus +import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate +import net.pokeranalytics.android.ui.fragment.components.input.InputFragment +import net.pokeranalytics.android.ui.fragment.components.input.InputFragmentType +import net.pokeranalytics.android.ui.view.RowEditableDataSource +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.util.NULL_TEXT +import java.util.* + +open class Comment : RealmObject(), Manageable, RowRepresentable { + + @PrimaryKey + override var id = UUID.randomUUID().toString() + var content: String = "" + var date: Date = Date() + + @Ignore + override val realmObjectClass: Class = Comment::class.java + + @Ignore + override val viewType: Int = RowViewType.TITLE.ordinal + + @Ignore + override val inputFragmentType: InputFragmentType = InputFragmentType.EDIT_TEXT_MULTI_LINES + + override fun localizedTitle(context: Context): String { + return context.getString(R.string.comment) + } + + override fun getDisplayName(context: Context): String { + return if (content.isNotEmpty()) content else NULL_TEXT + } + + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + data.append(this.content, R.string.value) + InputFragment.buildAndShow(this, parent, data, isDeletable = true) + } + + override fun updateValue(value: Any?, row: RowRepresentable) { + this.content = value as String? ?: "" + } + + 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 isValidForDelete(realm: Realm): Boolean { + return true + } + + override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { + return R.string.cf_entry_delete_popup_message + } +} diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt index 608a625c..49d0625c 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt @@ -2,6 +2,7 @@ package net.pokeranalytics.android.model.realm import android.content.Context import io.realm.Realm +import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey @@ -13,38 +14,38 @@ import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.PlayerRow import net.pokeranalytics.android.util.NULL_TEXT +import net.pokeranalytics.android.util.extensions.isSameDay +import net.pokeranalytics.android.util.extensions.mediumDate import java.util.* open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresentableDataSource, RowRepresentable { - companion object { - val rowRepresentation: List by lazy { - val rows = ArrayList() - rows.add(PlayerRow.IMAGE) - rows.add(PlayerRow.NAME) - rows.add(PlayerRow.SUMMARY) - rows - } - } - - @Ignore - override val realmObjectClass: Class = Player::class.java - @PrimaryKey override var id = UUID.randomUUID().toString() // The name of the player override var name: String = "" - @Ignore - override val viewType: Int = RowViewType.ROW_PLAYER.ordinal - // New fields var summary: String = "" var color: Int? = null var picture: String? = null + var comments: RealmList = RealmList() + + @Ignore + override val realmObjectClass: Class = Player::class.java + + @Ignore + override val viewType: Int = RowViewType.ROW_PLAYER.ordinal + + @Ignore + private var rowRepresentation: List = mutableListOf() + + @Ignore + private var commentsToDelete: ArrayList = ArrayList() override fun isValidForDelete(realm: Realm): Boolean { @@ -80,6 +81,42 @@ open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresent } } + + /** + * Update the row representation + */ + private fun updatedRowRepresentationForCurrentState(): List { + val rows = ArrayList() + rows.add(PlayerRow.IMAGE) + rows.add(PlayerRow.NAME) + rows.add(PlayerRow.SUMMARY) + + if (comments.size > 0) { + rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, R.string.comments)) + + val currentCommentCalendar = Calendar.getInstance() + val currentDateCalendar = Calendar.getInstance() + + val commentsToDisplay = ArrayList() + commentsToDisplay.addAll(comments) + commentsToDisplay.sortByDescending { it.date } + + commentsToDisplay.forEachIndexed { index, comment -> + currentCommentCalendar.time = comment.date + + if (!currentCommentCalendar.isSameDay(currentDateCalendar) || index == 0) { + currentDateCalendar.time = currentCommentCalendar.time + rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, title = currentDateCalendar.time.mediumDate())) + } + + rows.add(comment) + } + + } + + return rows + } + /** * Return if the player has a picture */ @@ -87,4 +124,31 @@ open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresent return picture != null && picture?.isNotEmpty() == true } + /** + * Update row representation + */ + fun updateRowRepresentation() { + this.rowRepresentation = this.updatedRowRepresentationForCurrentState() + } + + + /** + * Add an entry + */ + fun addComent(): Comment { + val entry = Comment() + this.comments.add(entry) + updateRowRepresentation() + return entry + } + + /** + * Delete an entry + */ + fun deleteComment(comment: Comment) { + commentsToDelete.add(comment) + this.comments.remove(comment) + updateRowRepresentation() + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/PlayerDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/PlayerDataFragment.kt index 58101f39..7ce92df0 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/PlayerDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/PlayerDataFragment.kt @@ -8,7 +8,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import kotlinx.android.synthetic.main.fragment_player.* import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.Comment import net.pokeranalytics.android.model.realm.Player import net.pokeranalytics.android.ui.activity.ColorPickerActivity import net.pokeranalytics.android.ui.activity.components.MediaActivity @@ -105,9 +107,47 @@ class PlayerDataFragment : EditableDataFragment(), StaticRowRepresentableDataSou } override fun onRowValueChanged(value: Any?, row: RowRepresentable) { - super.onRowValueChanged(value, row) when (row) { - PlayerRow.NAME -> rowRepresentableAdapter.refreshRow(PlayerRow.IMAGE) + is Comment -> { + row.updateValue(value, row) + player.updateRowRepresentation() + rowRepresentableAdapter.notifyDataSetChanged() + } + else -> { + super.onRowValueChanged(value, row) + if (row == PlayerRow.NAME) { + rowRepresentableAdapter.refreshRow(PlayerRow.IMAGE) + + } + } + } + } + + override fun onRowDeleted(row: RowRepresentable) { + super.onRowDeleted(row) + when (row) { + is Comment -> { + if (!row.isValidForDelete(getRealm())) { + + } else { + player.deleteComment(row) + rowRepresentableAdapter.notifyDataSetChanged() + } + + /* + if (!row.isValidForDelete(getRealm())) { + val status = row.getDeleteStatus(requireContext(), getRealm()) + val message = row.getFailedDeleteMessage(status) + showAlertDialog(requireContext(), R.string.cf_entry_delete_popup_title, message, showCancelButton = true, positiveAction = { + customField.deleteEntry(row) + rowRepresentableAdapter.notifyDataSetChanged() + }) + } else { + customField.deleteEntry(row) + rowRepresentableAdapter.notifyDataSetChanged() + } + */ + } } } @@ -117,9 +157,17 @@ class PlayerDataFragment : EditableDataFragment(), StaticRowRepresentableDataSou private fun initUI() { mediaActivity = parentActivity as MediaActivity? + player.updateRowRepresentation() + if (!deleteButtonShouldAppear) { onRowSelected(0, SimpleRow.NAME) } + + addComment.setOnClickListener { + val comment = player.addComent() + rowRepresentableAdapter.notifyDataSetChanged() + onRowSelected(-1, comment) + } } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomFieldRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomFieldRow.kt index bd0d8f8b..d9caebe1 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomFieldRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomFieldRow.kt @@ -7,8 +7,8 @@ import net.pokeranalytics.android.model.realm.CustomField import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.fragment.components.input.InputFragment import net.pokeranalytics.android.ui.fragment.components.input.InputFragmentType -import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowEditableDataSource +import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType enum class CustomFieldRow : RowRepresentable { @@ -64,30 +64,6 @@ enum class CustomFieldRow : RowRepresentable { return list } -/* - - override fun editingDescriptors(map: Map): ArrayList? { - return when (type) { - Type.LIST.uniqueIdentifier -> { - val defaultValue: Any? by map - val data: RealmList? by map - arrayListOf( - RowEditableDescriptor(defaultValue, staticData = data) - ) - } - else -> { - val defaultValue: Double? by map - arrayListOf( - RowEditableDescriptor( - defaultValue, inputType = InputType.TYPE_CLASS_NUMBER - or InputType.TYPE_NUMBER_FLAG_DECIMAL - or InputType.TYPE_NUMBER_FLAG_SIGNED - ) - ) - } - } - } - */ override fun startEditing(dataSource: Any?, parent: Fragment?) { if (dataSource == null) return if (dataSource !is CustomField) return diff --git a/app/src/main/res/layout/fragment_custom_view.xml b/app/src/main/res/layout/fragment_custom_view.xml index e06f0ab9..ec51385d 100644 --- a/app/src/main/res/layout/fragment_custom_view.xml +++ b/app/src/main/res/layout/fragment_custom_view.xml @@ -9,6 +9,8 @@ android:id="@+id/nestedScrollView" android:layout_width="match_parent" android:layout_height="match_parent" + android:paddingBottom="56dp" + android:clipToPadding="false" android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> diff --git a/app/src/main/res/layout/fragment_player.xml b/app/src/main/res/layout/fragment_player.xml index a1ef7ddb..f2ad2b2a 100644 --- a/app/src/main/res/layout/fragment_player.xml +++ b/app/src/main/res/layout/fragment_player.xml @@ -1,13 +1,14 @@ - + app:title="@string/player" /> + - + + + + + + + + + app:layout_constraintStart_toStartOf="parent"> + + + + + + + + - \ No newline at end of file + \ No newline at end of file