commit
3984357077
@ -0,0 +1,85 @@ |
||||
package net.pokeranalytics.android.unitTests.filter |
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
||||
import net.pokeranalytics.android.model.filter.Query |
||||
import net.pokeranalytics.android.model.filter.QueryCondition |
||||
import net.pokeranalytics.android.model.realm.CustomField |
||||
import net.pokeranalytics.android.model.realm.CustomFieldEntry |
||||
import net.pokeranalytics.android.model.realm.Filter |
||||
import net.pokeranalytics.android.model.realm.Session |
||||
import org.junit.Assert |
||||
import org.junit.Test |
||||
import org.junit.runner.RunWith |
||||
import java.util.* |
||||
|
||||
@RunWith(AndroidJUnit4::class) |
||||
class CustomFieldFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { |
||||
|
||||
@Test |
||||
fun testCustomFieldListFilter() { |
||||
|
||||
val realm = this.mockRealm |
||||
realm.beginTransaction() |
||||
|
||||
val cf1 = CustomField() |
||||
cf1.id = "1" |
||||
cf1.type = CustomField.Type.LIST.ordinal |
||||
|
||||
val cfe1 = CustomFieldEntry() |
||||
val cfe2 = CustomFieldEntry() |
||||
cfe1.value = "super" |
||||
cfe2.value = "nul" |
||||
|
||||
cf1.entries.add(cfe1) |
||||
cf1.entries.add(cfe2) |
||||
|
||||
val s1 = Session.testInstance(100.0, false, Date(), 1) |
||||
s1.customFieldEntries.add(cfe1) |
||||
val s2 = Session.testInstance(100.0, true, Date(), 1) |
||||
s2.customFieldEntries.add(cfe2) |
||||
realm.commitTransaction() |
||||
|
||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.CustomFieldListQuery(cfe2))) |
||||
|
||||
Assert.assertEquals(1, sessions.size) |
||||
sessions[0]?.run { |
||||
Assert.assertEquals(s2.id, (this).id) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun testCustomFieldAmountFilter() { |
||||
|
||||
val realm = this.mockRealm |
||||
realm.beginTransaction() |
||||
|
||||
val cf1 = CustomField() |
||||
cf1.id = "1234" |
||||
cf1.type = CustomField.Type.AMOUNT.ordinal |
||||
|
||||
|
||||
val cfe1 = CustomFieldEntry() |
||||
cfe1.id = "999" |
||||
cf1.entries.add(cfe1) |
||||
cfe1.numericValue = 30.0 |
||||
|
||||
val cfe2 = CustomFieldEntry() |
||||
cfe2.id = "888" |
||||
cf1.entries.add(cfe2) |
||||
cfe2.numericValue = 100.0 |
||||
|
||||
val s1 = Session.testInstance(100.0, false, Date(), 1) |
||||
s1.customFieldEntries.add(cfe1) |
||||
val s2 = Session.testInstance(100.0, true, Date(), 1) |
||||
s2.customFieldEntries.add(cfe2) |
||||
realm.commitTransaction() |
||||
|
||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.CustomFieldNumberQuery(cf1.id, 100.0))) |
||||
|
||||
Assert.assertEquals(1, sessions.size) |
||||
sessions[0]?.run { |
||||
Assert.assertEquals(s2.id, (this).id) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,57 @@ |
||||
package net.pokeranalytics.android.unitTests.filter |
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
import androidx.test.platform.app.InstrumentationRegistry |
||||
import io.realm.RealmList |
||||
import io.realm.RealmResults |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
||||
import net.pokeranalytics.android.model.filter.Query |
||||
import net.pokeranalytics.android.model.filter.QueryCondition |
||||
import net.pokeranalytics.android.model.realm.* |
||||
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow |
||||
import org.junit.Assert |
||||
import org.junit.Test |
||||
import org.junit.runner.RunWith |
||||
import java.util.* |
||||
|
||||
@RunWith(AndroidJUnit4::class) |
||||
class TransactionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { |
||||
|
||||
@Test |
||||
fun testTransactionTypeFilter() { |
||||
val context = InstrumentationRegistry.getInstrumentation().context |
||||
|
||||
val realm = this.mockRealm |
||||
realm.beginTransaction() |
||||
TransactionType.Value.values().forEachIndexed { index, value -> |
||||
val type = TransactionType() |
||||
val name = "test" |
||||
type.name = name |
||||
type.additive = value.additive |
||||
type.kind = index |
||||
type.lock = true |
||||
realm.insertOrUpdate(type) |
||||
} |
||||
|
||||
val t1: Transaction = realm.createObject(Transaction::class.java, "1") |
||||
t1.type = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm) |
||||
val t2: Transaction = realm.createObject(Transaction::class.java, "2") |
||||
t2.type = TransactionType.getByValue(TransactionType.Value.WITHDRAWAL, realm) |
||||
|
||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
||||
t1.bankroll = b1 |
||||
|
||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
||||
t2.bankroll = b2 |
||||
|
||||
realm.commitTransaction() |
||||
|
||||
val transactions = Filter.queryOn<Transaction>(realm, Query(QueryCondition.AnyTransactionType(t1.type!!))) |
||||
|
||||
Assert.assertEquals(1, transactions.size) |
||||
transactions[0]?.run { |
||||
Assert.assertEquals(t1.type!!.id, (this).type!!.id) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,40 @@ |
||||
package net.pokeranalytics.android.calculus |
||||
|
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.model.Criteria |
||||
import net.pokeranalytics.android.ui.graph.AxisFormatting |
||||
|
||||
enum class AggregationType { |
||||
SESSION, |
||||
MONTH, |
||||
YEAR, |
||||
DURATION; |
||||
|
||||
val resId: Int |
||||
get() { |
||||
return when (this) { |
||||
SESSION -> R.string.session |
||||
MONTH -> R.string.month |
||||
YEAR -> R.string.year |
||||
DURATION -> R.string.duration |
||||
} |
||||
} |
||||
|
||||
val axisFormatting: AxisFormatting |
||||
get() { |
||||
return when (this) { |
||||
DURATION -> AxisFormatting.X_DURATION |
||||
else -> AxisFormatting.DEFAULT |
||||
} |
||||
} |
||||
|
||||
val criterias: List<Criteria> |
||||
get() { |
||||
return when (this) { |
||||
MONTH -> listOf(Criteria.AllMonthsUpToNow) |
||||
YEAR -> listOf(Criteria.Years) |
||||
else -> listOf() |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,12 +0,0 @@ |
||||
package net.pokeranalytics.android.calculus |
||||
|
||||
class AggregationParameter<T> { |
||||
var values: List<T>? = null |
||||
} |
||||
|
||||
class Aggregator { |
||||
|
||||
var parameters: List<AggregationParameter<*>> = listOf() |
||||
|
||||
} |
||||
|
||||
@ -1,19 +1,313 @@ |
||||
package net.pokeranalytics.android.model.realm |
||||
|
||||
import android.content.Context |
||||
import android.text.InputType |
||||
import io.realm.Realm |
||||
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.Criteria |
||||
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.adapter.StaticRowRepresentableDataSource |
||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor |
||||
import net.pokeranalytics.android.ui.view.RowViewType |
||||
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomFieldRow |
||||
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable |
||||
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow |
||||
import net.pokeranalytics.android.util.enumerations.IntIdentifiable |
||||
import java.util.* |
||||
import kotlin.collections.ArrayList |
||||
|
||||
|
||||
open class CustomField : RealmObject() { |
||||
open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { |
||||
|
||||
/** |
||||
* The custom field type: a list of items, a number or an amont |
||||
*/ |
||||
enum class Type(override var uniqueIdentifier: Int, var resId: Int, var isEnabled: Boolean = true) : |
||||
IntIdentifiable { |
||||
LIST(0, R.string.enum_custom_field_type), |
||||
NUMBER(1, R.string.number), |
||||
AMOUNT(2, R.string.amount) |
||||
} |
||||
|
||||
/** |
||||
* The sorting used for the list, either custom, or alphabetically asc/desc |
||||
*/ |
||||
enum class Sort(override var uniqueIdentifier: Int) : IntIdentifiable { |
||||
DEFAULT(0), |
||||
ASCENDING(1), |
||||
DESCENDING(2) |
||||
} |
||||
|
||||
@PrimaryKey |
||||
var id = UUID.randomUUID().toString() |
||||
override var id = UUID.randomUUID().toString() |
||||
|
||||
/** |
||||
* The name of the custom field |
||||
*/ |
||||
override var name: String = "" |
||||
|
||||
// The type of the custom fields, mapped with the CustomField.Type enum |
||||
var type: Int = Type.LIST.uniqueIdentifier |
||||
set(value) { |
||||
if (field == Type.LIST.uniqueIdentifier && value != Type.LIST.uniqueIdentifier) { |
||||
this.removeListEntries() |
||||
} |
||||
field = value |
||||
|
||||
this.updateRowRepresentation() |
||||
} |
||||
|
||||
/** |
||||
* Indicates whether the custom field value should be copied when a session is duplicated |
||||
*/ |
||||
var duplicateValue: Boolean = false |
||||
|
||||
/** |
||||
* The list of entries for the LIST type |
||||
*/ |
||||
var entries: RealmList<CustomFieldEntry> = RealmList() |
||||
|
||||
/** |
||||
* The sorting of the entries, mapped with the CustomField.Sort enum |
||||
*/ |
||||
var sortCondition: Int = Sort.DEFAULT.uniqueIdentifier |
||||
set(value) { |
||||
field = value |
||||
sortEntries() |
||||
updateRowRepresentation() |
||||
} |
||||
|
||||
@Ignore |
||||
private var entriesToDelete: ArrayList<CustomFieldEntry> = ArrayList() |
||||
|
||||
@Ignore |
||||
override var viewType: Int = RowViewType.TITLE_VALUE_ARROW.ordinal |
||||
|
||||
@Ignore |
||||
private var rowRepresentation: List<RowRepresentable> = mutableListOf() |
||||
|
||||
|
||||
//helper |
||||
|
||||
val isListType: Boolean |
||||
get() { |
||||
return this.type == Type.LIST.uniqueIdentifier |
||||
} |
||||
|
||||
val isAmountType: Boolean |
||||
get() { |
||||
return this.type == Type.AMOUNT.uniqueIdentifier |
||||
} |
||||
|
||||
override fun localizedTitle(context: Context): String { |
||||
return this.name |
||||
} |
||||
|
||||
override fun getDisplayName(context: Context): String { |
||||
return this.name |
||||
} |
||||
|
||||
override fun adapterRows(): List<RowRepresentable>? { |
||||
return rowRepresentation |
||||
} |
||||
|
||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||
when (row) { |
||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||
CustomFieldRow.TYPE -> this.type = (value as Type?)?.uniqueIdentifier ?: Type.LIST.uniqueIdentifier |
||||
CustomFieldRow.COPY_ON_DUPLICATE -> this.duplicateValue = value as Boolean? ?: false |
||||
} |
||||
} |
||||
|
||||
override fun isValidForSave(): Boolean { |
||||
return super.isValidForSave() |
||||
} |
||||
|
||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
||||
return when (status) { |
||||
SaveValidityStatus.DATA_INVALID -> R.string.cf_empty_field_error |
||||
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_cf_error |
||||
else -> super.getFailedSaveMessage(status) |
||||
} |
||||
} |
||||
|
||||
override fun alreadyExists(realm: Realm): Boolean { |
||||
return realm.where(this::class.java).equalTo("name", this.name).and().notEqualTo("id", this.id).findAll() |
||||
.isNotEmpty() |
||||
} |
||||
|
||||
override fun isValidForDelete(realm: Realm): Boolean { |
||||
val sessions = realm.where<Session>().contains("customFieldEntries.customField.id", id).findAll() |
||||
return sessions.isEmpty() |
||||
} |
||||
|
||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
||||
//TODO: |
||||
return R.string.cf_entry_delete_popup_message |
||||
} |
||||
|
||||
// The name of the currency field |
||||
var name: String = "" |
||||
override val bottomSheetType: BottomSheetType |
||||
get() { |
||||
return when (type) { |
||||
Type.LIST.uniqueIdentifier -> BottomSheetType.LIST_STATIC |
||||
else -> BottomSheetType.NUMERIC_TEXT |
||||
} |
||||
} |
||||
|
||||
// @todo |
||||
override fun deleteDependencies() { |
||||
if (isValid) { |
||||
val entries = realm.where<CustomFieldEntry>().equalTo("customField.id", id).findAll() |
||||
entries.deleteAllFromRealm() |
||||
} |
||||
} |
||||
|
||||
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? { |
||||
return when (row) { |
||||
is CustomFieldEntry -> row.editingDescriptors( |
||||
mapOf( |
||||
"defaultValue" to row.value |
||||
) |
||||
) |
||||
else -> null |
||||
} |
||||
} |
||||
|
||||
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? { |
||||
return when (type) { |
||||
Type.LIST.uniqueIdentifier -> { |
||||
val defaultValue: Any? by map |
||||
val data: RealmList<CustomFieldEntry>? by map |
||||
arrayListOf( |
||||
RowRepresentableEditDescriptor(defaultValue, staticData = data) |
||||
) |
||||
} |
||||
else -> { |
||||
val defaultValue: Double? by map |
||||
arrayListOf( |
||||
RowRepresentableEditDescriptor( |
||||
defaultValue, inputType = InputType.TYPE_CLASS_NUMBER |
||||
or InputType.TYPE_NUMBER_FLAG_DECIMAL |
||||
or InputType.TYPE_NUMBER_FLAG_SIGNED |
||||
) |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Update the row representation |
||||
*/ |
||||
private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> { |
||||
val rows = ArrayList<RowRepresentable>() |
||||
rows.add(SimpleRow.NAME) |
||||
rows.add(CustomFieldRow.TYPE) |
||||
|
||||
if (type == Type.LIST.uniqueIdentifier && entries.size >= 0) { |
||||
if (entries.isNotEmpty()) { |
||||
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, R.string.items_list)) |
||||
sortEntries() |
||||
entries.forEach { customFieldEntry -> |
||||
customFieldEntry.isMovable = sortCondition == Sort.DEFAULT.uniqueIdentifier |
||||
} |
||||
rows.addAll(entries) |
||||
} |
||||
} |
||||
|
||||
return rows |
||||
} |
||||
|
||||
/** |
||||
* Sort the entries element |
||||
*/ |
||||
private fun sortEntries() { |
||||
when (sortCondition) { |
||||
Sort.ASCENDING.uniqueIdentifier -> entries.sortBy { it.value } |
||||
Sort.DESCENDING.uniqueIdentifier -> entries.sortByDescending { it.value } |
||||
} |
||||
entries.forEachIndexed { index, customFieldEntry -> |
||||
customFieldEntry.order = index |
||||
} |
||||
} |
||||
|
||||
fun updateRowRepresentation() { |
||||
this.rowRepresentation = this.updatedRowRepresentationForCurrentState() |
||||
} |
||||
|
||||
/** |
||||
* Add an entry |
||||
*/ |
||||
fun addEntry(): CustomFieldEntry { |
||||
val entry = CustomFieldEntry() |
||||
this.entries.add(entry) |
||||
sortEntries() |
||||
updateRowRepresentation() |
||||
return entry |
||||
} |
||||
|
||||
/** |
||||
* Delete an entry |
||||
*/ |
||||
fun deleteEntry(entry: CustomFieldEntry) { |
||||
entries.remove(entry) |
||||
entriesToDelete.add(entry) |
||||
sortEntries() |
||||
updateRowRepresentation() |
||||
} |
||||
|
||||
private fun removeListEntries() { |
||||
|
||||
this.entriesToDelete.addAll(entries) |
||||
this.entries.clear() |
||||
|
||||
if (realm != null) { |
||||
realm.executeTransaction { |
||||
this.entriesToDelete.forEach { |
||||
if (it.isManaged) { |
||||
it.deleteFromRealm() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Clean the entries if the type is not a list & remove the deleted entries from realm |
||||
*/ |
||||
// fun cleanEntries(realm: Realm) { |
||||
// realm.executeTransaction { |
||||
// |
||||
// if (!isListType) { |
||||
// entriesToDelete.addAll(entries) |
||||
// entries.clear() |
||||
// } |
||||
// |
||||
// // @TODO |
||||
// entriesToDelete.forEach { |
||||
// Timber.d("Delete entry: V=${it.value} N=${it.numericValue} / ID=${it.id}") |
||||
// realm.where<CustomFieldEntry>().equalTo("id", it.id).findFirst()?.deleteFromRealm() |
||||
// } |
||||
// entriesToDelete.clear() |
||||
// } |
||||
// } |
||||
|
||||
/** |
||||
* Returns a comparison criteria based on this custom field |
||||
*/ |
||||
val criteria: Criteria |
||||
get() { |
||||
return when (this.type) { |
||||
CustomField.Type.LIST.uniqueIdentifier -> Criteria.ListCustomFields(this.id) |
||||
else -> Criteria.ValueCustomFields(this.id) |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,148 @@ |
||||
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.RealmResults |
||||
import io.realm.annotations.Ignore |
||||
import io.realm.annotations.LinkingObjects |
||||
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.NameManageable |
||||
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 |
||||
import net.pokeranalytics.android.ui.view.RowViewType |
||||
import net.pokeranalytics.android.util.NULL_TEXT |
||||
import net.pokeranalytics.android.util.extensions.toCurrency |
||||
import java.text.NumberFormat |
||||
import java.util.* |
||||
import java.util.Currency |
||||
|
||||
|
||||
open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable { |
||||
|
||||
@PrimaryKey |
||||
override var id = UUID.randomUUID().toString() |
||||
|
||||
/** |
||||
* The order in the list |
||||
*/ |
||||
var order: Int = 0 |
||||
|
||||
/** |
||||
* The inverse relationship with CustomField |
||||
*/ |
||||
@LinkingObjects("entries") |
||||
val customFields: RealmResults<CustomField>? = null |
||||
|
||||
val customField: CustomField? |
||||
get() { |
||||
return this.customFields?.first() |
||||
} |
||||
|
||||
/** |
||||
* The string value of the entry |
||||
*/ |
||||
var value: String = "" |
||||
|
||||
/** |
||||
* The numeric value of the entry |
||||
*/ |
||||
var numericValue: Double? = null |
||||
|
||||
@Ignore |
||||
override var name: String = value |
||||
get() { return value } |
||||
|
||||
@Ignore |
||||
var isMovable: Boolean = false |
||||
|
||||
@Ignore |
||||
override val viewType: Int = RowViewType.TITLE_VALUE_ACTION.ordinal |
||||
|
||||
override val imageRes: Int? |
||||
get() { |
||||
return if (isMovable) R.drawable.ic_reorder else null |
||||
} |
||||
|
||||
override val imageTint: Int? |
||||
get() { |
||||
return R.color.kaki |
||||
} |
||||
|
||||
@Ignore |
||||
override val bottomSheetType: BottomSheetType = BottomSheetType.EDIT_TEXT |
||||
|
||||
override fun localizedTitle(context: Context): String { |
||||
return context.getString(R.string.value) |
||||
} |
||||
|
||||
override fun getDisplayName(context: Context): String { |
||||
return if (value.isNotEmpty()) value else NULL_TEXT |
||||
} |
||||
|
||||
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? { |
||||
val defaultValue: Any? by map |
||||
return arrayListOf( |
||||
RowRepresentableEditDescriptor(defaultValue, R.string.value, InputType.TYPE_CLASS_TEXT) |
||||
) |
||||
} |
||||
|
||||
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<CustomFieldEntry>().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<Session>().contains("customFieldEntries.id", id).findAll().isNotEmpty()) { |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
/** |
||||
* Return the amount |
||||
*/ |
||||
fun getFormattedValue(currency: Currency? = null): String { |
||||
return when (customField?.type) { |
||||
CustomField.Type.AMOUNT.uniqueIdentifier -> { |
||||
numericValue?.toCurrency(currency) ?: run { NULL_TEXT } |
||||
} |
||||
CustomField.Type.NUMBER.uniqueIdentifier -> { |
||||
NumberFormat.getInstance().format(this.numericValue) |
||||
} |
||||
else -> { |
||||
value |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,33 +1,98 @@ |
||||
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 |
||||
import net.pokeranalytics.android.calculus.Calculator |
||||
import net.pokeranalytics.android.calculus.Stat |
||||
import net.pokeranalytics.android.model.Criteria |
||||
import net.pokeranalytics.android.model.interfaces.Deletable |
||||
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.RowViewType |
||||
import net.pokeranalytics.android.util.extensions.findById |
||||
import java.util.* |
||||
|
||||
enum class ReportDisplay { |
||||
TABLE, |
||||
GRAPH, |
||||
MAP |
||||
} |
||||
|
||||
open class ReportSetup : RealmObject() { |
||||
open class ReportSetup : RealmObject(), RowRepresentable, Deletable { |
||||
|
||||
@PrimaryKey |
||||
var id = UUID.randomUUID().toString() |
||||
override var id = UUID.randomUUID().toString() |
||||
|
||||
// The name of the report |
||||
var name: String = "" |
||||
|
||||
// The type of display of the report |
||||
var display: Int = ReportDisplay.TABLE.ordinal |
||||
var display: Int = Calculator.Options.Display.TABLE.ordinal |
||||
|
||||
/** |
||||
* A list of statIds to compute |
||||
* Must contain at least 1 |
||||
*/ |
||||
var statIds: RealmList<Int> = RealmList() |
||||
|
||||
/** |
||||
* An optional list of criteriaIds to compare statIds |
||||
*/ |
||||
var criteriaIds: RealmList<Int> = RealmList() |
||||
|
||||
/** |
||||
* An optional list of custom fields ids to be compared |
||||
*/ |
||||
var criteriaCustomFieldIds: RealmList<String> = RealmList() |
||||
|
||||
/** |
||||
* An optional filter to narrow the results |
||||
*/ |
||||
var filter: Filter? = null |
||||
|
||||
// RowRepresentable |
||||
override fun getDisplayName(context: Context): String { |
||||
return this.name |
||||
} |
||||
|
||||
@Ignore |
||||
override val viewType: Int = RowViewType.TITLE_ARROW.ordinal |
||||
|
||||
// @todo define the configuration options |
||||
/** |
||||
* Returns the Options based on the ReportSetup parameters |
||||
*/ |
||||
val options: Calculator.Options |
||||
get() { |
||||
|
||||
// var criteria: List<Int> = listOf() |
||||
// var stats: List<Int> = listOf() |
||||
val realm = Realm.getDefaultInstance() |
||||
val stats = this.statIds.map { Stat.valueByIdentifier(it) } |
||||
|
||||
// The filters associated with the report |
||||
var filters: RealmList<Filter> = RealmList() |
||||
// Comparison criteria |
||||
val criteria = this.criteriaIds.map { Criteria.valueByIdentifier(it) } |
||||
val customFields = this.criteriaCustomFieldIds.mapNotNull { realm.findById<CustomField>(it) } |
||||
val cfCriteria = customFields.map { it.criteria } |
||||
|
||||
val allCriteria = mutableListOf<Criteria>() |
||||
allCriteria.addAll(criteria) |
||||
allCriteria.addAll(cfCriteria) |
||||
|
||||
return Calculator.Options( |
||||
display = Calculator.Options.Display.values()[this.display], |
||||
stats = stats, |
||||
criterias = allCriteria, |
||||
filter = this.filter, |
||||
userGenerated = true, |
||||
reportSetupId = this.id |
||||
) |
||||
} |
||||
|
||||
// Deletable |
||||
|
||||
override fun isValidForDelete(realm: Realm): Boolean { |
||||
return true |
||||
} |
||||
|
||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. |
||||
} |
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,21 @@ |
||||
package net.pokeranalytics.android.model.utils |
||||
|
||||
import io.realm.Realm |
||||
import net.pokeranalytics.android.model.realm.Session |
||||
import java.util.* |
||||
|
||||
class SessionUtils { |
||||
|
||||
companion object { |
||||
|
||||
/** |
||||
* Returns true if the provided parameters doesn't correspond to an existing session |
||||
*/ |
||||
fun unicityCheck(realm: Realm, startDate: Date, endDate: Date, net: Double) : Boolean { |
||||
val sessions = realm.where(Session::class.java).equalTo("startDate", startDate).equalTo("endDate", endDate).equalTo("result.net", net).findAll() |
||||
return sessions.isEmpty() |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,48 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import androidx.fragment.app.Fragment |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.calculus.bankroll.BankrollReport |
||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||
import net.pokeranalytics.android.ui.fragment.BankrollDetailsFragment |
||||
|
||||
class BankrollDetailsActivity : PokerAnalyticsActivity() { |
||||
|
||||
companion object { |
||||
|
||||
private var bankrollReport: BankrollReport? = null |
||||
|
||||
/** |
||||
* Default constructor |
||||
*/ |
||||
fun newInstanceForResult(fragment: Fragment, bankrollReport: BankrollReport, requestCode: Int) { |
||||
this.bankrollReport = bankrollReport |
||||
val intent = Intent(fragment.requireContext(), BankrollDetailsActivity::class.java) |
||||
fragment.startActivityForResult(intent, requestCode) |
||||
} |
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_bankroll_details) |
||||
initUI() |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
bankrollReport?.let { |
||||
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||
val reportDetailsFragment = BankrollDetailsFragment.newInstance(it) |
||||
fragmentTransaction.add(R.id.container, reportDetailsFragment) |
||||
fragmentTransaction.commit() |
||||
|
||||
bankrollReport = null |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,31 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||
import net.pokeranalytics.android.util.billing.AppGuard |
||||
|
||||
class BillingActivity : PokerAnalyticsActivity() { |
||||
|
||||
companion object { |
||||
fun newInstance(context: Context) { |
||||
val intent = Intent(context, BillingActivity::class.java) |
||||
context.startActivity(intent) |
||||
} |
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_billing) |
||||
} |
||||
|
||||
override fun onResume() { |
||||
super.onResume() |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
@ -0,0 +1,36 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.os.Bundle |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.ui.activity.components.ReportActivity |
||||
import net.pokeranalytics.android.ui.fragment.report.ComparisonReportFragment |
||||
|
||||
|
||||
class ComparisonReportActivity : ReportActivity() { |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_report_details) |
||||
initUI() |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
parameters?.let { |
||||
|
||||
val report = it.report |
||||
val title = it.title |
||||
|
||||
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||
val reportDetailsFragment = ComparisonReportFragment.newInstance(report, title) |
||||
fragmentTransaction.add(R.id.reportDetailsContainer, reportDetailsFragment) |
||||
fragmentTransaction.commit() |
||||
} |
||||
parameters = null |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,70 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import com.github.mikephil.charting.data.BarDataSet |
||||
import com.github.mikephil.charting.data.LineDataSet |
||||
import kotlinx.android.synthetic.main.activity_graph.* |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||
import net.pokeranalytics.android.ui.fragment.GraphFragment |
||||
|
||||
|
||||
class GraphActivity : PokerAnalyticsActivity() { |
||||
|
||||
companion object { |
||||
|
||||
private var lineDataSets: List<LineDataSet>? = null |
||||
private var barDataSets: List<BarDataSet>? = null |
||||
private var style: GraphFragment.Style? = GraphFragment.Style.LINE |
||||
private var activityTitle: String? = null |
||||
|
||||
/** |
||||
* Default constructor |
||||
*/ |
||||
fun newInstance( |
||||
context: Context, lineDataSets: List<LineDataSet>? = null, barDataSets: List<BarDataSet>? = null, |
||||
style: GraphFragment.Style = GraphFragment.Style.LINE, title: String? = null |
||||
) { |
||||
this.lineDataSets = lineDataSets |
||||
this.barDataSets = barDataSets |
||||
this.style = style |
||||
this.activityTitle = title |
||||
val intent = Intent(context, GraphActivity::class.java) |
||||
context.startActivity(intent) |
||||
} |
||||
|
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_graph) |
||||
initUI() |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
activityTitle?.let { |
||||
setSupportActionBar(toolbar) |
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true) |
||||
title = activityTitle |
||||
activityTitle = null |
||||
} |
||||
|
||||
style?.let { |
||||
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||
val graphFragment = GraphFragment.newInstance(lineDataSets, barDataSets, it) |
||||
fragmentTransaction.add(R.id.container, graphFragment) |
||||
fragmentTransaction.commit() |
||||
} |
||||
|
||||
lineDataSets = null |
||||
barDataSets = null |
||||
style = null |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,86 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.net.Uri |
||||
import android.os.Bundle |
||||
import androidx.fragment.app.FragmentActivity |
||||
import io.realm.Realm |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||
import net.pokeranalytics.android.ui.activity.components.RequestCode |
||||
import net.pokeranalytics.android.ui.fragment.ImportFragment |
||||
import timber.log.Timber |
||||
|
||||
class ImportActivity : PokerAnalyticsActivity() { |
||||
|
||||
private lateinit var fileURI: Uri |
||||
|
||||
enum class IntentKey(val keyName: String) { |
||||
URI("uri") |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
/** |
||||
* Create a new instance for result |
||||
*/ |
||||
fun newInstanceForResult(context: FragmentActivity, uri: Uri) { |
||||
context.startActivityForResult(getIntent(context, uri), RequestCode.IMPORT.value) |
||||
} |
||||
|
||||
private fun getIntent(context: Context, uri: Uri): Intent { |
||||
val intent = Intent(context, ImportActivity::class.java) |
||||
intent.putExtra(ImportActivity.IntentKey.URI.keyName, uri) |
||||
return intent |
||||
} |
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
|
||||
this.fileURI = intent.getParcelableExtra(ImportActivity.IntentKey.URI.keyName) |
||||
|
||||
setContentView(R.layout.activity_import) |
||||
initUI() |
||||
|
||||
} |
||||
|
||||
override fun onStop() { |
||||
super.onStop() |
||||
|
||||
// Updates the main thread instance with newly inserted data |
||||
val realm = Realm.getDefaultInstance() |
||||
realm.refresh() |
||||
realm.close() |
||||
} |
||||
|
||||
private fun initUI() { |
||||
|
||||
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||
val fragment = ImportFragment() |
||||
|
||||
val fis = contentResolver.openInputStream(fileURI) |
||||
Timber.d("Load fragment data with: $fis") |
||||
fis?.let { |
||||
fragment.setData(it) |
||||
} |
||||
|
||||
fragmentTransaction.add(R.id.container, fragment) |
||||
fragmentTransaction.commit() |
||||
|
||||
} |
||||
|
||||
// private fun requestPermission() { |
||||
// if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { |
||||
// ActivityCompat.requestPermissions( |
||||
// this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PERMISSION_REQUEST_ACCESS_FINE_LOCATION |
||||
// ) |
||||
// } |
||||
// } |
||||
// |
||||
// override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { |
||||
// super.onRequestPermissionsResult(requestCode, permissions, grantResults) |
||||
// } |
||||
|
||||
} |
||||
@ -0,0 +1,127 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.animation.Animator |
||||
import android.animation.AnimatorListenerAdapter |
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.view.View |
||||
import android.view.ViewAnimationUtils |
||||
import kotlinx.android.synthetic.main.activity_new_data.* |
||||
import kotlinx.coroutines.Dispatchers |
||||
import kotlinx.coroutines.GlobalScope |
||||
import kotlinx.coroutines.delay |
||||
import kotlinx.coroutines.launch |
||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||
import net.pokeranalytics.android.ui.extensions.px |
||||
|
||||
|
||||
class NewDataMenuActivity : PokerAnalyticsActivity() { |
||||
|
||||
enum class IntentKey(val keyName: String) { |
||||
CHOICE("CHOICE"), |
||||
} |
||||
|
||||
companion object { |
||||
fun newInstance(context: Context) { |
||||
val intent = Intent(context, NewDataMenuActivity::class.java) |
||||
context.startActivity(intent) |
||||
} |
||||
} |
||||
|
||||
private val fabSize = 48.px |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(net.pokeranalytics.android.R.layout.activity_new_data) |
||||
initUI() |
||||
} |
||||
|
||||
override fun onBackPressed() { |
||||
hideMenu() |
||||
} |
||||
|
||||
override fun onPause() { |
||||
super.onPause() |
||||
overridePendingTransition(0, 0) |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
overridePendingTransition(0, 0) |
||||
|
||||
container.viewTreeObserver.addOnGlobalLayoutListener { |
||||
showMenu() |
||||
} |
||||
|
||||
newCashGame.setOnClickListener { |
||||
finishWithResult(0) |
||||
} |
||||
|
||||
newTournament.setOnClickListener { |
||||
finishWithResult(1) |
||||
} |
||||
|
||||
newTransaction.setOnClickListener { |
||||
finishWithResult(2) |
||||
} |
||||
|
||||
container.setOnClickListener { |
||||
hideMenu() |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Set the result and hide menu |
||||
*/ |
||||
private fun finishWithResult(choice: Int) { |
||||
val intent = Intent() |
||||
intent.putExtra(IntentKey.CHOICE.keyName, choice) |
||||
setResult(RESULT_OK, intent) |
||||
GlobalScope.launch(Dispatchers.Main) { |
||||
delay(200) |
||||
hideMenu(true) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Show menu |
||||
*/ |
||||
private fun showMenu() { |
||||
|
||||
val cx = menuContainer.measuredWidth - fabSize / 2 |
||||
val cy = menuContainer.measuredHeight - fabSize / 2 |
||||
val finalRadius = Math.max(menuContainer.width, menuContainer.height) |
||||
val anim = ViewAnimationUtils.createCircularReveal(menuContainer, cx, cy, 0f, finalRadius.toFloat()) |
||||
anim.duration = 150 |
||||
|
||||
menuContainer.visibility = View.VISIBLE |
||||
anim.start() |
||||
} |
||||
|
||||
/** |
||||
* Hide menu |
||||
*/ |
||||
private fun hideMenu(hideQuickly: Boolean = false) { |
||||
|
||||
val cx = menuContainer.measuredWidth - fabSize / 2 |
||||
val cy = menuContainer.measuredHeight - fabSize / 2 |
||||
val initialRadius = menuContainer.width |
||||
val anim = ViewAnimationUtils.createCircularReveal(menuContainer, cx, cy, initialRadius.toFloat(), 0f) |
||||
anim.duration = 150 |
||||
|
||||
anim.addListener(object : AnimatorListenerAdapter() { |
||||
override fun onAnimationEnd(animation: Animator?) { |
||||
super.onAnimationEnd(animation) |
||||
menuContainer.visibility = View.INVISIBLE |
||||
finish() |
||||
} |
||||
}) |
||||
|
||||
anim.start() |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,63 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import androidx.fragment.app.Fragment |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.calculus.Report |
||||
import net.pokeranalytics.android.calculus.Stat |
||||
import net.pokeranalytics.android.ui.activity.components.ReportActivity |
||||
import net.pokeranalytics.android.ui.activity.components.ReportParameters |
||||
import net.pokeranalytics.android.ui.activity.components.RequestCode |
||||
import net.pokeranalytics.android.ui.fragment.report.ProgressReportFragment |
||||
|
||||
class ProgressReportActivity : ReportActivity() { |
||||
|
||||
companion object { |
||||
|
||||
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects |
||||
|
||||
/** |
||||
* Default constructor |
||||
*/ |
||||
fun newInstance(context: Context, report: Report, title: String, stat: Stat? = null, displayAggregationChoices: Boolean = true) { |
||||
parameters = ReportParameters(report, title, stat, showAggregationChoices = displayAggregationChoices) |
||||
val intent = Intent(context, ProgressReportActivity::class.java) |
||||
context.startActivity(intent) |
||||
} |
||||
|
||||
fun newInstanceForResult(fragment: Fragment, report: Report, title: String, stat: Stat? = null, displayAggregationChoices: Boolean = true) { |
||||
parameters = ReportParameters(report, title, stat, showAggregationChoices = displayAggregationChoices) |
||||
val intent = Intent(fragment.context, ProgressReportActivity::class.java) |
||||
fragment.startActivityForResult(intent, RequestCode.DEFAULT.value) |
||||
} |
||||
|
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_progress_report) |
||||
initUI() |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||
val statisticDetailsFragment = ProgressReportFragment() |
||||
fragmentTransaction.add(R.id.statisticDetailsContainer, statisticDetailsFragment) |
||||
fragmentTransaction.commit() |
||||
|
||||
parameters?.let { |
||||
val report = it.report |
||||
val stat = it.stat ?: report.options.stats.first() |
||||
statisticDetailsFragment.setData(report, stat, it.showAggregationChoices, it.title) |
||||
parameters = null |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import androidx.fragment.app.Fragment |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.calculus.Calculator |
||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||
import net.pokeranalytics.android.ui.activity.components.RequestCode |
||||
|
||||
class ReportCreationActivity : PokerAnalyticsActivity() { |
||||
|
||||
companion object { |
||||
|
||||
var options: Calculator.Options? = null |
||||
|
||||
fun newInstanceForResult(fragment: Fragment, context: Context) { |
||||
val intent = Intent(context, ReportCreationActivity::class.java) |
||||
fragment.startActivityForResult(intent, RequestCode.NEW_REPORT.value) |
||||
} |
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_report_creation) |
||||
} |
||||
|
||||
} |
||||
@ -1,55 +0,0 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.calculus.Report |
||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||
import net.pokeranalytics.android.ui.fragment.ReportDetailsFragment |
||||
|
||||
|
||||
|
||||
class ReportDetailsActivity : PokerAnalyticsActivity() { |
||||
|
||||
companion object { |
||||
|
||||
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects |
||||
private var report: Report? = null |
||||
private var reportTitle: String = "" |
||||
|
||||
/** |
||||
* Default constructor |
||||
*/ |
||||
fun newInstance(context: Context, report: Report, reportTitle: String) { |
||||
//parameters = GraphParameters(stat, group, report) |
||||
this.report = report |
||||
this.reportTitle = reportTitle |
||||
val intent = Intent(context, ReportDetailsActivity::class.java) |
||||
context.startActivity(intent) |
||||
} |
||||
|
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_report_details) |
||||
initUI() |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
report?.let { |
||||
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||
val reportDetailsFragment = ReportDetailsFragment.newInstance(it, reportTitle) |
||||
fragmentTransaction.add(R.id.reportDetailsContainer, reportDetailsFragment) |
||||
fragmentTransaction.commit() |
||||
|
||||
report = null |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,59 +0,0 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.calculus.ComputableGroup |
||||
import net.pokeranalytics.android.calculus.Report |
||||
import net.pokeranalytics.android.calculus.Stat |
||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||
import net.pokeranalytics.android.ui.fragment.StatisticDetailsFragment |
||||
|
||||
|
||||
class StatisticsDetailsParameters(var stat: Stat, var computableGroup: ComputableGroup, var report: Report, var title: String? = null) |
||||
|
||||
class StatisticDetailsActivity : PokerAnalyticsActivity() { |
||||
|
||||
companion object { |
||||
|
||||
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects |
||||
private var parameters: StatisticsDetailsParameters? = null |
||||
private var displayAggregationChoices: Boolean = true |
||||
|
||||
/** |
||||
* Default constructor |
||||
*/ |
||||
fun newInstance(context: Context, stat: Stat, group: ComputableGroup, report: Report, displayAggregationChoices: Boolean = true, title: String? = null) { |
||||
parameters = StatisticsDetailsParameters(stat, group, report, title) |
||||
this.displayAggregationChoices = displayAggregationChoices |
||||
val intent = Intent(context, StatisticDetailsActivity::class.java) |
||||
context.startActivity(intent) |
||||
} |
||||
|
||||
} |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_statistic_details) |
||||
initUI() |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||
val statisticDetailsFragment = StatisticDetailsFragment() |
||||
fragmentTransaction.add(R.id.statisticDetailsContainer, statisticDetailsFragment) |
||||
fragmentTransaction.commit() |
||||
|
||||
parameters?.let { |
||||
statisticDetailsFragment.setData(it.stat, it.computableGroup, it.report, displayAggregationChoices, it.title) |
||||
parameters = null |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,35 @@ |
||||
package net.pokeranalytics.android.ui.activity |
||||
|
||||
import android.os.Bundle |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.ui.activity.components.ReportActivity |
||||
import net.pokeranalytics.android.ui.fragment.report.TableReportFragment |
||||
|
||||
class TableReportActivity : ReportActivity() { |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_table_report) |
||||
initUI() |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
parameters?.let { |
||||
|
||||
val report = it.report |
||||
val title = it.title |
||||
|
||||
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||
val fragment = TableReportFragment.newInstance(report, title) |
||||
fragmentTransaction.add(R.id.reportDetailsContainer, fragment) |
||||
fragmentTransaction.commit() |
||||
} |
||||
parameters = null |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
package net.pokeranalytics.android.ui.activity.components |
||||
|
||||
enum class RequestCode(var value: Int) { |
||||
DEFAULT(1), |
||||
NEW_SESSION(800), |
||||
NEW_TRANSACTION(801), |
||||
NEW_REPORT(802), |
||||
IMPORT(900) |
||||
} |
||||
|
||||
enum class ResultCode(var value: Int) { |
||||
IMPORT_UNRECOGNIZED_FORMAT(901) |
||||
} |
||||
@ -0,0 +1,37 @@ |
||||
package net.pokeranalytics.android.ui.activity.components |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import androidx.fragment.app.Fragment |
||||
import net.pokeranalytics.android.calculus.Report |
||||
import net.pokeranalytics.android.calculus.Stat |
||||
|
||||
class ReportParameters(var report: Report, var title: String, var stat: Stat? = null, var showAggregationChoices: Boolean = true) |
||||
|
||||
abstract class ReportActivity : PokerAnalyticsActivity() { |
||||
|
||||
companion object { |
||||
|
||||
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects |
||||
var parameters: ReportParameters? = null |
||||
|
||||
/** |
||||
* Default constructor |
||||
*/ |
||||
fun newInstance(context: Context, report: Report, reportTitle: String, stat: Stat? = null) { |
||||
val options = report.options |
||||
this.parameters = ReportParameters(report, reportTitle, stat) |
||||
val intent = Intent(context, options.display.activityClass) |
||||
context.startActivity(intent) |
||||
} |
||||
|
||||
fun newInstanceForResult(fragment: Fragment, report: Report, reportTitle: String, stat: Stat? = null) { |
||||
val options = report.options |
||||
this.parameters = ReportParameters(report, reportTitle, stat) |
||||
val intent = Intent(fragment.requireContext(), options.display.activityClass) |
||||
fragment.startActivityForResult(intent, RequestCode.DEFAULT.value) |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,163 @@ |
||||
package net.pokeranalytics.android.ui.adapter |
||||
|
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import androidx.appcompat.widget.AppCompatTextView |
||||
import androidx.recyclerview.widget.RecyclerView |
||||
import io.realm.RealmResults |
||||
import kotlinx.android.synthetic.main.row_transaction.view.* |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.model.realm.Transaction |
||||
import net.pokeranalytics.android.ui.view.BindableHolder |
||||
import net.pokeranalytics.android.ui.view.RowViewType |
||||
import net.pokeranalytics.android.util.NULL_TEXT |
||||
import net.pokeranalytics.android.util.extensions.getMonthAndYear |
||||
import java.util.* |
||||
import kotlin.collections.HashMap |
||||
|
||||
|
||||
/** |
||||
* An adapter capable of displaying a list of RowRepresentables |
||||
* @param dataSource the datasource providing rows |
||||
* @param delegate the delegate, notified of UI actions |
||||
*/ |
||||
class FeedTransactionRowRepresentableAdapter( |
||||
var delegate: RowRepresentableDelegate? = null, |
||||
var realmTransactions: RealmResults<Transaction>, |
||||
var distinctTransactionsHeaders: RealmResults<Transaction> |
||||
) : |
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() { |
||||
|
||||
private var headersPositions = HashMap<Int, Date?>() |
||||
private lateinit var sortedHeaders: SortedMap<Int, Date?> |
||||
|
||||
init { |
||||
refreshData() |
||||
} |
||||
|
||||
/** |
||||
* Display a transaction view |
||||
*/ |
||||
inner class RowTransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { |
||||
fun bind(position: Int, row: Transaction?, adapter: FeedTransactionRowRepresentableAdapter) { |
||||
|
||||
itemView.transactionRow.setData(row as Transaction) |
||||
val listener = View.OnClickListener { |
||||
adapter.delegate?.onRowSelected(position, row) |
||||
} |
||||
itemView.transactionRow.setOnClickListener(listener) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Display a header |
||||
*/ |
||||
inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { |
||||
fun bind(title: String) { |
||||
// Title |
||||
itemView.findViewById<AppCompatTextView>(R.id.title)?.let { |
||||
it.text = title |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { |
||||
return if (viewType == RowViewType.ROW_TRANSACTION.ordinal) { |
||||
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_transaction, parent, false) |
||||
RowTransactionViewHolder(layout) |
||||
} else { |
||||
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_header_title, parent, false) |
||||
HeaderTitleViewHolder(layout) |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
override fun getItemViewType(position: Int): Int { |
||||
if (sortedHeaders.containsKey(position)) { |
||||
return RowViewType.HEADER_TITLE.ordinal |
||||
} else { |
||||
return RowViewType.ROW_TRANSACTION.ordinal |
||||
} |
||||
} |
||||
|
||||
override fun getItemCount(): Int { |
||||
return realmTransactions.size + distinctTransactionsHeaders.size |
||||
} |
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { |
||||
if (holder is RowTransactionViewHolder) { |
||||
holder.bind(position, getTransactionForPosition(position), this) |
||||
} else if (holder is HeaderTitleViewHolder) { |
||||
holder.bind(getHeaderForPosition(position)) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return the header |
||||
*/ |
||||
private fun getHeaderForPosition(position: Int): String { |
||||
if (sortedHeaders.containsKey(position)) { |
||||
val realmHeaderPosition = sortedHeaders.keys.indexOf(position) |
||||
return distinctTransactionsHeaders[realmHeaderPosition]?.date?.getMonthAndYear() ?: "" |
||||
} |
||||
return NULL_TEXT |
||||
} |
||||
|
||||
/** |
||||
* Get real index |
||||
*/ |
||||
private fun getTransactionForPosition(position: Int): Transaction? { |
||||
|
||||
// Row position |
||||
var headersBefore = 0 |
||||
for (key in sortedHeaders.keys) { |
||||
if (position > key) { |
||||
headersBefore++ |
||||
} else { |
||||
break |
||||
} |
||||
} |
||||
|
||||
return realmTransactions[position - headersBefore] |
||||
} |
||||
|
||||
/** |
||||
* Refresh headers positions |
||||
*/ |
||||
fun refreshData() { |
||||
|
||||
headersPositions.clear() |
||||
|
||||
val start = System.currentTimeMillis() |
||||
|
||||
var previousYear = Int.MAX_VALUE |
||||
var previousMonth = Int.MAX_VALUE |
||||
|
||||
val calendar = Calendar.getInstance() |
||||
|
||||
// Add headers if the date doesn't exist yet |
||||
for ((index, transaction) in realmTransactions.withIndex()) { |
||||
calendar.time = transaction.date |
||||
if (checkHeaderCondition(calendar, previousYear, previousMonth)) { |
||||
headersPositions[index + headersPositions.size] = transaction.date |
||||
previousYear = calendar.get(Calendar.YEAR) |
||||
previousMonth = calendar.get(Calendar.MONTH) |
||||
} |
||||
} |
||||
|
||||
sortedHeaders = headersPositions.toSortedMap() |
||||
} |
||||
|
||||
/** |
||||
* Check if we need to add a header |
||||
* Can be change to manage different condition |
||||
*/ |
||||
private fun checkHeaderCondition(currentCalendar: Calendar, previousYear: Int, previousMonth: Int): Boolean { |
||||
return currentCalendar.get(Calendar.YEAR) == previousYear && currentCalendar.get(Calendar.MONTH) < previousMonth || (currentCalendar.get(Calendar.YEAR) < previousYear) |
||||
|
||||
} |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,167 @@ |
||||
package net.pokeranalytics.android.ui.fragment |
||||
|
||||
import android.app.Activity.RESULT_OK |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.view.* |
||||
import androidx.recyclerview.widget.LinearLayoutManager |
||||
import kotlinx.android.synthetic.main.fragment_bankroll.* |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.calculus.ComputedStat |
||||
import net.pokeranalytics.android.calculus.Stat |
||||
import net.pokeranalytics.android.calculus.bankroll.BankrollReport |
||||
import net.pokeranalytics.android.model.LiveData |
||||
import net.pokeranalytics.android.ui.activity.DataListActivity |
||||
import net.pokeranalytics.android.ui.activity.EditableDataActivity |
||||
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.PokerAnalyticsFragment |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.RowViewType |
||||
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable |
||||
|
||||
class BankrollDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { |
||||
|
||||
companion object { |
||||
|
||||
const val REQUEST_CODE_EDIT = 1000 |
||||
|
||||
/** |
||||
* Create new instance |
||||
*/ |
||||
fun newInstance(bankrollReport: BankrollReport): BankrollDetailsFragment { |
||||
val fragment = BankrollDetailsFragment() |
||||
fragment.bankrollReport = bankrollReport |
||||
return fragment |
||||
} |
||||
} |
||||
|
||||
private lateinit var bankrollAdapter: RowRepresentableAdapter |
||||
private lateinit var bankrollReport: BankrollReport |
||||
|
||||
private var bankrollDetailsMenu: Menu? = null |
||||
private var rows: ArrayList<RowRepresentable> = ArrayList() |
||||
|
||||
// Life Cycle |
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||
return inflater.inflate(R.layout.fragment_bankroll_details, container, false) |
||||
} |
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
super.onViewCreated(view, savedInstanceState) |
||||
initUI() |
||||
initData() |
||||
} |
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
||||
super.onActivityResult(requestCode, resultCode, data) |
||||
if (requestCode == REQUEST_CODE_EDIT && resultCode == RESULT_OK) { |
||||
if (data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) != null) { |
||||
activity?.setResult(RESULT_OK, data) |
||||
activity?.finish() |
||||
} else { |
||||
updateMenuUI() |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun adapterRows(): List<RowRepresentable>? { |
||||
return rows |
||||
} |
||||
|
||||
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||
|
||||
} |
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { |
||||
menu?.clear() |
||||
inflater?.inflate(R.menu.toolbar_comparison_chart, menu) |
||||
this.bankrollDetailsMenu = menu |
||||
updateMenuUI() |
||||
super.onCreateOptionsMenu(menu, inflater) |
||||
} |
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean { |
||||
when (item!!.itemId) { |
||||
R.id.settings -> editBankroll() |
||||
} |
||||
return true |
||||
} |
||||
|
||||
// Business |
||||
|
||||
/** |
||||
* Init data |
||||
*/ |
||||
private fun initData() { |
||||
|
||||
rows.clear() |
||||
|
||||
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.global)) |
||||
|
||||
val totalComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total) |
||||
val netComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netResult) |
||||
val netBankedComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netBanked) |
||||
|
||||
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, resId = R.string.bankroll, computedStat = totalComputedStat)) |
||||
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, resId = R.string.net_result, computedStat = netComputedStat)) |
||||
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, resId = R.string.net_banked, computedStat = netBankedComputedStat)) |
||||
|
||||
if (bankrollReport.transactionBuckets.isNotEmpty()) { |
||||
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.operations)) |
||||
bankrollReport.transactionBuckets.keys.forEach { key -> |
||||
bankrollReport.transactionBuckets[key]?.let { transactionBucket -> |
||||
val typeName = transactionBucket.transactions.firstOrNull()?.type?.getDisplayName(requireContext()) |
||||
val computedStat = ComputedStat(Stat.NET_RESULT, transactionBucket.total) |
||||
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, title = typeName, computedStat = computedStat)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
setDisplayHomeAsUpEnabled(true) |
||||
|
||||
updateMenuUI() |
||||
|
||||
bankrollAdapter = RowRepresentableAdapter(this, this) |
||||
|
||||
val viewManager = LinearLayoutManager(requireContext()) |
||||
|
||||
recyclerView.apply { |
||||
setHasFixedSize(true) |
||||
layoutManager = viewManager |
||||
adapter = bankrollAdapter |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Update menu UI |
||||
*/ |
||||
private fun updateMenuUI() { |
||||
|
||||
if (bankrollReport.setup.virtualBankroll) { |
||||
setToolbarTitle(getString(R.string.total)) |
||||
bankrollDetailsMenu?.findItem(R.id.settings)?.isVisible = false |
||||
} else { |
||||
setToolbarTitle(bankrollReport.setup.bankroll?.name) |
||||
bankrollDetailsMenu?.findItem(R.id.settings)?.isVisible = true |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Open Bankroll edit activity |
||||
*/ |
||||
private fun editBankroll() { |
||||
EditableDataActivity.newInstanceForResult(this, LiveData.BANKROLL, bankrollReport.setup.bankroll?.id, REQUEST_CODE_EDIT) |
||||
} |
||||
|
||||
} |
||||
@ -1,222 +0,0 @@ |
||||
package net.pokeranalytics.android.ui.fragment |
||||
|
||||
import android.app.Activity.RESULT_OK |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.view.* |
||||
import androidx.appcompat.app.AlertDialog |
||||
import androidx.recyclerview.widget.LinearLayoutManager |
||||
import io.realm.RealmObject |
||||
import kotlinx.android.synthetic.main.fragment_editable_data.* |
||||
import kotlinx.android.synthetic.main.fragment_editable_data.view.* |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.exceptions.ConfigurationException |
||||
import net.pokeranalytics.android.model.LiveData |
||||
import net.pokeranalytics.android.model.interfaces.Deletable |
||||
import net.pokeranalytics.android.model.interfaces.Editable |
||||
import net.pokeranalytics.android.model.interfaces.Savable |
||||
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus |
||||
import net.pokeranalytics.android.ui.activity.DataListActivity |
||||
import net.pokeranalytics.android.ui.activity.EditableDataActivity |
||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter |
||||
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource |
||||
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate |
||||
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
|
||||
|
||||
open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate { |
||||
|
||||
lateinit var parentActivity: PokerAnalyticsActivity |
||||
lateinit var item: RealmObject |
||||
lateinit var liveDataType: LiveData |
||||
lateinit var rowRepresentableAdapter: RowRepresentableAdapter |
||||
|
||||
private var editableMenu: Menu? = null |
||||
private var dataType: Int? = null |
||||
private var primaryKey: String? = null |
||||
|
||||
var isUpdating = false |
||||
var shouldOpenKeyboard = true |
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||
return inflater.inflate(R.layout.fragment_editable_data, container, false) |
||||
} |
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
super.onViewCreated(view, savedInstanceState) |
||||
initUI() |
||||
initData() |
||||
} |
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { |
||||
menu?.clear() |
||||
inflater?.inflate(R.menu.toolbar_editable_data, menu) |
||||
this.editableMenu = menu |
||||
updateMenuUI() |
||||
super.onCreateOptionsMenu(menu, inflater) |
||||
} |
||||
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean { |
||||
when (item!!.itemId) { |
||||
R.id.save -> saveData() |
||||
R.id.delete -> deleteData() |
||||
} |
||||
return true |
||||
} |
||||
|
||||
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||
BottomSheetFragment.create(fragmentManager, row, this, getDataSource().editDescriptors(row)) |
||||
} |
||||
|
||||
override fun onRowValueChanged(value: Any?, row: RowRepresentable) { |
||||
this.getRealm().executeTransaction { |
||||
(this.item as Editable).updateValue(value, row) |
||||
} |
||||
rowRepresentableAdapter.refreshRow(row) |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
parentActivity = activity as PokerAnalyticsActivity |
||||
parentActivity.setSupportActionBar(toolbar) |
||||
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) |
||||
setHasOptionsMenu(true) |
||||
|
||||
val viewManager = LinearLayoutManager(requireContext()) |
||||
|
||||
recyclerView.apply { |
||||
setHasFixedSize(true) |
||||
layoutManager = viewManager |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return the data source |
||||
*/ |
||||
open fun getDataSource(): RowRepresentableDataSource { |
||||
return this.item as RowRepresentableDataSource |
||||
} |
||||
|
||||
/** |
||||
* Init data |
||||
*/ |
||||
private fun initData() { |
||||
if (this.dataType != null) { |
||||
val proxyItem: RealmObject? = this.liveDataType.getData(this.getRealm(), primaryKey) |
||||
proxyItem?.let { |
||||
//TODO: Localize |
||||
this.appBar.toolbar.title = "Update ${this.liveDataType.localizedTitle(this.parentActivity).toLowerCase().capitalize()}" |
||||
isUpdating = true |
||||
} ?: run { |
||||
//TODO: Localize |
||||
this.appBar.toolbar.title = this.liveDataType.newEntityLocalizedTitle(requireContext()) |
||||
} |
||||
this.item = this.liveDataType.updateOrCreate(this.getRealm(), primaryKey) |
||||
|
||||
val dataSource = getDataSource() |
||||
this.rowRepresentableAdapter = RowRepresentableAdapter(getDataSource(), this) |
||||
this.recyclerView.adapter = rowRepresentableAdapter |
||||
|
||||
// When creating an object, open automatically the keyboard for the first row |
||||
if (!isUpdating && shouldOpenKeyboard) { |
||||
val row = dataSource.adapterRows()?.firstOrNull() |
||||
row?.let { |
||||
onRowSelected(0, it) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Update menu UI |
||||
*/ |
||||
private fun updateMenuUI() { |
||||
editableMenu?.findItem(R.id.delete)?.isVisible = isUpdating |
||||
editableMenu?.findItem(R.id.save)?.isVisible = true |
||||
} |
||||
|
||||
/** |
||||
* Save data |
||||
*/ |
||||
fun saveData() { |
||||
|
||||
val savable = this.item |
||||
when (savable) { |
||||
is Savable -> { |
||||
val status = savable.getSaveValidityStatus(realm = this.getRealm()) |
||||
when (status) { |
||||
SaveValidityStatus.VALID -> { |
||||
this.getRealm().executeTransaction { |
||||
val managedItem = it.copyToRealmOrUpdate(this.item) |
||||
if (managedItem is Savable) { |
||||
val uniqueIdentifier = (managedItem as Savable).id |
||||
finishActivityWithResult(uniqueIdentifier) |
||||
} |
||||
|
||||
} |
||||
} |
||||
else -> { |
||||
val message = savable.getFailedSaveMessage(status) |
||||
val builder = AlertDialog.Builder(requireContext()) |
||||
.setMessage(message) |
||||
.setNegativeButton(R.string.ok, null) |
||||
builder.show() |
||||
} |
||||
} |
||||
|
||||
} else -> { |
||||
throw ConfigurationException("Save action called on un-Savable object") |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Delete data |
||||
*/ |
||||
private fun deleteData() { |
||||
|
||||
val deletable = this.item as Deletable |
||||
val realm = this.getRealm() |
||||
|
||||
if (deletable.isValidForDelete(realm)) { |
||||
val intent = Intent() |
||||
intent.putExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, true) |
||||
activity?.setResult(RESULT_OK, intent) |
||||
activity?.finish() |
||||
} else { |
||||
val message = deletable.getFailedDeleteMessage() |
||||
val builder = AlertDialog.Builder(requireContext()) |
||||
.setMessage(message) |
||||
.setNegativeButton(R.string.ok, null) |
||||
builder.show() |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Finish the activity with a result |
||||
*/ |
||||
private fun finishActivityWithResult(uniqueIdentifier: String) { |
||||
val intent = Intent() |
||||
intent.putExtra(EditableDataActivity.IntentKey.DATA_TYPE.keyName, dataType) |
||||
intent.putExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName, uniqueIdentifier) |
||||
activity?.setResult(RESULT_OK, intent) |
||||
activity?.finish() |
||||
} |
||||
|
||||
/** |
||||
* Set fragment data |
||||
*/ |
||||
fun setData(dataType: Int, primaryKey: String?) { |
||||
this.dataType = dataType |
||||
this.liveDataType = LiveData.values()[dataType] |
||||
this.primaryKey = primaryKey |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,377 @@ |
||||
package net.pokeranalytics.android.ui.fragment |
||||
|
||||
import android.app.Activity.RESULT_OK |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
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.tabs.TabLayout |
||||
import io.realm.RealmModel |
||||
import io.realm.RealmResults |
||||
import io.realm.Sort |
||||
import io.realm.kotlin.where |
||||
import kotlinx.android.synthetic.main.fragment_feed.* |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.model.LiveData |
||||
import net.pokeranalytics.android.model.interfaces.Editable |
||||
import net.pokeranalytics.android.model.realm.Filter |
||||
import net.pokeranalytics.android.model.realm.Session |
||||
import net.pokeranalytics.android.model.realm.Transaction |
||||
import net.pokeranalytics.android.ui.activity.* |
||||
import net.pokeranalytics.android.ui.activity.components.RequestCode |
||||
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.fragment.components.FilterableFragment |
||||
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode |
||||
import net.pokeranalytics.android.ui.interfaces.FilterableType |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager |
||||
import net.pokeranalytics.android.util.Preferences |
||||
import java.text.SimpleDateFormat |
||||
import java.util.* |
||||
|
||||
|
||||
class FeedFragment : FilterableFragment(), RowRepresentableDelegate { |
||||
|
||||
private enum class Tab { |
||||
SESSIONS, |
||||
TRANSACTIONS |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
const val REQUEST_CODE_MENU = 100 |
||||
const val REQUEST_CODE_TRANSACTION_DETAILS = 101 |
||||
|
||||
fun newInstance(): FeedFragment { |
||||
val fragment = FeedFragment() |
||||
val bundle = Bundle() |
||||
fragment.arguments = bundle |
||||
return fragment |
||||
} |
||||
} |
||||
|
||||
private lateinit var feedSessionAdapter: FeedSessionRowRepresentableAdapter |
||||
private lateinit var feedTransactionAdapter: FeedTransactionRowRepresentableAdapter |
||||
private lateinit var realmSessions: RealmResults<Session> |
||||
private lateinit var realmTransactions: RealmResults<Transaction> |
||||
private lateinit var betaLimitDate: Date |
||||
|
||||
private var newSessionCreated: Boolean = false |
||||
private var adapterHasBeenSet: Boolean = false |
||||
private var selectedTransaction: Transaction? = null |
||||
private var selectedTransactionPosition: Int = -1 |
||||
|
||||
override val observedEntities: List<Class<out RealmModel>> = listOf(Session::class.java, Transaction::class.java) |
||||
|
||||
override fun entitiesChanged(clazz: Class<out RealmModel>) { |
||||
super.entitiesChanged(clazz) |
||||
|
||||
when (clazz.kotlin) { |
||||
Session::class -> { |
||||
this.feedSessionAdapter.refreshData() |
||||
this.feedSessionAdapter.notifyDataSetChanged() |
||||
} |
||||
Transaction::class -> { |
||||
this.feedTransactionAdapter.refreshData() |
||||
this.feedTransactionAdapter.notifyDataSetChanged() |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||
super.onCreateView(inflater, container, savedInstanceState) |
||||
return inflater.inflate(R.layout.fragment_feed, container, false) |
||||
} |
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
super.onViewCreated(view, savedInstanceState) |
||||
initUI() |
||||
initData() |
||||
} |
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
||||
super.onActivityResult(requestCode, resultCode, data) |
||||
if (requestCode == REQUEST_CODE_MENU && resultCode == RESULT_OK && data != null) { |
||||
when (data.getIntExtra(NewDataMenuActivity.IntentKey.CHOICE.keyName, -1)) { |
||||
0 -> createNewSession(false) |
||||
1 -> createNewSession(true) |
||||
2 -> createNewTransaction() |
||||
} |
||||
} else if (requestCode == REQUEST_CODE_TRANSACTION_DETAILS && resultCode == RESULT_OK && data != null) { |
||||
if (data.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) != null) { |
||||
deleteSelectedTransaction() |
||||
} |
||||
} else if (requestCode == FilterActivityRequestCode.CREATE_FILTER.ordinal && resultCode == RESULT_OK) { |
||||
data?.let { |
||||
this.saveFilter(this.requireContext(), it.getStringExtra(FiltersActivity.IntentKey.FILTER_ID.keyName)) |
||||
} |
||||
} else if (requestCode == RequestCode.NEW_TRANSACTION.value && resultCode == RESULT_OK) { |
||||
this.selectTab(Tab.TRANSACTIONS) |
||||
} else if (requestCode == RequestCode.NEW_SESSION.value && resultCode == RESULT_OK) { |
||||
this.selectTab(Tab.SESSIONS) |
||||
} |
||||
|
||||
} |
||||
|
||||
override fun onDestroyView() { |
||||
super.onDestroyView() |
||||
realmSessions.removeAllChangeListeners() |
||||
realmTransactions.removeAllChangeListeners() |
||||
} |
||||
|
||||
/* |
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) { |
||||
super.setUserVisibleHint(isVisibleToUser) |
||||
if (isVisibleToUser && view != null) { |
||||
if (FilterHandler.filterWasUpdated) { |
||||
applyFilter() |
||||
FilterHandler.filterWasUpdated = false |
||||
} |
||||
} |
||||
} |
||||
*/ |
||||
|
||||
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||
when (row) { |
||||
is Session -> SessionActivity.newInstance(requireContext(), sessionId = (row as Editable).id) |
||||
is Transaction -> { |
||||
selectedTransaction = row |
||||
selectedTransactionPosition = position |
||||
EditableDataActivity.newInstanceForResult( |
||||
this, |
||||
LiveData.TRANSACTION, |
||||
row.id, |
||||
REQUEST_CODE_TRANSACTION_DETAILS |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
disclaimerContainer.isVisible = Preferences.shouldShowDisclaimer(requireContext()) |
||||
|
||||
disclaimerDismiss.setOnClickListener { |
||||
Preferences.setStopShowingDisclaimer(requireContext()) |
||||
|
||||
disclaimerContainer.animate().translationY(disclaimerContainer.height.toFloat()) |
||||
.setInterpolator(FastOutSlowInInterpolator()) |
||||
.withEndAction { disclaimerContainer?.isVisible = false } |
||||
.start() |
||||
} |
||||
|
||||
addButton.setOnClickListener { |
||||
activity?.let { |
||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(it) |
||||
val intent = Intent(requireContext(), NewDataMenuActivity::class.java) |
||||
startActivityForResult(intent, REQUEST_CODE_MENU, options.toBundle()) |
||||
} |
||||
} |
||||
|
||||
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { |
||||
override fun onTabSelected(tab: TabLayout.Tab) { |
||||
when (tab.position) { |
||||
0 -> { |
||||
currentFilterable = FilterableType.SESSION |
||||
recyclerView.adapter = feedSessionAdapter |
||||
} |
||||
1 -> { |
||||
currentFilterable = FilterableType.TRANSACTION |
||||
recyclerView.adapter = feedTransactionAdapter |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) { |
||||
} |
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab) { |
||||
} |
||||
}) |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Init data |
||||
*/ |
||||
private fun initData() { |
||||
|
||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm", Locale.getDefault()) |
||||
betaLimitDate = sdf.parse("17/7/2019 10:00") |
||||
this.currentFilterable = FilterableType.SESSION |
||||
|
||||
val viewManager = SmoothScrollLinearLayoutManager(requireContext()) |
||||
recyclerView.apply { |
||||
setHasFixedSize(true) |
||||
layoutManager = viewManager |
||||
} |
||||
|
||||
applyFilter() |
||||
} |
||||
|
||||
private fun loadSessions(filter: Filter? = null) { |
||||
val sessionFilter: Filter? = filter?.let { |
||||
if (it.filterableType == FilterableType.SESSION) { |
||||
it |
||||
} else { |
||||
null |
||||
} |
||||
} |
||||
|
||||
// Sessions |
||||
this.realmSessions = |
||||
sessionFilter?.results() ?: run { getRealm().where<Session>().isNotNull("startDate").findAll() } |
||||
this.realmSessions = this.realmSessions.sort("startDate", Sort.DESCENDING) |
||||
|
||||
val pendingSessions = sessionFilter?.let { |
||||
getRealm().where<Session>().alwaysFalse().findAll() |
||||
} ?: run { |
||||
getRealm().where<Session>().isNull("year").isNull("month").findAll().sort("startDate", Sort.DESCENDING) |
||||
} |
||||
var distinctDateSessions = sessionFilter?.results("year", "month") ?: run { |
||||
getRealm().where<Session>().distinct("year", "month").findAll() |
||||
} |
||||
distinctDateSessions = distinctDateSessions.sort("startDate", Sort.DESCENDING) |
||||
this.feedSessionAdapter = |
||||
FeedSessionRowRepresentableAdapter(this, realmSessions, pendingSessions, distinctDateSessions) |
||||
} |
||||
|
||||
private fun loadTransactions(filter: Filter? = null) { |
||||
val transactionFilter: Filter? = filter?.let { |
||||
if (it.filterableType == FilterableType.TRANSACTION) { |
||||
it |
||||
} else { |
||||
null |
||||
} |
||||
} |
||||
|
||||
// Transactions |
||||
this.realmTransactions = transactionFilter?.results() ?: run { getRealm().where<Transaction>().findAll() } |
||||
this.realmTransactions = this.realmTransactions.sort("date", Sort.DESCENDING) |
||||
|
||||
var distinctDateTransactions = transactionFilter?.results("year", "month") ?: run { |
||||
getRealm().where<Transaction>().distinct("year", "month").findAll() |
||||
} |
||||
distinctDateTransactions = distinctDateTransactions.sort("date", Sort.DESCENDING) |
||||
this.feedTransactionAdapter = |
||||
FeedTransactionRowRepresentableAdapter(this, realmTransactions, distinctDateTransactions) |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Create a new cash game |
||||
*/ |
||||
private fun createNewSession(isTournament: Boolean) { |
||||
|
||||
// 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() |
||||
// BillingActivity.newInstanceForResult(requireContext()) |
||||
// return |
||||
// } |
||||
|
||||
if (Date().after(betaLimitDate)) { |
||||
this.showEndOfBetaMessage() |
||||
return |
||||
} |
||||
|
||||
SessionActivity.newInstanceforResult(this, isTournament, requestCode = RequestCode.NEW_SESSION.value) |
||||
newSessionCreated = true |
||||
} |
||||
|
||||
/** |
||||
* Create a new transaction |
||||
*/ |
||||
private fun createNewTransaction() { |
||||
|
||||
if (Date().after(betaLimitDate)) { |
||||
this.showEndOfBetaMessage() |
||||
return |
||||
} |
||||
EditableDataActivity.newInstanceForResult(this, LiveData.TRANSACTION, null, RequestCode.NEW_TRANSACTION.value) |
||||
|
||||
// EditableDataActivity.newInstance(requireContext(), LiveData.TRANSACTION.ordinal) |
||||
} |
||||
|
||||
/** |
||||
* Delete selected transaction |
||||
*/ |
||||
private fun deleteSelectedTransaction() { |
||||
val realm = getRealm() |
||||
realm.beginTransaction() |
||||
selectedTransaction?.deleteFromRealm() |
||||
realm.commitTransaction() |
||||
selectedTransactionPosition = -1 |
||||
} |
||||
|
||||
/** |
||||
* Show end of beta message |
||||
*/ |
||||
private fun showEndOfBetaMessage() { |
||||
Toast.makeText( |
||||
context, |
||||
"Beta has ended. Thanks a lot for your participation! Please update with the Google Play version to continue using the app", |
||||
Toast.LENGTH_LONG |
||||
).show() |
||||
} |
||||
|
||||
|
||||
// Filter Handler |
||||
|
||||
override fun applyFilter() { |
||||
super.applyFilter() |
||||
|
||||
val filter: Filter? = this.currentFilter(this.requireContext(), getRealm()) |
||||
this.loadSessions(filter) |
||||
this.loadTransactions(filter) |
||||
|
||||
filter?.let { |
||||
when (it.filterableType) { |
||||
FilterableType.SESSION -> { |
||||
recyclerView.adapter = feedSessionAdapter |
||||
this.selectTab(Tab.SESSIONS) |
||||
} |
||||
FilterableType.TRANSACTION -> { |
||||
recyclerView.adapter = feedTransactionAdapter |
||||
this.selectTab(Tab.TRANSACTIONS) |
||||
} |
||||
else -> { |
||||
} |
||||
} |
||||
adapterHasBeenSet = true |
||||
} |
||||
|
||||
if (!adapterHasBeenSet) { |
||||
adapterHasBeenSet = true |
||||
recyclerView.adapter = feedSessionAdapter |
||||
} |
||||
} |
||||
|
||||
override fun removeFilter() { |
||||
super.removeFilter() |
||||
|
||||
this.loadSessions() |
||||
this.loadTransactions() |
||||
if (currentFilterable == FilterableType.SESSION) { |
||||
recyclerView.adapter = feedSessionAdapter |
||||
} else { |
||||
recyclerView.adapter = feedTransactionAdapter |
||||
} |
||||
} |
||||
|
||||
private fun selectTab(tab: Tab) { |
||||
this.tabs.getTabAt(tab.ordinal)?.select() |
||||
} |
||||
|
||||
} |
||||
@ -1,152 +0,0 @@ |
||||
package net.pokeranalytics.android.ui.fragment |
||||
|
||||
import android.os.Bundle |
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import android.widget.Toast |
||||
import androidx.core.view.isVisible |
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator |
||||
import io.realm.RealmResults |
||||
import io.realm.Sort |
||||
import io.realm.kotlin.where |
||||
import kotlinx.android.synthetic.main.fragment_history.* |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.model.interfaces.Editable |
||||
import net.pokeranalytics.android.model.realm.Session |
||||
import net.pokeranalytics.android.ui.activity.SessionActivity |
||||
import net.pokeranalytics.android.ui.adapter.HistorySessionRowRepresentableAdapter |
||||
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource |
||||
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate |
||||
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager |
||||
import net.pokeranalytics.android.util.Preferences |
||||
import java.text.SimpleDateFormat |
||||
import java.util.* |
||||
|
||||
class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate { |
||||
|
||||
companion object { |
||||
fun newInstance(): HistoryFragment { |
||||
val fragment = HistoryFragment() |
||||
val bundle = Bundle() |
||||
fragment.arguments = bundle |
||||
return fragment |
||||
} |
||||
} |
||||
|
||||
private lateinit var historyAdapter: HistorySessionRowRepresentableAdapter |
||||
|
||||
private lateinit var realmSessions: RealmResults<Session> |
||||
private val rows: ArrayList<RowRepresentable> = ArrayList() |
||||
private var newSessionCreated: Boolean = false |
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||
return inflater.inflate(R.layout.fragment_history, container, false) |
||||
} |
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
super.onViewCreated(view, savedInstanceState) |
||||
initUI() |
||||
initData() |
||||
} |
||||
|
||||
override fun onDestroyView() { |
||||
super.onDestroyView() |
||||
realmSessions.removeAllChangeListeners() |
||||
} |
||||
|
||||
/** |
||||
* Init UI |
||||
*/ |
||||
private fun initUI() { |
||||
|
||||
disclaimerContainer.isVisible = Preferences.shouldShowDisclaimer(requireContext()) |
||||
|
||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
||||
val betaLimitDate = sdf.parse("17/7/2019 10:00") |
||||
|
||||
newCashGame.setOnClickListener { |
||||
|
||||
if (Date().after(betaLimitDate)) { |
||||
this.showEndOfBetaMessage() |
||||
return@setOnClickListener |
||||
} |
||||
|
||||
SessionActivity.newInstance(requireContext(), false) |
||||
newSessionCreated = true |
||||
} |
||||
|
||||
newTournament.setOnClickListener { |
||||
|
||||
if (Date().after(betaLimitDate)) { |
||||
this.showEndOfBetaMessage() |
||||
return@setOnClickListener |
||||
} |
||||
|
||||
SessionActivity.newInstance(requireContext(), true) |
||||
newSessionCreated = true |
||||
} |
||||
|
||||
disclaimerDismiss.setOnClickListener { |
||||
Preferences.setStopShowingDisclaimer(requireContext()) |
||||
|
||||
disclaimerContainer.animate().translationY(disclaimerContainer.height.toFloat()) |
||||
.setInterpolator(FastOutSlowInInterpolator()) |
||||
.withEndAction { disclaimerContainer?.isVisible = false } |
||||
.start() |
||||
} |
||||
|
||||
} |
||||
|
||||
private fun showEndOfBetaMessage() { |
||||
Toast.makeText(context, "Beta has ended. Please update with the Google Play version", Toast.LENGTH_LONG).show() |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Init data |
||||
*/ |
||||
private fun initData() { |
||||
|
||||
this.realmSessions = getRealm().where<Session>().findAll().sort("startDate", Sort.DESCENDING) |
||||
this.realmSessions.addChangeListener { _, _ -> |
||||
this.historyAdapter.refreshData() |
||||
this.historyAdapter.notifyDataSetChanged() |
||||
} |
||||
|
||||
val startedSessions = getRealm().where<Session>().isNotNull("year").isNotNull("month").findAll().sort("startDate", Sort.DESCENDING) |
||||
val pendingSessions = getRealm().where<Session>().isNull("year").isNull("month").findAll().sort("startDate", Sort.DESCENDING) |
||||
val distinctDateSessions = getRealm().where<Session>().distinct("year", "month").findAll().sort("startDate", Sort.DESCENDING) |
||||
|
||||
this.historyAdapter = HistorySessionRowRepresentableAdapter(this, startedSessions, pendingSessions, distinctDateSessions) |
||||
|
||||
val viewManager = SmoothScrollLinearLayoutManager(requireContext()) |
||||
recyclerView.apply { |
||||
setHasFixedSize(true) |
||||
layoutManager = viewManager |
||||
adapter = historyAdapter |
||||
} |
||||
} |
||||
|
||||
override fun rowRepresentableForPosition(position: Int): RowRepresentable? { |
||||
return this.rows[position] |
||||
} |
||||
|
||||
override fun numberOfRows(): Int { |
||||
return this.rows.size |
||||
} |
||||
|
||||
override fun viewTypeForPosition(position: Int): Int { |
||||
return rows[position].viewType |
||||
} |
||||
|
||||
override fun indexForRow(row: RowRepresentable): Int { |
||||
return this.rows.indexOf(row) |
||||
} |
||||
|
||||
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||
SessionActivity.newInstance(requireContext(), sessionId = (row as Editable).id) |
||||
} |
||||
} |
||||
@ -0,0 +1,84 @@ |
||||
package net.pokeranalytics.android.ui.fragment |
||||
|
||||
import android.os.Bundle |
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import kotlinx.coroutines.Dispatchers |
||||
import kotlinx.coroutines.GlobalScope |
||||
import kotlinx.coroutines.async |
||||
import kotlinx.coroutines.launch |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.ui.activity.components.ResultCode |
||||
import net.pokeranalytics.android.ui.fragment.components.RealmFragment |
||||
import net.pokeranalytics.android.util.csv.CSVImporter |
||||
import net.pokeranalytics.android.util.csv.ImportException |
||||
import timber.log.Timber |
||||
import java.io.InputStream |
||||
import java.util.* |
||||
import kotlin.coroutines.CoroutineContext |
||||
|
||||
class ImportFragment : RealmFragment() { |
||||
|
||||
val coroutineContext: CoroutineContext |
||||
get() = Dispatchers.Main |
||||
|
||||
private lateinit var filePath: String |
||||
private lateinit var inputStream: InputStream |
||||
|
||||
fun setData(path: String) { |
||||
this.filePath = path |
||||
} |
||||
|
||||
fun setData(inputStream: InputStream) { |
||||
this.inputStream = inputStream |
||||
} |
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||
super.onCreateView(inflater, container, savedInstanceState) |
||||
return inflater.inflate(R.layout.fragment_import, container, false) |
||||
} |
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
super.onViewCreated(view, savedInstanceState) |
||||
|
||||
this.startImport() |
||||
} |
||||
|
||||
fun startImport() { |
||||
|
||||
var shouldDismissActivity = false |
||||
|
||||
GlobalScope.launch(coroutineContext) { |
||||
|
||||
val test = GlobalScope.async { |
||||
val s = Date() |
||||
Timber.d(">>> Start Import...") |
||||
|
||||
try { |
||||
val csv = CSVImporter(inputStream) |
||||
csv.start() |
||||
} catch (e: ImportException) { |
||||
shouldDismissActivity = true |
||||
} |
||||
val e = Date() |
||||
val duration = (e.time - s.time) / 1000.0 |
||||
Timber.d(">>> Import ended in $duration seconds") |
||||
|
||||
} |
||||
test.await() |
||||
|
||||
if (shouldDismissActivity) { |
||||
|
||||
activity?.let { |
||||
it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) |
||||
it.finish() |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue