diff --git a/app/build.gradle b/app/build.gradle index 4304c29c..774ed53a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,8 +29,8 @@ android { applicationId "net.pokeranalytics.android" minSdkVersion 23 targetSdkVersion 28 - versionCode 30 - versionName "2.0" + versionCode 50 + versionName "2.1.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -39,6 +39,7 @@ android { ext.enableCrashlytics = false } release { + useProguard false minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' applicationVariants.all { variant -> @@ -78,7 +79,7 @@ dependencies { // Android implementation 'androidx.appcompat:appcompat:1.0.2' - implementation 'androidx.core:core-ktx:1.2.0-alpha01' + implementation 'androidx.core:core-ktx:1.2.0-alpha02' implementation 'com.google.android.material:material:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' @@ -99,7 +100,7 @@ dependencies { implementation 'com.android.billingclient:billing:1.2.2' // Firebase - implementation 'com.google.firebase:firebase-core:16.0.9' + implementation 'com.google.firebase:firebase-core:17.0.0' // Crashlytics implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' @@ -114,10 +115,10 @@ dependencies { implementation 'org.apache.commons:commons-csv:1.6' // Instrumented Tests - androidTestImplementation 'androidx.test:core:1.1.0' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test:rules:1.1.1' - androidTestImplementation 'androidx.test.ext:junit:1.1.0' + androidTestImplementation 'androidx.test:core:1.2.0' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' // Test testImplementation 'junit:junit:4.12' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e4b45cbc..ef92b7ab 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -29,6 +29,7 @@ -dontwarn javax.** -dontwarn io.realm.** -keep class net.pokeranalytics.android.model.** { *; } +-keep class net.pokeranalytics.android.ui.fragment.** { *; } # Retrofit diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt index a76308ed..5a21a604 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt @@ -19,7 +19,7 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() { TransactionType.Value.values().forEachIndexed { index, value -> val type = TransactionType() type.additive = value.additive - type.kind = index + type.kind = value.uniqueIdentifier type.lock = true realm.insertOrUpdate(type) } @@ -72,12 +72,12 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() { } val br1 = realm.where(Bankroll::class.java).equalTo("name", "br1").findFirst() - val brSetup1 = BankrollReportSetup(br1) + val brSetup1 = BankrollReportSetup(br1?.id) val report1 = BankrollCalculator.computeReport(realm, brSetup1) Assert.assertEquals(400.0, report1.total, EPSILON) val br2 = realm.where(Bankroll::class.java).equalTo("name", "br2").findFirst() - val brSetup2 = BankrollReportSetup(br2) + val brSetup2 = BankrollReportSetup(br2?.id) val report2 = BankrollCalculator.computeReport(realm, brSetup2) Assert.assertEquals(2000.0, report2.total, EPSILON) @@ -116,7 +116,7 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() { } - val brSetup1 = BankrollReportSetup(br1) + val brSetup1 = BankrollReportSetup(br1?.id) val report1 = BankrollCalculator.computeReport(realm, brSetup1) Assert.assertEquals(400.0, report1.total, EPSILON) diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/CustomFieldFilterInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/CustomFieldFilterInstrumentedUnitTest.kt index f3880227..8f732f0e 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/CustomFieldFilterInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/CustomFieldFilterInstrumentedUnitTest.kt @@ -22,12 +22,11 @@ class CustomFieldFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { val realm = this.mockRealm realm.beginTransaction() - val cf1 = CustomField() - cf1.id = "1" + val cf1 = realm.createObject(CustomField::class.java, "1") cf1.type = CustomField.Type.LIST.ordinal - val cfe1 = CustomFieldEntry() - val cfe2 = CustomFieldEntry() + val cfe1 = realm.createObject(CustomFieldEntry::class.java, "9") + val cfe2 = realm.createObject(CustomFieldEntry::class.java, "8") cfe1.value = "super" cfe2.value = "nul" @@ -51,35 +50,38 @@ class CustomFieldFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { @Test fun testCustomFieldAmountFilter() { - val realm = this.mockRealm - realm.beginTransaction() + val cfId = "1234" + var s2Id = "" - val cf1 = CustomField() - cf1.id = "1234" - cf1.type = CustomField.Type.AMOUNT.ordinal + val realm = this.mockRealm + realm.executeTransaction { + val cf = realm.createObject(CustomField::class.java, cfId) + cf.type = CustomField.Type.AMOUNT.ordinal - val cfe1 = CustomFieldEntry() - cfe1.id = "999" - cf1.entries.add(cfe1) - cfe1.numericValue = 30.0 + val cfe1 = realm.createObject(CustomFieldEntry::class.java, "999") + cf.entries.add(cfe1) + cfe1.numericValue = 30.0 - val cfe2 = CustomFieldEntry() - cfe2.id = "888" - cf1.entries.add(cfe2) - cfe2.numericValue = 100.0 + val cfe2 = realm.createObject(CustomFieldEntry::class.java, "888") + cf.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 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) + s2Id = s2.id + } - val sessions = Filter.queryOn(realm, Query(QueryCondition.CustomFieldNumberQuery(cf1.id, 100.0))) + val condition = QueryCondition.CustomFieldNumberQuery(cfId, 100.0) + val sessions = Filter.queryOn(realm, Query(condition)) Assert.assertEquals(1, sessions.size) - sessions[0]?.run { - Assert.assertEquals(s2.id, (this).id) + sessions.first()?.run { + Assert.assertEquals(s2Id, this.id) } + } + } \ No newline at end of file diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/TransactionFilterInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/TransactionFilterInstrumentedUnitTest.kt index e0b5314c..53d77748 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/TransactionFilterInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/TransactionFilterInstrumentedUnitTest.kt @@ -2,18 +2,16 @@ 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 net.pokeranalytics.android.model.realm.Bankroll +import net.pokeranalytics.android.model.realm.Filter +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.TransactionType import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith -import java.util.* @RunWith(AndroidJUnit4::class) class TransactionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { @@ -29,7 +27,7 @@ class TransactionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { val name = "test" type.name = name type.additive = value.additive - type.kind = index + type.kind = value.uniqueIdentifier type.lock = true realm.insertOrUpdate(type) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c296be62..6edfa7d8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,6 +32,13 @@ + + + + @@ -45,23 +52,16 @@ - - - - + + + + + - + ().findAll().size + val sessionsCount = realm.where().count() realm.close() - if (sessionsCount < 1) { + if (sessionsCount < 10) { GlobalScope.launch { FakeDataManager.createFakeSessions(200) } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt index 2f1726cb..7185f0d9 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt @@ -4,6 +4,7 @@ import android.content.Context import io.realm.Realm import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.Stat.* +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.Criteria import net.pokeranalytics.android.model.combined import net.pokeranalytics.android.model.extensions.hourlyDuration @@ -95,7 +96,7 @@ class Calculator { TABLE -> TableReportActivity::class.java PROGRESS -> ProgressReportActivity::class.java COMPARISON -> ComparisonReportActivity::class.java - else -> throw IllegalStateException("undefined activity for report display") + else -> throw PAIllegalStateException("undefined activity for report display") // MAP -> R.string.map // POLYNOMIAL -> null @@ -378,7 +379,7 @@ class Calculator { } val session = - computable.session ?: throw IllegalStateException("Computing lone ComputableResult") + computable.session ?: throw PAIllegalStateException("Computing lone ComputableResult") results.addEvolutionValue(tSum, stat = NET_RESULT, data = session) results.addEvolutionValue(tSum / index, stat = AVERAGE, data = session) results.addEvolutionValue(index.toDouble(), stat = NUMBER_OF_GAMES, data = session) diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/ComputedStat.kt b/app/src/main/java/net/pokeranalytics/android/calculus/ComputedStat.kt new file mode 100644 index 00000000..7058e349 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/calculus/ComputedStat.kt @@ -0,0 +1,34 @@ +package net.pokeranalytics.android.calculus + +import net.pokeranalytics.android.util.TextFormat +import java.util.* + +/** + * ComputedStat contains a [stat] and their associated [value] + */ +class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? = null, var currency: Currency? = null) { + + constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { + if (previousValue != null) { + this.variation = (value - previousValue) / previousValue + } + } + + /** + * The value used to get evolution dataset + */ + var progressValue: Double? = null + + /** + * The variation of the stat + */ + var variation: Double? = null + + /** + * Formats the value of the stat to be suitable for display + */ + fun format(): TextFormat { + return this.stat.format(this.value, this.secondValue, this.currency) + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt index 79a61c71..03698369 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt @@ -5,9 +5,11 @@ import com.github.mikephil.charting.data.* import io.realm.Realm import io.realm.RealmResults import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.QueryCondition -import net.pokeranalytics.android.model.interfaces.Timed +import net.pokeranalytics.android.model.interfaces.GraphIdentifiableEntry +//import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.SessionSet @@ -73,7 +75,7 @@ class Report(var options: Calculator.Options) { } } - val label = statToUse?.name ?: "" + val label = statToUse?.name ?: "" return DataSetFactory.barDataSetInstance(entries, label, context) } @@ -173,7 +175,8 @@ class ComputableGroup(var query: Query, var stats: List? = null) { } -class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) : GraphUnderlyingEntry { +class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) : + GraphUnderlyingEntry { /** * The session group used to computed the statIds @@ -195,7 +198,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu /** * Adds a value to the evolution values */ - fun addEvolutionValue(value: Double, duration: Double? = null, stat: Stat, data: Timed) { + fun addEvolutionValue(value: Double, duration: Double? = null, stat: Stat, data: GraphIdentifiableEntry) { val point = if (duration != null) { Point(duration, y = value, data = data.objectIdentifier) @@ -322,7 +325,8 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu computedStat.progressValue = Stat.returnOnInvestment(netResult, totalBuyin) } } - else -> {} + else -> { + } } } @@ -437,7 +441,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu this.computedStat(stat)?.let { return it.format() } ?: run { - throw IllegalStateException("Missing stat in results") + throw PAIllegalStateException("Missing stat in results") } } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt index d065af5f..9d7bff35 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt @@ -3,6 +3,7 @@ package net.pokeranalytics.android.calculus import android.content.Context import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.FormattingException +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.util.NULL_TEXT @@ -14,6 +15,7 @@ import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.toCurrency import java.util.* import kotlin.math.exp +import kotlin.math.pow class StatFormattingException(message: String) : Exception(message) { @@ -84,14 +86,14 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres return netBB / numberOfHands * 100 } - fun riskOfRuin(hourlyRate: Double, hourlyStandardDeviation: Double, bankrollValue: Double) : Double? { + fun riskOfRuin(hourlyRate: Double, hourlyStandardDeviation: Double, bankrollValue: Double): Double? { if (bankrollValue <= 0.0) { return null } val numerator = -2 * hourlyRate * bankrollValue - val denominator = Math.pow(hourlyStandardDeviation, 2.0) + val denominator = hourlyStandardDeviation.pow(2.0) val ratio = numerator / denominator return exp(ratio) @@ -127,7 +129,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres MAXIMUM_DURATION -> R.string.longest_session DAYS_PLAYED -> R.string.days_played TOTAL_BUYIN -> R.string.total_buyin - else -> throw IllegalStateException("Stat ${this.name} name required but undefined") + else -> throw PAIllegalStateException("Stat ${this.name} name required but undefined") } } @@ -159,12 +161,17 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> { return TextFormat(value.formattedHourlyDuration()) } // red/green percentages - WIN_RATIO, ROI, RISK_OF_RUIN -> { + WIN_RATIO, ROI -> { val color = if (value * 100 >= this.threshold) R.color.green else R.color.red return TextFormat("${(value * 100).formatted()}%", color) - } // white amountsr + } + RISK_OF_RUIN -> { + val color = if (value * 100 <= this.threshold) R.color.green else R.color.red + return TextFormat("${(value * 100).formatted()}%", color) + } + // white amountsr AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, - STANDARD_DEVIATION_BB_PER_100_HANDS -> { + STANDARD_DEVIATION_BB_PER_100_HANDS, TOTAL_BUYIN -> { return TextFormat(value.toCurrency(currency)) } LONGEST_STREAKS -> { @@ -177,6 +184,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres private val threshold: Double get() { return when (this) { + RISK_OF_RUIN -> 5.0 WIN_RATIO -> 50.0 else -> 0.0 } @@ -235,13 +243,24 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres } } + + val legendHideRightValue: Boolean + get() { + return when (this) { + AVERAGE, NUMBER_OF_SETS, NUMBER_OF_GAMES, WIN_RATIO, + HOURLY_DURATION, AVERAGE_HOURLY_DURATION -> true + else -> false + } + } + /** * Returns if the stat has a significant value to display in a progress graph */ val graphSignificantIndividualValue: Boolean get() { return when (this) { - WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false + AVERAGE, WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, + STANDARD_DEVIATION, HOURLY_DURATION -> false else -> true } } @@ -294,33 +313,3 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres override val viewType: Int = RowViewType.TITLE_VALUE.ordinal } - -/** - * ComputedStat contains a [stat] and their associated [value] - */ -class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? = null, var currency: Currency? = null) { - - constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { - if (previousValue != null) { - this.variation = (value - previousValue) / previousValue - } - } - - /** - * The value used to get evolution dataset - */ - var progressValue: Double? = null - - /** - * The variation of the stat - */ - var variation: Double? = null - - /** - * Formats the value of the stat to be suitable for display - */ - fun format(): TextFormat { - return this.stat.format(this.value, this.secondValue, this.currency) - } - -} diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt index 2f889e24..fe3e21a5 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt @@ -5,7 +5,9 @@ import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.ComputableGroup import net.pokeranalytics.android.calculus.ComputedResults import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.realm.* +import net.pokeranalytics.android.util.extensions.findById class BankrollCalculator { @@ -13,12 +15,15 @@ class BankrollCalculator { fun computeReport(realm: Realm, setup: BankrollReportSetup) : BankrollReport { - //val realm = Realm.getDefaultInstance() - val report = BankrollReport(setup) + realm.refresh() // fixes an issue where a newly created bankroll is not found, throwing an exception val bankrolls: List = - if (setup.bankroll != null) listOf(setup.bankroll) + if (setup.bankrollId != null) { + val bankroll = realm.findById(setup.bankrollId) ?: throw PAIllegalStateException("Bankroll not found with id=${setup.bankrollId}") + report.currency = bankroll.utilCurrency + listOf(bankroll) + } else realm.where(Bankroll::class.java).findAll() var initialValue = 0.0 @@ -41,7 +46,7 @@ class BankrollCalculator { report.transactionsNet = transactionNet report.initial = initialValue - val query = setup.query + val query = setup.query(realm) val transactions = Filter.queryOn(realm, query) report.addDatedItems(transactions) diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt index 2b7e4724..ce66fe36 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt @@ -3,54 +3,28 @@ package net.pokeranalytics.android.calculus.bankroll import android.content.Context import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.LineDataSet +import io.realm.Realm +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.QueryCondition -import net.pokeranalytics.android.model.interfaces.DatedValue +import net.pokeranalytics.android.model.interfaces.DatedBankrollGraphEntry import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Transaction import net.pokeranalytics.android.ui.graph.DataSetFactory -import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.util.extensions.findById import java.util.* import kotlin.collections.HashMap -//object BankrollReportManager { -// -// var mainReport: BankrollReport? = null -// var reports: MutableMap = mutableMapOf() -// -// fun udpateBankrolls(bankrolls: List) { -// this.invalidateMainReport() -// bankrolls.forEach { -// this.reports.remove(it.id) -// } -// } -// -// fun deleteBankrolls(bankrolls: List) { -// this.invalidateMainReport() -// bankrolls.forEach { -// this.reports.remove(it.id) -// } -// } -// -// private fun invalidateMainReport() { -// this.mainReport = null -// } -// -// private fun launchReports(bankrolls: List) { -// -// this.mainReport = BankrollCalculator.computeReport() -// -// -// } -// -//} - /** * This class holds the results from the BankrollCalculator computations * It has all the information required for the Bankroll various displays */ -class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable { +class BankrollReport(var setup: BankrollReportSetup) { + + /** + * The java.util.Currency + */ + var currency: Currency? = null /** * The value of the bankroll @@ -148,21 +122,12 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable { /** * The list of dated items used for the graph */ - private var evolutionItems: MutableList = mutableListOf() - - override val viewType: Int - get() { - return if (setup.bankroll == null) { - RowViewType.LEGEND_DEFAULT.ordinal - } else { - RowViewType.TITLE_VALUE_ARROW.ordinal - } - } + private var evolutionItems: MutableList = mutableListOf() /** * Adds a list of dated items to the evolution items used to get the bankroll graph */ - fun addDatedItems(items: Collection) { + fun addDatedItems(items: Collection) { this.evolutionItems.addAll(items) } @@ -176,7 +141,7 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable { var bucket = this.transactionBuckets[type.id] if (bucket == null) { - val b = TransactionBucket(this.setup.virtualBankroll) + val b = TransactionBucket(type.name, this.setup.virtualBankroll) this.transactionBuckets[type.id] = b bucket = b } @@ -184,7 +149,7 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable { bucket.addTransaction(transaction) } ?: run { - throw IllegalStateException("Transaction has no type") + throw PAIllegalStateException("Transaction has no type") } } @@ -200,10 +165,14 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable { this.evolutionItems.sortBy { it.date } - var total = 0.0 + var total = this.initial this.evolutionItems.forEach { - total += it.amount - val point = BRGraphPoint(total, it.date, it) + val rate = it.bankroll?.rate ?: 1.0 + +// Timber.d("rate = $rate, amount = ${it.amount}") + total += it.amount * rate +// Timber.d("total = $total") + val point = BRGraphPoint(total, it.date, it.objectIdentifier) this.evolutionPoints.add(point) } @@ -228,7 +197,7 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable { * A class describing the parameters required to launch a bankroll report * */ -class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null, val to: Date? = null) { +class BankrollReportSetup(val bankrollId: String? = null, val from: Date? = null, val to: Date? = null) { /** * Returns whether the setup concerns the virtual bankroll, @@ -236,32 +205,32 @@ class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null */ val virtualBankroll: Boolean get() { - return this.bankroll == null + return this.bankrollId == null } /** * the query used to get bankroll transactions */ - val query: Query - get() { - val query = Query() + fun query(realm: Realm): Query { + val query = Query() - this.bankroll?.let { - val bankrollCondition = QueryCondition.AnyBankroll(bankroll) - query.add(bankrollCondition) - } - this.from?.let { - val fromCondition = QueryCondition.StartedFromDate() - fromCondition.singleValue = it - query.add(fromCondition) - } - this.to?.let { - val toCondition = QueryCondition.StartedToDate() - toCondition.singleValue = it - query.add(toCondition) - } - return query + this.bankrollId?.let { + val bankroll = realm.findById(it) ?: throw PAIllegalStateException("Bankroll not found with id $it") + val bankrollCondition = QueryCondition.AnyBankroll(bankroll) + query.add(bankrollCondition) + } + this.from?.let { + val fromCondition = QueryCondition.StartedFromDate() + fromCondition.singleValue = it + query.add(fromCondition) } + this.to?.let { + val toCondition = QueryCondition.StartedToDate() + toCondition.singleValue = it + query.add(toCondition) + } + return query + } /** * Returns whether or not the initial value should be added for the bankroll total @@ -276,7 +245,7 @@ class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null /** * A TransactionBucket holds a list of _transactions and computes its amount sum */ -class TransactionBucket(useRate: Boolean = false) { +class TransactionBucket(var name: String, useRate: Boolean = false) { /** * Whether the bankroll rate should be used @@ -317,6 +286,6 @@ class TransactionBucket(useRate: Boolean = false) { data class BRGraphPoint(var value: Double, var date: Date, var data: Any? = null) { - var variation: Double = 0.0 +// var variation: Double = 0.0 } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt new file mode 100644 index 00000000..fef17f3f --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt @@ -0,0 +1,118 @@ +package net.pokeranalytics.android.calculus.bankroll + +import io.realm.Realm +import io.realm.RealmResults +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import net.pokeranalytics.android.model.realm.Bankroll +import net.pokeranalytics.android.model.realm.ComputableResult +import net.pokeranalytics.android.model.realm.Transaction +import timber.log.Timber +import java.util.* +import kotlin.coroutines.CoroutineContext + +object BankrollReportManager { + + val coroutineContext: CoroutineContext + get() = Dispatchers.Main + + private var reports: MutableMap = mutableMapOf() + + private var computableResults: RealmResults + private var bankrolls: RealmResults + private var transactions: RealmResults + + init { + + val realm = Realm.getDefaultInstance() + computableResults = realm.where(ComputableResult::class.java).findAll() + bankrolls = realm.where(Bankroll::class.java).findAll() + transactions = realm.where(Transaction::class.java).findAll() + + initializeListeners() + realm.close() + } + + /** + * Listens to all objects that might have an impact on any bankroll report + */ + private fun initializeListeners() { + + this.computableResults.addChangeListener { t, changeSet -> + val indexes = changeSet.changes.plus(changeSet.insertions).toList() + val bankrolls = indexes.mapNotNull { t[it]?.session?.bankroll }.toSet() + this.updateBankrolls(bankrolls) + } + this.bankrolls.addChangeListener { t, changeSet -> + val indexes = changeSet.changes.plus(changeSet.insertions).toList() + val bankrolls = indexes.mapNotNull { t[it] }.toSet() + this.updateBankrolls(bankrolls) + } + this.transactions.addChangeListener { t, changeSet -> + val indexes = changeSet.changes.plus(changeSet.insertions).toList() + val bankrolls = indexes.mapNotNull { t[it]?.bankroll }.toSet() + this.updateBankrolls(bankrolls) + } + } + + fun reportForBankroll(bankrollId: String?, handler: (BankrollReport) -> Unit) { + + Timber.d("Request bankroll report for bankrollId = $bankrollId") + // if the report exists, return it + val existingReport: BankrollReport? = this.reports[bankrollId] + if (existingReport != null) { + handler(existingReport) + return + } + + // otherwise compute it + GlobalScope.launch(coroutineContext) { + + var report: BankrollReport? = null + val coroutine = GlobalScope.async { + val s = Date() + Timber.d(">>>>> start computing bankroll...") + + val realm = Realm.getDefaultInstance() + + val setup = BankrollReportSetup(bankrollId) + report = BankrollCalculator.computeReport(realm, setup) + + realm.close() + + val e = Date() + val duration = (e.time - s.time) / 1000.0 + Timber.d(">>>>> ended in $duration seconds") + + } + coroutine.await() + + report?.let { + handler(it) + } + + } + } + + /** + * Notifies the manager of cases not managed by RealmResults listener, such as deletions + */ + fun notifyBankrollReportImpact(bankrollId: String) { + this.reports.remove(bankrollId) + this.reports.remove(null) + } + + private fun updateBankrolls(bankrolls: Set) { + this.invalidateReport(bankrolls) + } + + private fun invalidateReport(bankrolls: Set) { + this.reports.remove(null) + bankrolls.forEach { br -> + this.reports.remove(br.id) + } + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt b/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt index 7716e745..44849138 100644 --- a/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt +++ b/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt @@ -10,6 +10,7 @@ class ConfigurationException(message: String) : Exception(message) class EnumIdentifierNotFoundException(message: String) : Exception(message) class MisconfiguredSavableEnumException(message: String) : Exception(message) +class PAIllegalStateException(message: String) : Exception(message) sealed class PokerAnalyticsException(message: String) : Exception(message) { object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition") @@ -20,6 +21,8 @@ sealed class PokerAnalyticsException(message: String) : Exception(message) { object QueryTypeUnhandled: PokerAnalyticsException(message = "queryWith type not handled") object QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected") object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "queryWith is empty or null") + object InputFragmentException : PokerAnalyticsException(message = "RowEditableDelegate must be a Fragment") + object DateTimePickerException: PokerAnalyticsException(message = "DataSource is not a DateRowEditableDescriptor") data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "queryWith element '$filterElementRow' type is missing") data class QueryValueMapMissingKeys(val missingKeys: List) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys") data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow") diff --git a/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt b/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt index fa679d19..67e6f1da 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt @@ -4,6 +4,7 @@ import io.realm.Realm import io.realm.Sort import io.realm.kotlin.where import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.model.Criteria.Bankrolls.comparison import net.pokeranalytics.android.model.Criteria.Blinds.comparison @@ -329,7 +330,7 @@ interface CustomFieldCriteria { var customFieldId: String fun customField(realm: Realm) : CustomField { - return realm.findById(this.customFieldId) ?: throw IllegalStateException("Custom field not found") + return realm.findById(this.customFieldId) ?: throw PAIllegalStateException("Custom field not found") } fun customFieldType(realm: Realm): Int { diff --git a/app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt b/app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt index a6caa598..ba21ebea 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt @@ -1,6 +1,10 @@ package net.pokeranalytics.android.model.extensions +import android.content.Context +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.TournamentType import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.util.extensions.toCurrency import java.util.* enum class SessionState { @@ -39,7 +43,45 @@ fun Session.getState(): SessionState { return SessionState.STARTED } } +} + +/** + * Formate the session game type + */ +fun Session.getFormattedGameType(context: Context): String { + + var parameters = mutableListOf() + if (isTournament()) { + + tournamentEntryFee?.let { + parameters.add(it.toCurrency(currency)) + } + + tournamentName?.let { + parameters.add(it.name) + } ?: run { + parameters.add(getFormattedGame()) + tournamentType?.let { type -> + parameters.add(TournamentType.values()[type].localizedTitle(context)) + } + } + if (parameters.size == 0) { + parameters.add(context.getString(R.string.tournament).capitalize()) + } + } else { + if (cgSmallBlind != null && cgBigBlind != null) { + parameters.add(getFormattedBlinds()) + } + game?.let { + parameters.add(getFormattedGame()) + } + + if (parameters.size == 0) { + parameters.add(context.getString(R.string.cash_game).capitalize()) + } + } + return parameters.joinToString(separator = " ") } val AbstractList.hourlyDuration: Double @@ -95,4 +137,4 @@ fun MutableList.update(timeInterval: TimeInterval): MutableList NULL_TEXT 1, 2 -> { @@ -269,9 +269,9 @@ sealed class QueryCondition : FilterElementRow { return completeLabel } - open fun entityName(realm: Realm): String { - return baseId - } + open fun entityName(realm: Realm, context: Context): String { + return entityName(context) + } private fun labelForValue(realm: Realm, value: String): String { val query = realm.where(entity) @@ -320,7 +320,11 @@ sealed class QueryCondition : FilterElementRow { constructor(bankroll: Bankroll) : this() { this.setObject(bankroll) } - } + + override fun entityName(context: Context): String { + return context.getString(R.string.bankrolls) + } + } class AnyGame() : QueryDataCondition() { override val entity: Class = Game::class.java @@ -328,7 +332,11 @@ sealed class QueryCondition : FilterElementRow { constructor(game: Game) : this() { this.setObject(game) } - } + + override fun entityName(context: Context): String { + return context.getString(R.string.games) + } + } class AnyTournamentName() : QueryDataCondition() { override val entity: Class = TournamentName::class.java @@ -336,7 +344,11 @@ sealed class QueryCondition : FilterElementRow { constructor(tournamentName: TournamentName) : this() { this.setObject(tournamentName) } - } + + override fun entityName(context: Context): String { + return context.getString(R.string.tournament_names) + } + } class AnyTournamentFeature() : QueryDataCondition() { override val entity: Class = TournamentFeature::class.java @@ -344,7 +356,11 @@ sealed class QueryCondition : FilterElementRow { constructor(tournamentFeature: TournamentFeature) : this() { this.setObject(tournamentFeature) } - } + + override fun entityName(context: Context): String { + return context.getString(R.string.tournament_features) + } + } class AllTournamentFeature() : QueryDataCondition() { override var operator = Operator.ALL @@ -353,7 +369,11 @@ sealed class QueryCondition : FilterElementRow { constructor(tournamentFeature: TournamentFeature) : this() { this.setObject(tournamentFeature) } - } + + override fun entityName(context: Context): String { + return context.getString(R.string.tournament_features) + } + } class AnyLocation() : QueryDataCondition() { override val entity: Class = Location::class.java @@ -361,7 +381,11 @@ sealed class QueryCondition : FilterElementRow { constructor(location: Location) : this() { this.setObject(location) } - } + + override fun entityName(context: Context): String { + return context.getString(R.string.locations) + } + } class AnyTransactionType() : QueryDataCondition() { override val entity: Class = TransactionType::class.java @@ -369,27 +393,47 @@ sealed class QueryCondition : FilterElementRow { constructor(transactionType: TransactionType) : this() { this.setObject(transactionType) } - } + + override fun entityName(context: Context): String { + return context.getString(R.string.operation_types) + } + } class AnyLimit : ListOfInt() { override fun labelForValue(value: Int, context: Context): String { return Limit.values()[value].getDisplayName(context) } + + override fun entityName(context: Context): String { + return context.getString(R.string.limits) + } } class AnyTableSize : ListOfInt() { override fun labelForValue(value: Int, context: Context): String { return TableSize(value).getDisplayName(context) } + + override fun entityName(context: Context): String { + return context.getString(R.string.table_sizes) + } } class AnyTournamentType : ListOfInt() { override fun labelForValue(value: Int, context: Context): String { return TournamentType.values()[value].getDisplayName(context) } + + override fun entityName(context: Context): String { + return context.getString(R.string.tournament_types) + } } - class AnyBlind : ListOfString() + class AnyBlind : ListOfString() { + override fun entityName(context: Context): String { + return context.getString(R.string.blinds) + } + } object Last : SingleInt() { override var operator = Operator.EQUALS @@ -549,7 +593,7 @@ sealed class QueryCondition : FilterElementRow { } override val viewType: Int = RowViewType.TITLE_VALUE_CHECK.ordinal - override val bottomSheetType: BottomSheetType = BottomSheetType.DOUBLE_EDIT_TEXT + override val inputFragmentType: InputFragmentType = InputFragmentType.DOUBLE_EDIT_TEXT override fun labelForValue(value: Int, context: Context): String { return value.toMinutes(context) @@ -645,11 +689,11 @@ sealed class QueryCondition : FilterElementRow { constructor(customFieldEntry: CustomFieldEntry) : this() { this.setObject(customFieldEntry) - this.customFieldId = customFieldEntry.customFields?.firstOrNull()?.id + this.customFieldId = customFieldEntry.customField?.id ?: throw PokerAnalyticsException.QueryValueMapUnexpectedValue } - override fun entityName(realm: Realm): String { + override fun entityName(realm: Realm, context: Context): String { return customFieldName(realm) } @@ -668,6 +712,7 @@ sealed class QueryCondition : FilterElementRow { realmQuery: RealmQuery, otherQueryCondition: QueryCondition? = null ): RealmQuery { + val fieldName = FilterHelper.fieldNameForQueryType(this::class.java) if (BuildConfig.DEBUG) { fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown @@ -720,12 +765,14 @@ sealed class QueryCondition : FilterElementRow { return realmQuery } is DuringThisWeek -> { - val startDate = Date() val calendar = Calendar.getInstance() - calendar.time = startDate - calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY) + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.clear(Calendar.MINUTE) + calendar.clear(Calendar.SECOND) + calendar.clear(Calendar.MILLISECOND) + calendar.set(Calendar.DAY_OF_WEEK, calendar.firstDayOfWeek) return realmQuery.greaterThanOrEqualTo(fieldName, calendar.time.startOfDay()).and() - .lessThanOrEqualTo(fieldName, startDate.endOfDay()) + .lessThanOrEqualTo(fieldName, Date().endOfDay()) } is DuringThisMonth -> { val startDate = Date() @@ -905,15 +952,15 @@ sealed class QueryCondition : FilterElementRow { } } - override val bottomSheetType: BottomSheetType + override val inputFragmentType: InputFragmentType get() { return when (this) { - is PastDay -> BottomSheetType.EDIT_TEXT + is PastDay -> InputFragmentType.EDIT_TEXT else -> { when (this.operator) { - Operator.MORE -> BottomSheetType.EDIT_TEXT - Operator.LESS -> BottomSheetType.EDIT_TEXT - else -> BottomSheetType.NONE + Operator.MORE -> InputFragmentType.EDIT_TEXT + Operator.LESS -> InputFragmentType.EDIT_TEXT + else -> InputFragmentType.NONE } } } @@ -939,7 +986,7 @@ sealed class QueryCondition : FilterElementRow { is IsWeekDay -> R.string.week_days is IsWeekEnd -> R.string.weekend is PastDay -> R.string.period_in_days - is TournamentNumberOfPlayer -> { + is TournamentNumberOfPlayer -> { when (this.operator) { Operator.MORE -> R.string.minimum Operator.LESS -> R.string.maximum diff --git a/app/src/main/java/net/pokeranalytics/android/model/interfaces/CountableUsage.kt b/app/src/main/java/net/pokeranalytics/android/model/interfaces/CountableUsage.kt index 3d03abdb..10a5137b 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/interfaces/CountableUsage.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/CountableUsage.kt @@ -1,5 +1,7 @@ package net.pokeranalytics.android.model.interfaces +import io.realm.RealmModel + /** * An interface to be able to track the usage of an object */ @@ -7,4 +9,6 @@ interface CountableUsage : Identifiable { var useCount: Int get() { return 0 } set(_) {} + + val ownerClass: Class } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt index 86cea43b..a3d8f428 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt @@ -1,5 +1,6 @@ package net.pokeranalytics.android.model.interfaces +import net.pokeranalytics.android.model.realm.Bankroll import java.util.* interface Dated { @@ -12,4 +13,10 @@ interface DatedValue : Dated { var amount: Double -} \ No newline at end of file +} + +interface DatedBankrollGraphEntry : DatedValue, GraphIdentifiableEntry { + + var bankroll: Bankroll? + +} diff --git a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt index 74097255..4a6c077d 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt @@ -45,6 +45,7 @@ interface NameManageable : Manageable { } } +class ObjectIdentifier(var id: String, var clazz: Class) /** * An interface associate a unique uniqueIdentifier to an object @@ -55,6 +56,16 @@ interface Identifiable : RealmModel { * A unique uniqueIdentifier getter */ var id: String + + /** + * required because "this.class" returns the proxy class, making where queries crash + */ + val realmObjectClass: Class + + val objectIdentifier: ObjectIdentifier + get() { + return ObjectIdentifier(this.id, this.realmObjectClass) + } } /** @@ -125,6 +136,6 @@ interface Deletable : Identifiable { /** * A method to override if we need to delete linked objects or other stuff */ - fun deleteDependencies() {} + fun deleteDependencies(realm: Realm) {} } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt index 1123f298..4b4c752f 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt @@ -1,10 +1,11 @@ package net.pokeranalytics.android.model.interfaces import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry -import net.pokeranalytics.android.ui.graph.ObjectIdentifier import java.util.* -interface Timed : GraphUnderlyingEntry, Identifiable { +interface GraphIdentifiableEntry : GraphUnderlyingEntry, Identifiable + +interface Timed : GraphIdentifiableEntry { fun startDate() : Date? @@ -30,6 +31,4 @@ interface Timed : GraphUnderlyingEntry, Identifiable { val hourlyDuration: Double get() = this.netDuration / 3600000.0 - val objectIdentifier : ObjectIdentifier - } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt b/app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt index 0a7090f5..9a1c08c3 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt @@ -5,6 +5,7 @@ import io.realm.Realm import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.realm.* +import net.pokeranalytics.android.model.utils.Seed import net.pokeranalytics.android.util.Preferences class Patcher { @@ -19,7 +20,23 @@ class Patcher { Preferences.executeOnce(Preferences.Keys.PATCH_TRANSACTION_TYPES_NAMES, context) { patchDefaultTransactionTypes(context) } + Preferences.executeOnce(Preferences.Keys.PATCH_BLINDS_FORMAT, context) { + patchBlindFormat() + } + val realm = Realm.getDefaultInstance() + + val lockedTypes = realm.where(TransactionType::class.java).equalTo("lock", true).findAll() + if (lockedTypes.size == 3) { + Preferences.executeOnce(Preferences.Keys.ADD_NEW_TRANSACTION_TYPES, context) { + val newTypes = arrayOf(TransactionType.Value.STACKING_INCOMING, TransactionType.Value.STACKING_OUTGOING) + realm.executeTransaction { + Seed.createDefaultTransactionTypes(newTypes, context, realm) + } + } + } + + realm.close() } private fun patchBreaks() { @@ -40,6 +57,7 @@ class Patcher { it.computeNumberOfRebuy() } } + realm.close() } @@ -58,6 +76,17 @@ class Patcher { realm.close() } + private fun patchBlindFormat() { + val realm = Realm.getDefaultInstance() + realm.executeTransaction { + val sessions = realm.where(Session::class.java).findAll() + sessions.forEach { session -> + session.formatBlinds() + } + } + realm.close() + } + } } \ No newline at end of file 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 996f6da0..687fff28 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 @@ -144,6 +144,13 @@ class PokerAnalyticsMigration : RealmMigration { 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++ + } } override fun equals(other: Any?): Boolean { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt index 8f5574a2..2f129869 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt @@ -4,16 +4,18 @@ import android.content.Context 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.model.interfaces.DeleteValidityStatus +import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollRow -import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow +import net.pokeranalytics.android.util.UserDefaults import java.util.* open class Bankroll : RealmObject(), NameManageable, RowRepresentable { @@ -40,16 +42,25 @@ open class Bankroll : RealmObject(), NameManageable, RowRepresentable { val rate: Double get() { - return this.currency?.rate ?: 1.0 + return this.currency?.rate ?: Currency.DEFAULT_RATE } + val javaCurrency: java.util.Currency + get() { + return currency?.code?.let { + java.util.Currency.getInstance(it) + } ?: run { + UserDefaults.currency + } + } + override fun getDisplayName(context: Context): String { return this.name } override fun updateValue(value: Any?, row: RowRepresentable) { when (row) { - SimpleRow.NAME -> this.name = value as String? ?: "" + BankrollRow.NAME -> this.name = value as String? ?: "" BankrollRow.LIVE -> { this.live = if (value is Boolean) !value else false } @@ -118,4 +129,15 @@ open class Bankroll : RealmObject(), NameManageable, RowRepresentable { } + val utilCurrency: java.util.Currency + get() { + this.currency?.code?.let { + return java.util.Currency.getInstance(it) + } + return UserDefaults.currency + } + + @Ignore + override val realmObjectClass: Class = Bankroll::class.java + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt index e23f6045..ccca2ccb 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt @@ -20,14 +20,10 @@ open class ComputableResult() : RealmObject(), Filterable { var bbPer100Hands: BB = 0.0 -// var sessionSet: SessionSet? = null - var session: Session? = null fun updateWith(session: Session) { -// this.sessionSet = session.sessionSet - val rate = session.bankroll?.currency?.rate ?: 1.0 session.result?.let { result -> @@ -50,7 +46,7 @@ open class ComputableResult() : RealmObject(), Filterable { IS_POSITIVE("isPositive"), RATED_BUYIN("ratedBuyin"), ESTIMATED_HANDS("estimatedHands"), - BB_PER100HANDS("bbPer100Hands") +// BB_PER100HANDS("bbPer100Hands") } companion object { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt index 304f3a2b..75655d36 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt @@ -8,8 +8,12 @@ import java.util.* open class Currency : RealmObject() { - @Ignore - val DEFAULTRATE: Double = 1.0 + companion object { + + @Ignore + val DEFAULT_RATE: Double = 1.0 + + } @PrimaryKey var id = UUID.randomUUID().toString() @@ -22,11 +26,11 @@ open class Currency : RealmObject() { /** * The rate of the currency with the main currency */ - var rate: Double? = DEFAULTRATE + var rate: Double? = DEFAULT_RATE fun refreshRelatedRatedValues() { - val rate = this.rate ?: DEFAULTRATE + val rate = this.rate ?: DEFAULT_RATE val query = this.realm.where(ComputableResult::class.java) query.`in`("session.bankroll.currency.id", arrayOf(this.id)) val cResults = query.findAll() diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt index 9acf817e..bd98946d 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt @@ -2,6 +2,7 @@ package net.pokeranalytics.android.model.realm import android.content.Context import android.text.InputType +import androidx.fragment.app.Fragment import io.realm.Realm import io.realm.RealmList import io.realm.RealmObject @@ -11,16 +12,18 @@ 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.Identifiable import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.SaveValidityStatus +import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType +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.RowRepresentableEditDescriptor +import net.pokeranalytics.android.ui.view.RowEditableDataSource 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 @@ -28,6 +31,9 @@ import kotlin.collections.ArrayList open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { + @Ignore + override val realmObjectClass: Class = CustomField::class.java + /** * The custom field type: a list of items, a number or an amont */ @@ -59,7 +65,8 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa var type: Int = Type.LIST.uniqueIdentifier set(value) { if (field == Type.LIST.uniqueIdentifier && value != Type.LIST.uniqueIdentifier) { - this.removeListEntries() + this.entriesToDelete.addAll(this.entries) + this.entries.clear() } field = value @@ -120,9 +127,23 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa return rowRepresentation } + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (dataSource == null) return + if (dataSource !is Session) return + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + + when (type) { + Type.LIST.uniqueIdentifier -> data.append(dataSource.customFieldEntries.find { it.customField?.id == id }?.value, staticData = entries) + else -> data.append(dataSource.customFieldEntries.find { it.customField?.id == dataSource.id }?.numericValue, inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED) + } + InputFragment.buildAndShow(this, parent, data) + } + override fun updateValue(value: Any?, row: RowRepresentable) { when (row) { - SimpleRow.NAME -> this.name = value as String? ?: "" + CustomFieldRow.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 } @@ -146,8 +167,9 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa } override fun isValidForDelete(realm: Realm): Boolean { - val sessions = realm.where().contains("customFieldEntries.customField.id", id).findAll() - return sessions.isEmpty() + return true +// val sessions = realm.where().contains("customFieldEntries.customFields.id", id).findAll() +// return sessions.isEmpty() } override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { @@ -155,60 +177,27 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa return R.string.cf_entry_delete_popup_message } - override val bottomSheetType: BottomSheetType + override val inputFragmentType: InputFragmentType get() { return when (type) { - Type.LIST.uniqueIdentifier -> BottomSheetType.LIST_STATIC - else -> BottomSheetType.NUMERIC_TEXT + Type.LIST.uniqueIdentifier -> InputFragmentType.LIST_STATIC + else -> InputFragmentType.NUMERIC_TEXT } } - override fun deleteDependencies() { + override fun deleteDependencies(realm: Realm) { if (isValid) { - val entries = realm.where().equalTo("customField.id", id).findAll() + val entries = realm.where().equalTo("customFields.id", id).findAll() entries.deleteAllFromRealm() } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return when (row) { - is CustomFieldEntry -> row.editingDescriptors( - mapOf( - "defaultValue" to row.value - ) - ) - else -> null - } - } - - override fun editingDescriptors(map: Map): ArrayList? { - return when (type) { - Type.LIST.uniqueIdentifier -> { - val defaultValue: Any? by map - val data: RealmList? 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 { val rows = ArrayList() - rows.add(SimpleRow.NAME) + rows.add(CustomFieldRow.NAME) rows.add(CustomFieldRow.TYPE) if (type == Type.LIST.uniqueIdentifier && entries.size >= 0) { @@ -257,26 +246,24 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa * Delete an entry */ fun deleteEntry(entry: CustomFieldEntry) { - entries.remove(entry) entriesToDelete.add(entry) + entries.remove(entry) + sortEntries() updateRowRepresentation() } - private fun removeListEntries() { + fun cleanupEntries() { // called when saving the custom field - this.entriesToDelete.addAll(entries) - this.entries.clear() + val realm = Realm.getDefaultInstance() - if (realm != null) { - realm.executeTransaction { - this.entriesToDelete.forEach { - if (it.isManaged) { - it.deleteFromRealm() - } - } + realm.executeTransaction { + this.entriesToDelete.forEach { // entries are out of realm + realm.where().equalTo("id", it.id).findFirst()?.deleteFromRealm() } } + realm.close() + this.entriesToDelete.clear() } /** diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt index f6703266..0af6c81b 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt @@ -2,6 +2,7 @@ package net.pokeranalytics.android.model.realm import android.content.Context import android.text.InputType +import androidx.fragment.app.Fragment import io.realm.Realm import io.realm.RealmObject import io.realm.RealmResults @@ -12,11 +13,14 @@ 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.Identifiable 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.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.RowRepresentableEditDescriptor +import net.pokeranalytics.android.ui.view.RowEditableDataSource import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.extensions.toCurrency @@ -27,6 +31,9 @@ import java.util.Currency open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable { + @Ignore + override val realmObjectClass: Class = CustomFieldEntry::class.java + @PrimaryKey override var id = UUID.randomUUID().toString() @@ -77,7 +84,7 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable { } @Ignore - override val bottomSheetType: BottomSheetType = BottomSheetType.EDIT_TEXT + override val inputFragmentType: InputFragmentType = InputFragmentType.EDIT_TEXT override fun localizedTitle(context: Context): String { return context.getString(R.string.value) @@ -87,12 +94,16 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable { return if (value.isNotEmpty()) value else NULL_TEXT } - override fun editingDescriptors(map: Map): ArrayList? { - val defaultValue: Any? by map - return arrayListOf( - RowRepresentableEditDescriptor(defaultValue, R.string.value, InputType.TYPE_CLASS_TEXT) - ) - } + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + data.append(this.value, R.string.value, InputType.TYPE_CLASS_TEXT) + InputFragment.buildAndShow(this, parent, data, isDeletable = true) + } + + override val valueCanBeClearedWhenEditing: Boolean + get() = false override fun isValidForSave(): Boolean { return true @@ -110,9 +121,9 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable { return R.string.cf_entry_delete_popup_message } - override fun deleteDependencies() { + override fun deleteDependencies(realm: Realm) { if (isValid) { - val entries = realm.where().equalTo("customField.id", id).findAll() + val entries = realm.where().contains("customFieldEntries.id", id).findAll() entries.deleteAllFromRealm() } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt index 3f1e481b..f6e6e4c2 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt @@ -1,21 +1,23 @@ package net.pokeranalytics.android.model.realm import android.content.Context +import androidx.fragment.app.Fragment import io.realm.* +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.filter.Filterable import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.QueryCondition -import net.pokeranalytics.android.model.interfaces.CountableUsage -import net.pokeranalytics.android.model.interfaces.Deletable -import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus -import net.pokeranalytics.android.model.interfaces.Identifiable +import net.pokeranalytics.android.model.interfaces.* +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.interfaces.FilterableType -import net.pokeranalytics.android.ui.view.ImageDecorator -import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.* import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow +import timber.log.Timber import java.util.* /** @@ -23,156 +25,197 @@ import java.util.* * It contains a list of [FilterCondition] describing the complete query to launch * The [Filter] is working closely with a [Filterable] interface providing the entity we want the query being launched on */ -open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, CountableUsage, ImageDecorator { +open class Filter : RealmObject(), RowRepresentable, Editable, Deletable, CountableUsage, ImageDecorator { + + @Ignore + override val realmObjectClass: Class = Filter::class.java companion object { // Create a new instance - fun newInstance(filterableTypeUniqueIdentifier:Int): Filter { + fun newInstance(filterableTypeUniqueIdentifier: Int): Filter { val filter = Filter() - filter.filterableTypeUniqueIdentifier = filterableTypeUniqueIdentifier + filter.filterableTypeUniqueIdentifier = filterableTypeUniqueIdentifier return filter //return realm.copyToRealm(filter) } - // Get a queryWith by its id - fun getFilterBydId(realm: Realm, filterId: String): Filter? { - return realm.where().equalTo("id", filterId).findFirst() - } - inline fun queryOn(realm: Realm, query: Query, sortField: String? = null): RealmResults { - val realmQuery = realm.where() + val rootQuery = realm.where() + var realmQuery = query.queryWith(rootQuery) sortField?.let { - return query.queryWith(realmQuery).sort(it).findAll() - } ?: run { - return query.queryWith(realmQuery).findAll() + realmQuery = realmQuery.sort(it) } - } +// val desc = realmQuery.description + return realmQuery.findAll() + } } + override val viewType: Int + get() = RowViewType.TITLE_VALUE_ACTION.ordinal + override val imageRes: Int? + get() = R.drawable.ic_outline_settings + override val imageTint: Int? + get() = R.color.green + override val imageClickable: Boolean? + get() = true + + @PrimaryKey - override var id = UUID.randomUUID().toString() + override var id = UUID.randomUUID().toString() // the queryWith name var name: String = "" - get() { - if (field.isEmpty()) { - return this.query.defaultName - } - return field - } + get() { + if (field.isEmpty()) { + return this.query.defaultName + } + return field + } - override var useCount: Int = 0 + override var useCount: Int = 0 + + @Ignore + override val ownerClass: Class = Session::class.java var filterConditions: RealmList = RealmList() private set - private var filterableTypeUniqueIdentifier: Int? = null + private var filterableTypeUniqueIdentifier: Int? = null - val filterableType: FilterableType - get() { - this.filterableTypeUniqueIdentifier?.let { - return FilterableType.valueByIdentifier(it) - } - return FilterableType.ALL - } + val filterableType: FilterableType + get() { + this.filterableTypeUniqueIdentifier?.let { + return FilterableType.valueByIdentifier(it) + } + return FilterableType.ALL + } fun createOrUpdateFilterConditions(filterConditionRows: ArrayList) { - println("list of querys saving: ${filterConditionRows.map { it.id }}") - println("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}") - filterConditionRows + Timber.d("list of querys saving: ${filterConditionRows.map { it.id }}") + Timber.d("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}") + filterConditionRows .map { it.groupId } .distinct() - .forEach { groupId-> + .forEach { groupId -> filterConditionRows .filter { it.groupId == groupId } .apply { - println("list of querys: ${this.map { it.id }}") - val casted = arrayListOf() - casted.addAll(this) - val newFilterCondition = FilterCondition(casted) - val previousCondition = filterConditions.filter { - it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator - } - filterConditions.removeAll(previousCondition) - filterConditions.add(newFilterCondition) - } + Timber.d("list of querys: ${this.map { it.id }}") + val casted = arrayListOf() + casted.addAll(this) + val newFilterCondition = FilterCondition(casted) + val previousCondition = filterConditions.filter { + it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator + } + filterConditions.removeAll(previousCondition) + filterConditions.add(newFilterCondition) + } } } - fun remove(filterCategoryRow: FilterCategoryRow) { - val sections = filterCategoryRow.filterSectionRows.map { it.name } - val savedSections = filterConditions.filter { sections.contains(it.sectionName) } - this.filterConditions.removeAll(savedSections) - } + fun remove(filterCategoryRow: FilterCategoryRow) { + val sections = filterCategoryRow.filterSectionRows.map { it.name } + val savedSections = filterConditions.filter { sections.contains(it.sectionName) } + this.filterConditions.removeAll(savedSections) + } fun countBy(filterCategoryRow: FilterCategoryRow): Int { val sections = filterCategoryRow.filterSectionRows.map { it.name } - println("list of sections $sections") - val savedSections = filterConditions.filter { sections.contains(it.sectionName) }.flatMap { it.queryCondition.id } - println("list of savedSections $savedSections") - return savedSections.size + Timber.d("list of sections $sections") + val savedSections = filterConditions.filter { sections.contains(it.sectionName) }.flatMap { it.queryCondition.id } + Timber.d("list of savedSections $savedSections") + return savedSections.size } fun contains(filterElementRow: QueryCondition): Boolean { - println("list of saved queries ${filterConditions.map { it.queryCondition.id }}") - println("list of contains ${filterElementRow.id}") - val contained = filterConditions.flatMap{ it.queryCondition.id }.contains(filterElementRow.id.first()) - println("list of : $contained") - return contained + Timber.d("list of saved queries ${filterConditions.map { it.queryCondition.id }}") + Timber.d("list of contains ${filterElementRow.id}") + val contained = filterConditions.flatMap { it.queryCondition.id }.contains(filterElementRow.id.first()) + Timber.d("list of : $contained") + return contained } /** * Get the saved value for the given [filterElementRow] */ - fun loadValueForElement(filterElementRow: QueryCondition) { + fun loadValueForElement(filterElementRow: QueryCondition) { val filtered = filterConditions.filter { it.queryCondition.id == filterElementRow.id } if (filtered.isNotEmpty()) { - return filterElementRow.updateValueBy(filtered.first()) + return filterElementRow.updateValueBy(filtered.first()) } } - inline fun results(firstField: String? = null, secondField: String? = null): RealmResults { + inline fun query(firstField: String? = null, secondField: String? = null): RealmQuery { + val realmQuery = realm.where() - val realmQuery = realm.where() + if (firstField != null && secondField != null) { + return this.query.queryWith(realmQuery).distinct(firstField, secondField) + } - if (firstField != null && secondField != null) { - return this.query.queryWith(realmQuery).distinct(firstField, secondField).findAll() - } + if (firstField != null) { + return this.query.queryWith(realmQuery).distinct(firstField) + } - if (firstField != null) { - return this.query.queryWith(realmQuery).distinct(firstField).findAll() - } + return this.query.queryWith(realmQuery) + } - return this.query.queryWith(realmQuery).findAll() - } + inline fun results(firstField: String? = null, secondField: String? = null): RealmResults { + return this.query(firstField, secondField).findAll() + } - val query: Query - get() { - val query = Query() - this.filterConditions.forEach { - query.add(it.queryCondition) - } - return query - } + val query: Query + get() { + val query = Query() + this.filterConditions.forEach { + query.add(it.queryCondition) + } + return query + } - override fun getDisplayName(context: Context): String { - if (name.isNotEmpty()) return name - return this.query.getName(context) - } + override fun getDisplayName(context: Context): String { + if (name.isNotEmpty()) return name + return this.query.getName(context) + } - override fun isValidForDelete(realm: Realm): Boolean { + override fun isValidForDelete(realm: Realm): Boolean { return true } override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { return R.string.relationship_error } + + override val inputFragmentType: InputFragmentType + get() { + return InputFragmentType.EDIT_TEXT + } + + override fun localizedTitle(context: Context): String { + return context.getString(R.string.name) + } + + override fun updateValue(value: Any?, row: RowRepresentable) { + realm.executeTransaction { + val newName = value as String? ?: "" + if (newName.isNotEmpty()) { + name = newName + } + } + } + + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + data.append(this.name) + InputFragment.buildAndShow(this, parent, data, isDeletable = true, valueHasPlaceholder = false) + } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt index 2334baa0..b1e42811 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt @@ -2,28 +2,32 @@ package net.pokeranalytics.android.model.realm import android.content.Context import io.realm.Realm +import io.realm.RealmModel import io.realm.RealmObject +import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey import io.realm.kotlin.where import net.pokeranalytics.android.R import net.pokeranalytics.android.model.interfaces.CountableUsage +import net.pokeranalytics.android.model.interfaces.Identifiable 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.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.rowrepresentable.GameRow -import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.util.NULL_TEXT import java.util.* import kotlin.collections.ArrayList open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable, CountableUsage { + @Ignore + override val realmObjectClass: Class = Game::class.java + companion object { val rowRepresentation : List by lazy { val rows = ArrayList() - rows.add(SimpleRow.NAME) + rows.add(GameRow.NAME) // rows.addAll(GameRow.values()) rows } @@ -41,6 +45,9 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc // CountableUsage override var useCount: Int = 0 + @Ignore + override val ownerClass: Class = Session::class.java + fun getNotNullShortName() : String { this.shortName?.let { return it @@ -53,28 +60,20 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc } override fun adapterRows(): List? { - return Game.rowRepresentation + return rowRepresentation } override fun stringForRow(row: RowRepresentable): String { return when (row) { - SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT + GameRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT GameRow.SHORT_NAME -> this.shortName ?: NULL_TEXT else -> return super.stringForRow(row) } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return when (row) { - SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name)) - GameRow.SHORT_NAME -> row.editingDescriptors(mapOf("defaultValue" to this.shortName)) - else -> null - } - } - override fun updateValue(value: Any?, row: RowRepresentable) { when (row) { - SimpleRow.NAME -> this.name = value as String? ?: "" + GameRow.NAME -> this.name = value as String? ?: "" GameRow.SHORT_NAME -> this.shortName = value as String? ?: "" } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Location.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Location.kt index df4136e4..9f64f7fb 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Location.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Location.kt @@ -4,9 +4,11 @@ import android.content.Context import com.google.android.libraries.places.api.model.Place import io.realm.Realm import io.realm.RealmObject +import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey import io.realm.kotlin.where import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.ui.view.RowRepresentable @@ -16,6 +18,9 @@ import java.util.* open class Location : RealmObject(), NameManageable, RowRepresentable { + @Ignore + override val realmObjectClass: Class = Location::class.java + @PrimaryKey override var id = UUID.randomUUID().toString() diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt index 2db6f02e..f813dd75 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt @@ -11,6 +11,7 @@ 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.model.interfaces.Identifiable import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.util.extensions.findById @@ -19,6 +20,9 @@ import java.util.* open class ReportSetup : RealmObject(), RowRepresentable, Deletable { + @Ignore + override val realmObjectClass: Class = ReportSetup::class.java + @PrimaryKey override var id = UUID.randomUUID().toString() diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt index 644d0de3..19394fcd 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt @@ -6,6 +6,7 @@ import io.realm.RealmResults import io.realm.annotations.Ignore import io.realm.annotations.LinkingObjects import io.realm.annotations.RealmClass +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.QueryCondition @@ -52,10 +53,10 @@ open class Result : RealmObject(), Filterable { this.session?.bankroll?.let { bankroll -> if (bankroll.live) { - throw IllegalStateException("Can't set net result on a live bankroll") + throw PAIllegalStateException("Can't set net result on a live bankroll") } } ?: run { - throw IllegalStateException("Session doesn't have any bankroll") + throw PAIllegalStateException("Session doesn't have any bankroll") } field = value diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index 088ac609..97e80ed5 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -16,6 +16,7 @@ import net.pokeranalytics.android.calculus.ComputedStat import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.StatFormattingException import net.pokeranalytics.android.exceptions.ModelException +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.TableSize import net.pokeranalytics.android.model.TournamentType @@ -29,7 +30,6 @@ import net.pokeranalytics.android.model.utils.SessionSetManager import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException import net.pokeranalytics.android.ui.fragment.GraphFragment -import net.pokeranalytics.android.ui.graph.ObjectIdentifier import net.pokeranalytics.android.ui.view.* import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow @@ -46,7 +46,7 @@ import kotlin.collections.ArrayList typealias BB = Double open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed, - TimeFilterable, Filterable, DatedValue { + TimeFilterable, Filterable, DatedBankrollGraphEntry { enum class Type { CASH_GAME, @@ -67,7 +67,8 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } companion object { - fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session { + + fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null, managed: Boolean = true): Session { val session = Session() session.result = Result() if (bankroll != null) { @@ -76,7 +77,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat session.bankroll = realm.where().findFirst() } session.type = if (isTournament) Session.Type.TOURNAMENT.ordinal else Session.Type.CASH_GAME.ordinal - return realm.copyToRealm(session) + + return if (managed) { + realm.copyToRealm(session) + } else { + session + } } fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { @@ -235,9 +241,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat var creationDate: Date = Date() // The bankroll hosting the results - var bankroll: Bankroll? = null + override var bankroll: Bankroll? = null set(value) { field = value + this.formatBlinds() this.updateRowRepresentation() } @@ -399,7 +406,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat @Ignore override var amount: Double = 0.0 get() { - return this.computableResult?.ratedNet ?: 0.0 + return this.result?.net ?: 0.0 } /** @@ -420,7 +427,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat this.computableResults?.forEachIndexed { index, computableResult -> computableResult.updateWith(this) if (index > 0) { - throw IllegalStateException("Session cannot have more than one computable result") + throw PAIllegalStateException("Session cannot have more than one computable result") } } this.sessionSet?.computeStats() @@ -498,12 +505,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat if (pauseDate != null) { this.breakDuration += Date().time - pauseDate.time } else { - throw IllegalStateException("When resuming, the pause date must be set") + throw PAIllegalStateException("When resuming, the pause date must be set") } this.pauseDate = null } else -> { - throw IllegalStateException("unmanaged session state") + throw PAIllegalStateException("unmanaged session state") } } } @@ -524,7 +531,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat SessionState.STARTED -> { this.pauseDate = Date() } - else -> throw IllegalStateException("Pausing a session in an unmanaged state") + else -> throw PAIllegalStateException("Pausing a session in an unmanaged state") } } } @@ -613,7 +620,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat if (cgBigBlind == null) return cgBigBlind?.let { bb -> val sb = cgSmallBlind ?: bb / 2.0 - blinds = "${currency.symbol} ${sb.formatted()}/${bb.round()}" + val preFormattedBlinds = "${sb.formatted()}/${bb.round()}" + println("<<<<<< bb.toCurrency(currency) : ${bb.toCurrency(currency)}") + println("<<<<<< preFormattedBlinds : $preFormattedBlinds") + val regex = Regex("-?\\d+(\\.\\d+)?") + blinds = bb.toCurrency(currency).replace(regex, preFormattedBlinds) + println("<<<<<< blinds = $blinds") } } @@ -646,6 +658,24 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } + fun duplicate() : Session { + + val copy = Session.newInstance(this.realm, this.isTournament(), this.bankroll) + + copy.game = this.game + copy.limit = this.limit + copy.cgSmallBlind = this.cgSmallBlind + copy.cgBigBlind = this.cgBigBlind + copy.tournamentEntryFee = this.tournamentEntryFee + copy.tournamentFeatures = this.tournamentFeatures + copy.tournamentName = this.tournamentName + copy.tournamentType = this.tournamentType + copy.tableSize = this.tableSize + copy.numberOfTables = this.numberOfTables + + return copy + } + @Ignore override val viewType: Int = RowViewType.ROW_SESSION.ordinal @@ -791,7 +821,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } return NULL_TEXT } - else -> throw UnmanagedRowRepresentableException("Unmanaged row = ${row}") + else -> throw UnmanagedRowRepresentableException("Unmanaged row = $row") } } @@ -804,117 +834,6 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return when (row) { - SessionRow.BANKROLL -> row.editingDescriptors( - mapOf( - "defaultValue" to this.bankroll, - "data" to realm.sorted() // LiveData.Bankroll.items(realm) - ) - ) - SessionRow.GAME -> row.editingDescriptors( - mapOf( - "limit" to this.limit, - "defaultValue" to this.game, - "data" to realm.sorted() //LiveData.Game.items(realm) - ) - ) - SessionRow.LOCATION -> row.editingDescriptors( - mapOf( - "defaultValue" to this.location, - "data" to realm.sorted() // LiveData.Location.items(realm) - ) - ) - SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors( - mapOf( - "defaultValue" to this.tournamentFeatures, - "data" to realm.sorted() //LiveData.TournamentFeature.items(realm) - ) - ) - SessionRow.TOURNAMENT_NAME -> row.editingDescriptors( - mapOf( - "defaultValue" to this.tournamentName, - "data" to realm.sorted() //LiveData.TournamentName.items(realm) - ) - ) - SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors( - mapOf( - "defaultValue" to this.tournamentType - ) - ) - SessionRow.TABLE_SIZE -> row.editingDescriptors( - mapOf( - "defaultValue" to this.tableSize - ) - ) - SessionRow.BLINDS -> row.editingDescriptors( - mapOf( - "sb" to cgSmallBlind?.round(), - "bb" to cgBigBlind?.round() - ) - ) - SessionRow.BUY_IN -> row.editingDescriptors( - mapOf( - "bb" to cgBigBlind, - "fee" to this.tournamentEntryFee, - "ratedBuyin" to result?.buyin - ) - ) - SessionRow.BREAK_TIME -> row.editingDescriptors(mapOf()) - SessionRow.CASHED_OUT, SessionRow.PRIZE -> row.editingDescriptors( - mapOf( - "defaultValue" to result?.cashout - ) - ) - SessionRow.NET_RESULT -> row.editingDescriptors( - mapOf( - "defaultValue" to result?.netResult - ) - ) - SessionRow.COMMENT -> row.editingDescriptors( - mapOf( - "defaultValue" to this.comment - ) - ) - SessionRow.INITIAL_BUY_IN -> row.editingDescriptors( - mapOf( - "defaultValue" to this.tournamentEntryFee - ) - ) - SessionRow.PLAYERS -> row.editingDescriptors( - mapOf( - "defaultValue" to this.tournamentNumberOfPlayers - ) - ) - SessionRow.POSITION -> row.editingDescriptors( - mapOf( - "defaultValue" to this.result?.tournamentFinalPosition - ) - ) - SessionRow.TIPS -> row.editingDescriptors( - mapOf( - "sb" to cgSmallBlind?.round(), - "bb" to cgBigBlind?.round(), - "tips" to result?.tips - ) - ) - is CustomField -> { - row.editingDescriptors( - when (row.type) { - CustomField.Type.LIST.uniqueIdentifier -> mapOf( - "defaultValue" to customFieldEntries.find { it.customField?.id == row.id }?.value, - "data" to row.entries - ) - else -> mapOf( - "defaultValue" to customFieldEntries.find { it.customField?.id == row.id }?.numericValue - ) - } - ) - } - else -> null - } - } - override fun updateValue(value: Any?, row: RowRepresentable) { realm.executeTransaction { @@ -1098,7 +1017,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } } ?: run { - throw java.lang.IllegalStateException("Asking for statIds on Session without Result") + throw PAIllegalStateException("Asking for statIds on Session without Result") } } @@ -1138,7 +1057,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat DefaultLegendValues(this.entryTitle(context), left, right) } else -> { - super.legendValues(stat, entry, style, groupName, context) + super.legendValues(stat, entry, style, groupName, context) } } } @@ -1147,10 +1066,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } - // Timed - - override val objectIdentifier: ObjectIdentifier - get() = ObjectIdentifier(this.id, Session::class.java) - + @Ignore + override val realmObjectClass: Class = Session::class.java } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt index da9976b6..c447de69 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt @@ -4,16 +4,17 @@ import android.content.Context 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 net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.StatFormattingException -import net.pokeranalytics.android.util.TextFormat import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.Timed -import net.pokeranalytics.android.ui.graph.ObjectIdentifier import net.pokeranalytics.android.util.NULL_TEXT +import net.pokeranalytics.android.util.TextFormat import java.text.DateFormat import java.util.* @@ -138,10 +139,8 @@ open class SessionSet() : RealmObject(), Timed, Filterable { } } - // Timed - - override val objectIdentifier: ObjectIdentifier - get() = ObjectIdentifier(this.id, SessionSet::class.java) + @Ignore + override val realmObjectClass: Class = SessionSet::class.java } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt index f9fe2236..2e173268 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt @@ -233,7 +233,7 @@ // // /** // * Multiple session sets update: -// * Merges all sets into one (delete all then create a new one) +// * Merges all sets into one (delete all then buildAndShow a new one) // */ // private fun mergeSessionGroups(owner: Session, sessionSets: RealmResults) { // diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt index f11f0e73..e8761103 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt @@ -2,17 +2,18 @@ package net.pokeranalytics.android.model.realm import android.content.Context import io.realm.Realm +import io.realm.RealmModel import io.realm.RealmObject +import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey import io.realm.kotlin.where import net.pokeranalytics.android.R import net.pokeranalytics.android.model.interfaces.CountableUsage +import net.pokeranalytics.android.model.interfaces.Identifiable 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.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor -import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.ui.view.rowrepresentable.TournamentFeatureRow import net.pokeranalytics.android.util.NULL_TEXT import java.util.* @@ -24,12 +25,14 @@ open class TournamentFeature : RealmObject(), NameManageable, StaticRowRepresent companion object { val rowRepresentation : List by lazy { val rows = ArrayList() - rows.add(SimpleRow.NAME) rows.addAll(TournamentFeatureRow.values()) rows } } + @Ignore + override val realmObjectClass: Class = TournamentFeature::class.java + @PrimaryKey override var id = UUID.randomUUID().toString() @@ -39,29 +42,27 @@ open class TournamentFeature : RealmObject(), NameManageable, StaticRowRepresent // CountableUsage override var useCount: Int = 0 + @Ignore + override val ownerClass: Class = Session::class.java + override fun getDisplayName(context: Context): String { return this.name } override fun adapterRows(): List? { - return TournamentFeature.rowRepresentation + return rowRepresentation } override fun stringForRow(row: RowRepresentable): String { return when (row) { - SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT + TournamentFeatureRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT else -> return super.stringForRow(row) } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return row.editingDescriptors(mapOf( - "defaultValue" to this.name)) - } - override fun updateValue(value: Any?, row: RowRepresentable) { when (row) { - SimpleRow.NAME -> this.name = value as String? ?: "" + TournamentFeatureRow.NAME -> this.name = value as String? ?: "" } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentName.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentName.kt index cad45445..9a9c0d44 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentName.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentName.kt @@ -3,15 +3,15 @@ package net.pokeranalytics.android.model.realm import android.content.Context import io.realm.Realm import io.realm.RealmObject +import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey import io.realm.kotlin.where import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.interfaces.Identifiable 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.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor -import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.ui.view.rowrepresentable.TournamentNameRow import net.pokeranalytics.android.util.NULL_TEXT import java.util.* @@ -22,12 +22,14 @@ open class TournamentName : RealmObject(), NameManageable, StaticRowRepresentabl companion object { val rowRepresentation : List by lazy { val rows = ArrayList() - rows.add(SimpleRow.NAME) rows.addAll(TournamentNameRow.values()) rows } } + @Ignore + override val realmObjectClass: Class = TournamentName::class.java + @PrimaryKey override var id = UUID.randomUUID().toString() @@ -40,25 +42,21 @@ open class TournamentName : RealmObject(), NameManageable, StaticRowRepresentabl override fun updateValue(value: Any?, row: RowRepresentable) { when (row) { - SimpleRow.NAME -> this.name = value as String? ?: "" + TournamentNameRow.NAME -> this.name = value as String? ?: "" } } override fun adapterRows(): List? { - return TournamentName.rowRepresentation + return rowRepresentation } override fun stringForRow(row: RowRepresentable): String { return when (row) { - SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT + TournamentNameRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT else -> return super.stringForRow(row) } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return row.editingDescriptors(mapOf("defaultValue" to this.name)) - } - override fun getFailedSaveMessage(status: SaveValidityStatus): Int { return when (status) { SaveValidityStatus.DATA_INVALID -> R.string.tournament_name_empty_field_error diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt index 748b3e84..5efc1bbf 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt @@ -13,7 +13,6 @@ import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.interfaces.* import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.fragment.GraphFragment -import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry import net.pokeranalytics.android.ui.view.DefaultLegendValues import net.pokeranalytics.android.ui.view.LegendContent import net.pokeranalytics.android.ui.view.RowRepresentable @@ -26,11 +25,22 @@ import java.util.* import kotlin.collections.ArrayList -open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, TimeFilterable, Filterable, DatedValue, - GraphUnderlyingEntry { +open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, TimeFilterable, + Filterable, DatedBankrollGraphEntry { companion object { + fun newInstance(realm: Realm, bankroll: Bankroll, date: Date? = null, type: TransactionType, amount: Double): Transaction { + + val transaction = realm.copyToRealm(Transaction()) + transaction.date = date ?: Date() + transaction.amount = amount + transaction.type = type + transaction.bankroll = bankroll + + return transaction + } + val rowRepresentation: List by lazy { val rows = ArrayList() rows.addAll(TransactionRow.values()) @@ -60,7 +70,7 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo override var id = UUID.randomUUID().toString() // The bankroll of the transaction - var bankroll: Bankroll? = null + override var bankroll: Bankroll? = null // The amount of the transaction override var amount: Double = 0.0 @@ -134,14 +144,17 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo return SaveValidityStatus.VALID } - // GraphUnderlyingEntry + // GraphIdentifiableEntry + + @Ignore + override val realmObjectClass: Class = Transaction::class.java override fun entryTitle(context: Context): String { return DateFormat.getDateInstance(DateFormat.SHORT).format(this.date) } override fun formattedValue(stat: Stat): TextFormat { - return stat.format(this.amount) + return stat.format(this.amount, currency = this.bankroll?.utilCurrency) } override fun legendValues( @@ -158,4 +171,5 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo return DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName) } + } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt index 6c038aa3..4a141cd5 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt @@ -2,27 +2,40 @@ package net.pokeranalytics.android.model.realm import android.content.Context import io.realm.Realm +import io.realm.RealmModel import io.realm.RealmObject +import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey import net.pokeranalytics.android.R -import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus -import net.pokeranalytics.android.model.interfaces.NameManageable +import net.pokeranalytics.android.exceptions.PAIllegalStateException +import net.pokeranalytics.android.model.interfaces.* import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.view.Localizable import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor -import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionTypeRow +import net.pokeranalytics.android.util.enumerations.IntIdentifiable +import net.pokeranalytics.android.util.enumerations.IntSearchable import java.util.* import kotlin.collections.ArrayList -open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { +open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable, + CountableUsage { - enum class Value(val additive: Boolean) : Localizable { - WITHDRAWAL(false), - DEPOSIT(true), - BONUS(true); + enum class Value(override var uniqueIdentifier: Int, val additive: Boolean) : IntIdentifiable, Localizable { + + WITHDRAWAL(0, false), + DEPOSIT(1, true), + BONUS(2, true), + STACKING_INCOMING(3, true), + STACKING_OUTGOING(4, false); + + companion object : IntSearchable { + + override fun valuesInternal(): Array { + return values() + } + } override val resId: Int? get() { @@ -30,34 +43,39 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab WITHDRAWAL -> R.string.withdrawal DEPOSIT -> R.string.deposit BONUS -> R.string.bonus + STACKING_INCOMING -> R.string.stacking_incoming + STACKING_OUTGOING -> R.string.stacking_outgoing } } + } companion object { val rowRepresentation: List by lazy { val rows = ArrayList() - rows.add(SimpleRow.NAME) rows.addAll(TransactionTypeRow.values()) rows } fun getByValue(value: Value, realm: Realm): TransactionType { - val type = realm.where(TransactionType::class.java).equalTo("kind", value.ordinal).findFirst() + val type = realm.where(TransactionType::class.java).equalTo("kind", value.uniqueIdentifier).findFirst() type?.let { return it } - throw IllegalStateException("Transaction type ${value.name} should exist in database!") + throw PAIllegalStateException("Transaction type ${value.name} should exist in database!") } } + @Ignore + override val realmObjectClass: Class = TransactionType::class.java + @PrimaryKey override var id = UUID.randomUUID().toString() /** * The name of the transaction type - */ + */ override var name: String = "" /** @@ -72,6 +90,11 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab // The predefined kind, if necessary, like: Withdrawal, deposit, or tips var kind: Int? = null + override var useCount: Int = 0 + + @Ignore + override val ownerClass: Class = Transaction::class.java + override fun getDisplayName(context: Context): String { return this.name } @@ -82,7 +105,7 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab override fun stringForRow(row: RowRepresentable): String { return when (row) { - SimpleRow.NAME -> this.name + TransactionTypeRow.NAME -> this.name else -> return super.stringForRow(row) } } @@ -94,13 +117,9 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return row.editingDescriptors(mapOf("defaultValue" to this.name)) - } - override fun updateValue(value: Any?, row: RowRepresentable) { when (row) { - SimpleRow.NAME -> this.name = value as String? ?: "" + TransactionTypeRow.NAME -> this.name = value as String? ?: "" TransactionTypeRow.TRANSACTION_ADDITIVE -> this.additive = value as Boolean? ?: false } } @@ -114,5 +133,13 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab return R.string.transaction_relationship_error } + override fun getFailedSaveMessage(status: SaveValidityStatus): Int { + return when (status) { + SaveValidityStatus.DATA_INVALID -> R.string.operation_type_empty_field_error + SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_operation_type_error + else -> super.getFailedSaveMessage(status) + } + } + } diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/DataUtils.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/DataUtils.kt new file mode 100644 index 00000000..2e0bd3a6 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/DataUtils.kt @@ -0,0 +1,39 @@ +package net.pokeranalytics.android.model.utils + +import io.realm.Realm +import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.TransactionType +import java.util.* + +class DataUtils { + + companion object { + + /** + * Returns true if the provided parameters doesn't correspond to an existing session + */ + fun sessionCount(realm: Realm, startDate: Date, endDate: Date, net: Double): Int { + val sessions = realm.where(Session::class.java) + .equalTo("startDate", startDate) + .equalTo("endDate", endDate) + .equalTo("result.net", net) + .findAll() + return sessions.size + } + + /** + * Returns true if the provided parameters doesn't correspond to an existing transaction + */ + fun transactionUnicityCheck(realm: Realm, date: Date, amount: Double, type: TransactionType): Boolean { + val transactions = realm.where(Transaction::class.java) + .equalTo("date", date) + .equalTo("amount", amount) + .equalTo("type.id", type.id) + .findAll() + return transactions.isEmpty() + } + + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt index 57a8e17b..b33cae35 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt @@ -71,27 +71,27 @@ class FavoriteSessionFinder { private const val FAVORITE_SIGNIFICANT_SESSIONS = 15L /** - * Copies the favorite session parameters on the [newSession] + * Copies the favorite session parameters on the [session] */ - fun copyParametersFromFavoriteSession(newSession: Session, location: Location?, context: Context) { + fun copyParametersFromFavoriteSession(session: Session, location: Location?, context: Context) { val favoriteSession = - favoriteSession(newSession.type, location, newSession.realm, context) + favoriteSession(session.type, location, session.realm, context) favoriteSession?.let { fav -> - newSession.limit = fav.limit - newSession.game = fav.game - newSession.bankroll = fav.bankroll - newSession.tableSize = fav.tableSize + session.limit = fav.limit + session.game = fav.game + session.bankroll = fav.bankroll + session.tableSize = fav.tableSize - when (newSession.type) { + when (session.type) { Session.Type.CASH_GAME.ordinal -> { - newSession.cgSmallBlind = fav.cgSmallBlind - newSession.cgBigBlind = fav.cgBigBlind + session.cgSmallBlind = fav.cgSmallBlind + session.cgBigBlind = fav.cgBigBlind } Session.Type.TOURNAMENT.ordinal -> { - newSession.tournamentEntryFee = fav.tournamentEntryFee + session.tournamentEntryFee = fav.tournamentEntryFee } } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt index bb2875a8..72afe28a 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt @@ -12,11 +12,29 @@ import java.util.* class Seed(var context:Context) : Realm.Transaction { + companion object { + + fun createDefaultTransactionTypes(values: Array, context: Context, realm: Realm) { + values.forEach { value -> + + val existing = realm.where(TransactionType::class.java).equalTo("kind", value.uniqueIdentifier).findAll() + if (existing.isEmpty()) { + val type = TransactionType() + type.name = value.localizedTitle(context) + type.additive = value.additive + type.kind = value.uniqueIdentifier + type.lock = true + realm.insertOrUpdate(type) + } + } + } + } + override fun execute(realm: Realm) { this.createDefaultGames(realm) this.createDefaultTournamentFeatures(realm) this.createDefaultCurrencyAndBankroll(realm) - this.createDefaultTransactionTypes(realm) + createDefaultTransactionTypes(TransactionType.Value.values(), context, realm) } private fun createDefaultTournamentFeatures(realm: Realm) { @@ -56,15 +74,4 @@ class Seed(var context:Context) : Realm.Transaction { } } - private fun createDefaultTransactionTypes(realm: Realm) { - TransactionType.Value.values().forEachIndexed { index, value -> - val type = TransactionType() - type.name = value.localizedTitle(context) - type.additive = value.additive - type.kind = index - type.lock = true - realm.insertOrUpdate(type) - } - } - } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt index 76cce72c..31e3b8c0 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt @@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.utils import io.realm.RealmQuery import io.realm.RealmResults import net.pokeranalytics.android.exceptions.ModelException +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.SessionSet import kotlin.math.max @@ -23,7 +24,7 @@ class SessionSetManager { fun updateTimeline(session: Session) { if (!session.realm.isInTransaction) { - throw IllegalStateException("realm should be in transaction at this point") + throw PAIllegalStateException("realm should be in transaction at this point") } if (session.startDate == null) { @@ -96,7 +97,7 @@ class SessionSetManager { /** * Multiple session sets update: - * Merges all sets into one (delete all then create a new one) + * Merges all sets into one (delete all then buildAndShow a new one) */ private fun mergeSessionGroups(session: Session, sessionSets: RealmResults) { @@ -154,7 +155,7 @@ class SessionSetManager { fun removeFromTimeline(session: Session) { if (!session.realm.isInTransaction) { - throw IllegalStateException("realm should be in transaction at this point") + throw PAIllegalStateException("realm should be in transaction at this point") } val sessionSet = session.sessionSet diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt deleted file mode 100644 index d8d93f7e..00000000 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt +++ /dev/null @@ -1,21 +0,0 @@ -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() - } - - } - -} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt index 0f37c4d8..c37ca52b 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt @@ -37,54 +37,6 @@ class BankrollActivity : PokerAnalyticsActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_bankroll) -// this.computableResults = getRealm().where(ComputableResult::class.java).findAll() // ComputableResult are existing only if sessions are ended -// this.computableResults.addChangeListener { t, changeSet -> -// -// val bankrolls = mutableSetOf() -// val indexes = mutableSetOf() -// indexes.addAll(changeSet.changes.toList()) -// indexes.addAll(changeSet.insertions.toList()) -// indexes.addAll(changeSet.deletions.toList()) -// indexes.forEach { index -> -// t[index]?.session?.bankroll?.let { br -> -// bankrolls.add(br) -// } -// } -// this.computeBankrollReports(bankrolls) -// } -// this.bankrolls = getRealm().where(Bankroll::class.java).findAll() // ComputableResult are existing only if sessions are ended -// this.bankrolls.addChangeListener { _, changeSet -> -// -// -// -// -// -// } -// this.transactions = getRealm().where(Transaction::class.java).findAll() // ComputableResult are existing only if sessions are ended -// this.transactions.addChangeListener { t, changeSet -> -// -// val bankrolls = mutableSetOf() -// val indexes = mutableSetOf() -// indexes.addAll(changeSet.changes.toList()) -// indexes.addAll(changeSet.insertions.toList()) -// indexes.addAll(changeSet.deletions.toList()) -// indexes.forEach { index -> -// if (t.isNotEmpty()) { -// t[index]?.bankroll?.let { br -> -// bankrolls.add(br) -// } -// } -// } -// this.computeBankrollReports(bankrolls) -// } - - } - - fun computeBankrollReports(bankrolls: Collection) { - - - - } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt index 71aafcd9..39db989a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt @@ -1,28 +1,52 @@ package net.pokeranalytics.android.ui.activity -import android.content.Context +import android.app.Activity import android.content.Intent import android.os.Bundle +import androidx.fragment.app.Fragment import net.pokeranalytics.android.R import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity -import net.pokeranalytics.android.util.billing.AppGuard +import net.pokeranalytics.android.ui.activity.components.RequestCode +import net.pokeranalytics.android.ui.fragment.SubscriptionFragment class BillingActivity : PokerAnalyticsActivity() { + private enum class IntentKey(val keyName: String) { + SHOW_MESSAGE("showMessage"), + } + companion object { - fun newInstance(context: Context) { - val intent = Intent(context, BillingActivity::class.java) - context.startActivity(intent) + + fun newInstanceForResult(activity: Activity, showSessionMessage: Boolean) { + val intent = Intent(activity, BillingActivity::class.java) + intent.putExtra(IntentKey.SHOW_MESSAGE.keyName, showSessionMessage) + activity.startActivityForResult(intent, RequestCode.SUBSCRIPTION.value) + } + + fun newInstanceForResult(fragment: Fragment, showSessionMessage: Boolean) { + val intent = Intent(fragment.requireContext(), BillingActivity::class.java) + intent.putExtra(IntentKey.SHOW_MESSAGE.keyName, showSessionMessage) + fragment.startActivityForResult(intent, RequestCode.SUBSCRIPTION.value) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_billing) + initUI() } - override fun onResume() { - super.onResume() + private fun initUI() { + + val fragmentManager = supportFragmentManager + val fragmentTransaction = fragmentManager.beginTransaction() + val fragment = SubscriptionFragment() + + val showSessionMessage = intent.getBooleanExtra(IntentKey.SHOW_MESSAGE.keyName, false) + + fragmentTransaction.add(R.id.container, fragment) + fragmentTransaction.commit() + fragment.setData(showSessionMessage) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt index dacbc126..56a4009f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt @@ -67,10 +67,10 @@ class EditableDataActivity : PokerAnalyticsActivity() { LiveData.TRANSACTION_TYPE.ordinal -> TransactionTypeDataFragment() else -> EditableDataFragment() } + fragment.setData(dataType, primaryKey) fragmentTransaction.add(R.id.container, fragment) fragmentTransaction.commit() - fragment.setData(dataType, primaryKey) } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt index a78e3435..29189791 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt @@ -64,9 +64,9 @@ class FiltersActivity : PokerAnalyticsActivity() { val filterableType = FilterableType.valueByIdentifier(uniqueIdentifier) fragment = FiltersFragment() + fragment.setData(filterId, filterableType) fragmentTransaction.add(R.id.container, fragment) fragmentTransaction.commit() - fragment.setData(filterId, filterableType) fragment.updateMostUsedFiltersVisibility(!hideMostUsedFilters) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersListActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersListActivity.kt new file mode 100644 index 00000000..2dc2b16d --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersListActivity.kt @@ -0,0 +1,58 @@ +package net.pokeranalytics.android.ui.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.fragment.app.Fragment +import kotlinx.android.synthetic.main.activity_filters_list.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity +import net.pokeranalytics.android.ui.fragment.FiltersListFragment +import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode + +class FiltersListActivity : PokerAnalyticsActivity() { + + enum class IntentKey(val keyName: String) { + DATA_TYPE("DATA_TYPE"), + LIVE_DATA_TYPE("LIVE_DATA_TYPE"), + ITEM_DELETED("ITEM_DELETED"), + SHOW_ADD_BUTTON("SHOW_ADD_BUTTON"), + } + + companion object { + fun newInstance(context: Context, dataType: Int) { + context.startActivity(getIntent(context, dataType)) + } + + fun newSelectInstance(fragment: Fragment, dataType: Int, showAddButton: Boolean = true) { + val context = fragment.requireContext() + fragment.startActivityForResult(getIntent(context, dataType, showAddButton), FilterActivityRequestCode.SELECT_FILTER.ordinal) + } + + private fun getIntent(context: Context, dataType: Int, showAddButton: Boolean = true): Intent { + val intent = Intent(context, FiltersListActivity::class.java) + intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType) + intent.putExtra(IntentKey.SHOW_ADD_BUTTON.keyName, showAddButton) + return intent + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_filters_list) + + initUI() + } + + /** + * Init UI + */ + private fun initUI() { + val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0) + val showAddButton = intent.getBooleanExtra(IntentKey.SHOW_ADD_BUTTON.keyName, true) + val fragment = filtersListFragment as FiltersListFragment + fragment.setData(dataType) + fragment.updateUI(showAddButton) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt index e9a540db..ea18158f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt @@ -3,7 +3,6 @@ package net.pokeranalytics.android.ui.activity import android.app.KeyguardManager import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Build import android.os.Bundle import com.google.android.material.bottomnavigation.BottomNavigationView @@ -13,12 +12,8 @@ import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.R import net.pokeranalytics.android.model.realm.Currency import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity -import net.pokeranalytics.android.ui.activity.components.RequestCode -import net.pokeranalytics.android.ui.activity.components.ResultCode import net.pokeranalytics.android.ui.adapter.HomePagerAdapter -import net.pokeranalytics.android.ui.extensions.showAlertDialog import net.pokeranalytics.android.util.billing.AppGuard -import timber.log.Timber class HomeActivity : PokerAnalyticsActivity() { @@ -48,7 +43,7 @@ class HomeActivity : PokerAnalyticsActivity() { R.id.navigation_reports -> { displayFragment(3) } - R.id.navigation_more -> { + R.id.navigation_settings -> { displayFragment(4) } } @@ -80,39 +75,50 @@ class HomeActivity : PokerAnalyticsActivity() { } - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - - intent?.let { - - when (intent.action) { - "android.intent.action.VIEW" -> { // import - val data = it.data - if (data != null) { - this.requestImportConfirmation(data) - } else { - throw IllegalStateException("URI null on import") - } - } - else -> { - Timber.d("Intent ${intent.action} unmanaged") - } - } - } +// override fun onNewIntent(intent: Intent?) { +// super.onNewIntent(intent) +// +// setIntent(intent) +// intent?.let { +// +// when (intent.action) { +// "android.intent.action.VIEW" -> { // import +// val data = it.data +// if (data != null) { +// this.requestImportConfirmation(data) +// } else { +// throw PAIllegalStateException("URI null on import") +// } +// } +// else -> { +// Timber.w("Intent ${intent.action} unmanaged") +// } +// } +// } +// +// } + +// override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { +// super.onActivityResult(requestCode, resultCode, data) +// +// when (requestCode) { +// RequestCode.IMPORT.value -> { +// if (resultCode == ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) { +// showAlertDialog(context = this, message = R.string.unknown_import_format_popup_message) +// } +// } +// } +// } - } + // Import - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - when (requestCode) { - RequestCode.IMPORT.value -> { - if (resultCode == ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) { - showAlertDialog(context = this, message = R.string.unknown_import_format_popup_message) - } - } - } - } +// private fun requestImportConfirmation(uri: Uri) { +// +// showAlertDialog(context = this, title = R.string.import_confirmation, showCancelButton = true, positiveAction = { +// ImportActivity.newInstanceForResult(this, uri) +// }) +// +// } private fun observeRealmObjects() { @@ -164,14 +170,4 @@ class HomeActivity : PokerAnalyticsActivity() { viewPager.setCurrentItem(index, false) } - // Import - - private fun requestImportConfirmation(uri: Uri) { - - showAlertDialog(context = this, title = R.string.import_confirmation, showCancelButton = true, positiveAction = { - ImportActivity.newInstanceForResult(this, uri) - }) - - } - } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/ImportActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/ImportActivity.kt index fd9fef8c..2a83c802 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/ImportActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/ImportActivity.kt @@ -4,12 +4,18 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle +import android.widget.Toast import androidx.fragment.app.FragmentActivity import io.realm.Realm import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.RequestCode +import net.pokeranalytics.android.ui.activity.components.ResultCode +import net.pokeranalytics.android.ui.extensions.showAlertDialog import net.pokeranalytics.android.ui.fragment.ImportFragment +import net.pokeranalytics.android.util.billing.AppGuard +import net.pokeranalytics.android.util.extensions.count import timber.log.Timber class ImportActivity : PokerAnalyticsActivity() { @@ -39,20 +45,12 @@ class ImportActivity : PokerAnalyticsActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - this.fileURI = intent.getParcelableExtra(ImportActivity.IntentKey.URI.keyName) + intent?.data?.let { + this.fileURI = it + } 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() + requestImportConfirmation() } private fun initUI() { @@ -71,16 +69,66 @@ class ImportActivity : PokerAnalyticsActivity() { } -// 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 onNewIntent(intent: Intent?) { +// super.onNewIntent(intent) +// +// Timber.d("++++++ data = ${intent?.data}") +// +// setIntent(intent) +// intent?.let { +// +// when (intent.action) { +// "android.intent.action.VIEW" -> { // import +// val data = it.data +// if (data != null) { +// this.requestImportConfirmation(data) +// } else { +// throw PAIllegalStateException("URI null on import") +// } +// } +// else -> { +// Timber.w("Intent ${intent.action} unmanaged") +// } +// } // } -// } // -// override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { -// super.onRequestPermissionsResult(requestCode, permissions, grantResults) // } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + when (requestCode) { + RequestCode.IMPORT.value -> { + if (resultCode == ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) { + showAlertDialog(context = this, message = R.string.unknown_import_format_popup_message, positiveAction = { + finish() + }) + } + } + } + } + + // Import + + private fun requestImportConfirmation() { + + val realm = Realm.getDefaultInstance() + val sessionCount = realm.count(Session::class.java) + realm.close() + + if (!AppGuard.isProUser && sessionCount >= AppGuard.MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION) { // && !BuildConfig.DEBUG + Toast.makeText(this, "Please subscribe!", Toast.LENGTH_LONG).show() + BillingActivity.newInstanceForResult(this, true) + return + } + + showAlertDialog(context = this, title = R.string.import_confirmation, showCancelButton = true, positiveAction = { + initUI() + }, negativeAction = { + finish() + }) + + } + + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/NewDataMenuActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/NewDataMenuActivity.kt index ac7570b2..26d2fba7 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/NewDataMenuActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/NewDataMenuActivity.kt @@ -4,6 +4,8 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.Context import android.content.Intent +import android.content.pm.ActivityInfo +import android.os.Build import android.os.Bundle import android.view.View import android.view.ViewAnimationUtils @@ -12,6 +14,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import net.pokeranalytics.android.R import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.extensions.px @@ -29,11 +32,18 @@ class NewDataMenuActivity : PokerAnalyticsActivity() { } } + private var choiceSelected = false + private var menuWillBeHidden = false private val fabSize = 48.px override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(net.pokeranalytics.android.R.layout.activity_new_data) + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // used to fix Oreo crash + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + + setContentView(R.layout.activity_new_data) initUI() } @@ -78,12 +88,18 @@ class NewDataMenuActivity : PokerAnalyticsActivity() { * Set the result and hide menu */ private fun finishWithResult(choice: Int) { + + if (choiceSelected) { + return + } + choiceSelected = true + val intent = Intent() intent.putExtra(IntentKey.CHOICE.keyName, choice) setResult(RESULT_OK, intent) GlobalScope.launch(Dispatchers.Main) { delay(200) - hideMenu(true) + hideMenu() } } @@ -105,7 +121,12 @@ class NewDataMenuActivity : PokerAnalyticsActivity() { /** * Hide menu */ - private fun hideMenu(hideQuickly: Boolean = false) { + private fun hideMenu() { + + if (menuWillBeHidden) { + return + } + menuWillBeHidden = true val cx = menuContainer.measuredWidth - fabSize / 2 val cy = menuContainer.measuredHeight - fabSize / 2 diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/SessionActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/SessionActivity.kt index e082fb49..602762c9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/SessionActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/SessionActivity.kt @@ -14,32 +14,32 @@ class SessionActivity: PokerAnalyticsActivity() { enum class IntentKey(val keyName : String) { IS_TOURNAMENT("IS_TOURNAMENT"), + DUPLICATE("DUPLICATE"), SESSION_ID("SESSION_ID"); } companion object { - fun newInstance(context: Context, isTournament: Boolean? = false, sessionId: String? = "") { - val intent = Intent(context, SessionActivity::class.java) - isTournament?.let { - intent.putExtra(IntentKey.IS_TOURNAMENT.keyName, isTournament) - } - sessionId?.let { - intent.putExtra(IntentKey.SESSION_ID.keyName, sessionId) - } + fun newInstance(context: Context, isTournament: Boolean? = false, sessionId: String? = "", duplicate: Boolean = false) { + val intent = this.intent(context, isTournament, sessionId, duplicate) context.startActivity(intent) } - fun newInstanceforResult(fragment: Fragment, isTournament: Boolean? = false, sessionId: String? = "", requestCode: Int) { - val intent = Intent(fragment.requireContext(), SessionActivity::class.java) + fun newInstanceforResult(fragment: Fragment, isTournament: Boolean? = false, sessionId: String? = "", duplicate: Boolean = false, requestCode: Int) { + val intent = this.intent(fragment.requireContext(), isTournament, sessionId, duplicate) + fragment.startActivityForResult(intent, requestCode) + } + + private fun intent(context: Context, isTournament: Boolean? = false, sessionId: String? = "", duplicate: Boolean = false) : Intent { + val intent = Intent(context, SessionActivity::class.java) isTournament?.let { intent.putExtra(IntentKey.IS_TOURNAMENT.keyName, isTournament) } + intent.putExtra(IntentKey.DUPLICATE.keyName, duplicate) sessionId?.let { intent.putExtra(IntentKey.SESSION_ID.keyName, sessionId) } - - fragment.startActivityForResult(intent, requestCode) + return intent } } @@ -62,8 +62,9 @@ class SessionActivity: PokerAnalyticsActivity() { private fun initUI() { val sessionId = intent.getStringExtra(IntentKey.SESSION_ID.keyName) val isTournament = intent.getBooleanExtra(IntentKey.IS_TOURNAMENT.keyName, false) + val duplicate = intent.getBooleanExtra(IntentKey.DUPLICATE.keyName, false) val fragment = sessionFragment as SessionFragment - fragment.setData(isTournament, sessionId) + fragment.setData(isTournament, sessionId, duplicate) } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/Top10Activity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/Top10Activity.kt new file mode 100644 index 00000000..42086a88 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/Top10Activity.kt @@ -0,0 +1,33 @@ +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.ui.activity.components.PokerAnalyticsActivity + +class Top10Activity : PokerAnalyticsActivity() { + + companion object { + fun newInstance(context: Context) { + val intent = Intent(context, Top10Activity::class.java) + context.startActivity(intent) + } + + /** + * Create a new instance for result + */ + fun newInstanceForResult(fragment: Fragment, requestCode: Int) { + val intent = Intent(fragment.requireContext(), Top10Activity::class.java) + fragment.startActivityForResult(intent, requestCode) + } + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_top_10) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt index ed11d47f..a6dfb446 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt @@ -2,10 +2,17 @@ package net.pokeranalytics.android.ui.activity.components enum class RequestCode(var value: Int) { DEFAULT(1), + FEED_MENU(100), + FEED_TRANSACTION_DETAILS(101), + BANKROLL_DETAILS(700), + BANKROLL_CREATE(701), + BANKROLL_EDIT(702), NEW_SESSION(800), NEW_TRANSACTION(801), NEW_REPORT(802), - IMPORT(900) + IMPORT(900), + SUBSCRIPTION(901), + CURRENCY(902) } enum class ResultCode(var value: Int) { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/PokerAnalyticsActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/PokerAnalyticsActivity.kt index 77b324f0..f72d90b2 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/PokerAnalyticsActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/PokerAnalyticsActivity.kt @@ -1,6 +1,7 @@ package net.pokeranalytics.android.ui.activity.components import android.Manifest.permission.ACCESS_FINE_LOCATION +import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.os.Bundle import android.os.PersistableBundle @@ -33,6 +34,7 @@ open class PokerAnalyticsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) Crashlytics.log("$this.localClassName onCreate") + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT // fixes crash } override fun onResume() { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedSessionRowRepresentableAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedSessionRowRepresentableAdapter.kt index 405167e7..912d926e 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedSessionRowRepresentableAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedSessionRowRepresentableAdapter.kt @@ -6,13 +6,17 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView +import io.realm.Realm +import io.realm.RealmQuery import io.realm.RealmResults +import io.realm.Sort +import io.realm.kotlin.where import kotlinx.android.synthetic.main.row_feed_session.view.* -import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException +import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Session 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 timber.log.Timber import java.util.* @@ -21,28 +25,54 @@ 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 + * The [delegate] is an object notified of UI actions */ class FeedSessionRowRepresentableAdapter( - var delegate: RowRepresentableDelegate? = null, - var realmResults: RealmResults, - var pendingRealmResults: RealmResults, - var distinctHeaders: RealmResults + private var realm: Realm, + var delegate: RowRepresentableDelegate? = null ) : RecyclerView.Adapter() { - private var headersPositions = HashMap() + private lateinit var startedSessions: RealmResults + private lateinit var pendingSessions: RealmResults + private lateinit var sortedHeaders: SortedMap + private var allSessions = mutableListOf() + + var filter: Filter? = null + set(value) { + field = value + defineSessions() + refreshData() + } + init { + defineSessions() refreshData() } + private fun defineSessions() { + + this.startedSessions = requestNewQuery().isNotNull("startDate").findAll() + .sort("startDate", Sort.DESCENDING) + this.pendingSessions = requestNewQuery().isNull("startDate").findAll() + .sort("creationDate", Sort.DESCENDING) + } + + private fun requestNewQuery() : RealmQuery { + this.filter?.let { + return it.query() + } ?: run { + return realm.where() + } + } + /** * Display a session view */ inner class RowSessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { + fun bind(position: Int, row: Session?, adapter: FeedSessionRowRepresentableAdapter) { itemView.sessionRow.setData(row as Session) @@ -50,6 +80,10 @@ class FeedSessionRowRepresentableAdapter( adapter.delegate?.onRowSelected(position, row) } itemView.sessionRow.setOnClickListener(listener) + + itemView.sessionRow.setOnLongClickListener { + itemView.showContextMenu() + } } } @@ -59,7 +93,7 @@ class FeedSessionRowRepresentableAdapter( inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { fun bind(title: String) { // Title - itemView.findViewById(R.id.title)?.let { + itemView.findViewById(net.pokeranalytics.android.R.id.title)?.let { it.text = title } } @@ -67,25 +101,24 @@ class FeedSessionRowRepresentableAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return if (viewType == RowViewType.ROW_SESSION.ordinal) { - val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_feed_session, parent, false) + val layout = LayoutInflater.from(parent.context).inflate(net.pokeranalytics.android.R.layout.row_feed_session, parent, false) RowSessionViewHolder(layout) } else { - val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_header_title, parent, false) + val layout = LayoutInflater.from(parent.context).inflate(net.pokeranalytics.android.R.layout.row_header_title, parent, false) HeaderTitleViewHolder(layout) } - } override fun getItemViewType(position: Int): Int { - if (sortedHeaders.containsKey(position)) { - return RowViewType.HEADER_TITLE.ordinal + return if (sortedHeaders.containsKey(position)) { + RowViewType.HEADER_TITLE.ordinal } else { - return RowViewType.ROW_SESSION.ordinal + RowViewType.ROW_SESSION.ordinal } } override fun getItemCount(): Int { - return realmResults.size + pendingRealmResults.size + distinctHeaders.size + return allSessions.size + sortedHeaders.size } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { @@ -104,38 +137,34 @@ class FeedSessionRowRepresentableAdapter( // If the header has no date, it's a pending session return if (sortedHeaders[position] == null) { - context.getString(R.string.pending) + context.getString(net.pokeranalytics.android.R.string.pending) } else { // Else, return the formatted date - val realmHeaderPosition = if (pendingRealmResults.size > 0) sortedHeaders.keys.indexOf(position) - 1 else sortedHeaders.keys.indexOf(position) - distinctHeaders[realmHeaderPosition]?.startDate?.getMonthAndYear() ?: "" + sortedHeaders[position]?.getMonthAndYear() ?: throw PAIllegalStateException("Null date should not happen there") } } - return NULL_TEXT + throw PAIllegalStateException("Any position should always have a header, position = $position") + } + + fun sessionIdForPosition(position: Int): String? { + return this.getSessionForPosition(position)?.id } /** * Get real index */ private fun getSessionForPosition(position: Int): Session? { - return if (pendingRealmResults.size > 0 && position < pendingRealmResults.size + 1) { - // If we have pending session & the position is between these sessions - pendingRealmResults[position - 1] - } else { - // Else, return the correct session - - // Row position - var headersBefore = 0 - for (key in sortedHeaders.keys) { - if (position > key) { - headersBefore++ - } else { - break - } - } - realmResults[position - headersBefore - pendingRealmResults.size] + // Row position + var headersBefore = 0 + for (key in sortedHeaders.keys) { + if (position > key) { + headersBefore++ + } else { + break + } } + return allSessions[position - headersBefore] } /** @@ -143,12 +172,12 @@ class FeedSessionRowRepresentableAdapter( */ fun refreshData() { - headersPositions.clear() + allSessions.clear() + allSessions.addAll(this.pendingSessions) + allSessions.addAll(this.startedSessions) + Timber.d("Update session list, total count = ${allSessions.size}") - // If we have pending sessions, set the first header to null - if (pendingRealmResults.size > 0) { - headersPositions[0] = null - } + val headersPositions = HashMap() val start = System.currentTimeMillis() @@ -158,28 +187,31 @@ class FeedSessionRowRepresentableAdapter( val calendar = Calendar.getInstance() // Add headers if the date doesn't exist yet - for ((index, session) in realmResults.withIndex()) { - calendar.time = session.startDate ?: session.creationDate - if (checkHeaderCondition(calendar, previousYear, previousMonth)) { - headersPositions[index + headersPositions.size + pendingRealmResults.size] = session.startDate ?: session.creationDate - previousYear = calendar.get(Calendar.YEAR) - previousMonth = calendar.get(Calendar.MONTH) + for ((index, session) in allSessions.withIndex()) { + + val startDate = session.startDate + if (startDate == null) { + headersPositions[0] = null + } else { + calendar.time = startDate + if (checkHeaderCondition(calendar, previousYear, previousMonth)) { + headersPositions[index + headersPositions.size] = startDate + previousYear = calendar.get(Calendar.YEAR) + previousMonth = calendar.get(Calendar.MONTH) + } } } sortedHeaders = headersPositions.toSortedMap() - Timber.d("Create viewTypesPositions in: ${System.currentTimeMillis() - start}ms") } /** * Check if we need to add a header - * Can be change to manage different condition + * Can be changed 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) - } - } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedTransactionRowRepresentableAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedTransactionRowRepresentableAdapter.kt index ea262236..e66001d4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedTransactionRowRepresentableAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedTransactionRowRepresentableAdapter.kt @@ -40,6 +40,7 @@ class FeedTransactionRowRepresentableAdapter( * 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) @@ -75,10 +76,10 @@ class FeedTransactionRowRepresentableAdapter( override fun getItemViewType(position: Int): Int { - if (sortedHeaders.containsKey(position)) { - return RowViewType.HEADER_TITLE.ordinal + return if (sortedHeaders.containsKey(position)) { + RowViewType.HEADER_TITLE.ordinal } else { - return RowViewType.ROW_TRANSACTION.ordinal + RowViewType.ROW_TRANSACTION.ordinal } } @@ -130,8 +131,6 @@ class FeedTransactionRowRepresentableAdapter( headersPositions.clear() - val start = System.currentTimeMillis() - var previousYear = Int.MAX_VALUE var previousMonth = Int.MAX_VALUE @@ -148,6 +147,7 @@ class FeedTransactionRowRepresentableAdapter( } sortedHeaders = headersPositions.toSortedMap() + } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/HomePagerAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/HomePagerAdapter.kt index 22cd2c57..30b311e9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/HomePagerAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/HomePagerAdapter.kt @@ -21,7 +21,7 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda 1 -> StatisticsFragment.newInstance() 2 -> CalendarFragment.newInstance() 3 -> ReportsFragment.newInstance() - 4 -> MoreFragment.newInstance() + 4 -> SettingsFragment.newInstance() else -> FeedFragment.newInstance() } } @@ -47,7 +47,7 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda StatisticsFragment::class.java -> 1 CalendarFragment::class.java -> 2 ReportsFragment::class.java -> 3 - MoreFragment::class.java -> 4 + SettingsFragment::class.java -> 4 else -> -1 } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt index ad37ff4f..ff050f53 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt @@ -7,10 +7,13 @@ import net.pokeranalytics.android.ui.view.BindableHolder import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -interface RowRepresentableDelegate { +interface RowRepresentableDelegate: RowEditableDelegate { fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean = false) {} - fun onRowValueChanged(value: Any?, row: RowRepresentable) {} - fun onRowDeleted(row: RowRepresentable) {} +} + +interface RowEditableDelegate { + fun onRowValueChanged(value: Any?, row: RowRepresentable) {} + fun onRowDeleted(row: RowRepresentable) {} } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt index 26dec55a..1b5dc64e 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt @@ -1,14 +1,14 @@ package net.pokeranalytics.android.ui.adapter import android.content.Context +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.util.TextFormat /** * Base Interface to provide the RowRepresentable to the adapter */ -interface RowRepresentableDataSource: EditableDataSource, DisplayableDataSource, SelectableDataSource { +interface RowRepresentableDataSource: DisplayableDataSource, SelectableDataSource { /** * Returns a prebuild list of rows @@ -42,21 +42,21 @@ interface StaticRowRepresentableDataSource: RowRepresentableDataSource { this.adapterRows()?.let { return it[position] } - throw IllegalStateException("Need to implement Data Source") + throw PAIllegalStateException("Need to implement Data Source") } override fun numberOfRows(): Int { this.adapterRows()?.let { return it.size } - throw IllegalStateException("Need to implement Data Source") + throw PAIllegalStateException("Need to implement Data Source") } override fun viewTypeForPosition(position:Int): Int { this.rowRepresentableForPosition(position)?.let { return it.viewType } - throw IllegalStateException("Need to implement Data Source") + throw PAIllegalStateException("Need to implement Data Source") } } @@ -160,18 +160,6 @@ interface DisplayableDataSource { } } -/** - * An interface providing a way to describe how the edition of a [RowRepresentable] will be displayed - */ -interface EditableDataSource { - /** - * A list of [RowRepresentableEditDescriptor] object specifying the way the edition will be handled - */ - fun editDescriptors(row: RowRepresentable): ArrayList? { - return null - } -} - /** * An interface providing a way to select a row diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDetailsFragment.kt index ab2175f2..bdae834e 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDetailsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDetailsFragment.kt @@ -10,114 +10,83 @@ 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.calculus.bankroll.BankrollReportManager +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.LiveData +import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.activity.EditableDataActivity +import net.pokeranalytics.android.ui.activity.components.RequestCode 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.fragment.components.RealmFragment import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable +import net.pokeranalytics.android.util.extensions.findById +import timber.log.Timber -class BankrollDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { +class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { companion object { - const val REQUEST_CODE_EDIT = 1000 - - /** - * Create new instance - */ fun newInstance(bankrollReport: BankrollReport): BankrollDetailsFragment { val fragment = BankrollDetailsFragment() - fragment.bankrollReport = bankrollReport + fragment.bankrollId = bankrollReport.setup.bankrollId return fragment } } + private var bankrollId: String? = null + private lateinit var bankroll: Bankroll + + private var rows: ArrayList = ArrayList() private lateinit var bankrollAdapter: RowRepresentableAdapter - private lateinit var bankrollReport: BankrollReport private var bankrollDetailsMenu: Menu? = null - private var rows: ArrayList = ArrayList() // Life Cycle override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + super.onCreateView(inflater, container, savedInstanceState) return inflater.inflate(R.layout.fragment_bankroll_details, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initUI() initData() + initUI() + updateUI() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_CODE_EDIT && resultCode == RESULT_OK) { + if (requestCode == RequestCode.BANKROLL_EDIT.value && resultCode == RESULT_OK) { if (data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) != null) { activity?.setResult(RESULT_OK, data) activity?.finish() } else { - updateMenuUI() + updateUI() } } } - override fun adapterRows(): List? { - 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) + inflater?.inflate(R.menu.toolbar_comparison_chart, menu) // TODO R.menu.toolbar_comparison_chart? 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)) - } - } + this.bankrollId?.let { id -> + this.bankroll = getRealm().findById(id) ?: throw PAIllegalStateException("Bankroll not found, id=$id") } } @@ -128,8 +97,7 @@ class BankrollDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable private fun initUI() { setDisplayHomeAsUpEnabled(true) - - updateMenuUI() + this.updateMenuUI() bankrollAdapter = RowRepresentableAdapter(this, this) @@ -142,26 +110,107 @@ class BankrollDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable } } + private fun updateUI() { + this.updateMenuUI() + Timber.d("BankrollDetailsFragment > updateUI > reportForBankroll") + BankrollReportManager.reportForBankroll(this.bankrollId) { bankrollReport -> + this.initRows(bankrollReport) + this.bankrollAdapter.notifyDataSetChanged() + } + } + + private fun initRows(bankrollReport: BankrollReport) { + + rows.clear() + + rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.global)) + + val currency = if (this.bankrollId != null) { this.bankroll.utilCurrency } else { null } + val totalComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total, currency = currency) + val netComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netResult, currency = currency) + val netBankedComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netBanked, currency = currency) + + 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.name + val computedStat = ComputedStat(Stat.NET_RESULT, transactionBucket.total, currency = currency) + rows.add( + CustomizableRowRepresentable( + RowViewType.TITLE_VALUE, + title = typeName, + computedStat = computedStat + ) + ) + } + } + } + } + + /** * Update menu UI */ private fun updateMenuUI() { - if (bankrollReport.setup.virtualBankroll) { + if (this.bankrollId == null) { setToolbarTitle(getString(R.string.total)) bankrollDetailsMenu?.findItem(R.id.settings)?.isVisible = false } else { - setToolbarTitle(bankrollReport.setup.bankroll?.name) + setToolbarTitle(this.bankroll.name) bankrollDetailsMenu?.findItem(R.id.settings)?.isVisible = true } + } + + // StaticRowRepresentableDataSource + + override fun adapterRows(): List? { + return rows + } + + // RowRepresentableDelegate + + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { } + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item?.itemId) { + R.id.settings -> editBankroll() + } + return true + } + + // Business + /** * Open Bankroll edit activity */ private fun editBankroll() { - EditableDataActivity.newInstanceForResult(this, LiveData.BANKROLL, bankrollReport.setup.bankroll?.id, REQUEST_CODE_EDIT) + EditableDataActivity.newInstanceForResult(this, LiveData.BANKROLL, this.bankrollId, RequestCode.BANKROLL_EDIT.value) } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt index 58e954a9..60214e3a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt @@ -8,7 +8,6 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import com.github.mikephil.charting.data.LineDataSet -import io.realm.RealmObject import io.realm.RealmResults import kotlinx.android.synthetic.main.fragment_bankroll.* import kotlinx.coroutines.Dispatchers @@ -16,39 +15,38 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.pokeranalytics.android.R -import net.pokeranalytics.android.calculus.ComputedStat -import net.pokeranalytics.android.calculus.Stat -import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator -import net.pokeranalytics.android.calculus.bankroll.BankrollReport -import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup +import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.interfaces.Deletable -import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.activity.EditableDataActivity import net.pokeranalytics.android.ui.activity.GraphActivity +import net.pokeranalytics.android.ui.activity.components.RequestCode 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.DeletableItemFragment import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollGraphRow +import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollMainRow +import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollTotalRow import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable -import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow import net.pokeranalytics.android.util.extensions.sorted import timber.log.Timber import java.util.* import kotlin.collections.ArrayList +interface BankrollRowRepresentable : RowRepresentable { + var bankrollId: String? +} + class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { companion object { - const val REQUEST_CODE_DETAILS = 100 - const val REQUEST_CODE_CREATE = 101 - /** * Create new instance */ @@ -61,14 +59,11 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour } private var rows: ArrayList = ArrayList() - private var bankrollReportForRow: HashMap = HashMap() - private var lastItemClickedPosition: Int = 0 - private var lastItemClickedId: String = "" - private var deletedRow: RowRepresentable? = null + private var bankrollRowRepresentables: HashMap> = HashMap() private lateinit var bankrolls: RealmResults - override fun deletableItems() : List { + override fun deletableItems(): List { return this.bankrolls } @@ -85,101 +80,68 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour initData() } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_CODE_DETAILS && resultCode == Activity.RESULT_OK) { + if (requestCode == RequestCode.BANKROLL_DETAILS.value && resultCode == Activity.RESULT_OK) { val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) itemToDeleteId?.let { id -> GlobalScope.launch(Dispatchers.Main) { delay(300) deleteItem(dataListAdapter, bankrolls, id) + + // update view + BankrollReportManager.notifyBankrollReportImpact(id) + dataListAdapter.notifyDataSetChanged() } } - } else if (requestCode == REQUEST_CODE_CREATE && resultCode == Activity.RESULT_OK) { + } else if (requestCode == RequestCode.BANKROLL_CREATE.value && resultCode == Activity.RESULT_OK) { //TODO: Refresh bankrolls initData() + } else { + initData() } } - override fun adapterRows(): List? { - return rows - } - - override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { - when (row) { - is GraphRow -> { - val lineDataSet = row.dataSet as LineDataSet - GraphActivity.newInstance(requireContext(), listOf(lineDataSet), title = getString(R.string.bankroll)) - } - else -> { - if (bankrollReportForRow.containsKey(row)) { - bankrollReportForRow[row]?.let { bankrollReport -> - lastItemClickedPosition = position - lastItemClickedId = (row as? Identifiable)?.id ?: "" - BankrollDetailsActivity.newInstanceForResult(this, bankrollReport, REQUEST_CODE_DETAILS) - } - } - } - } - } - - // Business /** * Init data */ private fun initData() { - val realm = getRealm() - this.bankrolls = realm.sorted() - - rows.clear() - bankrollReportForRow.clear() - - GlobalScope.launch { - - launch(Dispatchers.Main) { - - // TODO: Improve that - // We are in the main thread... + this.bankrolls = getRealm().sorted() + this.bankrolls.addChangeListener { _, _ -> + this.createRowRepresentables() + } - val startDate = Date() + this.createRowRepresentables() + } - // Graph - val globalBankrollReportSetup = BankrollReportSetup() - val globalBankrollReport = BankrollCalculator.computeReport(getRealm(), globalBankrollReportSetup) - rows.add(0, GraphRow(dataSet = globalBankrollReport.lineDataSet(requireContext()))) - rows.add(globalBankrollReport) - bankrollReportForRow[globalBankrollReport] = globalBankrollReport + private fun createRowRepresentables() { - // Bankrolls - rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.bankrolls)) + rows.clear() + bankrollRowRepresentables.clear() - Timber.d("initData: ${System.currentTimeMillis() - startDate.time}ms") + // Virtual bankroll + val graphRow = BankrollGraphRow() + rows.add(0, graphRow) + val mainRow = BankrollMainRow() + rows.add(mainRow) -// val bankrolls = LiveData.Bankroll.items(getRealm()) as RealmResults + bankrollRowRepresentables[null] = listOf(graphRow, mainRow) - bankrolls.forEach { bankroll -> - val bankrollReportSetup = BankrollReportSetup(bankroll) - val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup) - val computedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total) - val row = - CustomizableRowRepresentable(RowViewType.TITLE_VALUE_ARROW, title = bankroll.name, computedStat = computedStat, isSelectable = true) - row.id = bankroll.id + // Bankrolls + rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.bankrolls)) - rows.add(row) - bankrollReportForRow[row] = bankrollReport - } - - if (!isDetached) { - dataListAdapter.notifyDataSetChanged() - } - } + bankrolls.forEach { bankroll -> + Timber.d("Creating row for br : ${bankroll.id}, name= ${bankroll.name}, isManaged = ${bankroll.isManaged()}, isValid = ${bankroll.isValid}") + val row = BankrollTotalRow(bankroll.id, bankroll.name) + rows.add(row) + bankrollRowRepresentables[bankroll.id] = listOf(row) } + dataListAdapter.notifyDataSetChanged() } /** @@ -200,29 +162,31 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour } addButton.setOnClickListener { - EditableDataActivity.newInstanceForResult(this@BankrollFragment, dataType = LiveData.BANKROLL, primaryKey = null, requestCode = REQUEST_CODE_CREATE) + EditableDataActivity.newInstanceForResult( + this@BankrollFragment, + dataType = LiveData.BANKROLL, + primaryKey = null, + requestCode = RequestCode.BANKROLL_CREATE.value + ) } } - override fun updateUIAfterDeletion(itemPosition: Int) { - lastItemClickedPosition = rows.indexOfFirst { if (it is Identifiable) it.id == lastItemClickedId else false } - deletedRow = rows.find { if (it is Identifiable) it.id == lastItemClickedId else false } - rows.removeAt(lastItemClickedPosition) - dataListAdapter.notifyItemRemoved(lastItemClickedPosition) + override fun adapterRows(): List? { + return rows } - override fun updateUIAfterUndoDeletion(newItem: RealmObject) { - - // TODO: Improve that - // We are recreating a Bankroll report because the last one is invalid => the bankroll of the setup has been deleted - - deletedRow?.let { row -> - val bankrollReportSetup = BankrollReportSetup(newItem as Bankroll) - val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup) - bankrollReportForRow[row] = bankrollReport - - rows.add(lastItemClickedPosition, row) - dataListAdapter.notifyItemInserted(lastItemClickedPosition) + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { + when (row) { + is BankrollGraphRow -> { + val lineDataSet = row.dataSet as LineDataSet + GraphActivity.newInstance(requireContext(), listOf(lineDataSet), title = getString(R.string.bankroll)) + } + is BankrollRowRepresentable -> { + Timber.d("BankrollFragment > onRowSelected > reportForBankroll") + BankrollReportManager.reportForBankroll(row.bankrollId) { bankrollReport -> + BankrollDetailsActivity.newInstanceForResult(this, bankrollReport, RequestCode.BANKROLL_DETAILS.value) + } + } } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt index e74e97bb..ca1eb5a7 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.tabs.TabLayout import io.realm.Realm import io.realm.RealmModel +import io.realm.RealmResults import kotlinx.android.synthetic.main.fragment_calendar.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -84,10 +85,13 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable super.onViewCreated(view, savedInstanceState) initData() initUI() - - launchStatComputation() } + override fun onResume() { + super.onResume() + launchStatComputation() + } + override fun adapterRows(): List? { return rows } @@ -117,7 +121,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable override val observedEntities: List> = listOf(ComputableResult::class.java) - override fun entitiesChanged(clazz: Class) { + override fun entitiesChanged(clazz: Class, results: RealmResults) { launchStatComputation() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/CurrenciesFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/CurrenciesFragment.kt index 2fa0c3a7..bc44a188 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/CurrenciesFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/CurrenciesFragment.kt @@ -17,6 +17,7 @@ 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.SeparatorRow +import net.pokeranalytics.android.util.UserDefaults import java.util.* class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { @@ -47,12 +48,8 @@ class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataS private val availableCurrencies = this.systemCurrencies.filter { !mostUsedCurrencyCodes.contains(it.currencyCode) }.filter { - Locale.getAvailableLocales().filter { locale -> - try { - Currency.getInstance(locale).currencyCode == it.currencyCode - } catch (e: Exception) { - false - } + UserDefaults.availableCurrencyLocales.filter { currencyLocale -> + Currency.getInstance(currencyLocale).currencyCode == it.currencyCode }.isNotEmpty() }.sortedBy { it.displayName diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt index dceb2885..13c4a9fc 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt @@ -13,6 +13,7 @@ import io.realm.Realm import io.realm.RealmResults import kotlinx.android.synthetic.main.fragment_data_list.* import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Identifiable @@ -24,7 +25,6 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback -import net.pokeranalytics.android.ui.interfaces.FilterableType import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.util.extensions.sorted @@ -44,7 +44,7 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS /** * Set fragment data */ - fun setData(dataType: Int) { + open fun setData(dataType: Int) { this.dataType = LiveData.values()[dataType] this.identifiableClass = this.dataType.relatedEntity @@ -87,7 +87,7 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS val itemId = item.id deleteItem(dataListAdapter, items, itemId) } else { - throw IllegalStateException("Item with position $position not found") + throw PAIllegalStateException("Item with position $position not found") } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt index 35454cc4..ac716528 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt @@ -3,9 +3,7 @@ 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.view.* import android.widget.Toast import androidx.core.app.ActivityOptionsCompat import androidx.core.view.isVisible @@ -17,6 +15,7 @@ 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.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.interfaces.Editable import net.pokeranalytics.android.model.realm.Filter @@ -30,13 +29,15 @@ 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.ContextMenuRecyclerView import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.util.Preferences +import net.pokeranalytics.android.util.billing.AppGuard +import net.pokeranalytics.android.util.extensions.count import java.text.SimpleDateFormat import java.util.* - class FeedFragment : FilterableFragment(), RowRepresentableDelegate { private enum class Tab { @@ -46,20 +47,20 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { 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 var currentTab = Tab.SESSIONS + private lateinit var feedSessionAdapter: FeedSessionRowRepresentableAdapter private lateinit var feedTransactionAdapter: FeedTransactionRowRepresentableAdapter - private lateinit var realmSessions: RealmResults + private lateinit var realmTransactions: RealmResults private lateinit var betaLimitDate: Date @@ -70,8 +71,8 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { override val observedEntities: List> = listOf(Session::class.java, Transaction::class.java) - override fun entitiesChanged(clazz: Class) { - super.entitiesChanged(clazz) + override fun entitiesChanged(clazz: Class, results: RealmResults) { + super.entitiesChanged(clazz, results) when (clazz.kotlin) { Session::class -> { @@ -91,6 +92,34 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { return inflater.inflate(R.layout.fragment_feed, container, false) } + override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) { + super.onCreateContextMenu(menu, v, menuInfo) + + if (v?.id == R.id.menuRecyclerView) { + activity?.menuInflater?.inflate(R.menu.menu_session, menu) + } + + } + + override fun onContextItemSelected(item: MenuItem?): Boolean { + + when (item?.itemId) { + R.id.duplicate -> { + val info = item.menuInfo as ContextMenuRecyclerView.RecyclerViewContextMenuInfo + val sessionId = this.feedSessionAdapter.sessionIdForPosition(info.position) + if (sessionId != null) { + createNewSession(true, sessionId = sessionId, duplicate = true) + } else { + throw PAIllegalStateException("Session not found for duplicate at position: ${info.position}") + } + } + else -> { + } + } + + return true + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initUI() @@ -99,13 +128,13 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_CODE_MENU && resultCode == RESULT_OK && data != null) { + if (requestCode == RequestCode.FEED_MENU.value && 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) { + } else if (requestCode == RequestCode.FEED_TRANSACTION_DETAILS.value && resultCode == RESULT_OK && data != null) { if (data.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) != null) { deleteSelectedTransaction() } @@ -123,22 +152,9 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { 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) @@ -149,7 +165,7 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { this, LiveData.TRANSACTION, row.id, - REQUEST_CODE_TRANSACTION_DETAILS + RequestCode.FEED_TRANSACTION_DETAILS.value ) } } @@ -160,37 +176,49 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { */ private fun initUI() { - disclaimerContainer.isVisible = Preferences.shouldShowDisclaimer(requireContext()) + this.feedSessionAdapter = FeedSessionRowRepresentableAdapter(getRealm(), this) + + registerForContextMenu(this.menuRecyclerView) + + val messageToShow: Preferences.FeedMessage? = Preferences.feedMessageToShow(requireContext()) + + if (messageToShow != null) { + messageBox.isVisible = true + message.text = getString(messageToShow.resId) - disclaimerDismiss.setOnClickListener { - Preferences.setStopShowingDisclaimer(requireContext()) + messageBoxDismiss.setOnClickListener { + Preferences.setStopShowingMessage(messageToShow, requireContext()) - disclaimerContainer.animate().translationY(disclaimerContainer.height.toFloat()) - .setInterpolator(FastOutSlowInInterpolator()) - .withEndAction { disclaimerContainer?.isVisible = false } - .start() + messageBox.animate().translationY(messageBox.height.toFloat()) + .setInterpolator(FastOutSlowInInterpolator()) + .withEndAction { messageBox?.isVisible = false } + .start() + } + } else { + messageBox.isVisible = false } + // Add button addButton.setOnClickListener { activity?.let { val options = ActivityOptionsCompat.makeSceneTransitionAnimation(it) val intent = Intent(requireContext(), NewDataMenuActivity::class.java) - startActivityForResult(intent, REQUEST_CODE_MENU, options.toBundle()) + startActivityForResult(intent, RequestCode.FEED_MENU.value, options.toBundle()) } } + // Tabs tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { when (tab.position) { - 0 -> { + Tab.SESSIONS.ordinal -> { currentFilterable = FilterableType.SESSION - recyclerView.adapter = feedSessionAdapter } - 1 -> { + Tab.TRANSACTIONS.ordinal -> { currentFilterable = FilterableType.TRANSACTION - recyclerView.adapter = feedTransactionAdapter } } + tabChanged(tab.position) } override fun onTabUnselected(tab: TabLayout.Tab) { @@ -209,10 +237,10 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { val sdf = SimpleDateFormat("dd/M/yyyy hh:mm", Locale.getDefault()) betaLimitDate = sdf.parse("17/7/2019 10:00") - this.currentFilterable = FilterableType.SESSION + this.currentFilterable = FilterableType.SESSION val viewManager = SmoothScrollLinearLayoutManager(requireContext()) - recyclerView.apply { + menuRecyclerView.apply { setHasFixedSize(true) layoutManager = viewManager } @@ -221,30 +249,15 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { } 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().isNotNull("startDate").findAll() } - this.realmSessions = this.realmSessions.sort("startDate", Sort.DESCENDING) - - val pendingSessions = sessionFilter?.let { - getRealm().where().alwaysFalse().findAll() - } ?: run { - getRealm().where().isNull("year").isNull("month").findAll().sort("startDate", Sort.DESCENDING) - } - var distinctDateSessions = sessionFilter?.results("year", "month") ?: run { - getRealm().where().distinct("year", "month").findAll() + when (filter?.filterableType) { + FilterableType.SESSION -> { + this.feedSessionAdapter.filter = filter + } + else -> { + this.feedSessionAdapter.filter = null + } } - distinctDateSessions = distinctDateSessions.sort("startDate", Sort.DESCENDING) - this.feedSessionAdapter = - FeedSessionRowRepresentableAdapter(this, realmSessions, pendingSessions, distinctDateSessions) } private fun loadTransactions(filter: Filter? = null) { @@ -272,21 +285,27 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { /** * 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 -// } + private fun createNewSession(isTournament: Boolean, sessionId: String? = null, duplicate: Boolean = false) { - if (Date().after(betaLimitDate)) { - this.showEndOfBetaMessage() + val sessionCount = getRealm().count(Session::class.java) + if (!AppGuard.isProUser && sessionCount >= AppGuard.MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION) { // && !BuildConfig.DEBUG + BillingActivity.newInstanceForResult(this, true) return } - SessionActivity.newInstanceforResult(this, isTournament, requestCode = RequestCode.NEW_SESSION.value) + // Keep commented code for special versions +// if (Date().after(betaLimitDate)) { +// this.showEndOfBetaMessage() +// return +// } + + SessionActivity.newInstanceforResult( + this, + isTournament, + sessionId = sessionId, + duplicate = duplicate, + requestCode = RequestCode.NEW_SESSION.value + ) newSessionCreated = true } @@ -295,38 +314,36 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { */ private fun createNewTransaction() { - if (Date().after(betaLimitDate)) { - this.showEndOfBetaMessage() - return - } +// 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() + getRealm().executeTransaction { + selectedTransaction?.deleteFromRealm() + } selectedTransactionPosition = -1 } /** * Show end of beta message + * Keep for possible future uses */ 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", + "App version has ended. Thanks a lot for using it! Please update with the Google Play version to continue using the app.", Toast.LENGTH_LONG ).show() } - // Filter Handler override fun applyFilter() { @@ -337,41 +354,48 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate { 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 -> { - } - } + when (it.filterableType) { + FilterableType.SESSION -> { + this.selectTab(Tab.SESSIONS) + } + FilterableType.TRANSACTION -> { + this.selectTab(Tab.TRANSACTIONS) + } + else -> { + } + } adapterHasBeenSet = true } if (!adapterHasBeenSet) { adapterHasBeenSet = true - recyclerView.adapter = feedSessionAdapter + this.setAdapter() } } override fun removeFilter() { super.removeFilter() - this.loadSessions() this.loadTransactions() - if (currentFilterable == FilterableType.SESSION) { - recyclerView.adapter = feedSessionAdapter - } else { - recyclerView.adapter = feedTransactionAdapter - } + this.setAdapter() } private fun selectTab(tab: Tab) { + this.currentTab = tab this.tabs.getTabAt(tab.ordinal)?.select() + setAdapter() + } + + private fun tabChanged(index: Int) { + this.currentTab = Tab.values()[index] + setAdapter() + } + + private fun setAdapter() { + when (this.currentTab) { + Tab.SESSIONS -> menuRecyclerView.adapter = feedSessionAdapter + Tab.TRANSACTIONS -> menuRecyclerView.adapter = feedTransactionAdapter + } } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt index 7fe858e8..289a8c91 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt @@ -18,8 +18,6 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.fragment.components.RealmFragment -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment -import net.pokeranalytics.android.ui.helpers.DateTimePickerManager import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow @@ -65,35 +63,7 @@ open class FilterDetailsFragment : RealmFragment(), StaticRowRepresentableDataSo updateRowsSelection(row) return } - - when (row) { - is QueryCondition.DateQuery -> DateTimePickerManager.create( - requireContext(), - row, - this, - row.singleValue, - onlyDate = !row.showTime, - onlyTime = row.showTime - ) - is QueryCondition.Duration -> { - var hours: String? = null - var minutes: String? = null - row.minutes?.let { - hours = if (it / 60 > 0) (it / 60).toString() else null - minutes = if (it % 60 > 0) (it % 60).toString() else null - } - val data = row.editingDescriptors(mapOf("hours" to hours, "minutes" to minutes)) - BottomSheetFragment.create(fragmentManager, row, this, data, true) - } - is QueryCondition.ListOfValues<*> -> { - var valueAsString: String? = null - row.listOfValues.firstOrNull()?.let { - valueAsString = row.listOfValues.firstOrNull()?.toString() - } - val data = row.editingDescriptors(mapOf("valueAsString" to valueAsString)) - BottomSheetFragment.create(fragmentManager, row, this, data, true) - } - } + row.startEditing(null, this) } override fun stringForRow(row: RowRepresentable, context: Context): String { @@ -243,12 +213,10 @@ open class FilterDetailsFragment : RealmFragment(), StaticRowRepresentableDataSo Timber.d("Selected rows: $it") } - val realm = getRealm() - realm.beginTransaction() - currentFilter?.remove(filterCategoryRow) - currentFilter?.createOrUpdateFilterConditions(selectedRows) - realm.commitTransaction() - + getRealm().executeTransaction { + currentFilter?.remove(filterCategoryRow) + currentFilter?.createOrUpdateFilterConditions(selectedRows) + } currentFilter?.filterConditions?.forEach { Timber.d("Condition: $it") } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt index eb36582f..c04d3e02 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt @@ -12,11 +12,12 @@ import kotlinx.android.synthetic.main.fragment_editable_data.recyclerView import kotlinx.android.synthetic.main.fragment_filters.* import kotlinx.android.synthetic.main.fragment_filters.view.toolbar import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.realm.Filter -import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.activity.FilterDetailsActivity import net.pokeranalytics.android.ui.activity.FiltersActivity +import net.pokeranalytics.android.ui.activity.FiltersListActivity import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource @@ -27,6 +28,7 @@ import net.pokeranalytics.android.ui.interfaces.FilterableType import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow import net.pokeranalytics.android.util.Preferences +import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.sorted import timber.log.Timber @@ -54,6 +56,14 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource, private var isUpdating = false private var showMostUsedFiltersLayout = true + /** + * Set fragment data + */ + fun setData(primaryKey: String?, filterableType: FilterableType) { + this.primaryKey = primaryKey + this.filterableType = filterableType + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) return inflater.inflate(R.layout.fragment_filters, container, false) @@ -159,7 +169,7 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource, moreFilters.setOnClickListener { LiveData.FILTER.subType = filterableType.uniqueIdentifier - DataListActivity.newSelectInstance(this, LiveData.FILTER.ordinal, false) + FiltersListActivity.newSelectInstance(this, LiveData.FILTER.ordinal, false) } mostUsedFiltersLayout.isVisible = showMostUsedFiltersLayout @@ -173,7 +183,9 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource, val realm = getRealm() primaryKey?.let { - currentFilter = realm.copyFromRealm(Filter.getFilterBydId(realm, it)) + + val filter = realm.findById(it) ?: throw PAIllegalStateException("Can't find filter with id=$it") + currentFilter = realm.copyFromRealm(filter) isUpdating = true } ?: run { currentFilter = Filter.newInstance(this.filterableType.uniqueIdentifier) //realm.copyFromRealm(Filter.newInstanceForResult(realm, this.filterableType.ordinal)) @@ -251,13 +263,12 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource, * Validate the updates of the queryWith */ private fun validateUpdates() { - val realm = getRealm() - realm.beginTransaction() - currentFilter?.let { - it.name = it.query.getName(requireContext()) - realm.copyToRealmOrUpdate(it) - } - realm.commitTransaction() + getRealm().executeTransaction { realm -> + currentFilter?.let { + it.name = it.query.getName(requireContext()) + realm.copyToRealmOrUpdate(it) + } + } val filterId = currentFilter?.id ?: "" finishActivityWithResult(filterId) @@ -268,13 +279,12 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource, */ private fun cancelUpdates() { val filterId = filterCopy?.id ?: "" - val realm = getRealm() - realm.beginTransaction() - filterCopy?.let { - realm.copyToRealmOrUpdate(it) - } - realm.commitTransaction() - finishActivityWithResult(filterId) + getRealm().executeTransaction { realm -> + filterCopy?.let { + realm.copyToRealmOrUpdate(it) + } + } + finishActivityWithResult(filterId) } /** @@ -287,14 +297,6 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource, activity?.finish() } - /** - * Set fragment data - */ - fun setData(primaryKey: String?, filterableType: FilterableType) { - this.primaryKey = primaryKey - this.filterableType = filterableType - } - /** * Update the most used filters visibility */ diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersListFragment.kt new file mode 100644 index 00000000..8388b964 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersListFragment.kt @@ -0,0 +1,112 @@ +package net.pokeranalytics.android.ui.fragment + +import android.app.Activity +import android.content.Context +import android.content.Intent +import io.realm.RealmResults +import net.pokeranalytics.android.model.LiveData +import net.pokeranalytics.android.model.interfaces.Deletable +import net.pokeranalytics.android.model.interfaces.Identifiable +import net.pokeranalytics.android.model.realm.Filter +import net.pokeranalytics.android.ui.activity.EditableDataActivity +import net.pokeranalytics.android.ui.activity.FiltersActivity +import net.pokeranalytics.android.ui.interfaces.FilterHandler.Companion.INTENT_FILTER_UPDATE_FILTER_UI +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.util.Preferences +import timber.log.Timber + + +open class FiltersListFragment : DataListFragment() { + + private var identifiableClass: Class = Filter::class.java + private var dataType: LiveData = LiveData.FILTER + private lateinit var items: RealmResults + + /** + * Set fragment data + */ + override fun setData(dataType: Int) { + super.setData(dataType) + + this.dataType = LiveData.FILTER + this.identifiableClass = Filter::class.java + setToolbarTitle(this.dataType.pluralLocalizedTitle(requireContext())) + this.items = this.retrieveItems(getRealm()) as RealmResults + } + + override fun rowRepresentableForPosition(position: Int): RowRepresentable? { + Timber.d("rowRepresentableForPosition: ${this.items[position] as RowRepresentable}") + return this.items[position] as RowRepresentable + } + + override fun numberOfRows(): Int { + return this.items.size + } + + override fun adapterRows(): List? { + return items + } + + override fun viewTypeForPosition(position: Int): Int { + val viewType = (this.items[position] as RowRepresentable).viewType + return if (viewType != -1) viewType else RowViewType.DATA.ordinal + } + + override fun onRowValueChanged(value: Any?, row: RowRepresentable) { + when (row) { + is Filter -> { + row.updateValue(value, row) + dataListAdapter.refreshRow(row) + updateFilterUIIfNecessary(requireContext(), row.id) + } + else -> super.onRowValueChanged(value, row) + } + } + + override fun onRowDeleted(row: RowRepresentable) { + when (row) { + is Filter -> { + val filterId = row.id + deleteItem(dataListAdapter, items, filterId) + if (filterId == Preferences.getActiveFilterId(requireContext())) { + Preferences.setActiveFilterId("", requireContext()) + updateFilterUIIfNecessary(requireContext(), "") + } + } + else -> super.onRowDeleted(row) + } + } + + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { + when (row) { + is Filter -> { + if (fromAction) { + row.startEditing(null, this) + } else { + val intent = Intent() + intent.putExtra(FiltersActivity.IntentKey.FILTER_ID.keyName, row.id) + activity?.setResult(Activity.RESULT_OK, intent) + activity?.finish() + } + } + else -> { + val identifier = (row as Identifiable).id + EditableDataActivity.newInstanceForResult(this, this.dataType, identifier, REQUEST_CODE_DETAILS) + } + } + } + + /** + * Update filter UI + */ + fun updateFilterUIIfNecessary(context: Context, filterId: String) { + if (filterId == Preferences.getActiveFilterId(context)) { + // Send broadcast + val intent = Intent() + intent.action = INTENT_FILTER_UPDATE_FILTER_UI + context.sendBroadcast(intent) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt index 72f7a605..b20a78cd 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt @@ -14,13 +14,15 @@ import com.github.mikephil.charting.listener.OnChartValueSelectedListener import kotlinx.android.synthetic.main.fragment_graph.* import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.model.interfaces.ObjectIdentifier import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.graph.AxisFormatting import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry -import net.pokeranalytics.android.ui.graph.ObjectIdentifier import net.pokeranalytics.android.ui.graph.setStyle import net.pokeranalytics.android.ui.view.LegendView import net.pokeranalytics.android.ui.view.MultiLineLegendView +import net.pokeranalytics.android.util.extensions.findById +import timber.log.Timber class GraphFragment : RealmFragment(), OnChartValueSelectedListener { @@ -150,8 +152,10 @@ class GraphFragment : RealmFragment(), OnChartValueSelectedListener { barChart.data = barData dataSets.firstOrNull()?.let { - val entry = it.getEntryForIndex(0) - this.chartView?.highlightValue(entry.x, 0) + if (it.entryCount > 0) { + val entry = it.getEntryForIndex(0) + this.chartView?.highlightValue(entry.x, 0) + } } } @@ -179,18 +183,23 @@ class GraphFragment : RealmFragment(), OnChartValueSelectedListener { val statEntry = when (entry.data) { is ObjectIdentifier -> { val identifier = entry.data as ObjectIdentifier - getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull() + getRealm().findById(identifier.clazz, identifier.id) } - is GraphUnderlyingEntry -> entry.data as GraphUnderlyingEntry? + is GraphUnderlyingEntry -> entry.data else -> null } - statEntry?.let { + if (statEntry is GraphUnderlyingEntry) { + val groupName = dataSet?.label ?: "" val color = dataSet?.color - val legendValue = it.legendValues(stat, entry, this.style, groupName, requireContext()) + val legendValue = statEntry.legendValues(stat, entry, this.style, groupName, requireContext()) this.legendView.setItemData(legendValue, color) + + } else { + Timber.w("Data $statEntry should implement GraphUnderlyingEntry") } + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt index 0727dd0d..7232cc85 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt @@ -4,27 +4,30 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import kotlinx.android.synthetic.main.fragment_import.* 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.ImportDelegate import net.pokeranalytics.android.util.csv.ImportException import timber.log.Timber import java.io.InputStream +import java.text.NumberFormat import java.util.* import kotlin.coroutines.CoroutineContext -class ImportFragment : RealmFragment() { +class ImportFragment : RealmFragment(), ImportDelegate { val coroutineContext: CoroutineContext get() = Dispatchers.Main private lateinit var filePath: String private lateinit var inputStream: InputStream + private lateinit var importer: CSVImporter fun setData(path: String) { this.filePath = path @@ -42,12 +45,34 @@ class ImportFragment : RealmFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + this.initUI() + this.startImport() } - fun startImport() { + private fun initUI() { + + this.imported.text = requireContext().getString(R.string.imported) + this.total.text = requireContext().getString(R.string.total) + + this.save.isEnabled = false + this.save.setOnClickListener { + this.end() + } + + this.cancel.setOnClickListener { + this.cancel() + this.end() + } + + } + + private fun startImport() { - var shouldDismissActivity = false +// var shouldDismissActivity = false + + this.importer = CSVImporter(inputStream) + this.importer.delegate = this GlobalScope.launch(coroutineContext) { @@ -56,10 +81,9 @@ class ImportFragment : RealmFragment() { Timber.d(">>> Start Import...") try { - val csv = CSVImporter(inputStream) - csv.start() + importer.start() } catch (e: ImportException) { - shouldDismissActivity = true +// shouldDismissActivity = true } val e = Date() val duration = (e.time - s.time) / 1000.0 @@ -68,17 +92,42 @@ class ImportFragment : RealmFragment() { } test.await() - if (shouldDismissActivity) { +// if (shouldDismissActivity) { +// +// activity?.let { +// it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) +// it.finish() +// } +// +// } else { +// } + importDidFinish() - activity?.let { - it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) - it.finish() - } + } - } + } - } + private fun cancel() { + this.importer.cancel(getRealm()) + } + + private fun importDidFinish() { + + this.save.isEnabled = true + + } + + private fun end() { + activity?.finish() + } + + val numberFormatter = NumberFormat.getNumberInstance() + + // ImportDelegate + override fun parsingCountUpdate(importedCount: Int, totalCount: Int) { + this.counter.text = this.numberFormatter.format(importedCount) + this.totalCounter.text = this.numberFormatter.format(totalCount) } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/MoreFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/MoreFragment.kt index 997ff82c..9eb52957 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/MoreFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/MoreFragment.kt @@ -9,6 +9,7 @@ import kotlinx.android.synthetic.main.fragment_more.* import net.pokeranalytics.android.R import net.pokeranalytics.android.ui.activity.BankrollActivity import net.pokeranalytics.android.ui.activity.SettingsActivity +import net.pokeranalytics.android.ui.activity.Top10Activity import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource @@ -62,6 +63,7 @@ class MoreFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, super.onRowSelected(position, row, fromAction) when(row) { MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext()) + MoreTabRow.TOP_10 -> Top10Activity.newInstance(requireContext()) MoreTabRow.SETTINGS -> SettingsActivity.newInstance(requireContext()) } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt index cb13262e..c159efe1 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt @@ -10,6 +10,8 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.recyclerview.widget.DiffUtil import kotlinx.android.synthetic.main.fragment_session.* import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.extensions.SessionState import net.pokeranalytics.android.model.extensions.getState @@ -21,8 +23,6 @@ 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.fragment.components.RealmFragment -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment -import net.pokeranalytics.android.ui.helpers.DateTimePickerManager import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentableDiffCallback import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager @@ -59,28 +59,41 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate { /** * Set fragment data */ - fun setData(isTournament: Boolean, sessionId: String) { + fun setData(isTournament: Boolean, sessionId: String? = null, duplicate: Boolean) { val realm = getRealm() - val sessionRealm = realm.findById(sessionId) - if (sessionRealm != null) { - currentSession = sessionRealm - sessionHasBeenCustomized = true - } else { - realm.beginTransaction() - currentSession = Session.newInstance(realm, isTournament) - FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, null, requireContext()) - realm.commitTransaction() - + if (sessionId != null) { + + val sessionRealm = realm.findById(sessionId) + if (sessionRealm != null) { + + if (duplicate) { // duplicate session + realm.executeTransaction { + val session = sessionRealm.duplicate() + currentSession = session + } + sessionHasBeenCustomized = false + } else { // show existing session + currentSession = sessionRealm + sessionHasBeenCustomized = true + } + } else { + throw PAIllegalStateException("Session cannot be null here, session id = $sessionId") + } + } else { // buildAndShow new session + realm.executeTransaction { executeRealm -> + currentSession = Session.newInstance(executeRealm, isTournament) + FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, null, requireContext()) + } // Find the nearest location around the user parentActivity?.findNearestLocation { it?.let { location -> - realm.beginTransaction() - val realmLocation = realm.findById(location.id) - FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, realmLocation, requireContext()) + realm.executeTransaction { executeRealm -> + val realmLocation = executeRealm.findById(location.id) + FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, realmLocation, requireContext()) - currentSession.location = realmLocation - realm.commitTransaction() + currentSession.location = realmLocation + } updateSessionUI(true) } } @@ -143,37 +156,14 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate { Toast.makeText(requireContext(), "Action for row: $row", Toast.LENGTH_SHORT).show() return } - - val data = currentSession.editDescriptors(row) - when (row) { - SessionRow.START_DATE -> DateTimePickerManager.create(requireContext(), row, this, currentSession.startDate) - SessionRow.END_DATE -> { - - if (this.currentSession.startDate == null) { - Toast.makeText(context, R.string.session_missing_start_date, Toast.LENGTH_SHORT).show() - } else { - DateTimePickerManager.create( - requireContext(), - row, - this, - currentSession.endDate ?: currentSession.startDate ?: Date(), - currentSession.startDate - ) - } - - } - SessionRow.BANKROLL -> { - BottomSheetFragment.create(fragmentManager, row, this, data, false, currentSession.currency) - } - else -> BottomSheetFragment.create(fragmentManager, row, this, data, currentCurrency = currentSession.currency) - } + row.startEditing(currentSession, this) } override fun onRowValueChanged(value: Any?, row: RowRepresentable) { sessionHasBeenCustomized = true try { currentSession.updateValue(value, row) - } catch (e: IllegalStateException) { + } catch (e: PAIllegalStateException) { Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() return } @@ -345,6 +335,9 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate { * Delete a session */ private fun deleteSession() { + currentSession.bankroll?.id?.let { id -> + BankrollReportManager.notifyBankrollReportImpact(id) + } currentSession.delete() activity?.finish() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt index 125fac2a..1f679742 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt @@ -13,11 +13,10 @@ import io.realm.Realm import kotlinx.android.synthetic.main.fragment_settings.* import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.Currency import net.pokeranalytics.android.model.realm.Session -import net.pokeranalytics.android.ui.activity.BillingActivity -import net.pokeranalytics.android.ui.activity.CurrenciesActivity -import net.pokeranalytics.android.ui.activity.DataListActivity -import net.pokeranalytics.android.ui.activity.GDPRActivity +import net.pokeranalytics.android.ui.activity.* +import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource @@ -57,7 +56,6 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta rows } - const val REQUEST_CODE_CURRENCY: Int = 100 } private lateinit var settingsAdapterRow: RowRepresentableAdapter @@ -73,17 +71,29 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == REQUEST_CODE_CURRENCY && resultCode == Activity.RESULT_OK) { - data?.let { - Preferences.setCurrencyCode(data.getStringExtra(CurrenciesFragment.INTENT_CURRENCY_CODE), requireContext()) - val realm = Realm.getDefaultInstance() - realm.executeTransaction { - it.where(Session::class.java).isNull("bankroll.currency.code").findAll().forEach { - it.bankrollHasBeenUpdated() - } - } - realm.close() - settingsAdapterRow.refreshRow(SettingRow.CURRENCY) + + when (requestCode) { + RequestCode.CURRENCY.value -> { + if (resultCode == Activity.RESULT_OK) { + data?.let { + Preferences.setCurrencyCode(data.getStringExtra(CurrenciesFragment.INTENT_CURRENCY_CODE), requireContext()) + val realm = Realm.getDefaultInstance() + realm.executeTransaction { + realm.where(Currency::class.java).isNull("code").or().equalTo("code", UserDefaults.currency.currencyCode).findAll().forEach { currency -> + currency.rate = Currency.DEFAULT_RATE + } + + realm.where(Session::class.java).isNull("bankroll.currency.code").findAll().forEach { session -> + session.bankrollHasBeenUpdated() + } + } + realm.close() + settingsAdapterRow.refreshRow(SettingRow.CURRENCY) + } + } + } + RequestCode.SUBSCRIPTION.value -> { + settingsAdapterRow.refreshRow(SettingRow.SUBSCRIPTION) } } } @@ -103,9 +113,11 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { when (row) { + SettingRow.BANKROLL_REPORT -> BankrollActivity.newInstance(requireContext()) + SettingRow.TOP_10 -> Top10Activity.newInstance(requireContext()) SettingRow.SUBSCRIPTION -> { if (!AppGuard.isProUser) { - BillingActivity.newInstance(requireContext()) + BillingActivity.newInstanceForResult(this, false) } else { this.openPlaystoreAccount() } @@ -113,7 +125,7 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta SettingRow.RATE_APP -> parentActivity?.openPlayStorePage() SettingRow.CONTACT_US -> parentActivity?.openContactMail(R.string.contact) SettingRow.BUG_REPORT -> parentActivity?.openContactMail(R.string.bug_report_subject, Realm.getDefaultInstance().path) - SettingRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@SettingsFragment, REQUEST_CODE_CURRENCY) + SettingRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@SettingsFragment, RequestCode.CURRENCY.value) SettingRow.FOLLOW_US -> { when (position) { 0 -> parentActivity?.openUrl(URL.BLOG.value) @@ -137,6 +149,8 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta */ private fun initUI() { + setToolbarTitle(getString(R.string.more)) + setDisplayHomeAsUpEnabled(true) val viewManager = LinearLayoutManager(requireContext()) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt index dfcc828a..f2d83208 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt @@ -8,6 +8,7 @@ import android.view.View import android.view.ViewGroup import io.realm.Realm import io.realm.RealmModel +import io.realm.RealmResults import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async @@ -32,7 +33,7 @@ import kotlin.coroutines.CoroutineContext class StatisticsFragment : FilterableFragment() { - val coroutineContext: CoroutineContext + private val coroutineContext: CoroutineContext get() = Dispatchers.Main private lateinit var tableReportFragment: ComposableTableReportFragment @@ -74,7 +75,7 @@ class StatisticsFragment : FilterableFragment() { override val observedEntities: List> = listOf(ComputableResult::class.java) - override fun entitiesChanged(clazz: Class) { + override fun entitiesChanged(clazz: Class, results: RealmResults) { this.launchStatComputation() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt index 3f26b19d..c83dd78f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt @@ -1,6 +1,9 @@ package net.pokeranalytics.android.ui.fragment +import android.content.Context import android.graphics.drawable.GradientDrawable +import android.net.ConnectivityManager +import android.net.NetworkInfo import android.os.Build import android.os.Bundle import android.text.SpannableStringBuilder @@ -24,6 +27,7 @@ import com.android.billingclient.api.SkuDetails import com.android.billingclient.api.SkuDetailsResponseListener import kotlinx.android.synthetic.main.fragment_subscription.* import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.ui.extensions.px import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.ScreenSlidePageFragment @@ -42,10 +46,20 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene private var pagerAdapter: ScreenSlidePagerAdapter? = null private var selectedProduct: SkuDetails? = null + private var showSessionMessage = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val cm = requireContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetwork: NetworkInfo? = cm.activeNetworkInfo + val isConnected: Boolean = activeNetwork?.isConnected == true + + if (!isConnected) { + Toast.makeText(requireContext(), R.string.billingclient_unavailable, Toast.LENGTH_LONG).show() + return + } + this.showLoader(R.string.loading_please_wait) if (!AppGuard.requestProducts(this)) { this.hideLoader() @@ -53,6 +67,10 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene } } + fun setData(showSessionMessage: Boolean) { + this.showSessionMessage = showSessionMessage + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_subscription, container, false) } @@ -88,6 +106,10 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene this.title.text = upgradeString } + if (showSessionMessage) { + this.message.text = getString(R.string.iap_session_message) + } + // Pager // The pager adapter, which provides the pages to the view pager widget. @@ -101,7 +123,7 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene this.selectedProduct?.let { AppGuard.initiatePurchase(this.requireActivity(), it, this) } ?: run { - throw IllegalStateException("Attempt to initiate purchase while no product has been chosen") + throw PAIllegalStateException("Attempt to initiate purchase while no product has been chosen") } } @@ -109,7 +131,7 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene for (i in 1..count) { val view = View(requireContext()) view.background = requireContext().getDrawable(R.drawable.circle_green) - val layoutParam = LinearLayout.LayoutParams(10.px, 10.px) + val layoutParam = LinearLayout.LayoutParams(8.px, 8.px) layoutParam.setMargins(6.px) this.pageIndicator.addView(view, layoutParam) } @@ -201,12 +223,8 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { - pagerAdapter?.getFragment(position)?.let { - it.updateViewsPosition(-positionOffset * parallax) - } - pagerAdapter?.getFragment(position + 1)?.let { - it.updateViewsPosition((1 - positionOffset) * parallax) - } + pagerAdapter?.getFragment(position)?.updateViewsPosition(-positionOffset * parallax) + pagerAdapter?.getFragment(position + 1)?.updateViewsPosition((1 - positionOffset) * parallax) } override fun onPageSelected(position: Int) { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/Top10Fragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/Top10Fragment.kt new file mode 100644 index 00000000..cd722bf1 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/Top10Fragment.kt @@ -0,0 +1,175 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.android.material.tabs.TabLayout +import io.realm.RealmResults +import io.realm.kotlin.where +import kotlinx.android.synthetic.main.fragment_top_10.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.Session +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.RealmFragment +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager + + +class Top10Fragment : RealmFragment(), RowRepresentableDataSource, RowRepresentableDelegate { + + private enum class Tab { + CASH_GAMES, + TOURNAMENTS + } + + companion object { + fun newInstance(): Top10Fragment { + val fragment = Top10Fragment() + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + } + + private lateinit var positiveSessions: RealmResults + private lateinit var dataListAdapter: RowRepresentableAdapter + + private var realmCashGames: List = mutableListOf() + private var realmTournaments: List = mutableListOf() + + private var currentTab: Tab = Tab.CASH_GAMES + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + super.onCreateView(inflater, container, savedInstanceState) + return inflater.inflate(R.layout.fragment_top_10, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initUI() + initData() + } + + /** + * Init UI + */ + private fun initUI() { + + dataListAdapter = RowRepresentableAdapter(this, this) + recyclerView.adapter = dataListAdapter + + setDisplayHomeAsUpEnabled(true) + + tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + when (tab.position) { + 0 -> { + currentTab = Tab.CASH_GAMES + dataListAdapter.notifyDataSetChanged() + } + 1 -> { + currentTab = Tab.TOURNAMENTS + } + } + dataListAdapter.notifyDataSetChanged() + } + + override fun onTabUnselected(tab: TabLayout.Tab) { + } + + override fun onTabReselected(tab: TabLayout.Tab) { + } + }) + + + val viewManager = SmoothScrollLinearLayoutManager(requireContext()) + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + } + + } + + /** + * Init data + */ + private fun initData() { + + this.positiveSessions = getRealm().where() + .isNotNull("endDate") + .greaterThan("result.net", 0.0) + .findAll() + + updateTop10() + + } + + private fun updateTop10() { + + val cashGames = mutableListOf() + val tournaments = mutableListOf() + + // filter by type: cash game or tournament + this.positiveSessions.forEach { + when (it.type) { + Session.Type.CASH_GAME.ordinal -> { + cashGames.add(it) + } + else -> { + tournaments.add(it) + } + } + } + + // Sort by rated net + val sortedCashGames = cashGames.sortedByDescending { + it.computableResult?.ratedNet + }.toMutableList() + val sortedTournaments = tournaments.sortedByDescending { + it.computableResult?.ratedNet + }.toMutableList() + + // Keep 10 items + if (sortedCashGames.size > 10) { + sortedCashGames.subList(10, sortedCashGames.size).clear() + } + if (sortedTournaments.size > 10) { + sortedTournaments.subList(10, sortedTournaments.size).clear() + } + + this.realmCashGames = sortedCashGames + this.realmTournaments = sortedTournaments + + dataListAdapter.notifyDataSetChanged() + } + + override fun adapterRows(): List? { + return when (currentTab) { + Tab.CASH_GAMES -> realmCashGames + Tab.TOURNAMENTS -> realmTournaments + } + } + + override fun rowRepresentableForPosition(position: Int): RowRepresentable? { + return when (currentTab) { + Tab.CASH_GAMES -> realmCashGames[position] + Tab.TOURNAMENTS -> realmTournaments[position] + } + } + + override fun numberOfRows(): Int { + return when (currentTab) { + Tab.CASH_GAMES -> realmCashGames.size + Tab.TOURNAMENTS -> realmTournaments.size + } + } + + override fun viewTypeForPosition(position: Int): Int { + return RowViewType.ROW_TOP_10.ordinal + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt index 232a7d46..5a7de0f4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter @@ -77,7 +78,7 @@ abstract class DeletableItemFragment : RealmFragment() { this.dataListAdapter = dataListAdapter - // Save the delete position & create a copy of the object + // Save the delete position & buildAndShow a copy of the object val itemPosition = items.indexOfFirst { it.id == itemId } val itemToDelete = items.find { it.id == itemId } @@ -88,11 +89,11 @@ abstract class DeletableItemFragment : RealmFragment() { deletedItem = getRealm().copyFromRealm(itemToDelete) lastDeletedItemPosition = itemPosition getRealm().executeTransaction { - itemToDelete.deleteDependencies() + itemToDelete.deleteDependencies(it) itemToDelete.deleteFromRealm() } itemHasBeenReInserted = false - updateUIAfterDeletion(itemPosition) + updateUIAfterDeletion(itemId, itemPosition) showUndoSnackBar() } else { dataListAdapter.notifyItemChanged(itemPosition) @@ -126,14 +127,14 @@ abstract class DeletableItemFragment : RealmFragment() { } snackBar?.show() } ?: run { - throw IllegalStateException("mainLayout is not defined") + throw PAIllegalStateException("mainLayout is not defined") } } /** * Called once the object has been deleted */ - open fun updateUIAfterDeletion(itemPosition: Int) { + open fun updateUIAfterDeletion(itemId: String, itemPosition: Int) { dataListAdapter.notifyItemRemoved(itemPosition) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt index 8c583927..fc818918 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup import io.realm.Realm import io.realm.RealmModel import io.realm.RealmResults +import timber.log.Timber open class RealmFragment : PokerAnalyticsFragment() { @@ -26,8 +27,8 @@ open class RealmFragment : PokerAnalyticsFragment() { this.observedEntities.forEach { val realmResults = realm.where(it).findAll() - realmResults.addChangeListener { _,_ -> - this.entitiesChanged(it) + realmResults.addChangeListener { t, _ -> + this.entitiesChanged(it, t) } this.observedRealmResults.add(realmResults) @@ -61,6 +62,6 @@ open class RealmFragment : PokerAnalyticsFragment() { /** * The method called when a change happened in any RealmResults */ - open fun entitiesChanged(clazz: Class) {} + open fun entitiesChanged(clazz: Class, results: RealmResults) {} } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetType.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetType.kt deleted file mode 100644 index 447453b0..00000000 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetType.kt +++ /dev/null @@ -1,48 +0,0 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet - -enum class BottomSheetType { - NONE, - LIST, - LIST_STATIC, - LIST_GAME, - DOUBLE_LIST, - MULTI_SELECTION, - GRID, - EDIT_TEXT, - EDIT_TEXT_MULTI_LINES, - DOUBLE_EDIT_TEXT, - NUMERIC_TEXT, - SUM; - - fun newInstance(): BottomSheetFragment { - return when (this) { - NONE -> BottomSheetFragment() - LIST -> BottomSheetListFragment() - LIST_STATIC -> BottomSheetStaticListFragment() - LIST_GAME -> BottomSheetListGameFragment() - DOUBLE_LIST -> BottomSheetListGameFragment() - MULTI_SELECTION -> BottomSheetMultiSelectionFragment() - GRID -> BottomSheetTableSizeGridFragment() - EDIT_TEXT -> BottomSheetEditTextFragment() - EDIT_TEXT_MULTI_LINES -> BottomSheetEditTextMultiLinesFragment() - DOUBLE_EDIT_TEXT -> BottomSheetDoubleEditTextFragment() - NUMERIC_TEXT -> BottomSheetNumericTextFragment() - SUM -> BottomSheetSumFragment() - } - } - - val validationRequired: Boolean - get() = when (this) { - LIST, LIST_GAME, LIST_STATIC, GRID, DOUBLE_LIST -> false - else -> true - } - - val clearRequired: Boolean - get() = true - - val addRequired: Boolean - get() = when (this) { - EDIT_TEXT, NUMERIC_TEXT, DOUBLE_EDIT_TEXT, EDIT_TEXT_MULTI_LINES, GRID, LIST_STATIC, SUM -> false - else -> true - } -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetDoubleEditTextFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputDoubleEditTextFragment.kt similarity index 70% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetDoubleEditTextFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputDoubleEditTextFragment.kt index 74c8d6b8..cbf8b0f0 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetDoubleEditTextFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputDoubleEditTextFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.os.Bundle import android.text.InputType @@ -10,11 +10,12 @@ import kotlinx.android.synthetic.main.bottom_sheet_double_edit_text.* import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException +import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow import net.pokeranalytics.android.util.extensions.round -class BottomSheetDoubleEditTextFragment : BottomSheetFragment() { +class InputDoubleEditTextFragment(row: RowRepresentable) : InputFragment(row) { private val values = ArrayList() private var isEditingBlinds: Boolean = false @@ -30,11 +31,13 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() { if (isEditingBlinds) { editText2.requestFocus() } else { - editText1.requestFocus() + editText.requestFocus() } } override fun getValue(): Any? { + if (values.isEmpty()) { return null } + if (values.all { it.isEmpty() }) { return null } return values } @@ -42,7 +45,6 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() { * Init data */ private fun initData() { - valueHasPlaceholder = true isEditingBlinds = row == SessionRow.BLINDS } @@ -50,9 +52,9 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() { * Init UI */ private fun initUI() { - val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (data.size != 2) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } values.add(0, "") @@ -64,20 +66,20 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() { values[0] = (data[0].defaultValue ?: "").toString() values[1] = (data[1].defaultValue ?: "").toString() - data[0].hint?.let { editText1.hint = getString(it) } - editText1.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + data[0].hint?.let { editText.hint = getString(it) } + editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES data[1].hint?.let { editText2.hint = getString(it) } editText2.inputType = data[1].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES if (valueHasPlaceholder) { - editText1.hint = values[0] - editText2.hint = values[1] + if (values[0].isNotBlank()) { editText.hint = values[0] } + if (values[1].isNotBlank()) { editText2.hint = values[1] } } else { - editText1.setText(values[0]) + editText.setText(values[0]) editText2.setText(values[1]) } - editText1.addTextChangedListener { + editText.addTextChangedListener { values[0] = it?.toString() ?: "" } @@ -86,9 +88,9 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() { if (isEditingBlinds) { try { val bigBlind = values[1].toDouble() - editText1.setText((bigBlind / 2.0).round()) + editText.setText((bigBlind / 2.0).round()) } catch (e: Exception) { - editText1.setText("") + editText.setText("") } } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputEditTextFragment.kt similarity index 69% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputEditTextFragment.kt index 537da59b..68f32b3f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputEditTextFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.os.Bundle import android.text.InputType @@ -10,9 +10,10 @@ import kotlinx.android.synthetic.main.bottom_sheet_edit_text.* import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException +import net.pokeranalytics.android.ui.view.RowRepresentable -class BottomSheetEditTextFragment : BottomSheetFragment() { +class InputEditTextFragment(row: RowRepresentable) : InputFragment(row) { private var value: String? = null @@ -24,7 +25,7 @@ class BottomSheetEditTextFragment : BottomSheetFragment() { override fun onStart() { super.onStart() - editText1.requestFocus() + editText.requestFocus() } override fun getValue(): Any? { @@ -43,7 +44,6 @@ class BottomSheetEditTextFragment : BottomSheetFragment() { * Init data */ private fun initData() { - valueHasPlaceholder = true } /** @@ -51,28 +51,28 @@ class BottomSheetEditTextFragment : BottomSheetFragment() { */ private fun initUI() { - val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (data.size != 1) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_edit_text, view?.bottomSheetContainer, true) - data[0].hint?.let { editText1.hint = getString(it) } - editText1.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES - editText1.addTextChangedListener { + data[0].hint?.let { editText.hint = getString(it) } + editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + editText.addTextChangedListener { this.value = it?.toString() } data[0].defaultValue?.let { if (valueHasPlaceholder) { this.value = it.toString() - editText1.hint = it.toString() + editText.hint = it.toString() } else { - editText1.setText(it.toString()) + editText.setText(it.toString()) } } - editText1.setOnEditorActionListener { _, actionId, _ -> + editText.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_DONE) { delegate.onRowValueChanged(getValue(), row) dismiss() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextMultiLinesFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputEditTextMultiLinesFragment.kt similarity index 62% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextMultiLinesFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputEditTextMultiLinesFragment.kt index 1c929b6b..30b1a32a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextMultiLinesFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputEditTextMultiLinesFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.os.Bundle import android.text.InputType @@ -8,9 +8,10 @@ import androidx.core.widget.addTextChangedListener import kotlinx.android.synthetic.main.bottom_sheet_edit_text_multi_lines.* import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException +import net.pokeranalytics.android.ui.view.RowRepresentable -class BottomSheetEditTextMultiLinesFragment : BottomSheetFragment() { +class InputEditTextMultiLinesFragment(row: RowRepresentable) : InputFragment(row) { private var value: String? = null @@ -22,7 +23,7 @@ class BottomSheetEditTextMultiLinesFragment : BottomSheetFragment() { override fun onStart() { super.onStart() - editText1.requestFocus() + editText.requestFocus() } override fun getValue(): Any? { @@ -42,18 +43,18 @@ class BottomSheetEditTextMultiLinesFragment : BottomSheetFragment() { * Init UI */ private fun initUI() { - val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (data.size != 1) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } LayoutInflater.from(requireContext()).inflate(net.pokeranalytics.android.R.layout.bottom_sheet_edit_text_multi_lines, view?.bottomSheetContainer, true) - data[0].hint?.let { editText1.hint = getString(it) } - editText1.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES - editText1.addTextChangedListener { this.value = it?.toString() } + data[0].hint?.let { editText.hint = getString(it) } + editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + editText.addTextChangedListener { this.value = it?.toString() } data[0].defaultValue?.let { - editText1.setText(it.toString()) + editText.setText(it.toString()) } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputFragment.kt similarity index 68% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputFragment.kt index 8c33bf13..da27b369 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.annotation.SuppressLint import android.app.Activity.RESULT_OK @@ -10,57 +10,59 @@ import android.view.View import android.view.ViewGroup import android.view.WindowManager import androidx.appcompat.view.ContextThemeWrapper -import androidx.fragment.app.FragmentManager +import androidx.fragment.app.Fragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment import io.realm.RealmModel import kotlinx.android.synthetic.main.fragment_bottom_sheet.* import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException +import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.ui.activity.EditableDataActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity -import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate -import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor +import net.pokeranalytics.android.ui.adapter.RowEditableDelegate +import net.pokeranalytics.android.ui.helpers.DateTimePickerManager +import net.pokeranalytics.android.ui.view.* import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionRow import java.util.* -open class BottomSheetFragment : BottomSheetDialogFragment() { +open class InputFragment(val row: RowRepresentable) : BottomSheetDialogFragment() { - lateinit var row: RowRepresentable - lateinit var delegate: RowRepresentableDelegate +// lateinit var row: RowRepresentable + lateinit var delegate: RowEditableDelegate var currentCurrency: Currency? = null var valueHasPlaceholder: Boolean = false private var isClearable: Boolean = true private var isDeletable: Boolean = false - private var rowRepresentableEditDescriptors: ArrayList? = null + private var rowEditableDescriptors: ArrayList? = null companion object { const val REQUEST_CODE_ADD_NEW_OBJECT = 100 - fun create( - fragmentManager: FragmentManager?, - row: RowRepresentable, - delegate: RowRepresentableDelegate, - rowRepresentableEditDescriptors: ArrayList?, - isClearable: Boolean? = true, - currentCurrency: Currency? = null, - isDeletable: Boolean? = false, - valueHasPlaceholder: Boolean? = false - ): BottomSheetFragment { - val bottomSheetFragment = row.bottomSheetType.newInstance() - bottomSheetFragment.show(fragmentManager, "bottomSheet") - bottomSheetFragment.row = row - bottomSheetFragment.delegate = delegate - bottomSheetFragment.rowRepresentableEditDescriptors = rowRepresentableEditDescriptors - bottomSheetFragment.isClearable = isClearable ?: true - bottomSheetFragment.isDeletable = isDeletable ?: true - bottomSheetFragment.valueHasPlaceholder = valueHasPlaceholder ?: true - bottomSheetFragment.currentCurrency = currentCurrency - return bottomSheetFragment - } + fun buildAndShow( + row: RowRepresentable, + delegate: RowEditableDelegate, + dataSource: RowEditableDataSource?, + isDeletable: Boolean? = false, + valueHasPlaceholder: Boolean? = null + ) { + if (delegate !is Fragment) throw PokerAnalyticsException.InputFragmentException + if (dataSource?.descriptorType == RowEditableDescriptorType.DATE) { + DateTimePickerManager.buildAndShow(row, delegate, dataSource.descriptors.first()) + } else { + val bottomSheetFragment = row.inputFragmentType.newInstance(row) + bottomSheetFragment.show(delegate.fragmentManager, "bottomSheet") + bottomSheetFragment.delegate = delegate + bottomSheetFragment.rowEditableDescriptors = dataSource?.descriptors + bottomSheetFragment.isClearable = row.valueCanBeClearedWhenEditing + bottomSheetFragment.isDeletable = isDeletable ?: true + bottomSheetFragment.valueHasPlaceholder = valueHasPlaceholder ?: true + bottomSheetFragment.currentCurrency = dataSource?.currency + } + } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -107,9 +109,9 @@ open class BottomSheetFragment : BottomSheetDialogFragment() { bottomSheetToolbar.setOnMenuItemClickListener { false } - bottomSheetToolbar.menu.findItem(R.id.actionCheck).isVisible = row.bottomSheetType.validationRequired - bottomSheetToolbar.menu.findItem(R.id.actionClear).isVisible = row.bottomSheetType.clearRequired - bottomSheetToolbar.menu.findItem(R.id.actionAdd).isVisible = row.bottomSheetType.addRequired + bottomSheetToolbar.menu.findItem(R.id.actionCheck).isVisible = row.inputFragmentType.validationRequired + bottomSheetToolbar.menu.findItem(R.id.actionClear).isVisible = row.inputFragmentType.clearRequired + bottomSheetToolbar.menu.findItem(R.id.actionAdd).isVisible = row.inputFragmentType.addRequired // Menu bottomSheetToolbar.menu.findItem(R.id.actionClear).setOnMenuItemClickListener { @@ -132,7 +134,7 @@ open class BottomSheetFragment : BottomSheetDialogFragment() { SessionRow.TOURNAMENT_NAME -> LiveData.TOURNAMENT_NAME SessionRow.TOURNAMENT_FEATURE -> LiveData.TOURNAMENT_FEATURE TransactionRow.TYPE -> LiveData.TRANSACTION_TYPE - else -> throw IllegalStateException("row $row does not have an associated LiveData value") + else -> throw PAIllegalStateException("row $row does not have an associated LiveData value") } EditableDataActivity.newInstanceForResult( @@ -157,8 +159,8 @@ open class BottomSheetFragment : BottomSheetDialogFragment() { /** * Return the data list */ - fun getData(): ArrayList? { - return this.rowRepresentableEditDescriptors + fun getData(): ArrayList? { + return this.rowEditableDescriptors } open fun getValue(): Any? { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputFragmentType.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputFragmentType.kt new file mode 100644 index 00000000..bb37dc46 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputFragmentType.kt @@ -0,0 +1,50 @@ +package net.pokeranalytics.android.ui.fragment.components.input + +import net.pokeranalytics.android.ui.view.RowRepresentable + +enum class InputFragmentType { + NONE, + LIST, + LIST_STATIC, + LIST_GAME, + DOUBLE_LIST, + MULTI_SELECTION, + GRID, + EDIT_TEXT, + EDIT_TEXT_MULTI_LINES, + DOUBLE_EDIT_TEXT, + NUMERIC_TEXT, + SUM; + + fun newInstance(row: RowRepresentable): InputFragment { + return when (this) { + NONE -> InputFragment(row) + LIST -> InputListFragment(row) + LIST_STATIC -> InputStaticListFragment(row) + LIST_GAME -> InputListGameFragment(row) + DOUBLE_LIST -> InputListGameFragment(row) + MULTI_SELECTION -> InputMultiSelectionFragment(row) + GRID -> InputTableSizeGridFragment(row) + EDIT_TEXT -> InputEditTextFragment(row) + EDIT_TEXT_MULTI_LINES -> InputEditTextMultiLinesFragment(row) + DOUBLE_EDIT_TEXT -> InputDoubleEditTextFragment(row) + NUMERIC_TEXT -> InputNumericTextFragment(row) + SUM -> InputSumFragment(row) + } + } + + val validationRequired: Boolean + get() = when (this) { + LIST, LIST_GAME, LIST_STATIC, GRID, DOUBLE_LIST -> false + else -> true + } + + val clearRequired: Boolean + get() = true + + val addRequired: Boolean + get() = when (this) { + EDIT_TEXT, NUMERIC_TEXT, DOUBLE_EDIT_TEXT, EDIT_TEXT_MULTI_LINES, GRID, LIST_STATIC, SUM -> false + else -> true + } +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputListFragment.kt similarity index 71% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputListFragment.kt index eeece10e..6e3c8a8c 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputListFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.os.Bundle import android.view.LayoutInflater @@ -8,14 +8,16 @@ import io.realm.RealmResults import kotlinx.android.synthetic.main.bottom_sheet_list.* import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate +import net.pokeranalytics.android.ui.view.DataRowEditableDescriptor import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate { +open class InputListFragment(row: RowRepresentable) : InputFragment(row), LiveRowRepresentableDataSource, RowRepresentableDelegate { lateinit var dataAdapter: RowRepresentableAdapter var realmData: RealmResults? = null @@ -39,14 +41,14 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable realmData?.let { return it[position] as RowRepresentable } - throw IllegalStateException("Need to implement Data Source") + throw PAIllegalStateException("Need to implement Data Source") } override fun numberOfRows(): Int { realmData?.let { return it.size } - throw IllegalStateException("Need to implement Data Source") + throw PAIllegalStateException("Need to implement Data Source") } override fun viewTypeForPosition(position: Int): Int { @@ -68,14 +70,14 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable * Init data */ open fun initData() { - val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (bottomSheetData.size != 1) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } - if (bottomSheetData.first().data == null) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") - } - this.realmData = bottomSheetData.first().data as RealmResults + val dataList = bottomSheetData[0] + if (dataList !is DataRowEditableDescriptor) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } + if (dataList.data == null) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } + this.realmData = dataList.data } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputListGameFragment.kt similarity index 77% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputListGameFragment.kt index 9c3b7ca8..a53326ac 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputListGameFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.os.Bundle import android.view.LayoutInflater @@ -13,13 +13,14 @@ import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorExcep import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.extensions.px +import net.pokeranalytics.android.ui.view.DataRowEditableDescriptor import net.pokeranalytics.android.ui.view.RowRepresentable /** * Bottom Sheet List Game Fragment * Display a list of game + chips to choose the game limit */ -class BottomSheetListGameFragment : BottomSheetListFragment() { +class InputListGameFragment(row: RowRepresentable) : InputListFragment(row) { private var limit: Int? = 0 private val values = ArrayList() @@ -49,15 +50,15 @@ class BottomSheetListGameFragment : BottomSheetListFragment() { * Init data */ override fun initData() { - val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (bottomSheetData.size != 2) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") - } - if (bottomSheetData[1].data == null) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } + val dataList = bottomSheetData[1] + if (dataList !is DataRowEditableDescriptor) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } + if (dataList.data == null) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } + this.realmData = dataList.data this.limit = bottomSheetData[0].defaultValue as Int? - this.realmData = bottomSheetData[1].data } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetMultiSelectionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputMultiSelectionFragment.kt similarity index 88% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetMultiSelectionFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputMultiSelectionFragment.kt index 7bbe3c9e..7a8a659f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetMultiSelectionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputMultiSelectionFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.app.Activity import android.content.Intent @@ -14,7 +14,7 @@ import net.pokeranalytics.android.ui.view.RowViewType /** * Manage multiple items selection in a bottom sheet list */ -open class BottomSheetMultiSelectionFragment : BottomSheetListFragment() { +open class InputMultiSelectionFragment(row: RowRepresentable) : InputListFragment(row) { private val selectedRows: ArrayList = ArrayList() @@ -54,9 +54,9 @@ open class BottomSheetMultiSelectionFragment : BottomSheetListFragment() { override fun initData() { super.initData() val bottomSheetData = - getData() ?: throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + getData() ?: throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (bottomSheetData.size != 1) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } bottomSheetData.first().defaultValue?.let { (it as RealmList<*>).forEach { row -> diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetNumericTextFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputNumericTextFragment.kt similarity index 70% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetNumericTextFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputNumericTextFragment.kt index a5213f7b..5570fcf6 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetNumericTextFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputNumericTextFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.os.Bundle import android.text.InputType @@ -10,10 +10,11 @@ import kotlinx.android.synthetic.main.bottom_sheet_edit_text.* import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException +import net.pokeranalytics.android.ui.view.RowRepresentable import java.text.NumberFormat -class BottomSheetNumericTextFragment : BottomSheetFragment() { +class InputNumericTextFragment(row: RowRepresentable) : InputFragment(row) { private var value: Double? = null @@ -25,7 +26,7 @@ class BottomSheetNumericTextFragment : BottomSheetFragment() { override fun onStart() { super.onStart() - editText1.requestFocus() + editText.requestFocus() } override fun getValue(): Any? { @@ -36,26 +37,25 @@ class BottomSheetNumericTextFragment : BottomSheetFragment() { * Init data */ private fun initData() { - valueHasPlaceholder = true } /** * Init UI */ private fun initUI() { - val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (data.size != 1) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_edit_text, view?.bottomSheetContainer, true) - data[0].hint?.let { editText1.hint = getString(it) } - editText1.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES - editText1.addTextChangedListener { + data[0].hint?.let { editText.hint = getString(it) } + editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + editText.addTextChangedListener { this.value = try { - editText1.text.toString().toDouble() + editText.text.toString().toDouble() } catch (e: Exception) { null } @@ -73,13 +73,13 @@ class BottomSheetNumericTextFragment : BottomSheetFragment() { if (valueHasPlaceholder) { this.value = it.toString().toDoubleOrNull() - editText1.hint = valueString + editText.hint = valueString } else { - editText1.setText(valueString) + editText.setText(valueString) } } - editText1.setOnEditorActionListener { _, actionId, _ -> + editText.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_DONE) { delegate.onRowValueChanged(getValue(), row) dismiss() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetStaticListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputStaticListFragment.kt similarity index 70% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetStaticListFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputStaticListFragment.kt index 7af3f1da..37fa6c4c 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetStaticListFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputStaticListFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.os.Bundle import android.view.LayoutInflater @@ -12,8 +12,9 @@ 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.view.RowRepresentable +import net.pokeranalytics.android.ui.view.StaticDataRowEditableDescriptor -class BottomSheetStaticListFragment : BottomSheetFragment(), StaticRowRepresentableDataSource, +class InputStaticListFragment(row: RowRepresentable) : InputFragment(row), StaticRowRepresentableDataSource, RowRepresentableDelegate { private var staticRows: List = emptyList() @@ -44,14 +45,14 @@ class BottomSheetStaticListFragment : BottomSheetFragment(), StaticRowRepresenta * Init data */ private fun initData() { - val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (bottomSheetData.size != 1) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } - if (bottomSheetData.first().staticData == null) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") - } - this.staticRows = bottomSheetData.first().staticData as List + val dataList = bottomSheetData[0] + if (dataList !is StaticDataRowEditableDescriptor) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } + if (dataList.staticData == null) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } + this.staticRows= dataList.staticData as List } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetSumFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputSumFragment.kt similarity index 85% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetSumFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputSumFragment.kt index a672e2c3..bcd2b3b7 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetSumFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputSumFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.os.Bundle import android.text.InputType @@ -10,12 +10,13 @@ import kotlinx.android.synthetic.main.bottom_sheet_sum.* import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException +import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.util.extensions.round import net.pokeranalytics.android.util.extensions.toCurrency import java.text.NumberFormat -class BottomSheetSumFragment : BottomSheetFragment() { +class InputSumFragment(row: RowRepresentable) : InputFragment(row) { private var value = 0.0 private var currentDefaultValue = 0.0 @@ -28,7 +29,7 @@ class BottomSheetSumFragment : BottomSheetFragment() { override fun onStart() { super.onStart() - editText1.requestFocus() + editText.requestFocus() } override fun getValue(): Any? { @@ -49,9 +50,9 @@ class BottomSheetSumFragment : BottomSheetFragment() { * Init UI */ private fun initUI() { - val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (data.size != 5) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_sum, view?.bottomSheetContainer, true) @@ -98,11 +99,11 @@ class BottomSheetSumFragment : BottomSheetFragment() { } // First edit text - data[3].hint?.let { editText1.hint = getString(it) } - editText1.inputType = data[3].inputType ?: InputType.TYPE_CLASS_TEXT - editText1.addTextChangedListener { + data[3].hint?.let { editText.hint = getString(it) } + editText.inputType = data[3].inputType ?: InputType.TYPE_CLASS_TEXT + editText.addTextChangedListener { val valueToAdd = try { - editText1.text.toString().toDouble() + editText.text.toString().toDouble() } catch (e: Exception) { 0.0 } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetTableSizeGridFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputTableSizeGridFragment.kt similarity index 87% rename from app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetTableSizeGridFragment.kt rename to app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputTableSizeGridFragment.kt index 7c199c09..3fc34b44 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetTableSizeGridFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputTableSizeGridFragment.kt @@ -1,4 +1,4 @@ -package net.pokeranalytics.android.ui.fragment.components.bottomsheet +package net.pokeranalytics.android.ui.fragment.components.input import android.os.Bundle import android.view.LayoutInflater @@ -11,11 +11,11 @@ import net.pokeranalytics.android.model.TableSize 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.extensions.px import net.pokeranalytics.android.ui.view.GridSpacingItemDecoration import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.extensions.px -class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { +class InputTableSizeGridFragment(row: RowRepresentable) : InputFragment(row), StaticRowRepresentableDataSource, RowRepresentableDelegate { private lateinit var dataAdapter: RowRepresentableAdapter private var defaultSize: Int? = null @@ -39,9 +39,9 @@ class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowReprese * Init data */ private fun initData() { - val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") + val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found") if (bottomSheetData.size != 1) { - throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") + throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") } defaultSize = bottomSheetData.first().defaultValue as Int? } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/BankrollDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/BankrollDataFragment.kt index eb790102..677289d4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/BankrollDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/BankrollDataFragment.kt @@ -9,15 +9,14 @@ import net.pokeranalytics.android.api.CurrencyConverterApi import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.retrofit.CurrencyConverterValue import net.pokeranalytics.android.ui.activity.CurrenciesActivity +import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.fragment.CurrenciesFragment 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.BankrollRow import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable -import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.UserDefaults import net.pokeranalytics.android.util.extensions.toCurrency @@ -33,10 +32,6 @@ import java.util.* */ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataSource { - companion object { - const val REQUEST_CODE_CURRENCY: Int = 100 - } - // Return the item as a Bankroll object private val bankroll: Bankroll get() { @@ -67,7 +62,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_CODE_CURRENCY && resultCode == RESULT_OK) { + if (requestCode == RequestCode.CURRENCY.value && resultCode == RESULT_OK) { data?.let { val currencyCode = it.getStringExtra(CurrenciesFragment.INTENT_CURRENCY_CODE) onRowValueChanged(currencyCode, BankrollRow.CURRENCY) @@ -88,7 +83,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS override fun stringForRow(row: RowRepresentable): String { return when (row) { - SimpleRow.NAME -> if (bankroll.name.isNotEmpty()) bankroll.name else NULL_TEXT + BankrollRow.NAME -> if (bankroll.name.isNotEmpty()) bankroll.name else NULL_TEXT BankrollRow.CURRENCY -> { bankroll.currency?.code?.let { code -> Currency.getInstance(code).currencyCode @@ -119,24 +114,10 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return when (row) { - SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.bankroll.name)) - BankrollRow.INITIAL_VALUE -> { - row.editingDescriptors(mapOf("defaultValue" to this.bankroll.initialValue)) - } - BankrollRow.RATE -> { - val rate = this.bankroll.currency?.rate - row.editingDescriptors(mapOf("defaultValue" to rate)) - } - else -> null - } - } - override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { when (row) { BankrollRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@BankrollDataFragment, - REQUEST_CODE_CURRENCY + RequestCode.CURRENCY.value ) BankrollRow.REFRESH_RATE -> refreshRate() else -> super.onRowSelected(position, row, fromAction) @@ -146,9 +127,11 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS override fun onRowValueChanged(value: Any?, row: RowRepresentable) { super.onRowValueChanged(value, row) - // Clear the value when the currency has been updated - if (row == BankrollRow.CURRENCY) { - this.lastRefreshRateCall = 0 + when (row) { + BankrollRow.CURRENCY -> { // Clear the value when the currency has been updated + this.lastRefreshRateCall = 0 + this.rowRepresentableAdapter.notifyDataSetChanged() + } } updateAdapterUI() @@ -175,7 +158,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS private fun refreshRows() { rows.clear() - rows.add(SimpleRow.NAME) + rows.add(BankrollRow.NAME) rows.add(BankrollRow.LIVE) rows.add(BankrollRow.INITIAL_VALUE) rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency)) @@ -213,7 +196,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS } lastRefreshRateCall = System.currentTimeMillis() - val currenciesConverterValue = "${defaultCurrency.currencyCode}_${bankroll.currency?.code}" + val currenciesConverterValue = "${bankroll.currency?.code}_${defaultCurrency.currencyCode}" val call = CurrencyConverterApi.getApi(requireContext())?.convert(currenciesConverterValue) call?.enqueue(object : retrofit2.Callback> { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt index 768b8088..1651bcb9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt @@ -17,11 +17,8 @@ import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.extensions.ChipGroupExtension import net.pokeranalytics.android.ui.extensions.px import net.pokeranalytics.android.ui.extensions.showAlertDialog -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.rowrepresentable.CustomFieldRow -import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.util.NULL_TEXT import java.util.* @@ -122,7 +119,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa override fun stringForRow(row: RowRepresentable): String { return when (row) { - SimpleRow.NAME -> if (customField.name.isNotEmpty()) customField.name else NULL_TEXT + CustomFieldRow.NAME -> if (customField.name.isNotEmpty()) customField.name else NULL_TEXT else -> super.stringForRow(row) } } @@ -142,20 +139,19 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { + /* + override fun editDescriptors(row: RowRepresentable): ArrayList? { return when (row) { SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.customField.name)) is CustomFieldEntry -> row.editingDescriptors(mapOf("defaultValue" to row.value)) else -> null } } + */ override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { when (row) { - is CustomFieldEntry -> { - val data = customField.editDescriptors(row) - BottomSheetFragment.create(fragmentManager, row, this, data, isClearable = false, isDeletable = true) - } + is CustomFieldEntry -> row.startEditing(null, this) else -> super.onRowSelected(position, row, fromAction) } } @@ -187,10 +183,10 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa customField.deleteEntry(row) rowRepresentableAdapter.notifyDataSetChanged() }) - return + } else { + customField.deleteEntry(row) + rowRepresentableAdapter.notifyDataSetChanged() } - customField.deleteEntry(row) - rowRepresentableAdapter.notifyDataSetChanged() } } } @@ -271,4 +267,11 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa } } + override fun onDataSaved() { + super.onDataSaved() + + this.customField.cleanupEntries() + + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/DataManagerFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/DataManagerFragment.kt index a7ee9216..1234528b 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/DataManagerFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/DataManagerFragment.kt @@ -128,6 +128,8 @@ open class DataManagerFragment : RealmFragment() { */ protected open fun deleteData() { + this.willDeleteData() + val realm = this.getRealm() if (this.item.isValidForDelete(realm)) { @@ -145,6 +147,8 @@ open class DataManagerFragment : RealmFragment() { } } + open fun willDeleteData() { } + /** * Finish the activity with a result */ diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt index bbc9ec70..acc420d5 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt @@ -14,7 +14,6 @@ import net.pokeranalytics.android.model.interfaces.Editable 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.bottomsheet.BottomSheetFragment import net.pokeranalytics.android.ui.view.RowRepresentable @@ -62,7 +61,7 @@ open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegat } override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { - BottomSheetFragment.create(fragmentManager, row, this, getDataSource().editDescriptors(row)) + row.startEditing(this.item, this) } override fun onRowValueChanged(value: Any?, row: RowRepresentable) { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/LocationDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/LocationDataFragment.kt index 34853369..88418033 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/LocationDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/LocationDataFragment.kt @@ -8,11 +8,9 @@ import net.pokeranalytics.android.model.realm.Location import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource 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.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.LocationRow -import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.util.NULL_TEXT /** @@ -47,7 +45,7 @@ class LocationDataFragment : EditableDataFragment(), StaticRowRepresentableDataS } else if (locationActivated) { - // If we create a new location, we try to locate the user by default + // If we buildAndShow a new location, we try to locate the user by default isLookingForPlaces = true getSuggestionsPlaces() @@ -66,7 +64,7 @@ class LocationDataFragment : EditableDataFragment(), StaticRowRepresentableDataS override fun stringForRow(row: RowRepresentable): String { return when (row) { - SimpleRow.NAME -> if (location.name.isNotEmpty())location.name else NULL_TEXT + LocationRow.NAME -> if (location.name.isNotEmpty())location.name else NULL_TEXT else -> return super.stringForRow(row) } } @@ -78,13 +76,6 @@ class LocationDataFragment : EditableDataFragment(), StaticRowRepresentableDataS } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return when (row) { - SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.location.name)) - else -> null - } - } - override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { // If we click on a location row, save the location (and finish activity) placesForRows[row]?.place?.let { place -> @@ -128,7 +119,7 @@ class LocationDataFragment : EditableDataFragment(), StaticRowRepresentableDataS */ private fun refreshRows() { rows.clear() - rows.add(SimpleRow.NAME) + rows.add(LocationRow.NAME) rows.add(LocationRow.LOCATION_PERMISSION_SWITCH) // Add info row to explain why we need the location permission rows.add(CustomizableRowRepresentable(customViewType = RowViewType.INFO, resId = R.string.location_when_in_use_usage_description)) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt index 789c00ed..cf082993 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt @@ -6,20 +6,15 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import net.pokeranalytics.android.model.realm.Bankroll +import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager import net.pokeranalytics.android.model.realm.Transaction -import net.pokeranalytics.android.model.realm.TransactionType import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource -import net.pokeranalytics.android.ui.helpers.DateTimePickerManager import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionRow import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.extensions.round import net.pokeranalytics.android.util.extensions.shortDate -import net.pokeranalytics.android.util.extensions.sorted -import java.util.* /** * Custom EditableDataFragment to manage the Transaction data @@ -56,40 +51,6 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return when (row) { - TransactionRow.BANKROLL -> row.editingDescriptors( - mapOf( - "defaultValue" to this.transaction.bankroll, - "data" to getRealm().sorted() - ) - ) - TransactionRow.TYPE -> row.editingDescriptors( - mapOf( - "defaultValue" to this.transaction.type, - "data" to getRealm().sorted() - ) - ) - TransactionRow.AMOUNT -> row.editingDescriptors(mapOf("defaultValue" to (if (this.transaction.amount != 0.0) this.transaction.amount.round() else ""))) - TransactionRow.COMMENT -> row.editingDescriptors(mapOf("defaultValue" to this.transaction.comment)) - else -> super.editDescriptors(row) - } - } - - override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { - when (row) { - TransactionRow.DATE -> DateTimePickerManager.create( - requireContext(), - row, - this, - this.transaction.date, - onlyDate = true, - isClearable = false - ) - else -> super.onRowSelected(position, row, fromAction) - } - } - override fun onRowValueChanged(value: Any?, row: RowRepresentable) { super.onRowValueChanged(value, row) rowRepresentableAdapter.refreshRow(row) @@ -115,4 +76,11 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa } } + override fun willDeleteData() { + super.willDeleteData() + this.transaction.bankroll?.id?.let { id -> + BankrollReportManager.notifyBankrollReportImpact(id) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionTypeDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionTypeDataFragment.kt index a5000654..8131a057 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionTypeDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionTypeDataFragment.kt @@ -4,7 +4,6 @@ import net.pokeranalytics.android.model.realm.Transaction import net.pokeranalytics.android.model.realm.TransactionType import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionTypeRow @@ -47,10 +46,6 @@ class TransactionTypeDataFragment : EditableDataFragment(), RowRepresentableData } } - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return row.editingDescriptors(mapOf("defaultValue" to this.transactionType.name)) - } - override fun isEnabled(row: RowRepresentable): Boolean { return when (row) { TransactionTypeRow.TRANSACTION_ADDITIVE -> { @@ -65,8 +60,8 @@ class TransactionTypeDataFragment : EditableDataFragment(), RowRepresentableData TransactionTypeRow.TRANSACTION_ADDITIVE -> { val realm = getRealm() val useCount = realm.where(Transaction::class.java) - .equalTo("type.id", this.transactionType.id).findAll().size - return useCount == 0 + .equalTo("type.id", this.transactionType.id).count() + return useCount == 0L } else -> super.isSelectable(row) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt index 3addaa19..fa900fa2 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt @@ -9,6 +9,7 @@ import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import kotlinx.android.synthetic.main.fragment_progress_report.* import net.pokeranalytics.android.calculus.Report +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.CustomFieldCriteria import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.realm.ReportSetup @@ -91,7 +92,7 @@ abstract class AbstractReportFragment : DataManagerFragment() { dialog.show() - } ?: throw IllegalStateException("Activity cannot be null") + } ?: throw PAIllegalStateException("Activity cannot be null") } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComparisonReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComparisonReportFragment.kt index 5750e3c7..520f9a38 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComparisonReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComparisonReportFragment.kt @@ -27,9 +27,9 @@ class ComparisonReportFragment : AbstractReportFragment() { /** * Set data */ - fun setData(report: Report) { - this.setReport(report) - } +// fun setData(report: Report) { +// this.setReport(report) +// } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt index 04036972..aca25041 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt @@ -10,7 +10,11 @@ import io.realm.Realm import kotlinx.android.synthetic.main.fragment_composable_table_report.* import kotlinx.coroutines.* import net.pokeranalytics.android.R -import net.pokeranalytics.android.calculus.* +import net.pokeranalytics.android.calculus.Calculator +import net.pokeranalytics.android.calculus.ComputableGroup +import net.pokeranalytics.android.calculus.Report +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.ui.activity.components.ReportActivity import net.pokeranalytics.android.ui.adapter.DisplayDescriptor @@ -35,7 +39,7 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab private var rowRepresentables: ArrayList = ArrayList() - var statsAdapter: RowRepresentableAdapter? = null + private var statsAdapter: RowRepresentableAdapter? = null var report: Report? = null companion object { @@ -214,10 +218,9 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab hideLoader() report?.let { val title = stat.localizedTitle(requireContext()) - ReportActivity.newInstance(requireContext(), it, title, stat) - -// ProgressReportActivity.newInstanceForResult(requireContext(), stat, it, title = title) + } ?: run { + throw PAIllegalStateException("Report should never be null here") } } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt index 897ff3f9..18c37842 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt @@ -3,16 +3,11 @@ package net.pokeranalytics.android.ui.graph import android.content.Context import com.github.mikephil.charting.data.Entry import net.pokeranalytics.android.calculus.Stat -import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.ui.fragment.GraphFragment import net.pokeranalytics.android.ui.view.DefaultLegendValues import net.pokeranalytics.android.ui.view.LegendContent import net.pokeranalytics.android.util.TextFormat -class ObjectIdentifier(var id: String, var clazz: Class) { - -} - interface GraphUnderlyingEntry { fun entryTitle(context: Context): String @@ -27,16 +22,13 @@ interface GraphUnderlyingEntry { ): LegendContent { val leftName = stat.localizedTitle(context) - return when (stat) { - Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO, Stat.HOURLY_DURATION, Stat.AVERAGE_HOURLY_DURATION -> { - val totalStatValue = stat.format(entry.y.toDouble(), currency = null) - DefaultLegendValues(this.entryTitle(context), totalStatValue, leftName = leftName) - } - else -> { - val entryValue = this.formattedValue(stat) - val totalStatValue = stat.format(entry.y.toDouble(), currency = null) - DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName) - } + val totalStatValue = stat.format(entry.y.toDouble(), currency = null) + + return if (stat.legendHideRightValue) { + DefaultLegendValues(this.entryTitle(context), totalStatValue, leftName = leftName) + } else { + val entryValue = this.formattedValue(stat) + DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName) } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/helpers/DateTimePickerManager.kt b/app/src/main/java/net/pokeranalytics/android/ui/helpers/DateTimePickerManager.kt index 0e1d1e45..a50fd1bd 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/helpers/DateTimePickerManager.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/helpers/DateTimePickerManager.kt @@ -8,9 +8,11 @@ import android.text.format.DateFormat import android.widget.DatePicker import android.widget.TimePicker import android.widget.Toast +import androidx.fragment.app.Fragment import net.pokeranalytics.android.R -import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate -import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.exceptions.PokerAnalyticsException +import net.pokeranalytics.android.ui.adapter.RowEditableDelegate +import net.pokeranalytics.android.ui.view.* import java.util.* class DateTimePickerManager : DatePickerDialog.OnDateSetListener, @@ -19,7 +21,7 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener, private var context: Context? = null private lateinit var row: RowRepresentable - private lateinit var delegate: RowRepresentableDelegate + private lateinit var delegate: RowEditableDelegate private lateinit var calendar: Calendar private var minimumDate: Date? = null private var onlyDate: Boolean = false @@ -27,39 +29,34 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener, private var isClearable: Boolean = true companion object { - fun create( - context: Context, - row: RowRepresentable, - delegate: RowRepresentableDelegate, - date: Date?, - minimumDate: Date? = null, - onlyDate: Boolean? = false, - onlyTime: Boolean? = false, - isClearable: Boolean? = true - ): DateTimePickerManager { - - val calendar = Calendar.getInstance() - calendar.time = date ?: Date() - - val dateTimePickerManager = DateTimePickerManager() - dateTimePickerManager.context = context - dateTimePickerManager.row = row - dateTimePickerManager.delegate = delegate - dateTimePickerManager.calendar = calendar - dateTimePickerManager.minimumDate = minimumDate - dateTimePickerManager.onlyDate = onlyDate ?: false - dateTimePickerManager.onlyTime = onlyTime ?: false - dateTimePickerManager.isClearable = isClearable ?: true - - if (dateTimePickerManager.onlyTime) { - dateTimePickerManager.showTimePicker() - } else { - dateTimePickerManager.showDatePicker() - } - - return dateTimePickerManager - } - } + fun buildAndShow( + row: RowRepresentable, + delegate: RowEditableDelegate, + dataSource: RowEditableDescriptor + ) { + + if (delegate !is Fragment) throw PokerAnalyticsException.InputFragmentException + if (dataSource !is DateRowEditableDescriptor) throw PokerAnalyticsException.DateTimePickerException + val calendar = Calendar.getInstance() + calendar.time = dataSource.date ?: Date() + + val dateTimePickerManager = DateTimePickerManager() + dateTimePickerManager.context = delegate.context + dateTimePickerManager.row = row + dateTimePickerManager.delegate = delegate + dateTimePickerManager.calendar = calendar + dateTimePickerManager.minimumDate = dataSource.minimumDate + dateTimePickerManager.onlyDate = dataSource.onlyDate + dateTimePickerManager.onlyTime = dataSource.onlyTime + dateTimePickerManager.isClearable = row.valueCanBeClearedWhenEditing + + if (dateTimePickerManager.onlyTime) { + dateTimePickerManager.showTimePicker() + } else { + dateTimePickerManager.showDatePicker() + } + } + } override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { calendar.set(Calendar.YEAR, year) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/interfaces/FilterHandler.kt b/app/src/main/java/net/pokeranalytics/android/ui/interfaces/FilterHandler.kt index 64f687ba..8e8c1167 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/interfaces/FilterHandler.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/interfaces/FilterHandler.kt @@ -12,69 +12,69 @@ import net.pokeranalytics.android.util.enumerations.IntIdentifiable import net.pokeranalytics.android.util.enumerations.IntSearchable enum class FilterActivityRequestCode { - SELECT_FILTER, - CREATE_FILTER, - ; + SELECT_FILTER, + CREATE_FILTER, + ; } -enum class FilterableType(override var uniqueIdentifier: Int): IntIdentifiable { - ALL(0), - SESSION(1), - TRANSACTION(2), - BANKROLL(3), - HAND_HISTORY(4), - ; +enum class FilterableType(override var uniqueIdentifier: Int) : IntIdentifiable { + ALL(0), + SESSION(1), + TRANSACTION(2), + BANKROLL(3), + HAND_HISTORY(4), + ; - companion object : IntSearchable { + companion object : IntSearchable { - override fun valuesInternal(): Array { - return values() - } - } + override fun valuesInternal(): Array { + return values() + } + } } interface FilterHandler { - companion object { - const val INTENT_FILTER_UPDATE_FILTER_UI = "net.pokeranalytics.android.UPDATE_FILTER_UI" - } + companion object { + const val INTENT_FILTER_UPDATE_FILTER_UI = "net.pokeranalytics.android.UPDATE_FILTER_UI" + } - fun applyFilter() - fun removeFilter() + fun applyFilter() + fun removeFilter() - fun saveFilter(context: Context, filterId:String) { - Preferences.setActiveFilterId(filterId, context) + fun saveFilter(context: Context, filterId: String) { + Preferences.setActiveFilterId(filterId, context) - val realm = Realm.getDefaultInstance() - realm.beginTransaction() - currentFilter(context, realm)?.let { - it.useCount++ + val realm = Realm.getDefaultInstance() + realm.executeTransaction { executeRealm -> + currentFilter(context, executeRealm)?.let { + it.useCount++ + } } - realm.commitTransaction() realm.close() - // Send broadcast - val intent = Intent() - intent.action = INTENT_FILTER_UPDATE_FILTER_UI - context.sendBroadcast(intent) - } - - fun currentFilter(context: Context, realm: Realm): Filter? { - return Preferences.getActiveFilterId(context)?.let { - realm.where().equalTo("id", it).findFirst() - } ?: run { - null - } - } - - var currentFilterable: FilterableType - - - /** - * Manage filters - */ - fun manageFilters(fragment: Fragment) { - FiltersActivity.newInstanceForResult(fragment = fragment, currentFilterable = currentFilterable) - } + // Send broadcast + val intent = Intent() + intent.action = INTENT_FILTER_UPDATE_FILTER_UI + context.sendBroadcast(intent) + } + + fun currentFilter(context: Context, realm: Realm): Filter? { + return Preferences.getActiveFilterId(context)?.let { + realm.where().equalTo("id", it).findFirst() + } ?: run { + null + } + } + + var currentFilterable: FilterableType + + + /** + * Manage filters + */ + fun manageFilters(fragment: Fragment) { + FiltersActivity.newInstanceForResult(fragment = fragment, currentFilterable = currentFilterable) + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/ContextMenuRecyclerView.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/ContextMenuRecyclerView.kt new file mode 100644 index 00000000..a473357d --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/ContextMenuRecyclerView.kt @@ -0,0 +1,37 @@ +package net.pokeranalytics.android.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.view.ContextMenu +import android.view.View +import androidx.recyclerview.widget.RecyclerView + + +class ContextMenuRecyclerView : RecyclerView { + + constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle) + constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet) + constructor(context: Context) : super(context) + + private var mContextMenuInfo: RecyclerViewContextMenuInfo? = null + + override fun getContextMenuInfo(): ContextMenu.ContextMenuInfo? { + return mContextMenuInfo + } + + override fun showContextMenuForChild(originalView: View): Boolean { + + val longPressPosition = getChildAdapterPosition(originalView) + val longPressId = adapter?.getItemId(longPressPosition) + + if (longPressPosition >= 0 && longPressId != null) { + mContextMenuInfo = RecyclerViewContextMenuInfo(longPressPosition, longPressId) + return super.showContextMenuForChild(originalView) + } + return false + } + + class RecyclerViewContextMenuInfo(val position: Int, val id: Long) : ContextMenu.ContextMenuInfo + +} + diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/LegendView.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/LegendView.kt index 3ab9580d..14d18c8a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/LegendView.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/LegendView.kt @@ -108,9 +108,11 @@ open class LegendView : FrameLayout { this.title.text = content.title.capitalize() this.stat1Value.setTextFormat(content.leftFormat, context) + content.rightFormat?.let { this.stat2Value.setTextFormat(it, context) } + content.leftName?.let { name -> this.stat1Name.text = name } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowEditableDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowEditableDescriptor.kt new file mode 100644 index 00000000..65ea70a0 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowEditableDescriptor.kt @@ -0,0 +1,93 @@ +package net.pokeranalytics.android.ui.view + +import io.realm.RealmResults +import net.pokeranalytics.android.exceptions.PokerAnalyticsException +import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException +import net.pokeranalytics.android.util.UserDefaults +import java.util.* +import kotlin.collections.ArrayList + +/** + * An container class to describe the way an field of an object will be edited + */ + +enum class RowEditableDescriptorType { + DATE, + DATA, + STATIC, + DEFAULT +} + +open class RowEditableDescriptor(var defaultValue: Any? = null, + var hint: Int? = null, + var inputType: Int? = null) + +class DateRowEditableDescriptor(date: Date? = null, + val minimumDate: Date? = null, + var onlyDate: Boolean = false, + var onlyTime: Boolean = false): RowEditableDescriptor(defaultValue = date) { + val date: Date? + get() { + return defaultValue as Date? + } +} + +class DataRowEditableDescriptor( + defaultValue: Any? = null, + hint: Int? = null, + inputType: Int? = null, + data: RealmResults<*>? = null): RowEditableDescriptor(defaultValue, hint, inputType) { + + var data: RealmResults? = null + + init { + if (data != null && data.count() > 0) { + if (data.first() is RowRepresentable) { + this.data = data as RealmResults? + } + } + } +} + +class StaticDataRowEditableDescriptor( + defaultValue: Any? = null, + hint: Int? = null, + inputType: Int? = null, + var staticData: List? = null): RowEditableDescriptor(defaultValue, hint, inputType) { +} + +class RowEditableDataSource(customCurrency: Currency? = null) { + var currency: Currency = UserDefaults.currency + + init { + customCurrency?.let { currency = it } + } + + var descriptors = ArrayList() + + fun append(defaultValue: Any? = null, hint: Int? = null, inputType: Int? = null, data: RealmResults<*>? = null, staticData: List? = null) { + when { + data != null -> descriptors.add(DataRowEditableDescriptor(defaultValue, hint, inputType, data)) + staticData != null -> descriptors.add(StaticDataRowEditableDescriptor(defaultValue, hint, inputType, staticData)) + else -> descriptors.add(RowEditableDescriptor(defaultValue, hint, inputType)) + } + } + + fun appendDateDescriptor(date:Date?= null, + minimumDate: Date? = null, + onlyDate: Boolean = false, + onlyTime: Boolean = false) { + descriptors.add(DateRowEditableDescriptor(date, minimumDate, onlyDate, onlyTime)) + } + + val descriptorType: RowEditableDescriptorType + get() { + return when (descriptors.firstOrNull()) { + null -> throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") + is DateRowEditableDescriptor -> RowEditableDescriptorType.DATE + is DataRowEditableDescriptor -> RowEditableDescriptorType.DATA + is StaticDataRowEditableDescriptor -> RowEditableDescriptorType.STATIC + else -> RowEditableDescriptorType.DEFAULT + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt index 1f6ce50f..1e0d241b 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt @@ -1,31 +1,26 @@ package net.pokeranalytics.android.ui.view import android.content.Context +import androidx.fragment.app.Fragment import net.pokeranalytics.android.model.LiveData -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType +import net.pokeranalytics.android.ui.fragment.components.input.InputFragmentType import net.pokeranalytics.android.util.NULL_TEXT /** * An interface extending Displayable to add a way to represent an object as a String */ -interface RowRepresentable : Displayable, EditDataSource, ImageDecorator { +interface RowRepresentable : Displayable, RowEditable, ImageDecorator { fun getDisplayName(context: Context): String { return NULL_TEXT } } -interface EditDataSource { - fun editingDescriptors(map: Map): ArrayList? { - return null - } -} +interface RowEditable { + fun startEditing(dataSource:Any?, parent:Fragment?) {} -interface DefaultEditDataSource : EditDataSource, Localizable { - override fun editingDescriptors(map: Map): ArrayList? { - val defaultValue: String? by map - return arrayListOf(RowRepresentableEditDescriptor(defaultValue, this.resId)) - } + val valueCanBeClearedWhenEditing: Boolean + get() { return true } } /** @@ -62,9 +57,9 @@ interface Displayable : Localizable { return null } - val bottomSheetType: BottomSheetType + val inputFragmentType: InputFragmentType get() { - return BottomSheetType.NONE + return InputFragmentType.NONE } val selectedChoice: Int diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentableEditDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentableEditDescriptor.kt deleted file mode 100644 index bdb6bf4b..00000000 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentableEditDescriptor.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.pokeranalytics.android.ui.view - -import io.realm.RealmResults - -/** - * An container class to describe the way an field of an object will be edited - */ -class RowRepresentableEditDescriptor( - var defaultValue: Any? = null, - var hint: Int? = null, - var inputType: Int? = null, - var data: RealmResults? = null, - var staticData: List? = null -) \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt index 12ccaece..747338c7 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt @@ -14,10 +14,7 @@ import androidx.core.widget.ContentLoadingProgressBar import androidx.recyclerview.widget.RecyclerView import com.github.mikephil.charting.charts.BarChart import com.github.mikephil.charting.charts.LineChart -import com.github.mikephil.charting.data.BarData -import com.github.mikephil.charting.data.BarDataSet -import com.github.mikephil.charting.data.LineData -import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.data.* import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup import kotlinx.android.synthetic.main.row_feed_session.view.* @@ -25,7 +22,9 @@ import kotlinx.android.synthetic.main.row_transaction.view.* 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.calculus.bankroll.BankrollReportManager +import net.pokeranalytics.android.model.TableSize +import net.pokeranalytics.android.model.extensions.getFormattedGameType import net.pokeranalytics.android.model.realm.CustomField import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Transaction @@ -34,9 +33,12 @@ import net.pokeranalytics.android.ui.extensions.ChipGroupExtension import net.pokeranalytics.android.ui.extensions.addCircleRipple import net.pokeranalytics.android.ui.extensions.px import net.pokeranalytics.android.ui.extensions.setTextFormat +import net.pokeranalytics.android.ui.fragment.BankrollRowRepresentable import net.pokeranalytics.android.ui.graph.AxisFormatting import net.pokeranalytics.android.ui.graph.setStyle import net.pokeranalytics.android.ui.view.rowrepresentable.* +import net.pokeranalytics.android.util.extensions.longDate +import timber.log.Timber /** * An interface used to factor the configuration of RecyclerView.ViewHolder @@ -78,6 +80,7 @@ enum class RowViewType(private var layoutRes: Int) { // Custom row ROW_SESSION(R.layout.row_feed_session), ROW_TRANSACTION(R.layout.row_transaction), + ROW_TOP_10(R.layout.row_top_10), ROW_BUTTON(R.layout.row_button), ROW_FOLLOW_US(R.layout.row_follow_us), STATS(R.layout.row_stats_title_value), @@ -110,6 +113,9 @@ enum class RowViewType(private var layoutRes: Int) { // Row Transaction ROW_TRANSACTION -> RowTransactionViewHolder(layout) + // Row Transaction + ROW_TOP_10 -> RowTop10ViewHolder(layout) + // Row Button ROW_BUTTON -> RowButtonViewHolder(layout) @@ -137,95 +143,122 @@ enum class RowViewType(private var layoutRes: Int) { */ @SuppressWarnings("ResourceType") inner class RowViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { - override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { - if (row is CustomizableRowRepresentable) { + override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { - // Customizable Row + when (row) { - // Title - itemView.findViewById(R.id.title)?.let { - it.text = row.localizedTitle(itemView.context) - } + is BankrollRowRepresentable -> { - // Value - itemView.findViewById(R.id.value)?.let { - if (row.computedStat != null) { - val format = row.computedStat!!.format() - it.setTextFormat(format, itemView.context) - } else if (row.value != null) { - it.text = row.value + // Title + itemView.findViewById(R.id.title)?.let { + it.text = row.localizedTitle(itemView.context) } - } - // Listener - row.isSelectable?.let { isSelectable -> - if (isSelectable) { - val listener = View.OnClickListener { - adapter.delegate?.onRowSelected(position, row) + Timber.d("RowViewHolder > bind > reportForBankroll") + BankrollReportManager.reportForBankroll(row.bankrollId) { report -> + + itemView.findViewById(R.id.title)?.let { + it.text = row.localizedTitle(itemView.context) } - itemView.findViewById(R.id.container)?.setOnClickListener(listener) + val computedStat = ComputedStat(Stat.NET_RESULT, report.total, currency = report.currency) + itemView.findViewById(R.id.value)?.setTextFormat(computedStat.format(), itemView.context) } - } - } else { + val listener = View.OnClickListener { + adapter.delegate?.onRowSelected(position, row) + } + itemView.findViewById(R.id.container)?.setOnClickListener(listener) + } + is CustomizableRowRepresentable -> { - // Classic row + // Customizable Row - // Title - itemView.findViewById(R.id.title)?.let { - if (row.resId != null) { + // Title + itemView.findViewById(R.id.title)?.let { it.text = row.localizedTitle(itemView.context) - } else { - it.text = row.getDisplayName(itemView.context) } - } - // Value - itemView.findViewById(R.id.value)?.let { - it.text = adapter.dataSource.stringForRow(row, itemView.context) - } + // Value + itemView.findViewById(R.id.value)?.let { + if (row.computedStat != null) { + val format = row.computedStat!!.format() + it.setTextFormat(format, itemView.context) + } else if (row.value != null) { + it.text = row.value + } + } - // Icon - itemView.findViewById(R.id.icon)?.let { imageView -> - imageView.setImageDrawable(null) - row.imageRes?.let { imageRes -> - imageView.setImageResource(imageRes) + // Listener + row.isSelectable?.let { isSelectable -> + if (isSelectable) { + val listener = View.OnClickListener { + adapter.delegate?.onRowSelected(position, row) + } + itemView.findViewById(R.id.container)?.setOnClickListener(listener) + } } + } + else -> { - // Action - itemView.findViewById(R.id.action)?.let { imageView -> - imageView.setImageDrawable(null) - row.imageRes?.let { imageRes -> - imageView.visibility = View.VISIBLE - imageView.setImageResource(imageRes) + // Classic row + + // Title + itemView.findViewById(R.id.title)?.let { + if (row.resId != null) { + it.text = row.localizedTitle(itemView.context) + } else { + it.text = row.getDisplayName(itemView.context) + } } - row.imageTint?.let { color -> - imageView.setColorFilter(ContextCompat.getColor(imageView.context, color)) + + // Value + itemView.findViewById(R.id.value)?.let { + it.text = adapter.dataSource.stringForRow(row, itemView.context) } - if (row.imageClickable == true) { - imageView.addCircleRipple() - imageView.setOnClickListener { - adapter.delegate?.onRowSelected(position, row, true) + + // Icon + itemView.findViewById(R.id.icon)?.let { imageView -> + imageView.setImageDrawable(null) + row.imageRes?.let { imageRes -> + imageView.setImageResource(imageRes) } - } else { - imageView.setBackgroundResource(0) } - } - // Listener - val listener = View.OnClickListener { - itemView.findViewById(R.id.switchView)?.let { - if (adapter.dataSource.isEnabled(row)) { - it.isChecked = !it.isChecked + // Action + itemView.findViewById(R.id.action)?.let { imageView -> + imageView.setImageDrawable(null) + row.imageRes?.let { imageRes -> + imageView.visibility = View.VISIBLE + imageView.setImageResource(imageRes) + } + row.imageTint?.let { color -> + imageView.setColorFilter(ContextCompat.getColor(imageView.context, color)) + } + if (row.imageClickable == true) { + imageView.addCircleRipple() + imageView.setOnClickListener { + adapter.delegate?.onRowSelected(position, row, true) + } + } else { + imageView.setBackgroundResource(0) } - } ?: run { - adapter.delegate?.onRowSelected(position, row) } - } - itemView.findViewById(R.id.container)?.setOnClickListener(listener) + // Listener + val listener = View.OnClickListener { + itemView.findViewById(R.id.switchView)?.let { + if (adapter.dataSource.isEnabled(row)) { + it.isChecked = !it.isChecked + } + } ?: run { + adapter.delegate?.onRowSelected(position, row) + } + } + + itemView.findViewById(R.id.container)?.setOnClickListener(listener) + } } // Switch @@ -346,43 +379,53 @@ enum class RowViewType(private var layoutRes: Int) { /** * Display a graph */ - inner class GraphViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), - BindableHolder { - override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { + inner class GraphViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { + + private fun loadWithDataSet(dataSet: DataSet<*>) { + val context = itemView.context + + val chartView = when (dataSet) { + is LineDataSet -> { + val lineChart = LineChart(context) + lineChart.data = LineData(dataSet) + lineChart + } + is BarDataSet -> { + val barChart = BarChart(context) + barChart.data = BarData(dataSet) + barChart + } + else -> null + } - if (row is GraphRow) { + itemView.findViewById(R.id.chartContainer)?.let { + it.removeAllViews() + it.addView(chartView) + } - row.dataSet?.let { dataSet -> + chartView?.let { + chartView.setStyle(true, AxisFormatting.DEFAULT, context) + chartView.setTouchEnabled(false) + } - val context = itemView.context + } - val chartView = when (dataSet) { - is LineDataSet -> { - val lineChart = LineChart(context) - lineChart.data = LineData(dataSet) - lineChart - } - is BarDataSet -> { - val barChart = BarChart(context) - barChart.data = BarData(dataSet) - barChart - } - else -> null - } + override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { - itemView.findViewById(R.id.chartContainer)?.let { - it.removeAllViews() - it.addView(chartView) + when (row) { + is BankrollGraphRow -> { + Timber.d("GraphViewHolder > bind > reportForBankroll") + BankrollReportManager.reportForBankroll(row.bankrollId) { report -> + val dataSet = report.lineDataSet(itemView.context) + row.dataSet = dataSet + loadWithDataSet(dataSet) } - - chartView?.let { - chartView.setStyle(true, AxisFormatting.DEFAULT, context) - chartView.setTouchEnabled(false) + } + is GraphRow -> { + row.dataSet?.let { dataSet -> + loadWithDataSet(dataSet) } - -// chartView.highlightValue((entries.size - 1).toFloat(), 0) } - } // Listener @@ -396,26 +439,32 @@ enum class RowViewType(private var layoutRes: Int) { /** * Display a legend */ - inner class LegendDefaultViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), - BindableHolder { + inner class LegendDefaultViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { + override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { - if (row is BankrollReport) { + if (row is BankrollRowRepresentable) { + + Timber.d("LegendDefaultViewHolder > bind > reportForBankroll") + BankrollReportManager.reportForBankroll(row.bankrollId) { report -> + + itemView.findViewById(R.id.stat1Value)?.let { + val formattedStat = ComputedStat(Stat.NET_RESULT, report.total).format() + it.setTextFormat(formattedStat, itemView.context) + } + itemView.findViewById(R.id.stat2Value)?.let { + val riskOfRuin = report.riskOfRuin ?: 0.0 + val formattedStat = ComputedStat(Stat.RISK_OF_RUIN, riskOfRuin).format() + it.setTextFormat(formattedStat, itemView.context) + } + } + itemView.findViewById(R.id.stat1Name)?.let { it.text = itemView.context.getString(R.string.total) } - itemView.findViewById(R.id.stat1Value)?.let { - val formattedStat = ComputedStat(Stat.NET_RESULT, row.total).format() - it.setTextFormat(formattedStat, itemView.context) - } itemView.findViewById(R.id.stat2Name)?.let { it.text = itemView.context.getString(R.string.risk_of_ruin) } - itemView.findViewById(R.id.stat2Value)?.let { - val riskOfRuin = row.riskOfRuin ?: 0.0 - val formattedStat = ComputedStat(Stat.RISK_OF_RUIN, riskOfRuin).format() - it.setTextFormat(formattedStat, itemView.context) - } val listener = View.OnClickListener { adapter.delegate?.onRowSelected(position, row) } @@ -513,6 +562,67 @@ enum class RowViewType(private var layoutRes: Int) { } } + + /** + * Display a top 10 row + */ + inner class RowTop10ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { + override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { + + if (row is Session) { + + itemView.findViewById(R.id.gameResult)?.let { gameResult -> + val result = row.result?.net ?: 0.0 + val formattedStat = ComputedStat(Stat.NET_RESULT, result, currency = row.currency).format() + gameResult.setTextFormat(formattedStat, itemView.context) + } + + itemView.findViewById(R.id.sessionGameType)?.let { part1 -> + part1.text = row.getFormattedGameType(itemView.context) + } + + // Duration + val durationIcon = itemView.findViewById(R.id.sessionInfoDurationIcon) + val durationText = itemView.findViewById(R.id.sessionInfoDuration) + durationIcon?.isVisible = true + durationText?.isVisible = true + durationText?.text = row.getFormattedDuration() + + // Location + val locationIcon = itemView.findViewById(R.id.sessionInfoLocationIcon) + val locationText = itemView.findViewById(R.id.sessionInfoLocation) + + if (!row.location?.name.isNullOrEmpty()) { + locationIcon?.isVisible = true + locationText?.isVisible = true + locationText?.text = row.location?.name + } else { + locationIcon?.isVisible = false + locationText?.isVisible = false + } + + // Table Size + val tableSizeIcon = itemView.findViewById(R.id.sessionInfoTableSizeIcon) + val tableSizeText = itemView.findViewById(R.id.sessionInfoTableSize) + + row.tableSize?.let { + tableSizeIcon?.isVisible = true + tableSizeText?.isVisible = true + tableSizeText?.text = TableSize(it).localizedTitle(itemView.context) + } ?: run { + tableSizeIcon?.isVisible = false + tableSizeText?.isVisible = false + } + + itemView.findViewById(R.id.sessionDate)?.let { part3 -> + part3.text = row.creationDate.longDate() + } + } + + } + } + + /** * Display a separator */ diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt index 40db96da..69edd181 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt @@ -11,15 +11,14 @@ import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.ComputedStat import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.model.TableSize -import net.pokeranalytics.android.model.TournamentType import net.pokeranalytics.android.model.extensions.SessionState +import net.pokeranalytics.android.model.extensions.getFormattedGameType import net.pokeranalytics.android.model.extensions.getState import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.ui.extensions.setTextFormat import net.pokeranalytics.android.util.extensions.getDayNumber import net.pokeranalytics.android.util.extensions.getShortDayName import net.pokeranalytics.android.util.extensions.shortTime -import net.pokeranalytics.android.util.extensions.toCurrency /** * Display a row session @@ -65,41 +64,7 @@ class SessionRowView : FrameLayout { rowSession.dateNumber.text = date.getDayNumber() // Title / Game type - - var parameters = mutableListOf() - if (session.isTournament()) { - - session.tournamentEntryFee?.let { - parameters.add(it.toCurrency(session.currency)) - } - - session.tournamentName?.let { - parameters.add(it.name) - } ?: run { - parameters.add(session.getFormattedGame()) - session.tournamentType?.let { type -> - parameters.add(TournamentType.values()[type].localizedTitle(context)) - } - } - - if (parameters.size == 0) { - parameters.add(context.getString(R.string.tournament).capitalize()) - } - } else { - if (session.cgSmallBlind != null && session.cgBigBlind != null) { - parameters.add(session.getFormattedBlinds()) - } - session.game?.let { - parameters.add(session.getFormattedGame()) - } - - if (parameters.size == 0) { - parameters.add(context.getString(R.string.cash_game).capitalize()) - } - } - val title = parameters.joinToString(separator = " ") - - rowSession.sessionTitle.text = title + rowSession.sessionTitle.text = session.getFormattedGameType(context) // Duration rowSession.sessionInfoDurationValue.text = session.getFormattedDuration() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/TransactionRowView.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/TransactionRowView.kt index f9971876..eb9b6fa4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/TransactionRowView.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/TransactionRowView.kt @@ -63,7 +63,7 @@ class TransactionRowView : FrameLayout { rowTransaction.transactionSubtitle.text = subtitle // Amount - val formattedStat = ComputedStat(Stat.NET_RESULT, transaction.amount).format() + val formattedStat = ComputedStat(Stat.NET_RESULT, transaction.amount, currency = transaction.bankroll?.utilCurrency).format() rowTransaction.transactionAmount.setTextFormat(formattedStat, context) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollMainRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollMainRow.kt new file mode 100644 index 00000000..1e42445f --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollMainRow.kt @@ -0,0 +1,12 @@ +package net.pokeranalytics.android.ui.view.rowrepresentable + +import net.pokeranalytics.android.ui.fragment.BankrollRowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType + +class BankrollMainRow : BankrollRowRepresentable { + + override var bankrollId: String? = null + + override val viewType: Int = RowViewType.LEGEND_DEFAULT.ordinal + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollRow.kt index 1e3b3152..2b88c9bc 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollRow.kt @@ -1,15 +1,18 @@ package net.pokeranalytics.android.ui.view.rowrepresentable import android.text.InputType +import androidx.fragment.app.Fragment import net.pokeranalytics.android.R -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType -import net.pokeranalytics.android.ui.view.DefaultEditDataSource -import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor -import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.exceptions.PokerAnalyticsException +import net.pokeranalytics.android.model.realm.Bankroll +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.* +import java.util.* - -enum class BankrollRow : RowRepresentable, DefaultEditDataSource { +enum class BankrollRow : RowRepresentable { + NAME, LIVE, INITIAL_VALUE, CURRENCY, @@ -19,6 +22,7 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { override val resId: Int? get() { return when (this) { + NAME -> R.string.name LIVE -> R.string.online INITIAL_VALUE -> R.string.initial_value CURRENCY -> R.string.currency @@ -30,6 +34,7 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { override val viewType: Int get() { return when (this) { + NAME -> RowViewType.TITLE_VALUE.ordinal LIVE -> RowViewType.TITLE_SWITCH.ordinal INITIAL_VALUE -> RowViewType.TITLE_VALUE.ordinal CURRENCY -> RowViewType.TITLE_VALUE_ARROW.ordinal @@ -38,34 +43,32 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { } } - override val bottomSheetType: BottomSheetType + override val inputFragmentType: InputFragmentType get() { return when (this) { - LIVE -> BottomSheetType.NONE - INITIAL_VALUE -> BottomSheetType.NUMERIC_TEXT - CURRENCY -> BottomSheetType.NONE - RATE -> BottomSheetType.NUMERIC_TEXT - REFRESH_RATE -> BottomSheetType.NONE + NAME -> InputFragmentType.EDIT_TEXT + LIVE -> InputFragmentType.NONE + INITIAL_VALUE -> InputFragmentType.NUMERIC_TEXT + CURRENCY -> InputFragmentType.NONE + RATE -> InputFragmentType.NUMERIC_TEXT + REFRESH_RATE -> InputFragmentType.NONE } } - override fun editingDescriptors(map: Map): ArrayList? { - return when (this) { - INITIAL_VALUE -> { - val defaultValue : Any? by map - arrayListOf( - RowRepresentableEditDescriptor(defaultValue, R.string.initial_value, InputType.TYPE_CLASS_NUMBER - or InputType.TYPE_NUMBER_FLAG_DECIMAL) - ) - } - RATE -> { - val defaultValue : Any? by map - arrayListOf( - RowRepresentableEditDescriptor(defaultValue, R.string.rate, InputType.TYPE_CLASS_NUMBER - or InputType.TYPE_NUMBER_FLAG_DECIMAL) - ) - } - else -> super.editingDescriptors(map) - } - } + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (dataSource == null) return + if (dataSource !is Bankroll) return + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + + val bankrollCurrency: Currency? = dataSource.currency?.code?.let { Currency.getInstance(it) } + val data = RowEditableDataSource(bankrollCurrency) + when (this) { + NAME -> data.append(dataSource.name) + INITIAL_VALUE -> data.append(dataSource.initialValue, R.string.initial_value, InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL) + RATE -> data.append(dataSource.currency?.rate, R.string.rate, InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL) + else -> PokerAnalyticsException.InputFragmentException + } + InputFragment.buildAndShow(this, parent, data) + } } \ No newline at end of file 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 f85d24c9..bd0d8f8b 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 @@ -1,19 +1,25 @@ package net.pokeranalytics.android.ui.view.rowrepresentable +import androidx.fragment.app.Fragment import net.pokeranalytics.android.R +import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.model.realm.CustomField -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType -import net.pokeranalytics.android.ui.view.DefaultEditDataSource +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.RowViewType -enum class CustomFieldRow : RowRepresentable, DefaultEditDataSource { +enum class CustomFieldRow : RowRepresentable { + NAME, TYPE, COPY_ON_DUPLICATE; override val resId: Int? get() { return when (this) { + NAME -> R.string.name COPY_ON_DUPLICATE -> R.string.copy_on_duplicate else -> null } @@ -22,16 +28,18 @@ enum class CustomFieldRow : RowRepresentable, DefaultEditDataSource { override val viewType: Int get() { return when (this) { + NAME -> RowViewType.TITLE_VALUE.ordinal TYPE -> RowViewType.LIST.ordinal COPY_ON_DUPLICATE -> RowViewType.TITLE_SWITCH.ordinal } } - override val bottomSheetType: BottomSheetType + override val inputFragmentType: InputFragmentType get() { return when (this) { - TYPE -> BottomSheetType.NONE - COPY_ON_DUPLICATE -> BottomSheetType.NONE + NAME -> InputFragmentType.EDIT_TEXT + TYPE -> InputFragmentType.NONE + COPY_ON_DUPLICATE -> InputFragmentType.NONE } } @@ -55,4 +63,41 @@ enum class CustomFieldRow : RowRepresentable, DefaultEditDataSource { } 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 + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + when (this) { + NAME -> data.append(dataSource.name) + else -> PokerAnalyticsException.InputFragmentException + } + InputFragment.buildAndShow(this, parent, data) + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomizableRowRepresentable.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomizableRowRepresentable.kt index 3cdc0ace..47255dde 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomizableRowRepresentable.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomizableRowRepresentable.kt @@ -2,10 +2,20 @@ package net.pokeranalytics.android.ui.view.rowrepresentable import android.content.Context import net.pokeranalytics.android.calculus.ComputedStat -import net.pokeranalytics.android.model.interfaces.Identifiable +import net.pokeranalytics.android.ui.fragment.BankrollRowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType +class BankrollTotalRow(override var bankrollId: String?, var name: String) : BankrollRowRepresentable { + + override val viewType: Int = RowViewType.TITLE_VALUE_ARROW.ordinal + + override fun localizedTitle(context: Context): String { + return name + } + +} + /** * A class to display a titleResId (and a value) as a Row Representable object */ @@ -16,7 +26,7 @@ class CustomizableRowRepresentable( var value: String? = null, var computedStat: ComputedStat? = null, var isSelectable: Boolean? = false - ) : RowRepresentable, Identifiable { + ) : RowRepresentable { override fun localizedTitle(context: Context): String { @@ -31,5 +41,5 @@ class CustomizableRowRepresentable( override val viewType: Int = customViewType?.ordinal ?: RowViewType.HEADER_TITLE.ordinal - override var id: String = "" +// override var id: String = "" } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt index 3e8b783e..fd6cc87a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt @@ -1,33 +1,52 @@ package net.pokeranalytics.android.ui.view.rowrepresentable import android.text.InputType +import androidx.fragment.app.Fragment import net.pokeranalytics.android.R import net.pokeranalytics.android.model.filter.QueryCondition -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType +import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate +import net.pokeranalytics.android.ui.fragment.components.input.InputFragment import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor -import net.pokeranalytics.android.ui.view.RowViewType -import java.util.* +import net.pokeranalytics.android.ui.view.RowEditableDataSource interface FilterElementRow : RowRepresentable { - override fun editingDescriptors(map: Map): ArrayList? { - return when (this) { + + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + when (this) { + is QueryCondition.DateQuery -> data.appendDateDescriptor(this.singleValue, onlyDate = !this.showTime, onlyTime = this.showTime) is QueryCondition.Duration -> { - val hours: String? by map - val minutes: String? by map - arrayListOf( - RowRepresentableEditDescriptor(hours, R.string.hour, inputType = InputType.TYPE_CLASS_NUMBER), - RowRepresentableEditDescriptor(minutes, R.string.minute, inputType = InputType.TYPE_CLASS_NUMBER) - ) + var hours: String? = null + var minutes: String? = null + this.minutes?.let { + hours = if (it / 60 > 0) (it / 60).toString() else null + minutes = if (it % 60 > 0) (it % 60).toString() else null + } + data.append(hours, R.string.hour, inputType = InputType.TYPE_CLASS_NUMBER) + data.append(minutes, R.string.minute, inputType = InputType.TYPE_CLASS_NUMBER) } is QueryCondition.ListOfValues<*> -> { - val valueAsString: String? by map - arrayListOf( - RowRepresentableEditDescriptor(valueAsString, this.resId, inputType = InputType.TYPE_CLASS_NUMBER) - ) + var valueAsString: String? = null + this.listOfValues.firstOrNull()?.let { + valueAsString = this.listOfValues.firstOrNull()?.toString() + } + val hint = when (this.operator) { + QueryCondition.Operator.MORE, QueryCondition.Operator.LESS -> { + when (this) { + is QueryCondition.CustomFieldNumberQuery -> R.string.value + is QueryCondition.CustomFieldAmountQuery -> R.string.amount + else -> this.filterSectionRow.resId + } + } + else -> this.resId + } + data.append(valueAsString, hint, inputType = InputType.TYPE_CLASS_NUMBER) } - else -> super.editingDescriptors(map) } + InputFragment.buildAndShow(this, parent, data) + } var filterSectionRow: FilterSectionRow diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/GameRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/GameRow.kt index 19fada44..770c2bd3 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/GameRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/GameRow.kt @@ -1,17 +1,23 @@ package net.pokeranalytics.android.ui.view.rowrepresentable +import androidx.fragment.app.Fragment import net.pokeranalytics.android.R -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType -import net.pokeranalytics.android.ui.view.DefaultEditDataSource +import net.pokeranalytics.android.model.realm.Game +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.RowViewType -enum class GameRow : RowRepresentable, DefaultEditDataSource { +enum class GameRow : RowRepresentable { + NAME, SHORT_NAME; override val resId: Int? get() { return when (this) { + NAME -> R.string.name SHORT_NAME -> R.string.short_name } } @@ -19,15 +25,30 @@ enum class GameRow : RowRepresentable, DefaultEditDataSource { override val viewType: Int get() { return when (this) { + NAME -> RowViewType.TITLE_VALUE.ordinal SHORT_NAME -> RowViewType.TITLE_VALUE.ordinal } } - override val bottomSheetType: BottomSheetType + override val inputFragmentType: InputFragmentType get() { return when (this) { - SHORT_NAME -> BottomSheetType.EDIT_TEXT + NAME -> InputFragmentType.EDIT_TEXT + SHORT_NAME -> InputFragmentType.EDIT_TEXT } } + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (dataSource == null) return + if (dataSource !is Game) return + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + when (this) { + NAME -> data.append(dataSource.name) + SHORT_NAME -> data.append(dataSource.shortName) + } + InputFragment.buildAndShow(this, parent, data) + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/GraphRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/GraphRow.kt index 7af17e6b..3f8f185b 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/GraphRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/GraphRow.kt @@ -3,11 +3,18 @@ package net.pokeranalytics.android.ui.view.rowrepresentable import com.github.mikephil.charting.data.DataSet import net.pokeranalytics.android.calculus.Report import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.ui.fragment.BankrollRowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -class GraphRow(var dataSet: DataSet<*>?, var title: String? = null, var report: Report? = null, var stat: Stat? = null) : RowRepresentable { +class BankrollGraphRow : GraphRow(null, null, null, null), BankrollRowRepresentable { + + override var bankrollId: String? = null + +} + +open class GraphRow(var dataSet: DataSet<*>? = null, var title: String? = null, var report: Report? = null, var stat: Stat? = null) : RowRepresentable { override val viewType: Int get() = RowViewType.GRAPH.ordinal diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/LocationRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/LocationRow.kt index abff9ff2..b4014f06 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/LocationRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/LocationRow.kt @@ -1,19 +1,26 @@ package net.pokeranalytics.android.ui.view.rowrepresentable +import androidx.fragment.app.Fragment import net.pokeranalytics.android.R -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType -import net.pokeranalytics.android.ui.view.DefaultEditDataSource +import net.pokeranalytics.android.exceptions.PokerAnalyticsException +import net.pokeranalytics.android.model.realm.Location +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.RowViewType -enum class LocationRow : RowRepresentable, DefaultEditDataSource { +enum class LocationRow : RowRepresentable { + NAME, LOCATION_PERMISSION_SWITCH, LOCATION_LOADER; override val resId: Int? get() { return when (this) { + NAME -> R.string.name LOCATION_PERMISSION_SWITCH -> R.string.geo_locate LOCATION_LOADER -> null } @@ -22,17 +29,32 @@ enum class LocationRow : RowRepresentable, DefaultEditDataSource { override val viewType: Int get() { return when (this) { + NAME -> RowViewType.TITLE_VALUE.ordinal LOCATION_PERMISSION_SWITCH -> RowViewType.TITLE_SWITCH.ordinal LOCATION_LOADER -> RowViewType.LOADER.ordinal } } - override val bottomSheetType: BottomSheetType + override val inputFragmentType: InputFragmentType get() { return when (this) { - LOCATION_PERMISSION_SWITCH -> BottomSheetType.NONE - LOCATION_LOADER -> BottomSheetType.NONE + NAME -> InputFragmentType.EDIT_TEXT + LOCATION_PERMISSION_SWITCH -> InputFragmentType.NONE + LOCATION_LOADER -> InputFragmentType.NONE } } + + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (dataSource == null) return + if (dataSource !is Location) return + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + when (this) { + NAME -> data.append(dataSource.name) + else -> PokerAnalyticsException.InputFragmentException + } + InputFragment.buildAndShow(this, parent, data) + } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/MoreTabRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/MoreTabRow.kt index 4e2fe8bc..3885a4a1 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/MoreTabRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/MoreTabRow.kt @@ -9,12 +9,14 @@ import net.pokeranalytics.android.ui.view.RowViewType */ enum class MoreTabRow : RowRepresentable { BANKROLL, + TOP_10, SETTINGS; override val resId: Int? get() { return when(this) { BANKROLL -> R.string.bankroll + TOP_10 -> R.string.top_10 SETTINGS -> R.string.services } } @@ -23,6 +25,7 @@ enum class MoreTabRow : RowRepresentable { get() { return when(this) { BANKROLL -> R.drawable.ic_outline_lock + TOP_10 -> R.drawable.ic_outline_star SETTINGS -> R.drawable.ic_outline_settings } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt index fd43faa8..30424067 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt @@ -1,314 +1,286 @@ package net.pokeranalytics.android.ui.view.rowrepresentable import android.text.InputType -import io.realm.RealmResults +import android.widget.Toast +import androidx.fragment.app.Fragment import net.pokeranalytics.android.R import net.pokeranalytics.android.model.TournamentType import net.pokeranalytics.android.model.extensions.SessionState import net.pokeranalytics.android.model.extensions.getState -import net.pokeranalytics.android.model.realm.Session -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType +import net.pokeranalytics.android.model.realm.* +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.RowRepresentableEditDescriptor +import net.pokeranalytics.android.ui.view.RowEditableDataSource import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.util.extensions.round +import net.pokeranalytics.android.util.extensions.sorted +import java.util.* enum class SessionRow : RowRepresentable { - PRIZE, - CASHED_OUT, - NET_RESULT, - INITIAL_BUY_IN, - BUY_IN, - POSITION, - PLAYERS, - TIPS, + PRIZE, + CASHED_OUT, + NET_RESULT, + INITIAL_BUY_IN, + BUY_IN, + POSITION, + PLAYERS, + TIPS, - GAME, - BLINDS, - LOCATION, - BANKROLL, - TABLE_SIZE, - TOURNAMENT_TYPE, - TOURNAMENT_NAME, - TOURNAMENT_FEATURE, - START_DATE, - END_DATE, + GAME, + BLINDS, + LOCATION, + BANKROLL, + TABLE_SIZE, + TOURNAMENT_TYPE, + TOURNAMENT_NAME, + TOURNAMENT_FEATURE, + START_DATE, + END_DATE, - BREAK_TIME, - COMMENT; + BREAK_TIME, + COMMENT; - companion object { - /** - * Return the rows to display for the current session state - */ - fun getRows(session: Session): List { - when (session.type) { - Session.Type.TOURNAMENT.ordinal -> { - return when (session.getState()) { - SessionState.PENDING, SessionState.PLANNED -> { - arrayListOf( - GAME, - INITIAL_BUY_IN, - LOCATION, - BANKROLL, - TABLE_SIZE, - TOURNAMENT_TYPE, - TOURNAMENT_NAME, - TOURNAMENT_FEATURE, - START_DATE, - END_DATE - ) - } - SessionState.STARTED, SessionState.PAUSED, SessionState.FINISHED -> { - arrayListOf( - PRIZE, - BUY_IN, - POSITION, - PLAYERS, - TIPS, - SeparatorRow(), - GAME, - INITIAL_BUY_IN, - LOCATION, - BANKROLL, - TABLE_SIZE, - TOURNAMENT_TYPE, - TOURNAMENT_NAME, - TOURNAMENT_FEATURE, - SeparatorRow(), - START_DATE, - END_DATE, - BREAK_TIME, - COMMENT - ) - } - } - } - Session.Type.CASH_GAME.ordinal -> { - when (session.getState()) { - SessionState.PENDING, SessionState.PLANNED -> { - return arrayListOf(GAME, BLINDS, LOCATION, BANKROLL, TABLE_SIZE, START_DATE, END_DATE) - } - SessionState.STARTED, SessionState.PAUSED, SessionState.FINISHED -> { + companion object { + /** + * Return the rows to display for the current session state + */ + fun getRows(session: Session): List { + when (session.type) { + Session.Type.TOURNAMENT.ordinal -> { + return when (session.getState()) { + SessionState.PENDING, SessionState.PLANNED -> { + arrayListOf( + GAME, + INITIAL_BUY_IN, + LOCATION, + BANKROLL, + TABLE_SIZE, + TOURNAMENT_TYPE, + TOURNAMENT_NAME, + TOURNAMENT_FEATURE, + START_DATE, + END_DATE + ) + } + SessionState.STARTED, SessionState.PAUSED, SessionState.FINISHED -> { + arrayListOf( + PRIZE, + BUY_IN, + POSITION, + PLAYERS, + TIPS, + COMMENT, + SeparatorRow(), + GAME, + INITIAL_BUY_IN, + LOCATION, + BANKROLL, + TABLE_SIZE, + TOURNAMENT_TYPE, + TOURNAMENT_NAME, + TOURNAMENT_FEATURE, + SeparatorRow(), + START_DATE, + END_DATE, + BREAK_TIME + ) + } + } + } + Session.Type.CASH_GAME.ordinal -> { + when (session.getState()) { + SessionState.PENDING, SessionState.PLANNED -> { + return arrayListOf(GAME, BLINDS, LOCATION, BANKROLL, TABLE_SIZE, START_DATE, END_DATE) + } + SessionState.STARTED, SessionState.PAUSED, SessionState.FINISHED -> { - val fields = mutableListOf() - when { - session.hasBuyin -> fields.addAll(listOf(CASHED_OUT, BUY_IN, TIPS)) - session.hasNetResult -> fields.add(NET_RESULT) - session.isLive -> fields.addAll(listOf(CASHED_OUT, BUY_IN, TIPS)) - else -> fields.add(NET_RESULT) - } - fields.add(SeparatorRow()) - fields.addAll(listOf( - GAME, - BLINDS, - LOCATION, - BANKROLL, - TABLE_SIZE, - START_DATE, - END_DATE, - BREAK_TIME, - COMMENT - ) - ) - return fields - } - } - } - } - return arrayListOf() - } - } + val fields = mutableListOf() + when { + session.hasBuyin -> fields.addAll(listOf(CASHED_OUT, BUY_IN, TIPS)) + session.hasNetResult -> fields.add(NET_RESULT) + session.isLive -> fields.addAll(listOf(CASHED_OUT, BUY_IN, TIPS)) + else -> fields.add(NET_RESULT) + } + fields.add(COMMENT) + fields.add(SeparatorRow()) + fields.addAll( + listOf( + GAME, + BLINDS, + LOCATION, + BANKROLL, + TABLE_SIZE, + START_DATE, + END_DATE, + BREAK_TIME - override val resId: Int? - get() { - return when (this) { - NET_RESULT -> R.string.net_result - PRIZE -> R.string.prize - POSITION -> R.string.position - PLAYERS -> R.string.players - CASHED_OUT -> R.string.cashed_out - INITIAL_BUY_IN -> R.string.initial_stack - BUY_IN -> R.string.buyin - TIPS -> R.string.tips - GAME -> R.string.game - BLINDS -> R.string.blinds - LOCATION -> R.string.location - BANKROLL -> R.string.bankroll - TABLE_SIZE -> R.string.table_size - TOURNAMENT_TYPE -> R.string.tournament_type - TOURNAMENT_NAME -> R.string.tournament_name - TOURNAMENT_FEATURE -> R.string.tournament_feature - START_DATE -> R.string.start_date - END_DATE -> R.string.end_date - BREAK_TIME -> R.string.break_time - COMMENT -> R.string.comment - } - } + ) + ) + return fields + } + } + } + } + return arrayListOf() + } + } - override val viewType: Int - get() { - return when (this) { - NET_RESULT, PRIZE, POSITION, PLAYERS, CASHED_OUT, INITIAL_BUY_IN, BUY_IN, TIPS, - GAME, BLINDS, LOCATION, BANKROLL, TABLE_SIZE, COMMENT, - TOURNAMENT_TYPE, TOURNAMENT_NAME, TOURNAMENT_FEATURE, START_DATE, END_DATE, BREAK_TIME -> RowViewType.TITLE_VALUE.ordinal - } - } + override val resId: Int? + get() { + return when (this) { + NET_RESULT -> R.string.net_result + PRIZE -> R.string.prize + POSITION -> R.string.position + PLAYERS -> R.string.players + CASHED_OUT -> R.string.cashed_out + INITIAL_BUY_IN -> R.string.initial_stack + BUY_IN -> R.string.buyin + TIPS -> R.string.tips + GAME -> R.string.game + BLINDS -> R.string.blinds + LOCATION -> R.string.location + BANKROLL -> R.string.bankroll + TABLE_SIZE -> R.string.table_size + TOURNAMENT_TYPE -> R.string.tournament_type + TOURNAMENT_NAME -> R.string.tournament_name + TOURNAMENT_FEATURE -> R.string.tournament_feature + START_DATE -> R.string.start_date + END_DATE -> R.string.end_date + BREAK_TIME -> R.string.break_time + COMMENT -> R.string.comment + } + } - override val bottomSheetType: BottomSheetType - get() { - return when (this) { - NET_RESULT, CASHED_OUT, INITIAL_BUY_IN, BREAK_TIME, POSITION, PLAYERS, PRIZE -> BottomSheetType.NUMERIC_TEXT - BUY_IN, TIPS -> BottomSheetType.SUM - BLINDS -> BottomSheetType.DOUBLE_EDIT_TEXT - GAME -> BottomSheetType.LIST_GAME - TOURNAMENT_TYPE -> BottomSheetType.LIST_STATIC - LOCATION, BANKROLL, TOURNAMENT_NAME -> BottomSheetType.LIST - TOURNAMENT_FEATURE -> BottomSheetType.MULTI_SELECTION - TABLE_SIZE -> BottomSheetType.GRID - COMMENT -> BottomSheetType.EDIT_TEXT_MULTI_LINES - else -> BottomSheetType.NONE - } - } + override val viewType: Int + get() { + return when (this) { + NET_RESULT, PRIZE, POSITION, PLAYERS, CASHED_OUT, INITIAL_BUY_IN, BUY_IN, TIPS, + GAME, BLINDS, LOCATION, BANKROLL, TABLE_SIZE, COMMENT, + TOURNAMENT_TYPE, TOURNAMENT_NAME, TOURNAMENT_FEATURE, START_DATE, END_DATE, BREAK_TIME -> RowViewType.TITLE_VALUE.ordinal + } + } + override val inputFragmentType: InputFragmentType + get() { + return when (this) { + NET_RESULT, CASHED_OUT, INITIAL_BUY_IN, BREAK_TIME, POSITION, PLAYERS, PRIZE -> InputFragmentType.NUMERIC_TEXT + BUY_IN, TIPS -> InputFragmentType.SUM + BLINDS -> InputFragmentType.DOUBLE_EDIT_TEXT + GAME -> InputFragmentType.LIST_GAME + TOURNAMENT_TYPE -> InputFragmentType.LIST_STATIC + LOCATION, BANKROLL, TOURNAMENT_NAME -> InputFragmentType.LIST + TOURNAMENT_FEATURE -> InputFragmentType.MULTI_SELECTION + TABLE_SIZE -> InputFragmentType.GRID + COMMENT -> InputFragmentType.EDIT_TEXT_MULTI_LINES + else -> InputFragmentType.NONE + } + } - override fun editingDescriptors(map: Map): ArrayList? { - return when (this) { - BLINDS -> { - val sb: String? by map - val bb: String? by map - arrayListOf( - RowRepresentableEditDescriptor( - sb, R.string.smallblind, InputType.TYPE_CLASS_NUMBER - or InputType.TYPE_NUMBER_FLAG_DECIMAL - ), - RowRepresentableEditDescriptor( - bb, R.string.bigblind, InputType.TYPE_CLASS_NUMBER - or InputType.TYPE_NUMBER_FLAG_DECIMAL - ) - ) - } - BUY_IN -> { - val bb: Double? by map - val fee: Double? by map - val ratedBuyin: Double? by map - val data = arrayListOf() - if (bb != null) { - data.add(RowRepresentableEditDescriptor(100.0 * (bb ?: 0.0))) - data.add(RowRepresentableEditDescriptor(200.0 * (bb ?: 0.0))) - } else if (fee != null) { - data.add(RowRepresentableEditDescriptor((fee ?: 0.0) * 1.0)) - data.add(RowRepresentableEditDescriptor((fee ?: 0.0) * 2.0)) - } else { - data.add(RowRepresentableEditDescriptor(0)) - data.add(RowRepresentableEditDescriptor(0)) - } - data.add(RowRepresentableEditDescriptor(ratedBuyin)) - data.add( - RowRepresentableEditDescriptor( - inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL - ) - ) - data.add( - RowRepresentableEditDescriptor( - inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL - ) - ) - data - } - CASHED_OUT, PRIZE, NET_RESULT -> { - 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 - ) - ) - } - COMMENT -> { - val defaultValue: String? by map - arrayListOf(RowRepresentableEditDescriptor(defaultValue, R.string.comment)) - } - BREAK_TIME -> { - arrayListOf( - RowRepresentableEditDescriptor( - hint = R.string.in_minutes, inputType = InputType.TYPE_CLASS_NUMBER - ) - ) - } - GAME -> { - val limit: Int? by map - val defaultValue: Any? by map - val data: RealmResults? by map - arrayListOf( - RowRepresentableEditDescriptor(limit), - RowRepresentableEditDescriptor(defaultValue, data = data) - ) - } - INITIAL_BUY_IN -> { - val defaultValue: Double? by map - arrayListOf( - RowRepresentableEditDescriptor(defaultValue?.round(), inputType = InputType.TYPE_CLASS_NUMBER) - ) - } - BANKROLL, LOCATION, TOURNAMENT_FEATURE, TOURNAMENT_NAME -> { - val defaultValue: Any? by map - val data: RealmResults? by map - arrayListOf( - RowRepresentableEditDescriptor(defaultValue, data = data) - ) - } - PLAYERS -> { - val defaultValue: Int? by map - arrayListOf( - RowRepresentableEditDescriptor( - defaultValue?.toString(), - inputType = InputType.TYPE_CLASS_NUMBER - ) - ) - } - POSITION -> { - val defaultValue: Int? by map - arrayListOf( - RowRepresentableEditDescriptor( - defaultValue, - inputType = InputType.TYPE_CLASS_NUMBER - ) - ) - } - TABLE_SIZE -> { - val defaultValue: Int? by map - arrayListOf(RowRepresentableEditDescriptor(defaultValue)) - } - TIPS -> { - val sb: String? by map - val bb: String? by map - val tips: Double? by map + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (dataSource == null) return + if (dataSource !is Session) return + if (parent == null) return + if (parent !is RowRepresentableDelegate) return - // Disable the buttons with value = 0, add current value & set the 2 edit texts - arrayListOf( - RowRepresentableEditDescriptor(sb ?: 0.0), - RowRepresentableEditDescriptor(bb ?: 0.0), - RowRepresentableEditDescriptor(tips ?: 0.0), - RowRepresentableEditDescriptor("", inputType = InputType.TYPE_CLASS_NUMBER), - RowRepresentableEditDescriptor("", inputType = InputType.TYPE_CLASS_NUMBER) - ) - } - TOURNAMENT_TYPE -> { - val defaultValue: Any? by map - arrayListOf( - RowRepresentableEditDescriptor(defaultValue, staticData = TournamentType.values().map { - it - }) - ) - } - else -> null - } - } + val session: Session = dataSource + val data = RowEditableDataSource(session.currency) + when (this) { + START_DATE -> { + data.appendDateDescriptor(session.startDate) + } + END_DATE -> { + if (session.startDate == null) { + Toast.makeText(parent.context, R.string.session_missing_start_date, Toast.LENGTH_SHORT).show() + return + } else { + data.appendDateDescriptor(session.endDate ?: session.startDate ?: Date(), session.startDate) + } + } + + BANKROLL -> data.append(session.bankroll, data = session.realm.sorted()) + CASHED_OUT, PRIZE -> data.append( + session.result?.cashout, + inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED + ) + NET_RESULT -> data.append( + session.result?.netResult, + inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED + ) + INITIAL_BUY_IN -> data.append(session.tournamentEntryFee?.round(), inputType = InputType.TYPE_CLASS_NUMBER) + BUY_IN -> { + val bb: Double? = session.cgBigBlind + val fee: Double? = session.tournamentEntryFee + val ratedBuyin: Double? = session.result?.buyin + if (bb != null) { + data.append(100.0 * bb) + data.append(200.0 * bb) + } else if (fee != null) { + data.append(fee) + data.append(fee * 2.0) + } else { + data.append(0) + data.append(0) + } + data.append(ratedBuyin) + data.append(inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL) + data.append(inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL) + } + POSITION -> data.append(session.result?.tournamentFinalPosition, inputType = InputType.TYPE_CLASS_NUMBER) + PLAYERS -> data.append( + session.tournamentNumberOfPlayers.toString(), + inputType = InputType.TYPE_CLASS_NUMBER + ) + TIPS -> { + val sb: String? = session.cgSmallBlind?.round() + val bb: String? = session.cgBigBlind?.round() + val tips: Double? = session.result?.tips + data.append(sb ?: 0.0) + data.append(bb ?: 0.0) + data.append(tips ?: 0.0) + data.append("", inputType = InputType.TYPE_CLASS_NUMBER) + data.append("", inputType = InputType.TYPE_CLASS_NUMBER) + } + GAME -> { + data.append(session.limit) + data.append(session.game, data = session.realm.sorted()) + } + BLINDS -> { + data.append( + session.cgSmallBlind?.round(), + R.string.smallblind, + InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + ) + data.append( + session.cgBigBlind?.round(), + R.string.bigblind, + InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + ) + } + LOCATION -> data.append(session.location, data = session.realm.sorted()) + TABLE_SIZE -> data.append(session.tableSize) + TOURNAMENT_TYPE -> data.append(session.tournamentType, staticData = TournamentType.values().map { it }) + TOURNAMENT_NAME -> data.append(session.tournamentName, data = session.realm.sorted()) + TOURNAMENT_FEATURE -> data.append( + session.tournamentFeatures, + data = session.realm.sorted() + ) + BREAK_TIME -> data.append(hint = R.string.in_minutes, inputType = InputType.TYPE_CLASS_NUMBER) + COMMENT -> data.append(session.comment, R.string.comment) + } + InputFragment.buildAndShow(this, parent, data) + } + + override val valueCanBeClearedWhenEditing: Boolean + get() { + return when (this) { + BANKROLL -> false + else -> true + } + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt index e3ab95ea..5ed0d293 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt @@ -8,6 +8,10 @@ import net.pokeranalytics.android.ui.view.RowViewType enum class SettingRow : RowRepresentable { + // More + BANKROLL_REPORT, + TOP_10, + // About SUBSCRIPTION, VERSION, @@ -37,7 +41,6 @@ enum class SettingRow : RowRepresentable { TERMS_OF_USE, GDPR; - companion object { /** * Return the rows to display for the current session state @@ -45,7 +48,12 @@ enum class SettingRow : RowRepresentable { fun getRows(): ArrayList { val rows = ArrayList() + + rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.reports)) + rows.addAll(arrayListOf(BANKROLL_REPORT, TOP_10)) + rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.information)) +// rows.addAll(arrayListOf(VERSION, RATE_APP, CONTACT_US, BUG_REPORT)) rows.addAll(arrayListOf(SUBSCRIPTION, VERSION, RATE_APP, CONTACT_US, BUG_REPORT)) rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.follow_us)) @@ -76,6 +84,8 @@ enum class SettingRow : RowRepresentable { return it.pluralResId } ?: run { return when (this) { + BANKROLL_REPORT -> R.string.bankroll + TOP_10 -> R.string.top_10 SUBSCRIPTION -> R.string.subscription VERSION -> R.string.version RATE_APP -> R.string.releasenote_rating @@ -96,6 +106,7 @@ enum class SettingRow : RowRepresentable { override val viewType: Int get() { return when (this) { + BANKROLL_REPORT, TOP_10 -> RowViewType.TITLE_ICON_ARROW.ordinal VERSION, SUBSCRIPTION -> RowViewType.TITLE_VALUE.ordinal LANGUAGE, CURRENCY -> RowViewType.TITLE_VALUE_ARROW.ordinal FOLLOW_US -> RowViewType.ROW_FOLLOW_US.ordinal @@ -103,6 +114,15 @@ enum class SettingRow : RowRepresentable { } } + override val imageRes: Int? + get() { + return when(this) { + BANKROLL_REPORT -> R.drawable.ic_outline_lock + TOP_10 -> R.drawable.ic_outline_star + else -> null + } + } + override val relatedResultsRepresentable: LiveData? get() { return when (this) { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SimpleRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SimpleRow.kt index 5dbfb1b7..d1a64f83 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SimpleRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SimpleRow.kt @@ -1,16 +1,15 @@ package net.pokeranalytics.android.ui.view.rowrepresentable import net.pokeranalytics.android.R -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType -import net.pokeranalytics.android.ui.view.DefaultEditDataSource +import net.pokeranalytics.android.ui.fragment.components.input.InputFragmentType import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -enum class SimpleRow : RowRepresentable, DefaultEditDataSource { +enum class SimpleRow : RowRepresentable { NAME; override val resId: Int? = R.string.name override val viewType: Int = RowViewType.TITLE_VALUE.ordinal - override val bottomSheetType: BottomSheetType = BottomSheetType.EDIT_TEXT + override val inputFragmentType: InputFragmentType = InputFragmentType.EDIT_TEXT } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TournamentFeatureRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TournamentFeatureRow.kt index 0d15fc15..1b52eb99 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TournamentFeatureRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TournamentFeatureRow.kt @@ -1,6 +1,49 @@ package net.pokeranalytics.android.ui.view.rowrepresentable -import net.pokeranalytics.android.ui.view.DefaultEditDataSource +import androidx.fragment.app.Fragment +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.TournamentFeature +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.RowViewType -enum class TournamentFeatureRow : RowRepresentable, DefaultEditDataSource +enum class TournamentFeatureRow : RowRepresentable { + NAME; + + override val resId: Int? + get() { + return when (this) { + NAME -> R.string.name + } + } + + override val viewType: Int + get() { + return when (this) { + NAME -> RowViewType.TITLE_VALUE.ordinal + } + } + + override val inputFragmentType: InputFragmentType + get() { + return when (this) { + NAME -> InputFragmentType.EDIT_TEXT + } + } + + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (dataSource == null) return + if (dataSource !is TournamentFeature) return + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + when (this) { + NAME -> data.append(dataSource.name) + } + InputFragment.buildAndShow(this, parent, data) + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TournamentNameRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TournamentNameRow.kt index d03a1d06..3d08f3f9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TournamentNameRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TournamentNameRow.kt @@ -1,6 +1,49 @@ package net.pokeranalytics.android.ui.view.rowrepresentable -import net.pokeranalytics.android.ui.view.DefaultEditDataSource +import androidx.fragment.app.Fragment +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.TournamentName +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.RowViewType -enum class TournamentNameRow : RowRepresentable, DefaultEditDataSource \ No newline at end of file +enum class TournamentNameRow : RowRepresentable { + NAME; + + override val resId: Int? + get() { + return when (this) { + NAME -> R.string.name + } + } + + override val viewType: Int + get() { + return when (this) { + NAME -> RowViewType.TITLE_VALUE.ordinal + } + } + + override val inputFragmentType: InputFragmentType + get() { + return when (this) { + NAME -> InputFragmentType.EDIT_TEXT + } + } + + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (dataSource == null) return + if (dataSource !is TournamentName) return + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + when (this) { + NAME -> data.append(dataSource.name) + } + InputFragment.buildAndShow(this, parent, data) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionRow.kt index c1cb566a..f8478ea4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionRow.kt @@ -1,16 +1,21 @@ package net.pokeranalytics.android.ui.view.rowrepresentable import android.text.InputType -import io.realm.RealmResults +import androidx.fragment.app.Fragment +import io.realm.Realm import net.pokeranalytics.android.R -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType -import net.pokeranalytics.android.ui.view.DefaultEditDataSource -import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor -import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.model.realm.Bankroll +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.TransactionType +import net.pokeranalytics.android.ui.adapter.RowEditableDelegate +import net.pokeranalytics.android.ui.fragment.components.input.InputFragment +import net.pokeranalytics.android.ui.fragment.components.input.InputFragmentType +import net.pokeranalytics.android.ui.view.* +import net.pokeranalytics.android.util.extensions.round +import net.pokeranalytics.android.util.extensions.sorted -enum class TransactionRow : RowRepresentable, DefaultEditDataSource { +enum class TransactionRow : RowRepresentable { BANKROLL, TYPE, AMOUNT, @@ -39,48 +44,50 @@ enum class TransactionRow : RowRepresentable, DefaultEditDataSource { } } - override val bottomSheetType: BottomSheetType + override val inputFragmentType: InputFragmentType get() { return when (this) { - BANKROLL -> BottomSheetType.LIST - TYPE -> BottomSheetType.LIST - AMOUNT -> BottomSheetType.EDIT_TEXT - COMMENT -> BottomSheetType.EDIT_TEXT_MULTI_LINES - DATE -> BottomSheetType.NONE + BANKROLL -> InputFragmentType.LIST + TYPE -> InputFragmentType.LIST + AMOUNT -> InputFragmentType.EDIT_TEXT + COMMENT -> InputFragmentType.EDIT_TEXT_MULTI_LINES + DATE -> InputFragmentType.NONE } } - override fun editingDescriptors(map: Map): ArrayList? { - return when (this) { - BANKROLL -> { - val defaultValue : Any? by map - val data : RealmResults? by map - arrayListOf( - RowRepresentableEditDescriptor(defaultValue, data = data) - ) - } - TYPE -> { - val defaultValue : Any? by map - val data : RealmResults? by map - arrayListOf( - RowRepresentableEditDescriptor(defaultValue, data = data) - ) - } - AMOUNT -> { - val defaultValue: String? by map - arrayListOf( - RowRepresentableEditDescriptor( - defaultValue, - inputType = InputType.TYPE_CLASS_NUMBER - or InputType.TYPE_NUMBER_FLAG_DECIMAL - or InputType.TYPE_NUMBER_FLAG_SIGNED - )) - } - COMMENT -> { - val defaultValue : String? by map - arrayListOf(RowRepresentableEditDescriptor(defaultValue, R.string.comment)) - } - else -> super.editingDescriptors(map) - } - } + override val valueCanBeClearedWhenEditing: Boolean + get() { + return when (this) { + DATE -> false + else -> true + } + } + + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (dataSource == null) return + if (dataSource !is Transaction) return + if (parent == null) return + if (parent !is RowEditableDelegate) return + val data = RowEditableDataSource() + val realm = dataSource.realm?.let { it } ?: run { Realm.getDefaultInstance() } + when (this) { + DATE -> { + data.appendDateDescriptor(dataSource.date, onlyDate = true) + } + BANKROLL -> data.append( + dataSource.bankroll, + data = realm.sorted() + ) + TYPE -> data.append( + dataSource.type, + data = realm.sorted() + ) + AMOUNT -> data.append( + (if (dataSource.amount != 0.0) dataSource.amount.round() else ""), + inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED + ) + COMMENT -> data.append(dataSource.comment, R.string.comment) + } + InputFragment.buildAndShow(this, parent, data) + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionTypeRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionTypeRow.kt index a54484b1..73ac7f11 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionTypeRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionTypeRow.kt @@ -1,17 +1,24 @@ package net.pokeranalytics.android.ui.view.rowrepresentable +import androidx.fragment.app.Fragment import net.pokeranalytics.android.R -import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType -import net.pokeranalytics.android.ui.view.DefaultEditDataSource +import net.pokeranalytics.android.exceptions.PokerAnalyticsException +import net.pokeranalytics.android.model.realm.TransactionType +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.RowViewType -enum class TransactionTypeRow : RowRepresentable, DefaultEditDataSource { +enum class TransactionTypeRow : RowRepresentable { + NAME, TRANSACTION_ADDITIVE; override val resId: Int? get() { return when (this) { + NAME -> R.string.name TRANSACTION_ADDITIVE -> R.string.additive } } @@ -19,17 +26,32 @@ enum class TransactionTypeRow : RowRepresentable, DefaultEditDataSource { override val viewType: Int get() { return when (this) { + NAME -> RowViewType.TITLE_VALUE.ordinal TRANSACTION_ADDITIVE -> RowViewType.TITLE_SWITCH.ordinal } } - override val bottomSheetType: BottomSheetType + override val inputFragmentType: InputFragmentType get() { return when (this) { - TRANSACTION_ADDITIVE -> BottomSheetType.NONE + NAME -> InputFragmentType.EDIT_TEXT + TRANSACTION_ADDITIVE -> InputFragmentType.NONE } } + override fun startEditing(dataSource: Any?, parent: Fragment?) { + if (dataSource == null) return + if (dataSource !is TransactionType) return + if (parent == null) return + if (parent !is RowRepresentableDelegate) return + val data = RowEditableDataSource() + when (this) { + NAME -> data.append(dataSource.name) + else -> PokerAnalyticsException.InputFragmentException + } + InputFragment.buildAndShow(this, parent, data) + } + } diff --git a/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt b/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt index 604b19f7..a5e94be2 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt @@ -5,7 +5,9 @@ import io.realm.kotlin.where import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Game +import net.pokeranalytics.android.model.realm.Location import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.util.extensions.getOrCreate import timber.log.Timber import java.util.* @@ -18,9 +20,9 @@ class FakeDataManager { */ fun createFakeSessions(numberOfSessions: Int = 1000, callback: ((success: Boolean) -> Unit)? = null) { - val buyinList = arrayListOf(100.0, 200.0, 300.0, 500.0, 1000.0, 2000.0) + val buyinList = arrayListOf(100.0, 200.0, 300.0, 500.0, 1000.0, 2000.0, 3000.0) val resultsList = arrayListOf( - -2500.0, -2000.0, -1500.0, -1000.0, -500.0, 200.0, 1000.0, 1500.0, 2500.0, 3000.0 + 0.0, 50.0, 100.0, 200.0, 1000.0, 1500.0, 2500.0, 3000.0 ) val commitFrequency = 100 @@ -30,6 +32,15 @@ class FakeDataManager { val realm = Realm.getDefaultInstance() val games = realm.where().findAll() val bankroll = realm.where().findAll().firstOrNull() + val locations = realm.where().findAll() + + if (locations.size == 0) { + realm.executeTransaction { + listOf("Bellagio", "Aria", "Borgata").map { + realm.getOrCreate(it) + } + } + } // Test endedSessions @@ -47,11 +58,8 @@ class FakeDataManager { realm.beginTransaction() } - val session = Session.newInstance(realm, Math.random() > 0.5, bankroll) - - val bigBlind = arrayListOf(1.0, 2.0, 4.0).random() - session.cgBigBlind = bigBlind - session.cgSmallBlind = bigBlind / 2.0 + val isTournament = Math.random() > 0.5 + val session = Session.newInstance(realm, isTournament, bankroll) val calendar = Calendar.getInstance() val twoDaysBetweenEachSession: Long = (2 * numberOfSessions) * 24 * 60 * 60 * 1000L // approx! @@ -70,13 +78,23 @@ class FakeDataManager { session.limit = Limit.values().random().ordinal session.game = games.random() + session.location = locations.random() + session.tableSize = (2..10).random() + val buyin = buyinList.random() session.result?.let { result -> - val buyin = buyinList.random() result.buyin = buyinList.random() result.cashout = resultsList.random() + buyin } + if (isTournament) { + session.tournamentEntryFee = buyin + } else { + val bigBlind = arrayListOf(1.0, 2.0, 4.0).random() + session.cgBigBlind = bigBlind + session.cgSmallBlind = bigBlind / 2.0 + } + } realm.commitTransaction() diff --git a/app/src/main/java/net/pokeranalytics/android/util/LocaleUtils.kt b/app/src/main/java/net/pokeranalytics/android/util/LocaleUtils.kt deleted file mode 100644 index da05d09a..00000000 --- a/app/src/main/java/net/pokeranalytics/android/util/LocaleUtils.kt +++ /dev/null @@ -1,36 +0,0 @@ -package net.pokeranalytics.android.util - -import android.content.Context -import java.util.* - - - -class LocaleUtils { - - companion object { - - /** - * Return the current locale - */ - fun getCurrentLocale(context: Context) : Locale { - val defaultLocaleCode = Preferences.getString(Preferences.Keys.LOCALE_CODE, context) - var locale = Locale.getDefault() - if (defaultLocaleCode != null) { - locale = Locale(defaultLocaleCode) - Locale.setDefault(locale) - } - return locale - } - - - /** - * - */ - fun setCurrentLocale(context: Context, language: String) { - Preferences.setString(Preferences.Keys.LOCALE_CODE, language, context) - } - - - } - -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/LocationManager.kt b/app/src/main/java/net/pokeranalytics/android/util/LocationManager.kt index 30d974d4..b66efa0f 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/LocationManager.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/LocationManager.kt @@ -38,7 +38,7 @@ class LocationManager(private var context: Context) { // Use fields to define the data types to return. val placeFields = Arrays.asList(Place.Field.NAME, Place.Field.ADDRESS, Place.Field.LAT_LNG) - // Use the builder to create a FindCurrentPlaceRequest. + // Use the builder to buildAndShow a FindCurrentPlaceRequest. val request = FindCurrentPlaceRequest.builder(placeFields).build() // Call findCurrentPlace and handle the response (first check that the user has granted permission). diff --git a/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt b/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt index 38ace451..b4c73024 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt @@ -2,170 +2,229 @@ package net.pokeranalytics.android.util import android.content.Context import android.preference.PreferenceManager +import io.realm.Realm +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.util.extensions.count import java.util.* class Preferences { - enum class Keys(var identifier: String) { - CURRENCY_CODE("CurrencyCode"), - LOCALE_CODE("LocaleCode"), - FIRST_LAUNCH("firstLaunch"), - STOP_SHOWING_DISCLAIMER("stopShowingDisclaimer"), - ACTIVE_FILTER_ID("ActiveFilterId"), - LATEST_PURCHASE("latestPurchase"), - PATCH_BREAK("patchBreaks"), - PATCH_TRANSACTION_TYPES_NAMES("patchTransactionTypesNames") - } - - companion object { - - fun setString(key: Keys, value: String, context: Context) { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - val editor = preferences.edit() - editor.putString(key.identifier, value) - editor.apply() - } - - private fun removeKey(key: Keys, context: Context) { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - val editor = preferences.edit() - editor.remove(key.identifier) - editor.apply() - } - - fun getString(key: Keys, context: Context) : String? { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - return preferences.getString(key.identifier, null) - } - - fun setBoolean(key: Keys, value: Boolean, context: Context) { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - val editor = preferences.edit() - editor.putBoolean(key.identifier, value) - editor.apply() - } - - fun getBoolean(key: Keys, context: Context, defaultValue: Boolean? = false) : Boolean { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - return preferences.getBoolean(key.identifier, defaultValue ?: false) - } - - fun setCurrencyCode(currencyCode: String, context: Context) { - setString(Keys.CURRENCY_CODE, currencyCode, context) - UserDefaults.setCurrencyValues(context) - } - - fun setActiveFilterId(filterId: String, context: Context) { - setString(Keys.ACTIVE_FILTER_ID, filterId, context) - } - - fun getActiveFilterId(context: Context) : String? { - return getString(Keys.ACTIVE_FILTER_ID, context) - } - - fun removeActiveFilterId(context: Context) { - removeKey(Keys.ACTIVE_FILTER_ID, context) - } - - private fun getCurrencyCode(context: Context) : String? { - return getString(Keys.CURRENCY_CODE, context) - } - - private fun getCurrencyLocale(context : Context) : Locale? { - getCurrencyCode(context)?.let { currencyCode -> - Locale.getAvailableLocales().filter { - try { - Currency.getInstance(it).currencyCode == currencyCode - } catch (e: Exception) { - false - } - }.firstOrNull()?.let { - return it - } - } - return null - } - - fun getDefaultCurrency(context: Context) : Currency? { - getCurrencyLocale(context)?.let { - return Currency.getInstance(it) - } - return null - } - - fun setStopShowingDisclaimer(context: Context) { - setBoolean(Keys.STOP_SHOWING_DISCLAIMER, true, context) - } - - fun shouldShowDisclaimer(context: Context) : Boolean { - return !getBoolean(Keys.STOP_SHOWING_DISCLAIMER, context) - } - - fun executeOnce(key: Keys, context: Context, executable: () -> Unit) { - if (!getBoolean(key, context)) { - executable.invoke() - setBoolean(key, true, context) - } - } - - } + interface PreferenceKey { + var identifier: String + } + + enum class DBPatch(var key: String) : PreferenceKey { + LONE_COMPUTABLE_RESULTS("loneComputableResult"); + + override var identifier: String = "" + get() { + return "dbpatch." + this.key + } + } + + enum class Keys(override var identifier: String) : PreferenceKey { + CURRENCY_CODE("CurrencyCode"), + LOCALE_CODE("LocaleCode"), + FIRST_LAUNCH("firstLaunch"), + STOP_SHOWING_DISCLAIMER("stopShowingDisclaimer"), + STOP_SHOWING_DUPLICATE("stopShowingDuplicate"), + ACTIVE_FILTER_ID("ActiveFilterId"), + LATEST_PURCHASE("latestPurchase"), + PATCH_BREAK("patchBreaks"), + PATCH_TRANSACTION_TYPES_NAMES("patchTransactionTypesNames"), + PATCH_BLINDS_FORMAT("patchBlindFormat"), + ADD_NEW_TRANSACTION_TYPES("addNewTransactionTypes") + } + + enum class FeedMessage { + DISCLAIMER, + DUPLICATE; + + val resId: Int + get() { + return when (this) { + DISCLAIMER -> R.string.disclaimer + DUPLICATE -> R.string.longtap_to_duplicate + } + } + + val key: Keys + get() { + return when (this) { + DISCLAIMER -> Keys.STOP_SHOWING_DISCLAIMER + DUPLICATE -> Keys.STOP_SHOWING_DUPLICATE + } + } + + } + + companion object { + + fun setString(key: PreferenceKey, value: String, context: Context) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val editor = preferences.edit() + editor.putString(key.identifier, value) + editor.apply() + } + + private fun removeKey(key: Keys, context: Context) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val editor = preferences.edit() + editor.remove(key.identifier) + editor.apply() + } + + fun getString(key: Keys, context: Context): String? { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + return preferences.getString(key.identifier, null) + } + + fun setBoolean(key: PreferenceKey, value: Boolean, context: Context) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val editor = preferences.edit() + editor.putBoolean(key.identifier, value) + editor.apply() + } + + fun getBoolean(key: PreferenceKey, context: Context, defaultValue: Boolean? = false): Boolean { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + return preferences.getBoolean(key.identifier, defaultValue ?: false) + } + + fun setCurrencyCode(currencyCode: String, context: Context) { + setString(Keys.CURRENCY_CODE, currencyCode, context) + UserDefaults.setCurrencyValues(context) + } + + fun setActiveFilterId(filterId: String, context: Context) { + setString(Keys.ACTIVE_FILTER_ID, filterId, context) + } + + fun getActiveFilterId(context: Context): String? { + return getString(Keys.ACTIVE_FILTER_ID, context) + } + + fun removeActiveFilterId(context: Context) { + removeKey(Keys.ACTIVE_FILTER_ID, context) + } + + private fun getCurrencyCode(context: Context): String? { + return getString(Keys.CURRENCY_CODE, context) + } + + fun getCurrencyLocale(context: Context): Locale? { + getCurrencyCode(context)?.let { currencyCode -> + UserDefaults.availableCurrencyLocales.filter { + Currency.getInstance(it).currencyCode == currencyCode + }.firstOrNull()?.let { + return it + } + } + return null + } + + fun getDefaultCurrency(context: Context): Currency? { + getCurrencyLocale(context)?.let { + return Currency.getInstance(it) + } + return null + } + + fun setStopShowingMessage(message: FeedMessage, context: Context) { + setBoolean(message.key,true, context) + } + + fun feedMessageToShow(context: Context): FeedMessage? { + + if (!getBoolean(Keys.STOP_SHOWING_DISCLAIMER, context)) { + return FeedMessage.DISCLAIMER + } + + val realm = Realm.getDefaultInstance() + val sessionCount = realm.count(Session::class.java) + realm.close() + if (sessionCount > 1 && !getBoolean(Keys.STOP_SHOWING_DUPLICATE, context)) { + return FeedMessage.DUPLICATE + } + return null + } + + fun executeOnce(key: Keys, context: Context, executable: () -> Unit) { + if (!getBoolean(key, context)) { + executable.invoke() + setBoolean(key, true, context) + } + } + + } } class UserDefaults private constructor(context: Context) { - init { - setCurrencyValues(context) - } - - companion object : SingletonHolder(::UserDefaults) { - lateinit var currency : Currency - - fun setCurrencyValues(context: Context) { - currency = Preferences.getDefaultCurrency(context) ?: getLocaleCurrency() - } - - /** - * Return the locale currency, or en_US if there - */ - fun getLocaleCurrency() : Currency { - return try { - Currency.getInstance(Locale.getDefault()) - } catch (ex: Exception) { - when (Locale.getDefault().language) { - "en" -> Currency.getInstance(Locale("en", "US")) - "fr" -> Currency.getInstance(Locale("fr", "FR")) - "es" -> Currency.getInstance(Locale("es", "ES")) - "de" -> Currency.getInstance(Locale("de", "DE")) - "ja" -> Currency.getInstance(Locale("ja", "JP")) - "zh" -> Currency.getInstance(Locale("zh", "CN")) - else -> Currency.getInstance(Locale("en", "US")) - } - } - } - } + init { + setCurrencyValues(context) + } + + companion object : SingletonHolder(::UserDefaults) { + lateinit var currency: Currency + + fun setCurrencyValues(context: Context) { + currency = Preferences.getDefaultCurrency(context) ?: getLocaleCurrency() + } + + val availableCurrencyLocales = Locale.getAvailableLocales().mapNotNull { + try { + Currency.getInstance(it) + it + } catch (e: Exception) { + null + } + } + + /** + * Return the locale currency, or en_US if there + */ + fun getLocaleCurrency(): Currency { + return try { + Currency.getInstance(Locale.getDefault()) + } catch (ex: Exception) { + when (Locale.getDefault().language) { + "en" -> Currency.getInstance(Locale("en", "US")) + "fr" -> Currency.getInstance(Locale("fr", "FR")) + "es" -> Currency.getInstance(Locale("es", "ES")) + "de" -> Currency.getInstance(Locale("de", "DE")) + "ja" -> Currency.getInstance(Locale("ja", "JP")) + "zh" -> Currency.getInstance(Locale("zh", "CN")) + else -> Currency.getInstance(Locale("en", "US")) + } + } + } + } } open class SingletonHolder(creator: (A) -> T) { - private var creator: ((A) -> T)? = creator - @Volatile private var instance: T? = null - - fun init(context: A): T { - val i = instance - if (i != null) { - return i - } - - return synchronized(this) { - val i2 = instance - if (i2 != null) { - i2 - } else { - val created = creator!!(context) - instance = created - creator = null - created - } - } - } + private var creator: ((A) -> T)? = creator + @Volatile + private var instance: T? = null + + fun init(context: A): T { + val i = instance + if (i != null) { + return i + } + + return synchronized(this) { + val i2 = instance + if (i2 != null) { + i2 + } else { + val created = creator!!(context) + instance = created + creator = null + created + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt b/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt index 955febdc..9328f756 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt @@ -2,7 +2,10 @@ package net.pokeranalytics.android.util.billing import android.app.Activity import android.content.Context +import android.os.Handler +import android.os.Looper import com.android.billingclient.api.* +import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.R import timber.log.Timber import java.io.IOException @@ -40,11 +43,23 @@ object AppGuard : PurchasesUpdatedListener { */ private var billingClientAvailable: Boolean = false + /** + * Returns the true state of the user's pro subscription + */ + private var _isProUser: Boolean = false + /** * Returns whether the user has the pro subscription + * Always true for debugging */ - var isProUser: Boolean = false - private set + val isProUser: Boolean + get() { + return if (BuildConfig.DEBUG) { + true + } else { + this._isProUser + } + } /** * A delegate to notify when the purchase has succeeded @@ -106,7 +121,7 @@ object AppGuard : PurchasesUpdatedListener { this.resetPurchases() // Automatically checks for purchases (when switching devices for example) val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS) - if (purchasesResult != null) { + if (purchasesResult != null && purchasesResult.purchasesList != null) { purchasesResult.purchasesList.forEach { this.handlePurchase(it) } @@ -118,7 +133,7 @@ object AppGuard : PurchasesUpdatedListener { * This is done before restoring in order to ensure that subscriptions stops */ private fun resetPurchases() { - this.isProUser = false + this._isProUser = false } /** @@ -191,11 +206,13 @@ object AppGuard : PurchasesUpdatedListener { val date = Date(purchase.purchaseTime) Timber.d("*** Auto renewing = ${purchase.isAutoRenewing}") - Timber.d("*** purchaseTime = ${date}") + Timber.d("*** purchaseTime = $date") - this.isProUser = true + this._isProUser = true this.purchaseDelegate?.let { - it.purchaseDidSucceed(purchase) + Handler(Looper.getMainLooper()).post { + it.purchaseDidSucceed(purchase) + } this.purchaseDelegate = null } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt index f4ef3174..b28c5182 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -1,8 +1,13 @@ package net.pokeranalytics.android.util.csv import io.realm.Realm -import io.realm.RealmModel +import io.realm.kotlin.deleteFromRealm +import net.pokeranalytics.android.model.interfaces.Identifiable +import net.pokeranalytics.android.model.interfaces.ObjectIdentifier +import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.util.extensions.findById import org.apache.commons.csv.CSVRecord +import timber.log.Timber /** * The various sources of CSV @@ -16,21 +21,58 @@ enum class DataSource { /** * A DataCSVDescriptor produces RealmModel instances for each row */ -abstract class DataCSVDescriptor(source: DataSource, vararg elements: CSVField) : CSVDescriptor(source, *elements) { +abstract class DataCSVDescriptor(source: DataSource, vararg elements: CSVField) : CSVDescriptor(source, *elements) { - val realmModels = mutableListOf() + /** + * List of Realm object identificators + */ + val realmModelIds = mutableListOf() abstract fun parseData(realm: Realm, record: CSVRecord): T? - override fun parse(realm: Realm, record: CSVRecord) { + override fun parse(realm: Realm, record: CSVRecord): Int { val data = this.parseData(realm, record) data?.let { - this.realmModels.add(it) +// Timber.d(">>>>>>> identifier added: ${it.id}") + this.realmModelIds.add(it.objectIdentifier) + } + return if (data != null) 1 else 0 + } + + protected fun addAdditionallyCreatedIdentifiable(identifiable: Identifiable) { + this.realmModelIds.add(identifiable.objectIdentifier) + } + + override fun cancel(realm: Realm) { + + if (realm.isInTransaction) { + this.deleteInsertedFromRealm(realm) + } else { + realm.executeTransaction { + this.deleteInsertedFromRealm(realm) + } } } + private fun deleteInsertedFromRealm(realm: Realm) { + this.realmModelIds.forEach { identifier -> + val data = realm.findById(identifier.clazz, identifier.id) + if (data is Session) { + data.cleanup() + } + data?.deleteFromRealm() + } + this.realmModelIds.clear() + + } + + override fun save(realm: Realm) { + super.save(realm) + this.realmModelIds.clear() + } + } /** @@ -58,7 +100,23 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) * The list of all managed CSVDescriptors */ val all: List = - listOf(SessionCSVDescriptor.pokerIncomeCash, SessionCSVDescriptor.pokerBankrollTracker, SessionCSVDescriptor.runGoodCashGames, SessionCSVDescriptor.runGoodTournaments) + listOf(ProductCSVDescriptors.pokerIncomeCash, + ProductCSVDescriptors.pokerBankrollTracker, + ProductCSVDescriptors.runGoodCashGames, + ProductCSVDescriptors.runGoodTournaments) + } + + /** + * Method called when iterating on a CSVRecord + */ + abstract fun parse(realm: Realm, record: CSVRecord): Int + + open fun save(realm: Realm) { + + } + + open fun cancel(realm: Realm) { + } /** @@ -72,20 +130,14 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) this.fields.forEach { field -> val index = headers.indexOf(field.header) - this.fieldMapping[field] = index - if (index >= 0) { + this.fieldMapping[field] = index count++ } - } - - return count == this.fields.size + val mandatoryfields = this.fields.filter { it.optional == false } + Timber.d("source= ${this.source.name} > total fields = ${this.fields.size}, identified = $count") + return count >= mandatoryfields.size } - /** - * Method called when iterating on a CSVRecord - */ - abstract fun parse(realm: Realm, record: CSVRecord) - } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt index 3e7e6659..a5367951 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt @@ -25,12 +25,24 @@ interface NumberCSVField: TypedCSVField { return try { formatter.parse(value).toDouble() } catch (e: ParseException) { - Timber.d("Unparseable number: $value") + Timber.d("Field ${header} > Unparseable number: $value") null } } } +interface DataCSVField : TypedCSVField { + + override fun parse(value: String): T? { + + this.callback?.let { + return it(value) + } + return null + } + +} + interface DateCSVField : TypedCSVField { val dateFormat: String? @@ -58,8 +70,8 @@ interface BlindCSVField : TypedCSVField> { val strBlinds = value.split("/") if (strBlinds.size == 2) { - val bb = strBlinds.last().toDouble() val sb = strBlinds.first().toDouble() + val bb = strBlinds.last().toDouble() return Pair(sb, bb) } else { Timber.d("Blinds could not be parsed: $value") @@ -77,4 +89,8 @@ interface TypedCSVField : CSVField { interface CSVField { val header: String + val optional: Boolean + get() { + return false + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt index 7a1bfb90..4ffb6ca9 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt @@ -1,7 +1,10 @@ package net.pokeranalytics.android.util.csv +import android.os.Handler +import android.os.Looper import io.realm.Realm import org.apache.commons.csv.CSVFormat +import org.apache.commons.csv.CSVParser import org.apache.commons.csv.CSVRecord import timber.log.Timber import java.io.FileReader @@ -11,6 +14,10 @@ import java.io.Reader class ImportException(message: String) : Exception(message) +interface ImportDelegate { + fun parsingCountUpdate(importedCount: Int, totalCount: Int) +} + /** * A CSVImporter is a class in charge of parsing a CSV file and processing it * When starting the parsing of a file, the instance will search for a CSVDescriptor, which describes @@ -20,6 +27,11 @@ class ImportException(message: String) : Exception(message) */ open class CSVImporter(istream: InputStream) { + /** + * The object being notified of the import progress + */ + var delegate: ImportDelegate? = null + /** * Number of commits required to commit a Realm transaction */ @@ -51,11 +63,27 @@ open class CSVImporter(istream: InputStream) { * Stores the descriptors found */ private var usedDescriptors: MutableList = mutableListOf() + /** * The currently used CSVDescriptor for parsing */ private var currentDescriptor: CSVDescriptor? = null + /** + * The number of valid record parsed + */ + private var totalParsedRecords = 0 + + /** + * The number of successfully imported records + */ + private var importedRecords = 0 + + /** + * The CSV parser + */ + private lateinit var parser: CSVParser + /** * Constructs a CSVParser object and starts parsing the CSV */ @@ -71,7 +99,7 @@ open class CSVImporter(istream: InputStream) { reader = InputStreamReader(this.inputStream) } - val parser = CSVFormat.DEFAULT.withAllowMissingColumnNames().parse(reader) + this.parser = CSVFormat.DEFAULT.withAllowMissingColumnNames().parse(reader) Timber.d("Starting import...") @@ -79,7 +107,8 @@ open class CSVImporter(istream: InputStream) { parser.forEachIndexed { index, record -> - Timber.d("line $index") +// Timber.d("line $index") + this.notifyDelegate() if (this.currentDescriptor == null) { // find descriptor this.currentDescriptor = this.findDescriptor(record) @@ -97,6 +126,7 @@ open class CSVImporter(istream: InputStream) { } else { // parse + // batch commit val parsingIndex = index + 1 if (parsingIndex % COMMIT_FREQUENCY == 0) { Timber.d("****** committing at $parsingIndex sessions...") @@ -107,19 +137,27 @@ open class CSVImporter(istream: InputStream) { this.currentDescriptor?.let { if (record.size() == 0) { this.usedDescriptors.add(it) - this.currentDescriptor = null // reset descriptor when encountering an empty line (multiple descriptors can be found in a single file) + this.currentDescriptor = + null // reset descriptor when encountering an empty line (multiple descriptors can be found in a single file) this.descriptorFindingAttempts = 0 } else { - it.parse(realm, record) + val count = it.parse(realm, record) + + this.importedRecords += count + this.totalParsedRecords++ + + this.notifyDelegate() } } ?: run { realm.cancelTransaction() - throw IllegalStateException("CSVDescriptor should never be null here") + throw ImportException("CSVDescriptor should never be null here") } } } + this.notifyDelegate() + realm.commitTransaction() Timber.d("Ending import...") @@ -127,10 +165,16 @@ open class CSVImporter(istream: InputStream) { realm.close() } + private fun notifyDelegate() { + Handler(Looper.getMainLooper()).post { + this.delegate?.parsingCountUpdate(this.importedRecords, this.totalParsedRecords) + } + } + /** * Search for a descriptor in the list of managed formats */ - private fun findDescriptor(record: CSVRecord) : CSVDescriptor? { + private fun findDescriptor(record: CSVRecord): CSVDescriptor? { CSVDescriptor.all.forEach { descriptor -> if (descriptor.matches(record)) { @@ -143,16 +187,23 @@ open class CSVImporter(istream: InputStream) { } fun save(realm: Realm) { + this.parser.close() +// realm.refresh() + this.currentDescriptor?.save(realm) this.usedDescriptors.forEach { descriptor -> - - if (descriptor is DataCSVDescriptor<*>) { - realm.executeTransaction { - realm.copyToRealm(descriptor.realmModels) - } - } + descriptor.save(realm) } + } + fun cancel(realm: Realm) { + this.parser.close() +// realm.refresh() + + this.currentDescriptor?.cancel(realm) + this.usedDescriptors.forEach { descriptor -> + descriptor.cancel(realm) + } } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt new file mode 100644 index 00000000..02d86087 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt @@ -0,0 +1,114 @@ +package net.pokeranalytics.android.util.csv + +class ProductCSVDescriptors { + + companion object { + + val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor( + DataSource.POKER_INCOME, + false, + SessionField.Start("Start Time"), + SessionField.End("End Time"), + SessionField.Buyin("Buy In"), + SessionField.CashedOut("Cashed Out"), + SessionField.Break("Break Minutes"), + SessionField.LimitType("Limit Type"), + SessionField.Game("Game"), + SessionField.Bankroll("Bankroll"), + SessionField.Location("Location"), + SessionField.Location("Location Type"), + SessionField.Comment("Note"), + SessionField.Tips("Tips"), + SessionField.Blind("Stake") + ) + + val pokerBankrollTracker: CSVDescriptor = SessionCSVDescriptor( + DataSource.POKER_BANKROLL_TRACKER, + true, + SessionField.Start("starttime", dateFormat = "MM/dd/yy HH:mm"), + SessionField.End("endtime", dateFormat = "MM/dd/yy HH:mm"), + SessionField.SessionType("variant"), + SessionField.Buyin("buyin"), + SessionField.CashedOut("cashout"), + SessionField.Rebuy("rebuycosts"), + SessionField.Addon("addoncosts"), + SessionField.Break("breakminutes"), + SessionField.LimitType("limit"), + SessionField.Game("game"), + SessionField.Bankroll("currency"), // same as currency code + SessionField.Location("type"), + SessionField.Comment("comment", true), + SessionField.Tips("expensesfromstack"), + SessionField.SmallBlind("smallblind"), + SessionField.BigBlind("bigblind"), + SessionField.TournamentNumberOfPlayers("player"), + SessionField.TournamentPosition("place"), + SessionField.TournamentName("mttname"), + SessionField.CurrencyCode("currency"), + SessionField.StackingIn("sharesincomings"), + SessionField.StackingOut("sharesoutgoings"), + SessionField.CurrencyRate("exchangerate"), + SessionField.TableSize("tablesize") + ) + + val runGoodTournaments: CSVDescriptor = SessionCSVDescriptor( + DataSource.RUNGOOD, + true, + SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), + SessionField.StartTime("Start Time"), + SessionField.End("End Date"), + SessionField.EndTime("End Time"), + SessionField.Buyin("Total Buy-In"), + SessionField.CashedOut("Winnings"), + SessionField.NetResult("Profit"), + SessionField.Break("Break"), + SessionField.LimitType("Limit Type"), + SessionField.Game("Game"), + SessionField.Bankroll("Bankroll"), + SessionField.TableSize("Table Type"), + SessionField.Location("Location"), + SessionField.LocationType("Location Type"), + SessionField.Comment("Notes"), + SessionField.CurrencyCode("Currency"), + SessionField.TournamentName("Event Name"), + SessionField.TournamentNumberOfPlayers("Total Players"), + SessionField.TournamentPosition("Finished Place"), + SessionField.TournamentType("Single-Table/Multi-Table") + + ) + + val runGoodCashGames: CSVDescriptor = SessionCSVDescriptor( + DataSource.RUNGOOD, + false, + SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), + SessionField.StartTime("Start Time", dateFormat = "HH:mm"), + SessionField.End("End Date", dateFormat = "dd/MM/yyyy"), + SessionField.EndTime("End Time", dateFormat = "HH:mm"), + SessionField.Buyin("Total Buy-In"), + SessionField.CashedOut("Cashed Out"), + SessionField.NetResult("Profit"), + SessionField.Break("Break"), + SessionField.LimitType("Limit Type"), + SessionField.Game("Game"), + SessionField.Bankroll("Bankroll"), + SessionField.TableSize("Table Type"), + SessionField.Location("Location"), + SessionField.LocationType("Location Type"), + SessionField.Comment("Notes"), + SessionField.CurrencyCode("Currency"), + SessionField.Blind("Stakes", callback = { value -> + // $10/20 + value.drop(1) + val blinds = value.split("/") + if (blinds.size == 2) { + return@Blind Pair(blinds.first().toDouble(), blinds.last().toDouble()) + } else { + return@Blind null + } + }) + ) + + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt index d33e719f..5b76f362 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -4,235 +4,138 @@ import io.realm.Realm import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.TableSize import net.pokeranalytics.android.model.TournamentType +import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Session -import net.pokeranalytics.android.model.utils.SessionUtils +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.TransactionType +import net.pokeranalytics.android.model.utils.DataUtils import net.pokeranalytics.android.util.extensions.getOrCreate import net.pokeranalytics.android.util.extensions.setHourMinutes import org.apache.commons.csv.CSVRecord +import timber.log.Timber import java.util.* -/** - * The enumeration of Session fields - */ -sealed class SessionField { - - data class Start( - override var header: String, - override var callback: ((String) -> Date?)? = null, - override val dateFormat: String? = null - ) : DateCSVField - - data class StartTime( - override var header: String, - override var callback: ((String) -> Date?)? = null, - override val dateFormat: String? = null - ) : DateCSVField - - data class End( - override var header: String, - override var callback: ((String) -> Date?)? = null, - override val dateFormat: String? = null - ) : DateCSVField - - data class EndTime( - override var header: String, - override var callback: ((String) -> Date?)? = null, - override val dateFormat: String? = null - ) : DateCSVField - - data class Buyin( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class NetResult( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class CashedOut( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class Break( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class Tips( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class SmallBlind( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class BigBlind( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class Blind(override var header: String, override var callback: ((String) -> Pair?)? = null) : BlindCSVField - data class Game(override var header: String) : CSVField - data class Location(override var header: String) : CSVField - data class LocationType(override var header: String) : CSVField - data class Bankroll(override var header: String) : CSVField - data class LimitType(override var header: String) : CSVField - data class Comment(override var header: String) : CSVField - data class SessionType(override var header: String) : CSVField - data class TableSize(override var header: String) : CSVField - data class CurrencyCode(override var header: String) : CSVField - data class TournamentName(override var header: String) : CSVField - data class TournamentType(override var header: String) : CSVField - - data class CurrencyRate( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class TournamentPosition( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField - - data class TournamentNumberOfPlayers( - override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null - ) : NumberCSVField -} - /** * A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects */ -class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean, vararg elements: CSVField) : DataCSVDescriptor(source, *elements) { - - companion object { - val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor( - DataSource.POKER_INCOME, - false, - SessionField.Start("Start Time"), - SessionField.End("End Time"), - SessionField.Buyin("Buy In"), - SessionField.CashedOut("Cashed Out"), - SessionField.Break("Break Minutes"), - SessionField.LimitType("Limit Type"), - SessionField.Game("Game"), - SessionField.Bankroll("Bankroll"), - SessionField.Location("Location"), - SessionField.Location("Location Type"), - SessionField.Comment("Note"), - SessionField.Tips("Tips"), - SessionField.Blind("Stake") - ) - - val pokerBankrollTracker: CSVDescriptor = SessionCSVDescriptor( - DataSource.POKER_BANKROLL_TRACKER, - true, - SessionField.Start("starttime", dateFormat = "MM/dd/yyyy HH:mm"), - SessionField.End("endtime", dateFormat = "MM/dd/yyyy HH:mm"), - SessionField.SessionType("variant"), - SessionField.Buyin("buyin"), - SessionField.CashedOut("cashout"), - SessionField.Break("breakminutes"), - SessionField.LimitType("limit"), - SessionField.Game("game"), - SessionField.Bankroll("currency"), // same as currency code - SessionField.Location("location"), - SessionField.Comment("sessionnote"), - SessionField.Tips("expensesfromstack"), - SessionField.SmallBlind("smallblind"), - SessionField.BigBlind("bigblind"), - SessionField.TournamentNumberOfPlayers("player"), - SessionField.TournamentPosition("place"), - SessionField.TournamentName("mttname"), - SessionField.CurrencyCode("currency"), - SessionField.CurrencyRate("exchangerate"), - SessionField.TableSize("tablesize") - ) - - val runGoodTournaments: CSVDescriptor = SessionCSVDescriptor( - DataSource.RUNGOOD, - true, - SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), - SessionField.StartTime("Start Time"), - SessionField.End("End Date"), - SessionField.EndTime("End Time"), - SessionField.Buyin("Total Buy-In"), - SessionField.CashedOut("Winnings"), - SessionField.NetResult("Profit"), - SessionField.Break("Break"), - SessionField.LimitType("Limit Type"), - SessionField.Game("Game"), - SessionField.Bankroll("Bankroll"), - SessionField.TableSize("Table Type"), - SessionField.Location("Location"), - SessionField.LocationType("Location Type"), - SessionField.Comment("Notes"), - SessionField.CurrencyCode("Currency"), - SessionField.TournamentName("Event Name"), - SessionField.TournamentNumberOfPlayers("Total Players"), - SessionField.TournamentPosition("Finished Place"), - SessionField.TournamentType("Single-Table/Multi-Table") - - ) - - val runGoodCashGames: CSVDescriptor = SessionCSVDescriptor( - DataSource.RUNGOOD, - false, - SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), - SessionField.StartTime("Start Time", dateFormat = "HH:mm"), - SessionField.End("End Date", dateFormat = "dd/MM/yyyy"), - SessionField.EndTime("End Time", dateFormat = "HH:mm"), - SessionField.Buyin("Total Buy-In"), - SessionField.CashedOut("Cashed Out"), - SessionField.NetResult("Profit"), - SessionField.Break("Break"), - SessionField.LimitType("Limit Type"), - SessionField.Game("Game"), - SessionField.Bankroll("Bankroll"), - SessionField.TableSize("Table Type"), - SessionField.Location("Location"), - SessionField.LocationType("Location Type"), - SessionField.Comment("Notes"), - SessionField.CurrencyCode("Currency"), - SessionField.Blind("Stakes", callback = { value -> // $10/20 - value.drop(1) - val blinds = value.split("/") - if (blinds.size == 2) { - return@Blind Pair(blinds.first().toDouble(), blinds.last().toDouble()) - } else { - return@Blind null +class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean, vararg elements: CSVField) : + DataCSVDescriptor(source, *elements) { + + private enum class DataType { + TRANSACTION, + SESSION; + + companion object { + + fun valueForString(type: String): DataType? { + return when (type) { + "Deposit/Payout" -> TRANSACTION + "Cash Game", "Tournament" -> SESSION + else -> null } - }) - ) + } + } } /** * Parses a [record] and return an optional Session */ - override fun parseData(realm: Realm, record: CSVRecord): Session? { + override fun parseData(realm: Realm, record: CSVRecord): Identifiable? { + + var dataType: DataType? = null + val typeField = fields.firstOrNull { it is SessionField.SessionType } + typeField?.let { field -> + this.fieldMapping[field]?.let { index -> + val typeValue = record.get(index) + dataType = DataType.valueForString(typeValue) + } + } + + return when (dataType) { + DataType.TRANSACTION -> parseTransaction(realm, record) + else -> parseSession(realm, record) + } + + } + + private fun parseTransaction(realm: Realm, record: CSVRecord): Transaction? { + + var date: Date? = null + var type: TransactionType? = null + var currencyCode: String? = null + var currencyRate: Double? = null + + // Poker Bankroll Tracker specifics + var buyin: Double? = null + var cashedOut: Double? = null + + fields.forEach { field -> + + val index = this.fieldMapping[field] + if (index != null) { + val value = record.get(index) + when (field) { + is SessionField.Start -> { + date = field.parse(value) + } + is SessionField.Buyin -> buyin = field.parse(value) + is SessionField.CashedOut -> cashedOut = field.parse(value) + is SessionField.CurrencyCode -> currencyCode = value + is SessionField.CurrencyRate -> currencyRate = field.parse(value) + else -> { + } + } + } + } + + val amount = if (buyin != null && buyin!! > 0) { + type = TransactionType.getByValue(TransactionType.Value.WITHDRAWAL, realm) + buyin!! * -1 + } else if (cashedOut != null && cashedOut!! > 0) { + type = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm) + cashedOut + } else { + null + } + + if (date != null && amount != null && type != null && currencyCode != null) { - val session = Session.newInstance(realm, this.isTournament) + if (DataUtils.transactionUnicityCheck(realm, date!!, amount, type)) { + + val bankroll = Bankroll.getOrCreate( + realm, + currencyCode!!, + currencyCode = currencyCode!!, + currencyRate = currencyRate + ) + return Transaction.newInstance(realm, bankroll, date!!, type, amount) + } else { + Timber.d("Transaction already exists") + } + } else { + Timber.d("Can't import transaction: date=$date, amount=$amount, type=${type?.name}") + } + + return null + } + + private fun parseSession(realm: Realm, record: CSVRecord): Session? { + + val session = Session.newInstance(realm, this.isTournament, managed = false) + + var startDate: Date? = null + var endDate: Date? = null var isLive = true - var bankrollName: String? = null + var bankrollName = "" var currencyCode: String? = null var currencyRate: Double? = null + var additionalBuyins = 0.0 // rebuy + addon + + var stackingIn: Double? = null + var stackingOut: Double? = null fields.forEach { field -> @@ -241,23 +144,51 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean val value = record.get(index) when (field) { is SessionField.Start -> { - session.startDate = field.parse(value) + startDate = field.parse(value) } is SessionField.End -> { - session.endDate = field.parse(value) + endDate = field.parse(value) + } + is SessionField.StartTime -> { + startDate?.setHourMinutes(value) + } + is SessionField.EndTime -> { + endDate?.setHourMinutes(value) + } + is SessionField.Buyin -> { + val buyin = field.parse(value) + session.result?.buyin = buyin + if (session.type == Session.Type.TOURNAMENT.ordinal) { + session.tournamentEntryFee = buyin + } else { + } } - is SessionField.StartTime -> { session.startDate?.setHourMinutes(value) } - is SessionField.EndTime -> { session.endDate?.setHourMinutes(value) } - is SessionField.Buyin -> session.result?.buyin = field.parse(value) is SessionField.CashedOut -> session.result?.cashout = field.parse(value) + is SessionField.SessionType -> { + Session.Type.getValueFromString(value)?.let { type -> + session.type = type.ordinal + } + } + is SessionField.Addon -> additionalBuyins += field.parse(value) ?: 0.0 + is SessionField.Rebuy -> additionalBuyins += field.parse(value) ?: 0.0 is SessionField.Tips -> session.result?.tips = field.parse(value) is SessionField.Break -> { field.parse(value)?.let { session.breakDuration = it.toLong() * 60 * 1000 } } - is SessionField.Game -> session.game = realm.getOrCreate(value) - is SessionField.Location -> session.location = realm.getOrCreate(value) + is SessionField.Game -> { + if (value.isNotEmpty()) { + session.game = realm.getOrCreate(value) + } else { + } + } + is SessionField.Location -> { + if (value.isNotEmpty()) { + session.location = realm.getOrCreate(value) + } else { + } + } is SessionField.Bankroll -> bankrollName = value is SessionField.LimitType -> session.limit = Limit.getInstance(value)?.ordinal is SessionField.Comment -> session.comment = value @@ -269,36 +200,74 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean is SessionField.SmallBlind -> session.cgSmallBlind = field.parse(value) is SessionField.BigBlind -> session.cgBigBlind = field.parse(value) is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value) - is SessionField.SessionType -> { - Session.Type.getValueFromString(value)?.let { type -> - session.type = type.ordinal + is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition = + field.parse(value)?.toInt() + is SessionField.TournamentName -> { + if (value.isNotEmpty()) { + session.tournamentName = realm.getOrCreate(value) + } else { } } - is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition = field.parse(value)?.toInt() - is SessionField.TournamentName -> session.tournamentName = realm.getOrCreate(value) - is SessionField.TournamentType -> session.tournamentType = TournamentType.getValueForLabel(value)?.ordinal - is SessionField.TournamentNumberOfPlayers -> session.tournamentNumberOfPlayers = field.parse(value)?.toInt() + is SessionField.TournamentType -> session.tournamentType = + TournamentType.getValueForLabel(value)?.ordinal + is SessionField.TournamentNumberOfPlayers -> session.tournamentNumberOfPlayers = + field.parse(value)?.toInt() is SessionField.CurrencyCode -> currencyCode = value is SessionField.CurrencyRate -> currencyRate = field.parse(value) - else -> { } + is SessionField.StackingIn -> { + stackingIn = field.parse(value) + } + is SessionField.StackingOut -> { + stackingOut = field.parse(value) + } + else -> { + } } } } - session.bankroll = Bankroll.getOrCreate(realm, bankrollName ?: "Import", isLive, currencyCode, currencyRate) + if (bankrollName.isEmpty()) { + bankrollName = "Import" + } + + val bankroll = Bankroll.getOrCreate(realm, bankrollName, isLive, currencyCode, currencyRate) + session.bankroll = bankroll - val startDate = session.startDate - val endDate = session.endDate + session.result?.buyin?.let { + session.result?.buyin = it + additionalBuyins + } val net = session.result?.net - return if (startDate != null && endDate != null && net != null) { // valid session - val unique = SessionUtils.unicityCheck(realm, startDate, endDate, net) - if (unique) session else null - } else { // invalid session - null + if (startDate != null && endDate != null && net != null) { // valid session + // session already in realm, we'd love not put it in Realm before doing the check + val count = DataUtils.sessionCount(realm, startDate!!, endDate!!, net) + if (count == 0) { + val managedSession = realm.copyToRealm(session) + managedSession.startDate = startDate + managedSession.endDate = endDate + + if (stackingIn != null && stackingIn != 0.0) { + val type = TransactionType.getByValue(TransactionType.Value.STACKING_INCOMING, realm) + val transaction = Transaction.newInstance(realm, bankroll, startDate, type, stackingIn!!) + this.addAdditionallyCreatedIdentifiable(transaction) + } + + if (stackingOut != null && stackingOut != 0.0) { + val type = TransactionType.getByValue(TransactionType.Value.STACKING_OUTGOING, realm) + val transaction = Transaction.newInstance(realm, bankroll, startDate, type, stackingOut!!) + this.addAdditionallyCreatedIdentifiable(transaction) + } + + return managedSession + } else { + Timber.d("Session already exists(count=$count): sd=$startDate, ed=$endDate, net=$net") + } + } else { + Timber.d("Can't import session: sd=$startDate, ed=$endDate, net=$net") } + return null } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt new file mode 100644 index 00000000..677c60a4 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt @@ -0,0 +1,141 @@ +package net.pokeranalytics.android.util.csv + +import java.util.* + +sealed class TransactionField { + + data class TransactionType( + override var header: String, + override var callback: ((String) -> net.pokeranalytics.android.model.realm.TransactionType?)? = null + ) : DataCSVField + +} + +/** + * The enumeration of Session fields + */ +sealed class SessionField { + + data class Start( + override var header: String, + override var callback: ((String) -> Date?)? = null, + override val dateFormat: String? = null + ) : DateCSVField + + data class StartTime( + override var header: String, + override var callback: ((String) -> Date?)? = null, + override val dateFormat: String? = null + ) : DateCSVField + + data class End( + override var header: String, + override var callback: ((String) -> Date?)? = null, + override val dateFormat: String? = null + ) : DateCSVField + + data class EndTime( + override var header: String, + override var callback: ((String) -> Date?)? = null, + override val dateFormat: String? = null + ) : DateCSVField + + data class Buyin( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class NetResult( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class CashedOut( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Break( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Tips( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class SmallBlind( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class BigBlind( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Rebuy( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Addon( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class StackingIn( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class StackingOut( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Blind(override var header: String, override var callback: ((String) -> Pair?)? = null) : + BlindCSVField + + data class Game(override var header: String) : CSVField + data class Location(override var header: String) : CSVField + data class LocationType(override var header: String) : CSVField + data class Bankroll(override var header: String) : CSVField + data class LimitType(override var header: String) : CSVField + data class Comment(override var header: String, override val optional: Boolean = false) : CSVField + data class SessionType(override var header: String) : CSVField + data class TableSize(override var header: String) : CSVField + data class CurrencyCode(override var header: String) : CSVField + data class TournamentName(override var header: String) : CSVField + data class TournamentType(override var header: String) : CSVField + + data class CurrencyRate( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class TournamentPosition( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class TournamentNumberOfPlayers( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField +} diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt index 39a3f043..35f91407 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt @@ -2,6 +2,8 @@ package net.pokeranalytics.android.util.extensions import android.content.Context import net.pokeranalytics.android.R +import net.pokeranalytics.android.util.Preferences +import net.pokeranalytics.android.util.UserDefaults import java.lang.Math.abs import java.math.RoundingMode import java.text.DecimalFormat @@ -50,10 +52,12 @@ fun Double.formatted(): String { fun Double.toCurrency(currency: Currency? = null): String { - val currencyFormatter = NumberFormat.getCurrencyInstance() + val currencyFormatter = NumberFormat.getCurrencyInstance(Locale.getDefault()) currency?.let { currencyFormatter.currency = currency - } + } ?: run { + currencyFormatter.currency = UserDefaults.currency + } currencyFormatter.maximumFractionDigits = 2 currencyFormatter.minimumFractionDigits = 0 diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt index aa15c8e6..7a2b5687 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt @@ -1,12 +1,20 @@ package net.pokeranalytics.android.util.extensions -import io.realm.* -import io.realm.kotlin.where +import io.realm.Realm +import io.realm.RealmModel +import io.realm.RealmResults +import io.realm.Sort import net.pokeranalytics.android.model.interfaces.CountableUsage import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.NameManageable -import net.pokeranalytics.android.model.realm.* -import net.pokeranalytics.android.ui.interfaces.FilterableType +import net.pokeranalytics.android.model.realm.Filter +import net.pokeranalytics.android.model.realm.TournamentFeature +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.TransactionType + +fun Realm.count(clazz: Class) : Long { + return this.where(clazz).count() +} fun Realm.findById(clazz: Class, id: String) : T? { return this.where(clazz).equalTo("id", id).findFirst() @@ -90,10 +98,11 @@ fun Realm.updateUsageCount(clazz: Class) { val countable = (countableUsage as CountableUsage) val fieldName = when (clazz.kotlin) { + TransactionType::class -> "type.id" TournamentFeature::class -> "tournamentFeatures.id" else -> "${clazz.simpleName.decapitalize()}.id" } - val count = it.where().contains(fieldName, countable.id).count().toInt() + val count = it.where(countable.ownerClass).contains(fieldName, countable.id).count().toInt() countable.useCount = count } } diff --git a/app/src/main/res/drawable/ic_outline_star.xml b/app/src/main/res/drawable/ic_outline_star.xml new file mode 100644 index 00000000..3fc251d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_star.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_billing.xml b/app/src/main/res/layout/activity_billing.xml index 08d1f651..e1701185 100644 --- a/app/src/main/res/layout/activity_billing.xml +++ b/app/src/main/res/layout/activity_billing.xml @@ -1,15 +1,6 @@ - - + android:layout_height="match_parent"> - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_filters_list.xml b/app/src/main/res/layout/activity_filters_list.xml new file mode 100644 index 00000000..8560f7e1 --- /dev/null +++ b/app/src/main/res/layout/activity_filters_list.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_top_10.xml b/app/src/main/res/layout/activity_top_10.xml new file mode 100644 index 00000000..44318e12 --- /dev/null +++ b/app/src/main/res/layout/activity_top_10.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_double_edit_text.xml b/app/src/main/res/layout/bottom_sheet_double_edit_text.xml index 21005821..703d8f60 100644 --- a/app/src/main/res/layout/bottom_sheet_double_edit_text.xml +++ b/app/src/main/res/layout/bottom_sheet_double_edit_text.xml @@ -8,7 +8,7 @@ tools:background="@color/gray_darker"> diff --git a/app/src/main/res/layout/bottom_sheet_edit_text.xml b/app/src/main/res/layout/bottom_sheet_edit_text.xml index d32697a2..bac63ced 100644 --- a/app/src/main/res/layout/bottom_sheet_edit_text.xml +++ b/app/src/main/res/layout/bottom_sheet_edit_text.xml @@ -8,7 +8,7 @@ tools:background="@color/gray_darker"> + app:layout_constraintTop_toTopOf="@+id/editText" /> + app:layout_constraintStart_toEndOf="@+id/editText" + app:layout_constraintTop_toTopOf="@+id/editText" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_bankroll.xml b/app/src/main/res/layout/fragment_bankroll.xml index b4340482..827a2272 100644 --- a/app/src/main/res/layout/fragment_bankroll.xml +++ b/app/src/main/res/layout/fragment_bankroll.xml @@ -19,6 +19,7 @@ android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" + android:paddingBottom="72dp" android:clipToPadding="false" /> diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index 9ee9dc14..8fd01704 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -57,8 +57,8 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/appBar" /> - + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 8542d548..e1d3e396 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -25,33 +25,33 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_subscription.xml b/app/src/main/res/layout/fragment_subscription.xml index 7791cfb8..e281aa06 100644 --- a/app/src/main/res/layout/fragment_subscription.xml +++ b/app/src/main/res/layout/fragment_subscription.xml @@ -50,37 +50,50 @@ app:layout_constraintTop_toBottomOf="@id/title" tools:text="30 day free trial" /> + + + app:layout_constraintStart_toStartOf="parent" /> + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_bottom_sheet_grid_title.xml b/app/src/main/res/layout/row_bottom_sheet_grid_title.xml index acc6c7c2..d5ee0009 100644 --- a/app/src/main/res/layout/row_bottom_sheet_grid_title.xml +++ b/app/src/main/res/layout/row_bottom_sheet_grid_title.xml @@ -17,6 +17,7 @@ android:gravity="center" android:lines="1" android:maxLines="1" + style="@style/PokerAnalyticsTheme.TextView.RowTitle" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/row_bottom_sheet_title.xml b/app/src/main/res/layout/row_bottom_sheet_title.xml index 4f2cb4f6..df64dfdd 100644 --- a/app/src/main/res/layout/row_bottom_sheet_title.xml +++ b/app/src/main/res/layout/row_bottom_sheet_title.xml @@ -15,7 +15,7 @@ android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" - android:textSize="16sp" + style="@style/PokerAnalyticsTheme.TextView.RowTitle" android:layout_marginBottom="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/row_legend_default.xml b/app/src/main/res/layout/row_legend_default.xml index caeace10..974a8900 100644 --- a/app/src/main/res/layout/row_legend_default.xml +++ b/app/src/main/res/layout/row_legend_default.xml @@ -35,7 +35,7 @@ android:layout_width="wrap_content" android:layout_height="0dp" android:layout_marginTop="8dp" - android:layout_marginEnd="16dp" + android:layout_marginEnd="8dp" app:layout_constraintEnd_toStartOf="@+id/nextArrow" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toEndOf="@+id/stat1Name" @@ -48,7 +48,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" - android:layout_marginEnd="16dp" + android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/nextArrow" diff --git a/app/src/main/res/layout/row_session_view.xml b/app/src/main/res/layout/row_session_view.xml index 9075bdf3..8e220f95 100644 --- a/app/src/main/res/layout/row_session_view.xml +++ b/app/src/main/res/layout/row_session_view.xml @@ -176,11 +176,12 @@ android:text="@string/running_session_state" android:textAllCaps="true" android:textSize="10sp" + android:textColor="@color/white" android:visibility="gone" /> - + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_session.xml b/app/src/main/res/menu/menu_session.xml new file mode 100644 index 00000000..903fcfca --- /dev/null +++ b/app/src/main/res/menu/menu_session.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/navigation_home.xml b/app/src/main/res/menu/navigation_home.xml index f8b8501e..6885aca3 100644 --- a/app/src/main/res/menu/navigation_home.xml +++ b/app/src/main/res/menu/navigation_home.xml @@ -1,24 +1,6 @@ - - + + + + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 28d11de2..d0d5268c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -15,6 +15,27 @@ Passer Pro d\'essai gratuit Cette bankroll contient au moins une transaction empêchant la suppression. + Illimité + Suivez toute votre vie de joueur en ajoutant autant de données que vous le souhaitez + Hors ligne avant tout + Poker Analytics est disponible à tout moment et vos données vous appartiennent. Vous êtes pour l\'instant en charge d\'effectuer des sauvegarde mais cela changera dans le futur! + Vie privée + Nous ne sauvegardons pas vos données, nous ne savons rien de vos gains ni de vos pertes + Support + Nous essayons de répondre le plus vite possible, en français mais aussi en anglais ! + Chargement, veuillez patienter… + Choisissez un type de rapport + Choisissez une statistique, ou plusieurs + Sélectionnez un critère de comparaison, ou plusieurs + Sélectionnez un filtre ou lancez maintenant le rapport + Afficher le rapport + Evolution + Sauvegarder le rapport + Voulez-vous effectuer l\'import des données? + Graphique de comparaison + Le filtre ne peut être effacé car utilisé en ce moment + Champ perso + L\'élement est utilisé dans une ou plusieurs transactions…Veuillez effacer les transactions en premier lieu À partir de Jusqu\'à @@ -39,7 +60,7 @@ Noms de tournoi Propriétés de tournoi En attente - Poker Analytics est un tracker de sessions. Il s\'agit pour l\'instant d\'une version allégée de la version iOS existante. Nous allons ajouter les fonctionnalités au fur et à mesure. L\'app fonctionnera avec un abonnement illimité de 29,99€ par an, mais nous la proposons gratuitement en attendant plus de fonctionnalités + Poker Analytics est un tracker de sessions. L\'app fonctionne avec un abonnement illimité annuel, mais vous disposez de 10 sessions + un essai limité dans le temps pour la tester. C\'est compris ! La propriété doit avoir un nom Une propriété utilise déjà ce nom diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 47c82246..802da918 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,5 +1,8 @@ - + 16dp 16dp + + 12sp + 11sp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8d7265fb..538c113d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,7 +18,7 @@ Unlimited Track all your poker life by adding as many data as you want Offline first - Poker Analytics is available at all times and the data is yours. Export it at any times. Note: You’re currently in charge of backups, but that will change soon! + Poker Analytics is available at all times and the data is yours. Note: We will soon add exporting capabilities and you’re currently in charge of backups. Thanks for your patience! Private We do not own servers. We do not know anything about your wins and losses. Support @@ -37,6 +37,10 @@ The filter cannot be deleted because it is currently selected. Custom field The item is used in one or more transactions…Please delete the linked transactions first + Imported + You\'ve reached the maximum number of free sessions. Please subscribe for unlimited use and don\'t hesitate to tell us how you feel about your current experience! + Stacking incoming + Stacking outgoing Address Naming suggestions @@ -48,7 +52,7 @@ Tournament feature Tournament features Pending - Poker Analytics is a poker tracking app. We’re currently on our way to reproduce the iOS app and you’re currently using a lighter version. The app will work with a US$29.99 yearly subscription for an unlimited usage, but will be free until reaching the appropriate feature coverage. + Poker Analytics is a poker tracking app.\nThe app works with a yearly subscription for an unlimited usage, but you get 10 sessions + a free trial to test the app. I understand You need to give a name to this tournament feature A tournament feature with the same name already exists. @@ -156,7 +160,7 @@ Results will be filtered on cash games Cashed out You need to name the custom field - This item is linked to one or more sessions. Please confirm the delete of this item. + This item is linked to one or more sessions. Please confirm the deletion of this item. Confirmation The chart cannot be exported and shared, please contact the support team Chart @@ -284,7 +288,6 @@ Net hourly rate Range Export your data to iCloud, get the Pro version, open it, import your data from iCloud. - You\'ve reached the maximum number of sessions. Get a new subscription to get more sessions! iCloud iCloud settings were modified, please wait for synchronization. A backup of your data was found on iCloud, do you want to use it on this device? If yes, the local data from this device will be replaced by the backup and if no, you will keep using the local data from this device and your iCloud account won\'t be modified. @@ -659,7 +662,7 @@ Subscription terms of use:\n• Payment will be charged to iTunes Account at confirmation of purchase\n• Subscription automatically renews unless auto-renew is turned off at least 24-hours before the end of the current period\n• Account will be charged for renewal within 24-hours prior to the end of the current period, and identify the cost of the renewal\n• Subscriptions may be managed by the user and auto-renewal may be turned off by going to the user\'s Account Settings after purchase\n• Any unused portion of a free trial period, if offered, will be forfeited when the user purchases a subscription to that publication, where applicable Privacy policy We\'re truly sorry, but something is wrong here…You may be waiting for iCloud sync. Please wait and retry later. Would you mind sending us a report explaining your current state to help us solve this issue? - Tap and hold on a session to duplicate it. + Tap and hold on a session to duplicate it! Change statistic Compare Gross Amount diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index eecdebee..a9828a89 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -17,7 +17,7 @@ @style/PokerAnalyticsTheme.Toolbar @style/PokerAnalyticsTheme.BottomAppBar @style/PokerAnalyticsTheme.EditText - @style/PokerAnalyticsTheme.TextView + @style/PokerAnalyticsTheme.AlertDialog @style/PokerAnalyticsTheme.Chip @style/PokerAnalyticsTheme.TabLayout @@ -83,6 +83,11 @@ @font/roboto + + + + + + + + @@ -351,4 +372,33 @@ 28sp + + + + + + + + diff --git a/app/src/test/java/net/pokeranalytics/android/SavableEnumTest.kt b/app/src/test/java/net/pokeranalytics/android/SavableEnumTest.kt index 13f1e6e7..4b58795e 100644 --- a/app/src/test/java/net/pokeranalytics/android/SavableEnumTest.kt +++ b/app/src/test/java/net/pokeranalytics/android/SavableEnumTest.kt @@ -1,7 +1,9 @@ package net.pokeranalytics.android +import com.google.android.libraries.places.internal.it import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.model.Criteria +import net.pokeranalytics.android.model.realm.TransactionType import org.junit.Assert import org.junit.Test @@ -16,6 +18,9 @@ class SavableEnumTest { val criteriaIds = Criteria.valuesInternal().map { it.uniqueIdentifier } Assert.assertEquals(criteriaIds.toSet().size, criteriaIds.size) + val transactionTypeValueIds = TransactionType.Value.valuesInternal().map { it.uniqueIdentifier } + Assert.assertEquals(transactionTypeValueIds.toSet().size, transactionTypeValueIds.size) + } } \ No newline at end of file