diff --git a/app/build.gradle b/app/build.gradle index 62def270..d1cb2743 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,14 +28,14 @@ android { applicationId "net.pokeranalytics.android" minSdkVersion 23 targetSdkVersion 28 - versionCode 17 + versionCode 16 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' applicationVariants.all { variant -> variant.outputs.all { output -> diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index baeaae1c..e4b45cbc 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -57,4 +57,7 @@ # Guava -dontwarn com.google.j2objc.annotations.** --keep class com.google.j2objc.annotations.** { *; } \ No newline at end of file +-keep class com.google.j2objc.annotations.** { *; } + +# Enum +-optimizations !class/unboxing/enum \ No newline at end of file diff --git a/app/src/androidTest/assets/schema_3.realm b/app/src/androidTest/assets/schema_3.realm new file mode 100644 index 00000000..4409e5b8 Binary files /dev/null and b/app/src/androidTest/assets/schema_3.realm differ diff --git a/app/src/androidTest/java/net/pokeranalytics/android/components/BaseFilterInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/components/BaseFilterInstrumentedUnitTest.kt index 552f2b58..875fe032 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/components/BaseFilterInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/components/BaseFilterInstrumentedUnitTest.kt @@ -30,7 +30,7 @@ open class BaseFilterInstrumentedUnitTest : RealmInstrumentedUnitTest() { session.numberOfTables = numberOfTable session.tableSize = tableSize session.startDate = startDate - session.result?.netResult = netResult + session.result?.cashout = netResult val cal = Calendar.getInstance() // creates calendar cal.time = startDate // sets calendar time/date cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour diff --git a/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt index d44ea94d..30d84082 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt @@ -11,6 +11,8 @@ import java.util.* open class RealmInstrumentedUnitTest { + val EPSILON = 0.0001 + lateinit var mockRealm: Realm companion object { diff --git a/app/src/androidTest/java/net/pokeranalytics/android/components/SessionInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/components/SessionInstrumentedUnitTest.kt new file mode 100644 index 00000000..fd6cf759 --- /dev/null +++ b/app/src/androidTest/java/net/pokeranalytics/android/components/SessionInstrumentedUnitTest.kt @@ -0,0 +1,44 @@ +package net.pokeranalytics.android.components + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.RealmList +import net.pokeranalytics.android.model.realm.* +import org.junit.runner.RunWith +import java.util.* + +@RunWith(AndroidJUnit4::class) +open class SessionInstrumentedUnitTest : RealmInstrumentedUnitTest() { + + // convenience extension + fun Session.Companion.testInstance( + netResult: Double = 0.0, + isTournament: Boolean = false, + startDate: Date = Date(), + endDate: Int = 1, + bankroll: Bankroll? = null, + game: Game? = null, + location: Location? = null, + tournamentName: TournamentName? = null, + tournamentFeatures: RealmList = RealmList(), + numberOfTable: Int = 1, + limit: Int? = null, + tableSize: Int? = null + ): Session { + val session: Session = Session.newInstance(super.mockRealm, isTournament, bankroll) + session.game = game + session.location = location + session.tournamentFeatures = tournamentFeatures + session.tournamentName = tournamentName + session.limit = limit + session.numberOfTables = numberOfTable + session.tableSize = tableSize + session.startDate = startDate + session.result?.netResult = netResult + val cal = Calendar.getInstance() // creates calendar + cal.time = startDate // sets calendar time/date + cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour + session.endDate = cal.time // returns new date object, one hour in the future + return session + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/net/pokeranalytics/android/model/CriteriaTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/model/CriteriaTest.kt new file mode 100644 index 00000000..f2a1621b --- /dev/null +++ b/app/src/androidTest/java/net/pokeranalytics/android/model/CriteriaTest.kt @@ -0,0 +1,104 @@ +package net.pokeranalytics.android.model + +import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest +import net.pokeranalytics.android.components.RealmInstrumentedUnitTest +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.realm.Filter +import net.pokeranalytics.android.model.realm.FilterCondition +import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow +import org.junit.Assert +import org.junit.Test + +import org.junit.Assert.* +import java.util.* + +class CriteriaTest : BaseFilterInstrumentedUnitTest() { + + @Test + fun getQueryConditions() { + + val realm = this.mockRealm + realm.beginTransaction() + val cal = Calendar.getInstance() + cal.time = Date() + val s1 = Session.testInstance(100.0, false, cal.time) + cal.add(Calendar.YEAR, 1) + Session.testInstance(100.0, true, cal.time) + cal.add(Calendar.YEAR, -11) + val firstValue = cal.get(Calendar.YEAR) + Session.testInstance(100.0, true, cal.time) + cal.add(Calendar.YEAR, 7) + Session.testInstance(100.0, true, cal.time) + cal.add(Calendar.YEAR, -2) + Session.testInstance(100.0, true, cal.time) + cal.add(Calendar.YEAR, 10) + Session.testInstance(100.0, true, cal.time) + + val lastValue = firstValue + 10 + + realm.commitTransaction() + + val years = Criteria.Years.queryConditions as List + println("years = ${years.map { it.getDisplayName() }}") + + assertEquals(11, years.size) + assertEquals(firstValue, years.first().listOfValues.first()) + assertEquals(lastValue, years.last().listOfValues.first()) + } + + @Test + fun combined() { + + val critierias = listOf(Criteria.MonthsOfYear, Criteria.DaysOfWeek) + val combined = critierias.combined() + combined.forEach { + it.forEach {qc-> + println(qc.getDisplayName()) + } + } + } + + @Test + fun upToNow() { + val realm = this.mockRealm + realm.beginTransaction() + val cal = Calendar.getInstance() + cal.time = Date() + val s1 = Session.testInstance(100.0, false, cal.time) + cal.add(Calendar.YEAR, 1) + Session.testInstance(100.0, true, cal.time) + cal.add(Calendar.YEAR, -11) + val firstValue = cal.get(Calendar.YEAR) + Session.testInstance(100.0, true, cal.time) + cal.add(Calendar.YEAR, 7) + Session.testInstance(100.0, true, cal.time) + cal.add(Calendar.YEAR, -2) + Session.testInstance(100.0, true, cal.time) + cal.add(Calendar.YEAR, 10) + Session.testInstance(100.0, true, cal.time) + + val lastValue = firstValue + 10 + + realm.commitTransaction() + + val critierias = listOf(Criteria.Years, Criteria.MonthsOfYear) + val combined = critierias.combined() + combined.forEach { + it.forEach {qc-> + println("<<<<< ${qc.getDisplayName()}") + } + } + + println("<<<<< reduced") + + val reduced= critierias.combined().upToNow() + reduced.forEach { + it.forEach {qc-> + println("<<<<< ${qc.getDisplayName()}") + } + } + + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/net/pokeranalytics/android/performanceTests/PerfsInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/performanceTests/PerfsInstrumentedUnitTest.kt index fe5aff41..03d39f69 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/performanceTests/PerfsInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/performanceTests/PerfsInstrumentedUnitTest.kt @@ -3,11 +3,11 @@ package net.pokeranalytics.android.performanceTests import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import net.pokeranalytics.android.components.RealmInstrumentedUnitTest 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.components.RealmInstrumentedUnitTest import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.SessionSet @@ -53,23 +53,23 @@ class PerfsInstrumentedUnitTest : RealmInstrumentedUnitTest() { Timber.d("computableResults: ${computableResults.size}") Timber.d("sets: ${sets.size}") - val stats: List = listOf(Stat.NETRESULT, Stat.AVERAGE) - val group = ComputableGroup("test", computableResults, sets, stats) + val stats: List = listOf(Stat.NET_RESULT, Stat.AVERAGE) + val group = ComputableGroup("test", listOf(), stats) val options = Calculator.Options() options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) - val results: ComputedResults = Calculator.compute(group, options) + val results: ComputedResults = Calculator.compute(realm, group, options) Timber.d("*** ended in ${System.currentTimeMillis() - start} milliseconds") - val sum = results.computedStat(Stat.NETRESULT) + val sum = results.computedStat(Stat.NET_RESULT) Timber.d("*** NET RESULT: ${sum?.value}") val average = results.computedStat(Stat.AVERAGE) Timber.d("*** AVERAGE: ${average?.value}") - val duration = results.computedStat(Stat.DURATION) - Timber.d("*** DURATION: ${duration?.value}") + val duration = results.computedStat(Stat.HOURLY_DURATION) + Timber.d("*** HOURLY_DURATION: ${duration?.value}") } } 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 7fe1fb99..bc4792f4 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt @@ -1,11 +1,10 @@ package net.pokeranalytics.android.unitTests import androidx.test.ext.junit.runners.AndroidJUnit4 -import net.pokeranalytics.android.components.RealmInstrumentedUnitTest -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 io.realm.Realm +import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator +import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup +import net.pokeranalytics.android.components.SessionInstrumentedUnitTest import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.Currency import org.junit.Assert @@ -14,7 +13,17 @@ import org.junit.runner.RunWith import java.util.* @RunWith(AndroidJUnit4::class) -class BankrollInstrumentedUnitTest : RealmInstrumentedUnitTest() { +class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() { + + private fun createDefaultTransactionTypes(realm: Realm) { + TransactionType.Value.values().forEachIndexed { index, value -> + val type = TransactionType() + type.additive = value.additive + type.kind = index + type.lock = true + realm.insertOrUpdate(type) + } + } // convenience extension fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session { @@ -26,56 +35,88 @@ class BankrollInstrumentedUnitTest : RealmInstrumentedUnitTest() { } @Test - fun testSessionStats() { + fun testReport() { - val realm = this.mockRealm - realm.beginTransaction() + val realm = mockRealm - val s1 = newSessionInstance(realm) - val s2 = newSessionInstance(realm) + realm.executeTransaction { - val br1 = realm.createObject(Bankroll::class.java, "1") - val br2 = realm.createObject(Bankroll::class.java, "2") + this.createDefaultTransactionTypes(realm) - val c1 = realm.createObject(Currency::class.java, "1") - val c2 = realm.createObject(Currency::class.java, "2") - c1.rate = 0.1 - c2.rate = 2.0 - br1.currency = c1 - br2.currency = c2 + var br1 = realm.createObject(Bankroll::class.java, "1") + var br2 = realm.createObject(Bankroll::class.java, "2") - s1.bankroll = br1 - s2.bankroll = br2 + br1.initialValue = 100.0 + br2.initialValue = 1000.0 - s1.result?.netResult = 100.0 - s2.result?.netResult = 200.0 + val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) + t1.amount = 100.0 + t1.bankroll = br1 + t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) + val t2 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) + t2.amount = 500.0 + t2.bankroll = br2 + t2.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) - realm.commitTransaction() + val s1 = newSessionInstance(realm) + s1.bankroll = br1 + s1.result?.cashout = 200.0 + val s2 = newSessionInstance(realm) + s2.bankroll = br2 + s2.result?.cashout = 500.0 - val computableResults = realm.where(ComputableResult::class.java).findAll() - val sets = realm.where(SessionSet::class.java).findAll() - val stats: List = listOf(Stat.NETRESULT, Stat.AVERAGE) - val group = ComputableGroup("test", computableResults, sets, stats) + val brSetup1 = BankrollReportSetup(br1) + val report1 = BankrollCalculator.computeReport(brSetup1) + Assert.assertEquals(400.0, report1.total, EPSILON) - val options = Calculator.Options() + val brSetup2 = BankrollReportSetup(br2) + val report2 = BankrollCalculator.computeReport(brSetup2) + Assert.assertEquals(2000.0, report2.total, EPSILON) - val results: ComputedResults = Calculator.compute(group, options) - val delta = 0.01 + val brSetupAll = BankrollReportSetup() + val reportAll = BankrollCalculator.computeReport(brSetupAll) + Assert.assertEquals(2400.0, reportAll.total, EPSILON) - val sum = results.computedStat(Stat.NETRESULT) - if (sum != null) { - Assert.assertEquals(410.0, sum.value, delta) - } else { - Assert.fail("No Net result stat") } - val average = results.computedStat(Stat.AVERAGE) - if (average != null) { - Assert.assertEquals(205.0, average.value, delta) - } else { - Assert.fail("No AVERAGE stat") + } + + + @Test + fun testReportWithRate() { + + val realm = mockRealm + + var br1: Bankroll? = null + realm.executeTransaction { + + this.createDefaultTransactionTypes(realm) + + val c1 = realm.createObject(Currency::class.java, UUID.randomUUID().toString()) + c1.rate = 10.0 + + br1 = realm.createObject(Bankroll::class.java, "1") + br1?.currency = c1 + br1?.initialValue = 100.0 + + val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) + t1.amount = 100.0 + t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) + br1?.transactions?.add(t1) + + val s1 = newSessionInstance(realm) + s1.bankroll = br1 + s1.result?.cashout = 200.0 + } - } + val brSetup1 = BankrollReportSetup(br1) + val report1 = BankrollCalculator.computeReport(brSetup1) + Assert.assertEquals(400.0, report1.total, EPSILON) + val brSetupAll = BankrollReportSetup() + val reportAll = BankrollCalculator.computeReport(brSetupAll) + Assert.assertEquals(4000.0, reportAll.total, EPSILON) + + } } \ No newline at end of file diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt index cc304e89..5df4ab9e 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt @@ -18,7 +18,9 @@ class DeleteInstrumentedUnitTest : RealmInstrumentedUnitTest() { val s2 = newSessionInstance(realm) val br1 = realm.createObject(Bankroll::class.java, "1") + br1.live = false val br2 = realm.createObject(Bankroll::class.java, "2") + br2.live = false val c1 = realm.createObject(Currency::class.java, "1") val c2 = realm.createObject(Currency::class.java, "2") diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatPerformanceUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatPerformanceUnitTest.kt new file mode 100644 index 00000000..e7b9773b --- /dev/null +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatPerformanceUnitTest.kt @@ -0,0 +1,34 @@ +package net.pokeranalytics.android.unitTests + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import net.pokeranalytics.android.components.SessionInstrumentedUnitTest +import net.pokeranalytics.android.model.realm.Result +import net.pokeranalytics.android.model.realm.Session +import org.junit.Test +import org.junit.runner.RunWith +import java.util.* + +@RunWith(AndroidJUnit4::class) +class StatPerformanceUnitTest : SessionInstrumentedUnitTest() { + + @Test + fun testSessionNetResultOnLoad() { + val realm = mockRealm + realm.beginTransaction() + + for (index in 0..100) { + Session.testInstance((-2000..2000).random().toDouble()) + println("*** creating $index") + } + + realm.commitTransaction() + + val d1 = Date() + realm.where(Result::class.java).sum("netResult") + + val d2 = Date() + val duration = (d2.time - d1.time) + println("*** ended in $duration milliseconds") + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt index 347d0576..6ae76539 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt @@ -1,13 +1,13 @@ package net.pokeranalytics.android.unitTests import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.realm.RealmList import io.realm.RealmResults 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.components.RealmInstrumentedUnitTest +import net.pokeranalytics.android.components.SessionInstrumentedUnitTest +import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.Currency import org.junit.Assert @@ -23,62 +23,10 @@ import java.util.* * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) -class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { - - // convenience extension - private fun Session.Companion.testInstance( - netResult: Double = 0.0, - isTournament: Boolean = false, - startDate: Date = Date(), - endDate: Int = 1, - bankroll: Bankroll? = null, - game: Game? = null, - location: Location? = null, - tournamentName: TournamentName? = null, - tournamentFeatures: RealmList = RealmList(), - numberOfTable: Int = 1, - limit: Int? = null, - tableSize: Int? = null - ): Session { - val session: Session = Session.newInstance(super.mockRealm, isTournament, bankroll) - session.game = game - session.location = location - session.tournamentFeatures = tournamentFeatures - session.tournamentName = tournamentName - session.limit = limit - session.numberOfTables = numberOfTable - session.tableSize = tableSize - session.startDate = startDate - session.result?.netResult = netResult - val cal = Calendar.getInstance() // creates calendar - cal.time = startDate // sets calendar time/date - cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour - session.endDate = cal.time // returns new date object, one hour in the future - return session - } - - @Test - fun testSessionNetResultOnLoad() { - val realm = mockRealm - realm.beginTransaction() - - for (index in 0..100) { - Session.testInstance((-2000..2000).random().toDouble()) - println("*** creating $index") - } - - realm.commitTransaction() - - val d1 = Date() - realm.where(Result::class.java).sum("netResult") - - val d2 = Date() - val duration = (d2.time - d1.time) - println("*** ended in $duration milliseconds") - } +class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() { @Test - fun testSessionStats() { + fun testAllSessionStats() { val realm = this.mockRealm @@ -112,6 +60,10 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { s2.startDate = sd2 s2.endDate = ed2 + val l1 = realm.createObject(Location::class.java, UUID.randomUUID().toString()) + s1.location = l1 + s2.location = l1 + realm.commitTransaction() assertEquals(2, computableResults.size) @@ -120,17 +72,16 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { println(">>>>>> rated net = ${it.ratedNet} ") } - val sets = realm.where(SessionSet::class.java).findAll() - val stats: List = listOf(Stat.NETRESULT, Stat.AVERAGE) - val group = ComputableGroup("test", computableResults, sets, stats) + val group = ComputableGroup("test") val options = Calculator.Options() - options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) + options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION, + Stat.LONGEST_STREAKS, Stat.LOCATIONS_PLAYED, Stat.DAYS_PLAYED) - val results: ComputedResults = Calculator.compute(group, options) + val results: ComputedResults = Calculator.compute(realm, group, options) val delta = 0.01 - val sum = results.computedStat(Stat.NETRESULT) + val sum = results.computedStat(Stat.NET_RESULT) if (sum != null) { assertEquals(200.0, sum.value, delta) } else { @@ -144,7 +95,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { Assert.fail("No AVERAGE stat") } - val duration = results.computedStat(Stat.DURATION) + val duration = results.computedStat(Stat.HOURLY_DURATION) if (duration != null) { assertEquals(4.0, duration.value, delta) } else { @@ -181,7 +132,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { } else { Assert.fail("No avgBuyin stat") } - val avgDuration = results.computedStat(Stat.AVERAGE_DURATION) + val avgDuration = results.computedStat(Stat.AVERAGE_HOURLY_DURATION) if (avgDuration != null) { assertEquals(2.0, avgDuration.value, delta) } else { @@ -234,6 +185,42 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { Assert.fail("No std100 stat") } + results.computedStat(Stat.MAXIMUM_NETRESULT)?.let { + assertEquals(300.0, it.value, delta) + } ?: run { + Assert.fail("No MAXIMUM_NETRESULT") + } + + results.computedStat(Stat.MINIMUM_NETRESULT)?.let { + assertEquals(-100.0, it.value, delta) + } ?: run { + Assert.fail("No MINIMUM_NETRESULT") + } + + results.computedStat(Stat.MAXIMUM_DURATION)?.let { + assertEquals(3.0, it.value, delta) + } ?: run { + Assert.fail("No MAXIMUM_DURATION") + } + + results.computedStat(Stat.DAYS_PLAYED)?.let { + assertEquals(2.0, it.value, delta) + } ?: run { + Assert.fail("No DAYS_PLAYED") + } + + results.computedStat(Stat.LOCATIONS_PLAYED)?.let { + assertEquals(1.0, it.value, delta) + } ?: run { + Assert.fail("No LOCATIONS_PLAYED") + } + + results.computedStat(Stat.LONGEST_STREAKS)?.let { + assertEquals(1.0, it.value, delta) + assertEquals(1.0, it.secondValue!!, delta) + } ?: run { + Assert.fail("No LOCATIONS_PLAYED") + } } @Test @@ -263,18 +250,16 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { } - val computableResults = realm.where(ComputableResult::class.java).findAll() - val sets = realm.where(SessionSet::class.java).findAll() - val stats: List = listOf(Stat.NETRESULT, Stat.AVERAGE) - val group = ComputableGroup("test", computableResults, sets, stats) + val stats: List = listOf(Stat.NET_RESULT, Stat.AVERAGE) + val group = ComputableGroup("test", listOf(), stats) val options = Calculator.Options() options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) - val results: ComputedResults = Calculator.compute(group, options) + val results: ComputedResults = Calculator.compute(realm, group, options) val delta = 0.01 - val duration = results.computedStat(Stat.DURATION) + val duration = results.computedStat(Stat.HOURLY_DURATION) if (duration != null) { assertEquals(3.0, duration.value, delta) } else { @@ -332,18 +317,16 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { realm.commitTransaction() - val computableResults = realm.where(ComputableResult::class.java).findAll() - val sets = realm.where(SessionSet::class.java).findAll() - val stats: List = listOf(Stat.NETRESULT, Stat.AVERAGE) - val group = ComputableGroup("test", computableResults, sets, stats) + val stats: List = listOf(Stat.NET_RESULT, Stat.AVERAGE) + val group = ComputableGroup("test", listOf(), stats) val options = Calculator.Options() options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) - val results: ComputedResults = Calculator.compute(group, options) + val results: ComputedResults = Calculator.compute(realm, group, options) val delta = 0.01 - val duration = results.computedStat(Stat.DURATION) + val duration = results.computedStat(Stat.HOURLY_DURATION) if (duration != null) { assertEquals(8.0, duration.value, delta) } else { @@ -417,18 +400,16 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { realm.commitTransaction() - val computableResults = realm.where(ComputableResult::class.java).findAll() - val sets = realm.where(SessionSet::class.java).findAll() - val stats: List = listOf(Stat.NETRESULT, Stat.AVERAGE) - val group = ComputableGroup("test", computableResults, sets, stats) + val stats: List = listOf(Stat.NET_RESULT, Stat.AVERAGE) + val group = ComputableGroup("test", listOf(), stats) val options = Calculator.Options() options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) - val results: ComputedResults = Calculator.compute(group, options) + val results: ComputedResults = Calculator.compute(realm, group, options) val delta = 0.01 - val duration = results.computedStat(Stat.DURATION) + val duration = results.computedStat(Stat.HOURLY_DURATION) if (duration != null) { assertEquals(8.0, duration.value, delta) } else { @@ -439,14 +420,12 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { s1.deleteFromRealm() } - val computableResults2 = realm.where(ComputableResult::class.java).findAll() - val sets2 = realm.where(SessionSet::class.java).findAll() - val stats2: List = listOf(Stat.NETRESULT, Stat.AVERAGE) - val group2 = ComputableGroup("test", computableResults2, sets2, stats2) + val stats2: List = listOf(Stat.NET_RESULT, Stat.AVERAGE) + val group2 = ComputableGroup("test", listOf(), stats2) - val results2: ComputedResults = Calculator.compute(group2, options) + val results2: ComputedResults = Calculator.compute(realm, group2, options) - val duration2 = results2.computedStat(Stat.DURATION) + val duration2 = results2.computedStat(Stat.HOURLY_DURATION) if (duration2 != null) { assertEquals(7.0, duration2.value, delta) } else { @@ -478,12 +457,12 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { val sets = realm.where(SessionSet::class.java).findAll() - Assert.assertEquals(1, sets.size) + assertEquals(1, sets.size) val set = sets.first() if (set != null) { - Assert.assertEquals(sd1.time, set.startDate.time) - Assert.assertEquals(ed1.time, set.endDate.time) + assertEquals(sd1.time, set.startDate.time) + assertEquals(ed1.time, set.endDate.time) } else { Assert.fail("No set") } @@ -522,7 +501,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { val sets = realm.where(SessionSet::class.java).findAll() - Assert.assertEquals(2, sets.size) + assertEquals(2, sets.size) } @@ -566,18 +545,16 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { s1.endDate = null } - val computableResults = realm.where(ComputableResult::class.java).findAll() - val sets = realm.where(SessionSet::class.java).findAll() - val stats: List = listOf(Stat.NETRESULT, Stat.AVERAGE) - val group = ComputableGroup("test", computableResults, sets, stats) + val stats: List = listOf(Stat.NET_RESULT, Stat.AVERAGE) + val group = ComputableGroup("test", listOf(), stats) val options = Calculator.Options() // options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) - val results: ComputedResults = Calculator.compute(group, options) + val results: ComputedResults = Calculator.compute(realm, group, options) val delta = 0.01 - val duration = results.computedStat(Stat.DURATION) + val duration = results.computedStat(Stat.HOURLY_DURATION) if (duration != null) { assertEquals(7.0, duration.value, delta) } else { @@ -594,7 +571,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { } - @Test +// @Test fun testRatedNetResultSessions() { val realm = this.mockRealm @@ -620,15 +597,13 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { } - val computableResults = realm.where(ComputableResult::class.java).findAll() - val sets = realm.where(SessionSet::class.java).findAll() - val stats: List = listOf(Stat.NETRESULT) - val group = ComputableGroup("test", computableResults, sets, stats) + val stats: List = listOf(Stat.NET_RESULT) + val group = ComputableGroup("test", listOf(), stats) val options = Calculator.Options() - val results: ComputedResults = Calculator.compute(group, options) + val results: ComputedResults = Calculator.compute(realm, group, options) - val netResult = results.computedStat(Stat.NETRESULT) - Assert.assertEquals(250.0, netResult?.value) + val netResult = results.computedStat(Stat.NET_RESULT) + assertEquals(250.0, netResult?.value) } // @Test @@ -658,15 +633,13 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { } - val computableResults = realm.where(ComputableResult::class.java).findAll() - val sets = realm.where(SessionSet::class.java).findAll() - val stats: List = listOf(Stat.NETRESULT) - val group = ComputableGroup("test", computableResults, sets, stats) + val stats: List = listOf(Stat.NET_RESULT) + val group = ComputableGroup("test", listOf(), stats) val options = Calculator.Options() - val results: ComputedResults = Calculator.compute(group, options) + val results: ComputedResults = Calculator.compute(realm, group, options) - val netResult = results.computedStat(Stat.NETRESULT) - Assert.assertEquals(250.0, netResult?.value) + val netResult = results.computedStat(Stat.NET_RESULT) + assertEquals(250.0, netResult?.value) println("currency set rate real test starts here") @@ -679,13 +652,137 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { } } - val updatedComputableResults = realm.where(ComputableResult::class.java).findAll() - val updatedSets = realm.where(SessionSet::class.java).findAll() - val updatedGroup = ComputableGroup("test", updatedComputableResults, updatedSets, stats) - val updatedResults: ComputedResults = Calculator.compute(updatedGroup, options) - val updatedNetResult = updatedResults.computedStat(Stat.NETRESULT) - Assert.assertEquals(650.0, updatedNetResult?.value) + val updatedGroup = ComputableGroup("test", listOf(), stats) + val updatedResults: ComputedResults = Calculator.compute(realm, updatedGroup, options) + val updatedNetResult = updatedResults.computedStat(Stat.NET_RESULT) + assertEquals(650.0, updatedNetResult?.value) + + } + + @Test + fun testDaysPlayed() { + + val realm = this.mockRealm + + realm.executeTransaction { + + val s1 = newSessionInstance(realm) + val s2 = newSessionInstance(realm) + + val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") + + val sd1 = sdf.parse("01/1/2019 09:00") + val ed1 = sdf.parse("01/1/2019 10:00") + val sd2 = sdf.parse("01/1/2019 19:00") + val ed2 = sdf.parse("01/1/2019 20:00") + + s1.startDate = sd1 + s1.endDate = ed1 + + s2.startDate = sd2 + s2.endDate = ed2 + } + + val group = ComputableGroup("", listOf(), listOf()) + val options = Calculator.Options(stats = listOf(Stat.DAYS_PLAYED)) + val report = Calculator.computeGroups(realm, listOf(group), options) + + report.results.firstOrNull()?.computedStat(Stat.DAYS_PLAYED)?.let { + assertEquals(1, it.value.toInt()) + } ?: run { + Assert.fail("Missing DAYS_PLAYED") + } + + } + + @Test + fun testFilteredHourlyRate() { + + val realm = this.mockRealm + + realm.executeTransaction { + + val s1 = newSessionInstance(realm, true) + val s2 = newSessionInstance(realm, true) + val s3 = newSessionInstance(realm, false) + + val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") + + val sd1 = sdf.parse("01/1/2019 09:00") + val ed1 = sdf.parse("01/1/2019 10:00") + val sd2 = sdf.parse("01/1/2019 04:00") + val ed2 = sdf.parse("01/1/2019 05:00") + val sd3 = sdf.parse("01/1/2019 03:00") + val ed3 = sdf.parse("01/1/2019 11:00") + + s1.startDate = sd1 + s1.endDate = ed1 + s2.startDate = sd2 + s2.endDate = ed2 + s3.startDate = sd3 + s3.endDate = ed3 + + } + + val group = ComputableGroup("test", listOf(QueryCondition.IsCash)) + + val options = Calculator.Options() + options.displayedStats = listOf(Stat.HOURLY_DURATION) + + val results: ComputedResults = Calculator.compute(realm, group, options) + val delta = 0.01 + + val duration = results.computedStat(Stat.HOURLY_DURATION) + if (duration != null) { + assertEquals(2.0, duration.value, delta) + } else { + Assert.fail("No Net result stat") + } + } + + @Test + fun testFilteredHourlyRate2() { + + val realm = this.mockRealm + + realm.executeTransaction { + + val s1 = newSessionInstance(realm, true) + val s2 = newSessionInstance(realm, true) + val s3 = newSessionInstance(realm, false) + + val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") + + val sd1 = sdf.parse("01/1/2019 06:00") + val ed1 = sdf.parse("01/1/2019 09:00") + val sd2 = sdf.parse("01/1/2019 07:00") + val ed2 = sdf.parse("01/1/2019 10:00") + val sd3 = sdf.parse("01/1/2019 03:00") + val ed3 = sdf.parse("01/1/2019 11:00") + s1.startDate = sd1 + s1.endDate = ed1 + s2.startDate = sd2 + s2.endDate = ed2 + s3.startDate = sd3 + s3.endDate = ed3 + + } + + val group = ComputableGroup("test", listOf(QueryCondition.IsCash)) + + val options = Calculator.Options() + options.displayedStats = listOf(Stat.HOURLY_DURATION) + + val results: ComputedResults = Calculator.compute(realm, group, options) + val delta = 0.01 + + val duration = results.computedStat(Stat.HOURLY_DURATION) + if (duration != null) { + assertEquals(4.0, duration.value, delta) + } else { + Assert.fail("No Net result stat") + } } } \ No newline at end of file diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt index d19cee4a..94ff6f5f 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt @@ -6,7 +6,6 @@ import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.FilterCondition import net.pokeranalytics.android.model.realm.Session -import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow import org.junit.Assert import org.junit.Test @@ -21,7 +20,7 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() { realm.beginTransaction() val currency = realm.createObject(net.pokeranalytics.android.model.realm.Currency::class.java, "1") - currency.code = "AUD" + currency.code = "USD" val b1 = realm.createObject(Bankroll::class.java, "1") val b2 = realm.createObject(Bankroll::class.java, "2") @@ -41,14 +40,16 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() { realm.commitTransaction() + val filter = QueryCondition.AnyBlind() - val filter = QueryCondition.BLINDS - val blind = FilterElementRow.Blind(0.5, 1.0, null) - blind.filterSectionRow = FilterSectionRow.BLINDS - val filterElement = FilterCondition(filterElementRows = arrayListOf(blind)) - filter.updateValueMap(filterElement) + val blind = QueryCondition.AnyBlind().apply { + listOfValues = arrayListOf(s1.blinds!!) + } + blind.filterSectionRow = FilterSectionRow.BLIND + val filterElement = FilterCondition(filterElementRows = arrayListOf(blind)) + filter.updateValueMap(filterElement) val sessions = Filter.queryOn(realm, arrayListOf(filter)) @@ -86,12 +87,20 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() { realm.commitTransaction() - val filter = QueryCondition.BLINDS - val blind = FilterElementRow.Blind(null, 1.0, null) - blind.filterSectionRow = FilterSectionRow.BLINDS + val filter = QueryCondition.AnyBlind() - val filterElement = FilterCondition(filterElementRows = arrayListOf(blind)) - filter.updateValueMap(filterElement) + val blind1 = QueryCondition.AnyBlind().apply { + listOfValues = arrayListOf(s1.blinds!!) + } + val blind2 = QueryCondition.AnyBlind().apply { + listOfValues = arrayListOf(s2.blinds!!) + } + + blind1.filterSectionRow = FilterSectionRow.BLIND + blind2.filterSectionRow = FilterSectionRow.BLIND + + val filterElements = FilterCondition(filterElementRows = arrayListOf(blind1, blind2)) + filter.updateValueMap(filterElements) val sessions = Filter.queryOn(realm, arrayListOf(filter)) @@ -108,13 +117,14 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() { realm.beginTransaction() val currency = realm.createObject(net.pokeranalytics.android.model.realm.Currency::class.java, "1") - currency.code = "AUD" + currency.code = "USD" val b1 = realm.createObject(Bankroll::class.java, "1") val b2 = realm.createObject(Bankroll::class.java, "2") b2.currency = currency val s1 = Session.testInstance(100.0, false, Date(), 1, b1) + s1.cgBigBlind = 1.0 s1.cgSmallBlind = 0.5 @@ -129,13 +139,17 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() { realm.commitTransaction() - val filter = QueryCondition.BLINDS - val blind = FilterElementRow.Blind(1.0, 2.0, "AUD") - blind.filterSectionRow = FilterSectionRow.BLINDS + val filter = QueryCondition.AnyBlind() + + val blind = QueryCondition.AnyBlind().apply { + listOfValues = arrayListOf(s3.blinds!!) + } + + blind.filterSectionRow = FilterSectionRow.BLIND val filterElement = FilterCondition(filterElementRows = arrayListOf(blind)) filter.updateValueMap(filterElement) - + println("<<<< ${filter.listOfValues}") val sessions = Filter.queryOn(realm, arrayListOf(filter)) Assert.assertEquals(1, sessions.size) @@ -172,12 +186,19 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() { realm.commitTransaction() - val filter = QueryCondition.BLINDS - val blind1 = FilterElementRow.Blind(1.0, 2.0, null) - blind1.filterSectionRow = FilterSectionRow.BLINDS - val blind2 = FilterElementRow.Blind(0.5, 1.0, null) - blind2.filterSectionRow = FilterSectionRow.BLINDS + val filter = QueryCondition.AnyBlind() + + val blind1 = QueryCondition.AnyBlind().apply { + listOfValues = arrayListOf(s1.blinds!!) + } + val blind2 = QueryCondition.AnyBlind().apply { + listOfValues = arrayListOf(s2.blinds!!) + } + + blind1.filterSectionRow = FilterSectionRow.BLIND + blind2.filterSectionRow = FilterSectionRow.BLIND + val filterElement = FilterCondition(filterElementRows = arrayListOf(blind1, blind2)) filter.updateValueMap(filterElement) diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt index 2fd4b380..6f273150 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt @@ -6,7 +6,6 @@ import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.FilterCondition import net.pokeranalytics.android.model.realm.Session -import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow import net.pokeranalytics.android.util.extensions.startOfDay import org.junit.Assert @@ -30,19 +29,19 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(100.0, true, cal.time) realm.commitTransaction() - val filter = QueryCondition.DAY_OF_WEEK + val filter = QueryCondition.AnyDayOfWeek() cal.time = s1.startDate - val filterElementRow = FilterElementRow.Day(cal.get(Calendar.DAY_OF_WEEK)) + val filterElementRow = QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(cal.get(Calendar.DAY_OF_WEEK)) } filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE - val filterElement = FilterCondition(filterElementRow) + val filterElement = FilterCondition(arrayListOf(filterElementRow)) filter.updateValueMap(filterElement) val sessions = Filter.queryOn(realm, arrayListOf(filter)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(s1.id, (this as Session).id) + Assert.assertEquals(s1.id, (this).id) } } @@ -59,19 +58,19 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(100.0, true, cal.time) realm.commitTransaction() - val filter = QueryCondition.MONTH + val filter = QueryCondition.AnyMonthOfYear() cal.time = s1.startDate + val filterElementRow = QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(cal.get(Calendar.MONTH)) } - val filterElementRow = FilterElementRow.Month(cal.get(Calendar.MONTH)) filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE - val filterElement = FilterCondition(filterElementRow) + val filterElement = FilterCondition(arrayListOf(filterElementRow)) filter.updateValueMap(filterElement) val sessions = Filter.queryOn(realm, arrayListOf(filter)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(s1.id, (this as Session).id) + Assert.assertEquals(s1.id, (this).id) } } @@ -88,18 +87,18 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(100.0, true, cal.time) realm.commitTransaction() - val filter = QueryCondition.YEAR + val filter = QueryCondition.AnyYear() cal.time = s1.startDate - val filterElementRow = FilterElementRow.Year(cal.get(Calendar.YEAR)) + val filterElementRow = QueryCondition.AnyYear().apply { listOfValues = arrayListOf(cal.get(Calendar.YEAR)) } filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE - val filterElement = FilterCondition(filterElementRow) + val filterElement = FilterCondition(arrayListOf(filterElementRow)) filter.updateValueMap(filterElement) val sessions = Filter.queryOn(realm, arrayListOf(filter)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(s1.id, (this as Session).id) + Assert.assertEquals(s1.id, (this).id) } } @@ -118,11 +117,11 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(100.0, true, cal.time) realm.commitTransaction() - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.WEEK_END)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.IsWeekEnd)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(s1.id, (this as Session).id) + Assert.assertEquals(s1.id, (this).id) } } @@ -143,11 +142,11 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { realm.commitTransaction() - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.WEEK_DAY)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.IsWeekDay)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(s1.id, (this as Session).id) + Assert.assertEquals(s1.id, (this).id) } } @@ -168,7 +167,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2) - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.TODAY)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.IsToday)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { @@ -193,7 +192,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2) - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.TODAY)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.IsToday)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { @@ -221,7 +220,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3) - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.YESTERDAY)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.WasYesterday)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { @@ -248,7 +247,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3) - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.YESTERDAY)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.WasYesterday)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { @@ -275,7 +274,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3) - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.TODAY_AND_YESTERDAY)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.WasTodayAndYesterday)) Assert.assertEquals(2, sessions.size) Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2))) @@ -306,7 +305,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 5) - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.THIS_YEAR)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.DuringThisYear)) Assert.assertEquals(3, sessions.size) Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2, s3))) @@ -338,7 +337,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 5) - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.THIS_MONTH)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.DuringThisMonth)) Assert.assertEquals(2, sessions.size) Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2))) @@ -362,7 +361,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2) - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.THIS_WEEK)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.DuringThisWeek)) Assert.assertEquals(1, sessions.size) Assert.assertTrue(sessions.containsAll(arrayListOf(s1))) @@ -383,16 +382,16 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { val s2 = Session.testInstance(100.0, true, cal.time, 1) realm.commitTransaction() - val filter = QueryCondition.STARTED_FROM_DATE - val filterElementRow = FilterElementRow.From(s2.startDate!!) + val filter = QueryCondition.StartedFromDate() + val filterElementRow = QueryCondition.StartedFromDate().apply { singleValue = s2.startDate!!} filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE - filter.updateValueMap(FilterCondition(filterElementRow)) + filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow))) val sessions = Filter.queryOn(realm, arrayListOf(filter)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(s2.id, (this as Session).id) + Assert.assertEquals(s2.id, (this).id) } } @@ -411,16 +410,16 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { realm.commitTransaction() - val filter = QueryCondition.STARTED_TO_DATE - val filterElementRow = FilterElementRow.From(s1.startDate!!) + val filter = QueryCondition.StartedToDate() + val filterElementRow = QueryCondition.StartedToDate().apply { singleValue = s1.startDate!! } filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE - filter.updateValueMap(FilterCondition(filterElementRow)) + filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow))) val sessions = Filter.queryOn(realm, arrayListOf(filter)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(s1.id, (this as Session).id) + Assert.assertEquals(s1.id, (this).id) } } @@ -440,16 +439,16 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { realm.commitTransaction() - val filter = QueryCondition.ENDED_FROM_DATE - val filterElementRow = FilterElementRow.From(s2.endDate()) + val filter = QueryCondition.EndedFromDate() + val filterElementRow = QueryCondition.EndedFromDate().apply { singleValue = s2.endDate() } filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE - filter.updateValueMap(FilterCondition(filterElementRow)) + filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow))) val sessions = Filter.queryOn(realm, arrayListOf(filter)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(s2.id, (this as Session).id) + Assert.assertEquals(s2.id, (this).id) } } @@ -469,16 +468,16 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { realm.commitTransaction() - val filter = QueryCondition.ENDED_TO_DATE - val filterElementRow = FilterElementRow.From(s1.endDate()) + val filter = QueryCondition.EndedToDate() + val filterElementRow = QueryCondition.EndedToDate().apply { singleValue = s1.endDate() } filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE - filter.updateValueMap(FilterCondition(filterElementRow)) + filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow))) val sessions = Filter.queryOn(realm, arrayListOf(filter)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(s1.id, (this as Session).id) + Assert.assertEquals(s1.id, (this).id) } } } \ No newline at end of file diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt index fd756ada..baac1ade 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt @@ -13,17 +13,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ExceptionFilterInstrumentedTest: BaseFilterInstrumentedUnitTest() { - @Test(expected = PokerAnalyticsException.FilterElementExpectedValueMissing::class) - fun testValueKeyFilterException() { - val filter = QueryCondition.STARTED_FROM_DATE - val filterElement = FilterCondition() - filter.updateValueMap(filterElement) - - val realm = this.mockRealm - Filter.queryOn(realm, arrayListOf(filter)) - - } - @Test(expected = PokerAnalyticsException.FilterElementUnknownName::class) fun testFilterException() { FilterCondition().queryCondition diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt index 5ad36cbe..ac1c3186 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt @@ -2,12 +2,10 @@ package net.pokeranalytics.android.unitTests.filter import androidx.test.ext.junit.runners.AndroidJUnit4 import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest -import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow -import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow import org.junit.Assert import org.junit.Test @@ -25,7 +23,7 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { val filter = Filter() filter.name = "testSaveLoadCashFilter" - val filterElement = FilterElementRow.Cash + val filterElement = QueryCondition.IsCash filterElement.filterSectionRow = FilterSectionRow.CASH_TOURNAMENT filter.createOrUpdateFilterConditions(arrayListOf(filterElement)) @@ -38,7 +36,7 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { val filterComponent = filter.filterConditions.first() filterComponent?.let { - Assert.assertEquals(QueryCondition.CASH, QueryCondition.valueOf(it.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName)) + Assert.assertTrue(it.queryCondition is QueryCondition.IsCash) } ?: run { Assert.fail() } @@ -55,7 +53,7 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(Session.Type.CASH_GAME.ordinal, (this as Session).type) + Assert.assertEquals(Session.Type.CASH_GAME.ordinal, this.type) } ?: run { Assert.fail() } diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt index b4348fa2..8a477114 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt @@ -3,10 +3,8 @@ package net.pokeranalytics.android.unitTests.filter import androidx.test.ext.junit.runners.AndroidJUnit4 import io.realm.RealmList import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest -import net.pokeranalytics.android.model.TableSize import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.realm.* -import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow import org.junit.Assert import org.junit.Test @@ -26,11 +24,11 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(100.0, true, Date(), 1) realm.commitTransaction() - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.CASH)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.IsCash)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(Session.Type.CASH_GAME.ordinal, (this as Session).type) + Assert.assertEquals(Session.Type.CASH_GAME.ordinal, (this).type) } } @@ -44,11 +42,11 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(100.0, true, Date(), 1) realm.commitTransaction() - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.TOURNAMENT)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.IsTournament)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { - Assert.assertEquals(Session.Type.TOURNAMENT.ordinal, (this as Session).type) + Assert.assertEquals(Session.Type.TOURNAMENT.ordinal, (this).type) } } @@ -67,7 +65,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(100.0, true, Date(), 1, b2) realm.commitTransaction() - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.LIVE)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.IsLive)) Assert.assertEquals(1, sessions.size) (sessions[0] as Session).bankroll?.run { @@ -89,7 +87,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(100.0, true, Date(), 1, b2) realm.commitTransaction() - val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.ONLINE)) + val sessions = Filter.queryOn(realm, arrayListOf(QueryCondition.IsOnline)) Assert.assertEquals(1, sessions.size) (sessions[0] as Session).bankroll?.run { @@ -109,8 +107,8 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(bankroll = b2) realm.commitTransaction() - val filter = QueryCondition.BANKROLL - val filterElementRow = FilterElementRow.Bankroll(b1) + val filter = QueryCondition.AnyBankroll() + val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) } filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) @@ -140,11 +138,11 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(bankroll = b3) realm.commitTransaction() - val filter = QueryCondition.BANKROLL - val filterElementRow = FilterElementRow.Bankroll(b1) + val filter = QueryCondition.AnyBankroll() + val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) } filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL - val filterElementRow2 = FilterElementRow.Bankroll(b2) + val filterElementRow2 = QueryCondition.AnyBankroll().apply { setObject(b2) } filterElementRow2.filterSectionRow = FilterSectionRow.BANKROLL filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) @@ -169,12 +167,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(game = g2) realm.commitTransaction() - val filter = QueryCondition.GAME - val filterElementRow = FilterElementRow.Game(g2) - filterElementRow.filterSectionRow = FilterSectionRow.GAME - filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) - - val sessions = Filter.queryOn(realm, arrayListOf(filter)) + val anyGame = QueryCondition.AnyGame(g2) + val fc = FilterCondition(filterElementRows = arrayListOf(anyGame)) + val sessions = Filter.queryOn(realm, arrayListOf(fc.queryCondition)) Assert.assertEquals(1, sessions.size) (sessions[0] as Session).game?.run { @@ -200,15 +195,13 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(game = g3) realm.commitTransaction() - val filter = QueryCondition.GAME - - val filterElementRow = FilterElementRow.Game(g2) + val filterElementRow = QueryCondition.AnyGame().apply { setObject(g2) } filterElementRow.filterSectionRow = FilterSectionRow.GAME - val filterElementRow2 = FilterElementRow.Game(g3) + val filterElementRow2 = QueryCondition.AnyGame().apply { setObject(g3) } filterElementRow2.filterSectionRow = FilterSectionRow.GAME - filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) - - val sessions = Filter.queryOn(realm, arrayListOf(filter)) + val filterCondition = FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)) + val queryCondition = filterCondition.queryCondition + val sessions = Filter.queryOn(realm, arrayListOf(queryCondition)) Assert.assertEquals(6, sessions.size) @@ -229,8 +222,8 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(location = l2) realm.commitTransaction() - val filter = QueryCondition.LOCATION - val filterElementRow = FilterElementRow.Location(l1) + val filter = QueryCondition.AnyLocation() + val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) } filterElementRow.filterSectionRow = FilterSectionRow.LOCATION filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) @@ -260,11 +253,11 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(location = l3) realm.commitTransaction() - val filter = QueryCondition.LOCATION + val filter = QueryCondition.AnyLocation() - val filterElementRow = FilterElementRow.Location(l1) + val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) } filterElementRow.filterSectionRow = FilterSectionRow.LOCATION - val filterElementRow2 = FilterElementRow.Location(l3) + val filterElementRow2 = QueryCondition.AnyLocation().apply { setObject(l3) } filterElementRow2.filterSectionRow = FilterSectionRow.LOCATION filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) @@ -290,9 +283,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(tournamentName = t2) realm.commitTransaction() - val filter = QueryCondition.TOURNAMENT_NAME + val filter = QueryCondition.AnyTournamentName() - val filterElementRow = FilterElementRow.TournamentName(t1) + val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) } filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) @@ -322,10 +315,10 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(tournamentName = t3) realm.commitTransaction() - val filter = QueryCondition.TOURNAMENT_NAME - val filterElementRow = FilterElementRow.TournamentName(t1) + val filter = QueryCondition.AnyTournamentName() + val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) } filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME - val filterElementRow2 = FilterElementRow.TournamentName(t2) + val filterElementRow2 = QueryCondition.AnyTournamentName().apply { setObject(t2) } filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) @@ -358,12 +351,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(tournamentFeatures = RealmList(t1)) realm.commitTransaction() - val filter = QueryCondition.ALL_TOURNAMENT_FEATURES - val filterElementRow = FilterElementRow.AllTournamentFeature(t1) + val filter = QueryCondition.AllTournamentFeature() + val filterElementRow = QueryCondition.AllTournamentFeature().apply { setObject(t1) } filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE - val filterElementRow2 = FilterElementRow.AllTournamentFeature(t2) + val filterElementRow2 = QueryCondition.AllTournamentFeature().apply { setObject(t2) } filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE - val filterElementRow3 = FilterElementRow.AllTournamentFeature(t4) + val filterElementRow3 = QueryCondition.AllTournamentFeature().apply { setObject(t4) } filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3))) @@ -393,14 +386,14 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(tournamentFeatures = RealmList(t1)) realm.commitTransaction() - val filter = QueryCondition.ANY_TOURNAMENT_FEATURES - val filterElementRow = FilterElementRow.AnyTournamentFeature(t1) + val filter = QueryCondition.AnyTournamentFeature() + val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t1) } filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE - val filterElementRow2 = FilterElementRow.AnyTournamentFeature(t2) + val filterElementRow2 = QueryCondition.AnyTournamentFeature().apply { setObject(t2) } filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE - val filterElementRow3 = FilterElementRow.AnyTournamentFeature(t3) + val filterElementRow3 = QueryCondition.AnyTournamentFeature().apply { setObject(t3) } filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE - val filterElementRow4 = FilterElementRow.AnyTournamentFeature(t4) + val filterElementRow4 = QueryCondition.AnyTournamentFeature().apply { setObject(t4) } filterElementRow4.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3, filterElementRow4))) @@ -427,8 +420,8 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(tournamentFeatures = RealmList(t1)) realm.commitTransaction() - val filter = QueryCondition.ANY_TOURNAMENT_FEATURES - val filterElementRow = FilterElementRow.AnyTournamentFeature(t2) + val filter = QueryCondition.AnyTournamentFeature() + val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t2) } filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) @@ -452,10 +445,10 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(tableSize = 10) realm.commitTransaction() - val filter = QueryCondition.TABLE_SIZE - val filterElementRow = FilterElementRow.TableSize(TableSize(2)) + val filter = QueryCondition.AnyTableSize() + val filterElementRow = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(2) } filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE - val filterElementRow2 = FilterElementRow.TableSize(TableSize(4)) + val filterElementRow2 = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(4) } filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) @@ -479,12 +472,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { val s2 = Session.testInstance(netResult = 570.0) realm.commitTransaction() - val filter = QueryCondition.MORE_THAN_NET_RESULT - val filterElementRow = FilterElementRow.ResultMoreThan(204.0) + val filter = QueryCondition.NetAmountWon() + val filterElementRow = QueryCondition.more().apply { listOfValues = arrayListOf(204.0) } filterElementRow.filterSectionRow = FilterSectionRow.VALUE - filter.updateValueMap(FilterCondition(filterElementRow)) + filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow))) - val sessions = Filter.queryOn(realm, arrayListOf(filter)) + val sessions = Filter.queryOn(realm, arrayListOf(filterElementRow)) Assert.assertEquals(2, sessions.size) @@ -504,10 +497,10 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(netResult = 570.0) realm.commitTransaction() - val filter = QueryCondition.LESS_THAN_NET_RESULT - val filterElementRow = FilterElementRow.ResultLessThan(540.0) + val filter = QueryCondition.NetAmountWon() + val filterElementRow = QueryCondition.less().apply { listOfValues = arrayListOf(540.0) } filterElementRow.filterSectionRow = FilterSectionRow.VALUE - filter.updateValueMap(FilterCondition(filterElementRow)) + filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow))) val sessions = Filter.queryOn(realm, arrayListOf(filter)) @@ -529,15 +522,15 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { Session.testInstance(netResult = 570.0) realm.commitTransaction() - val filterMore = QueryCondition.MORE_THAN_NET_RESULT - val filterElementRow = FilterElementRow.ResultMoreThan(200.0) + val filterMore = QueryCondition.NetAmountWon() + val filterElementRow = QueryCondition.more().apply { listOfValues = arrayListOf(200.0) } filterElementRow.filterSectionRow = FilterSectionRow.VALUE - filterMore.updateValueMap(FilterCondition(filterElementRow)) + filterMore.updateValueMap(FilterCondition(arrayListOf(filterElementRow))) - val filterLess = QueryCondition.LESS_THAN_NET_RESULT - val filterElementRow2 = FilterElementRow.ResultLessThan(400.0) + val filterLess = QueryCondition.NetAmountWon() + val filterElementRow2 = QueryCondition.less().apply { listOfValues = arrayListOf(400.0) } filterElementRow2.filterSectionRow = FilterSectionRow.VALUE - filterLess.updateValueMap(FilterCondition(filterElementRow2)) + filterLess.updateValueMap(FilterCondition(arrayListOf(filterElementRow2))) val sessions = Filter.queryOn(realm, arrayListOf(filterMore, filterLess)) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f304e967..c5824c37 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,6 +34,36 @@ android:screenOrientation="portrait" android:windowSoftInputMode="adjustNothing" /> + + + + + + + + + + + + - - + + + + \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt index 7cbb59df..7a16edef 100644 --- a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt +++ b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt @@ -15,6 +15,7 @@ import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.utils.Seed import net.pokeranalytics.android.util.FakeDataManager import net.pokeranalytics.android.util.PokerAnalyticsLogs +import net.pokeranalytics.android.util.UserDefaults import timber.log.Timber @@ -23,12 +24,13 @@ class PokerAnalyticsApplication : Application() { override fun onCreate() { super.onCreate() + UserDefaults.init(this) // Realm Realm.init(this) val realmConfiguration = RealmConfiguration.Builder() .name(Realm.DEFAULT_REALM_NAME) - .schemaVersion(2) + .schemaVersion(3) .migration(PokerAnalyticsMigration()) .initialData(Seed(this)) .build() @@ -49,7 +51,8 @@ class PokerAnalyticsApplication : Application() { } if (BuildConfig.DEBUG) { - // this.createFakeSessions() + Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}") +// this.createFakeSessions() } Patcher.patchBreaks() 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 f3cb17b3..1347d7f4 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt @@ -2,11 +2,20 @@ package net.pokeranalytics.android.calculus import io.realm.Realm import net.pokeranalytics.android.calculus.Stat.* +import net.pokeranalytics.android.model.Criteria +import net.pokeranalytics.android.model.combined +import net.pokeranalytics.android.model.extensions.hourlyDuration +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.filter.filter +import net.pokeranalytics.android.model.filter.name import net.pokeranalytics.android.model.realm.ComputableResult -import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.SessionSet +import net.pokeranalytics.android.model.upToNow +import net.pokeranalytics.android.util.extensions.startOfDay import timber.log.Timber import java.util.* +import kotlin.math.max +import kotlin.math.min /** * The class performing stats computation @@ -16,7 +25,12 @@ class Calculator { /** * The options used for calculations or display */ - class Options { + class Options( + display: Display = Display.TABLE, + evolutionValues: EvolutionValues = EvolutionValues.NONE, + stats: List = listOf(), + aggregationType: AggregationType? = null + ) { /** * The way the stats are going to be displayed @@ -35,49 +49,122 @@ class Calculator { enum class EvolutionValues { NONE, STANDARD, - DATED + TIMED } - var display: Display = Display.TABLE - var evolutionValues: EvolutionValues = EvolutionValues.NONE - var displayedStats: List = listOf() + var display: Display = display + var evolutionValues: EvolutionValues = evolutionValues + var displayedStats: List = stats + var aggregationType: AggregationType? = aggregationType /** * This function determines whether the standard deviation should be computed */ - fun shouldComputeStandardDeviation(): Boolean { - this.displayedStats.forEach { stat -> - return when (stat) { - STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true - else -> false + val computeStandardDeviation: Boolean + get() { + this.displayedStats.forEach { + if (it == STANDARD_DEVIATION_BB_PER_100_HANDS || it == STANDARD_DEVIATION || it == STANDARD_DEVIATION_HOURLY) { + return true + } } + return false } - return true + + val computeLongestStreak: Boolean + get() { + return this.displayedStats.contains(LONGEST_STREAKS) } + val shouldSortValues: Boolean + get() { + return this.evolutionValues != EvolutionValues.NONE || this.computeLongestStreak + } + val computeLocationsPlayed: Boolean + get() { + return this.displayedStats.contains(LOCATIONS_PLAYED) + } + val computeDaysPlayed: Boolean + get() { + return this.displayedStats.contains(DAYS_PLAYED) + } + + val shouldManageMultiGroupProgressValues: Boolean + get() { + if (this.aggregationType != null) { + return this.aggregationType == AggregationType.MONTH || this.aggregationType == AggregationType.YEAR + } else { + return false + } + } -// var aggregation: Aggregation? = null } companion object { - fun computeStatsWithFilters(realm: Realm, filters: List, options: Options): List { + fun computeStatsWithEvolutionByAggregationType( + realm: Realm, + stat: Stat, + group: ComputableGroup, + aggregationType: AggregationType, + stats: List? = null + ): Report { + + val options = Options(evolutionValues = Options.EvolutionValues.STANDARD, aggregationType = aggregationType) + options.displayedStats = listOf(stat) + if (aggregationType == AggregationType.DURATION) { + options.evolutionValues = Options.EvolutionValues.TIMED + } - var computableGroups: MutableList = mutableListOf() - filters.forEach { filter -> + stats?.let { + options.displayedStats = stats + } - val group = ComputableGroup(filter.name, filter.filterConditions.map { it.queryCondition }) + return when (aggregationType) { + AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(realm, listOf(group), options) + AggregationType.MONTH -> { + val criteria: List = listOf(Criteria.Years, Criteria.MonthsOfYear) + this.computeStatsWithComparators(realm, criteria, group.conditions, options) + } + AggregationType.YEAR -> { + val criteria: List = listOf(Criteria.Years) + this.computeStatsWithComparators(realm, criteria, group.conditions, options) + } + } + } + + fun computeStatsWithComparators( + realm: Realm, + criteria: List = listOf(), + conditions: List = listOf(), + options: Options = Options() + ): Report { + + val computableGroups: MutableList = mutableListOf() + + criteria.combined().upToNow().forEach { comparatorConditions -> + + val allConditions = mutableListOf() + allConditions.addAll(conditions) + allConditions.addAll(comparatorConditions) + + val group = ComputableGroup(allConditions.name(), allConditions) computableGroups.add(group) } - return Calculator.computeGroups(realm, computableGroups, options) + + if (computableGroups.size == 0) { + val group = ComputableGroup(conditions.name(), conditions) + computableGroups.add(group) + } + + return this.computeGroups(realm, computableGroups, options) } /** * Computes all stats for list of Session sessionGroup */ - fun computeGroups(realm: Realm, groups: List, options: Options): List { + fun computeGroups(realm: Realm, groups: List, options: Options = Options()): Report { - val computedResults = mutableListOf() + val report = Report(options) groups.forEach { group -> val s = Date() @@ -85,18 +172,22 @@ class Calculator { group.cleanup() // Computes actual sessionGroup stats - val results: ComputedResults = Calculator.compute(realm, group, options = options) + val results: ComputedResults = this.compute(realm, group, options) // Computes the compared sessionGroup if existing - val comparedGroup = group.comparedComputables + val comparedGroup = group.comparedGroup if (comparedGroup != null) { - val comparedResults = Calculator.compute(realm, comparedGroup, options = options) + val comparedResults = this.compute(realm, comparedGroup, options) group.comparedComputedResults = comparedResults results.computeStatVariations(comparedResults) } + if (options.shouldManageMultiGroupProgressValues) { + group.comparedComputedResults = report.results.lastOrNull() + } + results.finalize(options) // later treatment, such as evolution numericValues sorting - computedResults.add(results) + report.addResults(results) val e = Date() val duration = (e.time - s.time) / 1000.0 @@ -104,128 +195,64 @@ class Calculator { } - return computedResults + return report } /** * Computes stats for a SessionSet */ - fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options): ComputedResults { + fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options = Options()): ComputedResults { - val computables = computableGroup.computables(realm) - Timber.d(">>>> Start computing group ${computableGroup.name}, ${computables.size} computables") + val results = ComputedResults(computableGroup, options.shouldManageMultiGroupProgressValues) - val results: ComputedResults = ComputedResults(computableGroup) + val computables = computableGroup.computables(realm, options.shouldSortValues) + Timber.d(">>>> Start computing group ${computableGroup.name}, ${computables.size} computables") + results.addStat(NUMBER_OF_GAMES, computables.size.toDouble()) val sum = computables.sum(ComputableResult.Field.RATED_NET.identifier).toDouble() + results.addStat(NET_RESULT, sum) + val totalHands = computables.sum(ComputableResult.Field.ESTIMATED_HANDS.identifier).toDouble() + results.addStat(HANDS_PLAYED, totalHands) + val bbSum = computables.sum(ComputableResult.Field.BB_NET.identifier).toDouble() + results.addStat(BB_NET_RESULT, bbSum) + val bbSessionCount = computables.sum(ComputableResult.Field.HAS_BIG_BLIND.identifier).toInt() + results.addStat(BB_SESSION_COUNT, bbSessionCount.toDouble()) + val winningSessionCount = computables.sum(ComputableResult.Field.IS_POSITIVE.identifier).toInt() - val totalBuyin = computables.sum(ComputableResult.Field.RATED_BUYIN.identifier).toDouble() + results.addStat(WINNING_SESSION_COUNT, winningSessionCount.toDouble()) - // Compute for each session - - when (options.evolutionValues) { - Options.EvolutionValues.STANDARD -> { - - var index: Int = 0 - var tSum = 0.0 - var tBBSum = 0.0 - var tBBSessionCount = 0 - var tWinningSessionCount = 0 - var tBuyinSum = 0.0 - var tHands = 0.0 - - computables.forEach { computable -> - index++ - tSum += computable.ratedNet - tBBSum += computable.bbNet - tBBSessionCount += computable.hasBigBlind - tWinningSessionCount += computable.isPositive - tBuyinSum += computable.ratedBuyin - tHands += computable.estimatedHands - - val session = computable.session ?: throw IllegalStateException("Computing lone ComputableResult") - results.addEvolutionValue(tSum, NETRESULT, session) - results.addEvolutionValue(tSum / index, AVERAGE, session) - results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES, session) - results.addEvolutionValue(tBBSum / tBBSessionCount, AVERAGE_NET_BB, session) - results.addEvolutionValue((tWinningSessionCount / index).toDouble(), WIN_RATIO, session) - results.addEvolutionValue(tBuyinSum / index, AVERAGE_BUYIN, session) - - Stat.netBBPer100Hands(tBBSum, tHands)?.let { netBB100 -> - results.addEvolutionValue(netBB100, NET_BB_PER_100_HANDS, session) - } + val totalBuyin = computables.sum(ComputableResult.Field.RATED_BUYIN.identifier).toDouble() + results.addStat(TOTAL_BUYIN, totalBuyin) - Stat.returnOnInvestment(tSum, tBuyinSum)?.let { roi -> - results.addEvolutionValue(roi, ROI, session) - } + val maxNetResult = computables.max(ComputableResult.Field.RATED_NET.identifier)?.toDouble() + maxNetResult?.let { + results.addStat(MAXIMUM_NETRESULT, it) + } - } - } - else -> { - // nothing - } + val minNetResult = computables.min(ComputableResult.Field.RATED_NET.identifier)?.toDouble() + minNetResult?.let { + results.addStat(MINIMUM_NETRESULT, it) } - val sessionSets = computableGroup.sessionSets(realm) - - // Compute for each serie - val gHourlyDuration = - sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours) - val gSum = sessionSets.sum(SessionSet.Field.RATED_NET.identifier).toDouble() - val gTotalHands = sessionSets.sum(SessionSet.Field.ESTIMATED_HANDS.identifier).toDouble() - val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble() - - val hourlyRate = gSum / gHourlyDuration -// var bbHourlyRate = gBBSum / gDuration - - when (options.evolutionValues) { - Options.EvolutionValues.DATED -> { - - var tHourlyDuration = 0.0 - var tIndex = 0 - var tSum = 0.0 - var tTotalHands = 0.0 - var tBBSum = 0.0 - var tHourlyRate = 0.0 - var tHourlyRateBB = 0.0 - - sessionSets.forEach { sessionSet -> - tIndex++ - tHourlyDuration += sessionSet.hourlyDuration - tSum += sessionSet.ratedNet - tTotalHands += sessionSet.estimatedHands - tBBSum += sessionSet.bbNet - - tHourlyRate = gSum / tHourlyDuration - tHourlyRateBB = gBBSum / tHourlyDuration - - results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, sessionSet) - results.addEvolutionValue(tSum / tHourlyDuration, tHourlyDuration, HOURLY_RATE, sessionSet) - results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet) - results.addEvolutionValue(tIndex.toDouble(), tHourlyDuration, NUMBER_OF_SETS, sessionSet) - results.addEvolutionValue(sessionSet.netDuration.toDouble(), tHourlyDuration, DURATION, sessionSet) - results.addEvolutionValue(tHourlyDuration / tIndex, tHourlyDuration, AVERAGE_DURATION, sessionSet) - results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet) - - Stat.netBBPer100Hands(gBBSum, gTotalHands)?.let { netBB100 -> - results.addEvolutionValue(netBB100, tHourlyDuration, NET_BB_PER_100_HANDS, sessionSet) - } + Stat.netBBPer100Hands(bbSum, totalHands)?.let { netBB100 -> + results.addStat(NET_BB_PER_100_HANDS, netBB100) + } + Stat.returnOnInvestment(sum, totalBuyin)?.let { roi -> + results.addStat(ROI, roi) + } - } - } - else -> { - // nothing - } + if (options.computeLocationsPlayed) { + results.addStat(LOCATIONS_PLAYED, computables.distinctBy { it.session?.location?.id }.size.toDouble()) } - var average = 0.0 + var average = 0.0 // also used for standard deviation later if (computables.size > 0) { average = sum / computables.size.toDouble() val winRatio = winningSessionCount.toDouble() / computables.size.toDouble() - val avgBuyin = totalBuyin / computables.size + val avgBuyin = totalBuyin / computables.size.toDouble() results.addStats( setOf( @@ -236,41 +263,228 @@ class Calculator { ) } - if (sessionSets.size > 0) { - val avgDuration = gHourlyDuration / sessionSets.size - results.addStats( - setOf( - ComputedStat(HOURLY_RATE, hourlyRate), - ComputedStat(AVERAGE_DURATION, avgDuration) + val shouldIterateOverComputables = + (options.evolutionValues == Options.EvolutionValues.STANDARD || options.computeLongestStreak) + + // Computable Result + if (shouldIterateOverComputables) { + + var index = 0 + var tSum = 0.0 + var tBBSum = 0.0 + var tBBSessionCount = 0 + var tWinningSessionCount = 0 + var tBuyinSum = 0.0 + var tHands = 0.0 + var longestWinStreak = 0 + var longestLoseStreak = 0 + var currentStreak = 0 + + computables.forEach { computable -> + index++ + tSum += computable.ratedNet + tBBSum += computable.bbNet + tBBSessionCount += computable.hasBigBlind + tWinningSessionCount += computable.isPositive + tBuyinSum += computable.ratedBuyin + tHands += computable.estimatedHands + + if (computable.isPositive == 1) { // positive result + if (currentStreak >= 0) { // currently positive streak + currentStreak++ + } else { // currently negative streak + longestLoseStreak = min(longestLoseStreak, currentStreak) + currentStreak = 1 + } + } else { // negative result + if (currentStreak <= 0) { // currently negative streak + currentStreak-- + } else { // currently positive streak + longestWinStreak = max(longestWinStreak, currentStreak) + currentStreak = -1 + } + } + + val session = + computable.session ?: throw IllegalStateException("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) + results.addEvolutionValue(tBBSum / tBBSessionCount, stat = AVERAGE_NET_BB, data = session) + results.addEvolutionValue( + (tWinningSessionCount.toDouble() / index.toDouble()), + stat = WIN_RATIO, + data = session ) - ) + results.addEvolutionValue(tBuyinSum / index, stat = AVERAGE_BUYIN, data = session) + results.addEvolutionValue(computable.ratedNet, stat = STANDARD_DEVIATION, data = session) + + Stat.netBBPer100Hands(tBBSum, tHands)?.let { netBB100 -> + results.addEvolutionValue(netBB100, stat = NET_BB_PER_100_HANDS, data = session) + } + + Stat.returnOnInvestment(tSum, tBuyinSum)?.let { roi -> + results.addEvolutionValue(roi, stat = ROI, data = session) + } + + } + + if (currentStreak >= 0) { + longestWinStreak = max(longestWinStreak, currentStreak) + } else { + longestLoseStreak = min(longestLoseStreak, currentStreak) + } + + // loseStreak is negative and we want it positive + results.addStat(LONGEST_STREAKS, longestWinStreak.toDouble(), -longestLoseStreak.toDouble()) + } - // Create stats - results.addStats( - setOf( - ComputedStat(NETRESULT, sum), - ComputedStat(DURATION, gHourlyDuration), - ComputedStat(NUMBER_OF_SETS, sessionSets.size.toDouble()), - ComputedStat(NUMBER_OF_GAMES, computables.size.toDouble()), - ComputedStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration), - ComputedStat(AVERAGE_NET_BB, gBBSum / bbSessionCount), - ComputedStat(HANDS_PLAYED, totalHands) + val sessionSets = computableGroup.sessionSets(realm, options.shouldSortValues) + results.addStat(NUMBER_OF_SETS, sessionSets.size.toDouble()) - ) - ) + var gHourlyDuration: Double? = null + var gBBSum: Double? = null + var maxDuration: Double? = null - Stat.returnOnInvestment(sum, totalBuyin)?.let { roi -> - results.addStats(setOf(ComputedStat(ROI, roi))) + if (computableGroup.conditions.size == 0) { // SessionSets are fine + gHourlyDuration = + sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours) + gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble() + + sessionSets.max(SessionSet.Field.NET_DURATION.identifier)?.let { + maxDuration = it.toDouble() / 3600000 + } } - Stat.netBBPer100Hands(bbSum, totalHands)?.let { netBB100 -> - results.addStats(setOf(ComputedStat(NET_BB_PER_100_HANDS, netBB100))) + + val shouldIterateOverSets = computableGroup.conditions.isNotEmpty() || + options.evolutionValues != Options.EvolutionValues.NONE || + options.computeDaysPlayed + + // Session Set + if (shouldIterateOverSets) { + + var tHourlyDuration = 0.0 + var tIndex = 0 + var tRatedNetSum = 0.0 + var tBBSum = 0.0 + var tTotalHands = 0.0 + var tHourlyRate: Double + var tHourlyRateBB: Double + val daysSet = mutableSetOf() + var tMaxDuration = 0.0 + + sessionSets.forEach { sessionSet -> + tIndex++ + + val setStats = SSStats(sessionSet, computableGroup.conditions) + + tRatedNetSum += setStats.ratedNet + tBBSum += setStats.bbSum + tHourlyDuration += setStats.hourlyDuration + tTotalHands += setStats.estimatedHands + tMaxDuration = max(tMaxDuration, setStats.hourlyDuration) + + tHourlyRate = tRatedNetSum / tHourlyDuration + tHourlyRateBB = tBBSum / tHourlyDuration + daysSet.add(sessionSet.startDate.startOfDay()) + + when (options.evolutionValues) { + Options.EvolutionValues.STANDARD -> { + results.addEvolutionValue(tHourlyRate, stat = HOURLY_RATE, data = sessionSet) + results.addEvolutionValue(tIndex.toDouble(), stat = NUMBER_OF_SETS, data = sessionSet) + results.addEvolutionValue( + sessionSet.hourlyDuration, + tHourlyDuration, + HOURLY_DURATION, + sessionSet + ) + results.addEvolutionValue( + tHourlyDuration / tIndex, + stat = AVERAGE_HOURLY_DURATION, + data = sessionSet + ) + results.addEvolutionValue(tHourlyRateBB, stat = HOURLY_RATE_BB, data = sessionSet) + + Stat.netBBPer100Hands(tBBSum, tTotalHands)?.let { netBB100 -> + results.addEvolutionValue(netBB100, stat = NET_BB_PER_100_HANDS, data = sessionSet) + } + + } + Options.EvolutionValues.TIMED -> { + results.addEvolutionValue(tRatedNetSum, tHourlyDuration, NET_RESULT, sessionSet) + results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet) + results.addEvolutionValue( + tIndex.toDouble(), + tHourlyDuration, + NUMBER_OF_SETS, + sessionSet + ) + results.addEvolutionValue( + sessionSet.hourlyDuration, + tHourlyDuration, + HOURLY_DURATION, + sessionSet + ) + results.addEvolutionValue( + tHourlyDuration / tIndex, + tHourlyDuration, + AVERAGE_HOURLY_DURATION, + sessionSet + ) + results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet) + + Stat.netBBPer100Hands(tBBSum, tTotalHands)?.let { netBB100 -> + results.addEvolutionValue( + netBB100, + tHourlyDuration, + NET_BB_PER_100_HANDS, + sessionSet + ) + } + } + else -> { + // nothing + } + } + + results.addStat(DAYS_PLAYED, daysSet.size.toDouble()) + + } + + gHourlyDuration = tHourlyDuration + gBBSum = tBBSum + maxDuration = tMaxDuration + + } + + var hourlyRate = 0.0 + if (gHourlyDuration != null) { + + hourlyRate = sum / gHourlyDuration + if (sessionSets.size > 0) { + val avgDuration = gHourlyDuration / sessionSets.size + results.addStat(HOURLY_RATE, hourlyRate) + results.addStat(AVERAGE_HOURLY_DURATION, avgDuration) + } + results.addStat(HOURLY_DURATION, gHourlyDuration) + } + + if (gBBSum != null) { + if (gHourlyDuration != null) { + results.addStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration) + } + results.addStat(AVERAGE_NET_BB, gBBSum / bbSessionCount) + } + + maxDuration?.let { maxd -> + results.addStat(MAXIMUM_DURATION, maxd) // (milliseconds to hours) } val bbPer100Hands = bbSum / totalHands * 100 // Standard Deviation - if (options.shouldComputeStandardDeviation()) { + if (options.computeStandardDeviation) { // Session var stdSum = 0.0 @@ -282,20 +496,22 @@ class Calculator { val standardDeviation = Math.sqrt(stdSum / computables.size) val standardDeviationBBper100Hands = Math.sqrt(stdBBper100HandsSum / computables.size) + results.addStat(STANDARD_DEVIATION, standardDeviation) + results.addStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands) + // Session Set - var hourlyStdSum = 0.0 - sessionSets.forEach { set -> - hourlyStdSum += Math.pow(set.hourlyRate - hourlyRate, 2.0) + if (gHourlyDuration != null) { + var hourlyStdSum = 0.0 + sessionSets.forEach { set -> + val ssStats = SSStats(set, computableGroup.conditions) + val sHourlyRate = ssStats.hourlyRate + hourlyStdSum += Math.pow(sHourlyRate - hourlyRate, 2.0) + } + val hourlyStandardDeviation = Math.sqrt(hourlyStdSum / sessionSets.size) + + results.addStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) } - val hourlyStandardDeviation = Math.sqrt(hourlyStdSum / sessionSets.size) - results.addStats( - setOf( - ComputedStat(STANDARD_DEVIATION, standardDeviation), - ComputedStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation), - ComputedStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands) - ) - ) } return results @@ -303,5 +519,43 @@ class Calculator { } +} + +class SSStats(sessionSet: SessionSet, conditions: List) { // Session Set Stats + + var hourlyDuration: Double = 0.0 + var estimatedHands: Double = 0.0 + var bbSum: Double = 0.0 + var ratedNet: Double = 0.0 + + val hourlyRate: Double + get() { + return this.ratedNet / this.hourlyDuration + } + + init { + + if (sessionSet.sessions?.size == 1) { // use precomputed values + this.initStatsWithSet(sessionSet) + } else { // dynamically filter and compute subset + val setSessions = sessionSet.sessions!! + val filteredSessions = setSessions.filter(conditions) + if (setSessions.size == filteredSessions.size) { + this.initStatsWithSet(sessionSet) + } else { + ratedNet = filteredSessions.sumByDouble { it.computableResult?.ratedNet ?: 0.0 } + bbSum = filteredSessions.sumByDouble { it.bbNet } + hourlyDuration = filteredSessions.hourlyDuration + estimatedHands = filteredSessions.sumByDouble { it.estimatedHands } + } + } + } + + private fun initStatsWithSet(sessionSet: SessionSet) { + ratedNet = sessionSet.ratedNet + bbSum = sessionSet.bbNet + hourlyDuration = sessionSet.hourlyDuration + estimatedHands = sessionSet.estimatedHands + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt b/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt deleted file mode 100644 index b1fdf74f..00000000 --- a/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt +++ /dev/null @@ -1,218 +0,0 @@ -package net.pokeranalytics.android.calculus - -import com.github.mikephil.charting.data.BarEntry -import com.github.mikephil.charting.data.Entry -import io.realm.Realm -import io.realm.RealmResults -import net.pokeranalytics.android.model.filter.QueryCondition -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 - -/** - * A sessionGroup of computable items identified by a name - */ -class ComputableGroup(name: String, conditions: List, stats: List? = null) { - - /** - * The display name of the group - */ - var name: String = name - - /** - * A list of conditions to get - */ - var conditions: List = conditions - - /** - * The list of endedSessions to compute - */ - private var _computables: RealmResults? = null - - /** - * Retrieves the computables on the relative [realm] filtered with the provided [conditions] - */ - fun computables(realm: Realm): RealmResults { - - // if computables exists and is valid (previous realm not closed) - this._computables?.let { - if (it.isValid) { - return it - } - } - - val computables: RealmResults = Filter.queryOn(realm, this.conditions) - this._computables = computables - return computables - } - - /** - * The list of sets to compute - */ - private var _sessionSets: RealmResults? = null - - /** - * Retrieves the session sets on the relative [realm] filtered with the provided [conditions] - */ - fun sessionSets(realm: Realm): RealmResults { - // if computables exists and is valid (previous realm not closed) - this._sessionSets?.let { - if (it.isValid) { - return it - } - } - - val sets: RealmResults = Filter.queryOn(realm, this.conditions) - this._sessionSets = sets - return sets - } - - /** - * The list of stats to display - */ - var stats: List? = stats - - /** - * A subgroup used to compute stat variation - */ - var comparedComputables: ComputableGroup? = null - - /** - * The computed stats of the comparable sessionGroup - */ - var comparedComputedResults: ComputedResults? = null - - fun cleanup() { - this._computables = null - this._sessionSets = null - } - -} - -class ComputedResults(group: ComputableGroup) { - - /** - * The session group used to computed the stats - */ - var group: ComputableGroup = group - - // The computed stats of the sessionGroup - private var _computedStats: MutableMap = mutableMapOf() - - // The map containing all evolution numericValues for all stats - private var _evolutionValues: MutableMap> = mutableMapOf() - - fun allStats(): Collection { - return this._computedStats.values - } - - /** - * Adds a value to the evolution values - */ - fun addEvolutionValue(value: Double, stat: Stat, data: Any) { - this._addEvolutionValue(Point(value, data), stat = stat) - } - - fun addEvolutionValue(value: Double, duration: Double, stat: Stat, data: Timed) { - stat.underlyingClass = data::class.java - this._addEvolutionValue(Point(value, y = duration, data = data.id), stat = stat) - } - - private fun _addEvolutionValue(point: Point, stat: Stat) { - val evolutionValues = this._evolutionValues[stat] - if (evolutionValues != null) { - evolutionValues.add(point) - } else { - val values: MutableList = mutableListOf(point) - this._evolutionValues[stat] = values - } - } - - fun addStats(computedStats: Set) { - computedStats.forEach { - this._computedStats[it.stat] = it - } - } - - fun computedStat(stat: Stat): ComputedStat? { - return this._computedStats[stat] - } - - fun computeStatVariations(resultsToCompare: ComputedResults) { - this._computedStats.keys.forEach { stat -> - val computedStat = this.computedStat(stat) - val comparedStat = resultsToCompare.computedStat(stat) - if (computedStat != null && comparedStat != null) { - computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value - } - } - } - - fun finalize(options: Calculator.Options) { - if (options.evolutionValues != Calculator.Options.EvolutionValues.NONE) { - - // Sort points as a distribution - this._computedStats.keys.filter { it.hasDistributionSorting() }.forEach { _ -> - // @todo sort -// var evolutionValues = this._evolutionValues[stat] -// evolutionValues.so - } - - } - } - - /** - * Returns the number of computed stats - */ - fun numberOfStats(): Int { - return this._computedStats.size - } - - // MPAndroidChart - - fun defaultStatEntries(stat: Stat): List { - return when (stat) { - Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES -> this.barEntries(stat) - else -> this.singleLineEntries(stat) - } - } - - fun singleLineEntries(stat: Stat): List { - val entries = mutableListOf() - this._evolutionValues[stat]?.let { points -> - points.forEachIndexed { index, p -> - entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) - } - } - return entries - } - - fun durationEntries(stat: Stat): List { - val entries = mutableListOf() - this._evolutionValues[stat]?.let { points -> - points.forEach { p -> - entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) - } - } - return entries - } - - fun barEntries(stat: Stat): List { - - val entries = mutableListOf() - this._evolutionValues[stat]?.let { points -> - points.forEach { p -> - entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) - } - } - return entries - } - -} - -class Point(val x: Double, val y: Double, val data: Any) { - - constructor(y: Double, data: Any) : this(0.0, y, data) - -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt new file mode 100644 index 00000000..186ba556 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt @@ -0,0 +1,496 @@ +package net.pokeranalytics.android.calculus + +import android.content.Context +import com.github.mikephil.charting.data.* +import io.realm.Realm +import io.realm.RealmResults +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.filter.QueryCondition +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 +import net.pokeranalytics.android.ui.fragment.GraphFragment +import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry +import net.pokeranalytics.android.ui.graph.PALineDataSet +import net.pokeranalytics.android.ui.view.DefaultLegendValues +import net.pokeranalytics.android.ui.view.LegendContent +import net.pokeranalytics.android.util.ColorUtils +import kotlin.math.abs + +/** + * The class returned after performing calculation in the Calculator object + */ +class Report(var options: Calculator.Options) { + + /** + * The mutable list of ComputedResults, one for each group of data + */ + private var _results: MutableList = mutableListOf() + + val results: List = this._results + + /** + * Adds a new result to the list of ComputedResults + */ + fun addResults(result: ComputedResults) { + this._results.add(result) + } + + /** + * Returns the list of entries corresponding to the provided [stat] + * One value will be returned by result + */ + fun lineEntries(stat: Stat? = null, context: Context): LineDataSet { + val entries = mutableListOf() + val statToUse = stat ?: options.displayedStats.firstOrNull() + val statName = statToUse?.name ?: "" + + statToUse?.let { + this._results.forEachIndexed { index, results -> + results.computedStat(it)?.progressValue?.let { progressValue -> + entries.add(Entry(index.toFloat(), progressValue.toFloat(), results)) + } + } + } + + return PALineDataSet(entries, statName, context) + } + + fun barEntries(stat: Stat? = null, context: Context): BarDataSet { + val entries = mutableListOf() + val statToUse = stat ?: options.displayedStats.firstOrNull() + + statToUse?.let { + this._results.forEachIndexed { index, results -> + val cs = results.computedStat(it) + cs?.let { computedStat -> + val barEntry = BarEntry(index.toFloat(), computedStat.value.toFloat(), results) + entries.add(barEntry) + } + } + } + + val barDataSet = BarDataSet(entries, statToUse?.name) + barDataSet.color = context.getColor(R.color.green) + barDataSet.setDrawValues(false) + return barDataSet + } + + fun multiLineEntries(context: Context): List { + val dataSets = mutableListOf() + + options.displayedStats.forEach { stat -> + this._results.forEachIndexed { index, result -> + val ds = result.singleLineEntries(stat, context) + ds.color = ColorUtils.almostRandomColor(index, context) + dataSets.add(ds) + } + } + + return dataSets + } + +} + +/** + * A sessionGroup of computable items identified by a name + */ +class ComputableGroup(name: String = "", conditions: List = listOf(), stats: List? = null) { + + /** + * The display name of the group + */ + var name: String = name + + /** + * A list of conditions to get + */ + var conditions: List = conditions + + /** + * The list of endedSessions to compute + */ + private var _computables: RealmResults? = null + + /** + * Retrieves the computables on the relative [realm] filtered with the provided [conditions] + */ + fun computables(realm: Realm, sorted: Boolean = false): RealmResults { + + // if computables exists and is valid (previous realm not closed) + this._computables?.let { + if (it.isValid) { + return it + } + } + + val sortedField = if (sorted) "session.startDate" else null + val computables = Filter.queryOn(realm, this.conditions, sortedField) + this._computables = computables + return computables + } + + /** + * The list of sets to compute + */ + private var _sessionSets: RealmResults? = null + + /** + * Retrieves the session sets on the relative [realm] filtered with the provided [conditions] + */ + fun sessionSets(realm: Realm, sorted: Boolean = false): RealmResults { + // if computables exists and is valid (previous realm not closed) + this._sessionSets?.let { + if (it.isValid) { + return it + } + } + + val sortedField = if (sorted) SessionSet.Field.START_DATE.identifier else null + val sets = Filter.queryOn(realm, this.conditions, sortedField) + this._sessionSets = sets + return sets + } + + /** + * The list of stats to display + */ + var stats: List? = stats + + /** + * A subgroup used to compute stat variation + */ + var comparedGroup: ComputableGroup? = null + + /** + * The computed stats of the comparable sessionGroup + */ + var comparedComputedResults: ComputedResults? = null + + fun cleanup() { + this._computables = null + this._sessionSets = null + } + + val isEmpty: Boolean + get() { + return this._computables?.isEmpty() ?: true + } + +} + +class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) : GraphUnderlyingEntry { + + /** + * The session group used to computed the stats + */ + var group: ComputableGroup = group + + // The computed stats of the sessionGroup + private var _computedStats: MutableMap = mutableMapOf() + + // The map containing all evolution numericValues for all stats + private var _evolutionValues: MutableMap> = mutableMapOf() + + private var shouldManageMultiGroupProgressValues = shouldManageMultiGroupProgressValues + + fun allStats(): Collection { + return this._computedStats.values + } + + /** + * Adds a value to the evolution values + */ + fun addEvolutionValue(value: Double, duration: Double? = null, stat: Stat, data: Timed) { + + val point = if (duration != null) { + Point(duration, y = value, data = data.objectIdentifier) + } else { + Point(value, data = data.objectIdentifier) + } + this._addEvolutionValue(point, stat = stat) + } + + private fun _addEvolutionValue(point: Point, stat: Stat) { + val evolutionValues = this._evolutionValues[stat] + if (evolutionValues != null) { + evolutionValues.add(point) + } else { + val values: MutableList = mutableListOf(point) + this._evolutionValues[stat] = values + } + } + + fun addStat(stat: Stat, value: Double, secondValue: Double? = null) { + val computedStat = ComputedStat(stat, value, secondValue = secondValue) + this.addComputedStat(computedStat) + } + + fun addStats(computedStats: Set) { + computedStats.forEach { + this.addComputedStat(it) + } + } + + /** + * Adds a [computedStat] to the list of stats + * Also computes evolution values using the previously computed values + */ + private fun addComputedStat(computedStat: ComputedStat) { + this._computedStats[computedStat.stat] = computedStat + } + + private fun consolidateProgressStats() { + + if (this.shouldManageMultiGroupProgressValues) { + + this.group.comparedComputedResults?.let { previousResult -> + this.allStats().forEach { computedStat -> + val stat = computedStat.stat + previousResult.computedStat(stat)?.let { previousComputedStat -> + when (stat) { + Stat.NET_RESULT, Stat.HOURLY_DURATION, Stat.BB_NET_RESULT, Stat.BB_SESSION_COUNT, + Stat.WINNING_SESSION_COUNT, Stat.TOTAL_BUYIN, Stat.HANDS_PLAYED, Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> { + val previousValue = previousComputedStat.progressValue ?: previousComputedStat.value + computedStat.progressValue = previousValue + computedStat.value + } + else -> {} + } + } ?: run { + computedStat.progressValue = computedStat.value + } + } + } + + val netResult = this.computedStat(Stat.NET_RESULT)?.progressValue + val bbNetResult = this.computedStat(Stat.BB_NET_RESULT)?.progressValue + val duration = this.computedStat(Stat.HOURLY_DURATION)?.progressValue + val numberOfGames = this.computedStat(Stat.NUMBER_OF_GAMES)?.progressValue + val numberOfSets = this.computedStat(Stat.NUMBER_OF_SETS)?.progressValue + val handsPlayed = this.computedStat(Stat.HANDS_PLAYED)?.progressValue + val winningCount = this.computedStat(Stat.WINNING_SESSION_COUNT)?.progressValue + val bbSessionCount = this.computedStat(Stat.BB_SESSION_COUNT)?.progressValue + val totalBuyin = this.computedStat(Stat.TOTAL_BUYIN)?.progressValue + + this.allStats().forEach { computedStat -> + when (computedStat.stat) { + Stat.HOURLY_RATE -> { + if (netResult != null && duration != null) { + computedStat.progressValue = netResult / duration + } + } + Stat.AVERAGE -> { + if (netResult != null && numberOfGames != null) { + computedStat.progressValue = netResult / numberOfGames + } + } + Stat.AVERAGE_HOURLY_DURATION -> { + if (duration != null && numberOfSets != null) { + computedStat.progressValue = duration / numberOfSets + } + } + Stat.NET_BB_PER_100_HANDS -> { + if (bbNetResult != null && handsPlayed != null) { + computedStat.progressValue = Stat.netBBPer100Hands(bbNetResult, handsPlayed) + } + } + Stat.HOURLY_RATE_BB -> { + if (bbNetResult != null && duration != null) { + computedStat.progressValue = bbNetResult / duration + } + } + Stat.AVERAGE_NET_BB -> { + if (bbNetResult != null && bbSessionCount != null) { + computedStat.progressValue = bbNetResult / bbSessionCount + } + } + Stat.WIN_RATIO -> { + if (winningCount != null && numberOfGames != null) { + computedStat.progressValue = winningCount / numberOfGames + } + } + Stat.AVERAGE_BUYIN -> { + if (totalBuyin != null && numberOfGames != null) { + computedStat.progressValue = totalBuyin / numberOfGames + } + } + Stat.ROI -> { + if (totalBuyin != null && netResult != null) { + computedStat.progressValue = Stat.returnOnInvestment(netResult, totalBuyin) + } + } + else -> {} + } + } + + } + + + } + + fun computedStat(stat: Stat): ComputedStat? { + return this._computedStats[stat] + } + + fun computeStatVariations(resultsToCompare: ComputedResults) { + this._computedStats.keys.forEach { stat -> + val computedStat = this.computedStat(stat) + val comparedStat = resultsToCompare.computedStat(stat) + if (computedStat != null && comparedStat != null) { + computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value + } + } + } + + fun finalize(options: Calculator.Options) { + + this.consolidateProgressStats() + + } + + // MPAndroidChart + + fun defaultStatEntries(stat: Stat, context: Context): DataSet { + return when (stat) { + Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HOURLY_DURATION -> this.barEntries(stat, context = context) + Stat.STANDARD_DEVIATION -> this.distributionEntries(stat, context) + else -> this.singleLineEntries(stat, context) + } + } + + fun singleLineEntries(stat: Stat, context: Context): LineDataSet { + val entries = mutableListOf() + this._evolutionValues[stat]?.let { points -> + points.forEachIndexed { index, p -> + entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) + } + } + return PALineDataSet(entries, this.group.name, context) + } + + fun durationEntries(stat: Stat, context: Context): LineDataSet { + val entries = mutableListOf() + this._evolutionValues[stat]?.let { points -> + points.forEach { p -> + entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) + } + } + return PALineDataSet(entries, stat.name, context) + } + + fun barEntries(stat: Stat, context: Context): BarDataSet { + + val entries = mutableListOf() + this._evolutionValues[stat]?.let { points -> + points.forEach { p -> + entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) + } + } + val dataSet = BarDataSet(entries, stat.name) + dataSet.color = context.getColor(R.color.green) +// dataSet.barBorderWidth = 1.0f + dataSet.setDrawValues(false) + return dataSet + } + + fun distributionEntries(stat: Stat, context: Context): BarDataSet { + + val colors = mutableListOf() + val entries = mutableListOf() + this._evolutionValues[stat]?.let { points -> + + val negative = mutableListOf() + val positive = mutableListOf() + + points.sortByDescending { it.y } + points.forEach { + if (it.y < 0) { + negative.add(it) + } else { + positive.add(it) + } + } + + negative.forEachIndexed { index, p -> + entries.add(BarEntry(index.toFloat(), abs(p.y.toFloat()), p.data)) + colors.add(context.getColor(R.color.red)) + } + positive.forEachIndexed { index, p -> + val x = negative.size + index.toFloat() + entries.add(BarEntry(x, p.y.toFloat(), p.data)) + colors.add(context.getColor(R.color.green)) + } + + } + val dataSet = BarDataSet(entries, stat.name) + dataSet.colors = colors + dataSet.setDrawValues(false) + return dataSet + } + + val isEmpty: Boolean + get() { + return this.group.isEmpty + } + + // Stat Entry + + override val entryTitle: String = this.group.name + + override fun formattedValue(stat: Stat): TextFormat { + this.computedStat(stat)?.let { + return it.format() + } ?: run { + throw IllegalStateException("Missing stat in results") + } + } + + override fun legendValues( + stat: Stat, + entry: Entry, + style: GraphFragment.Style, + groupName: String, + context: Context + ): LegendContent { + + when (style) { + + GraphFragment.Style.BAR -> { + return when (stat) { + Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { + val totalStatValue = stat.format(entry.y.toDouble(), currency = null) + DefaultLegendValues(this.entryTitle, totalStatValue) + } + else -> { + val entryValue = this.formattedValue(stat) + val countValue = this.computedStat(Stat.NUMBER_OF_GAMES)?.format() + DefaultLegendValues(this.entryTitle, entryValue, countValue) + } + } + } + else -> { + + return when (stat) { + Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { + val totalStatValue = stat.format(entry.y.toDouble(), currency = null) + DefaultLegendValues(this.entryTitle, totalStatValue) + } + else -> { + val entryValue = this.formattedValue(stat) + val totalStatValue = stat.format(entry.y.toDouble(), currency = null) + DefaultLegendValues(this.entryTitle, entryValue, totalStatValue) + } + } + + } + } + } + +} + +class Point(val x: Double, val y: Double, val data: Any) { + + constructor(y: Double, data: Any) : this(0.0, y, data) + +} \ No newline at end of file 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 2b8c7387..e8fc3c68 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt @@ -1,40 +1,66 @@ package net.pokeranalytics.android.calculus import android.content.Context -import io.realm.RealmModel import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.FormattingException import net.pokeranalytics.android.model.interfaces.Timed +import net.pokeranalytics.android.ui.graph.AxisFormatting import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -import net.pokeranalytics.android.util.CurrencyUtils import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.extensions.formatted import net.pokeranalytics.android.util.extensions.formattedHourlyDuration +import net.pokeranalytics.android.util.extensions.toCurrency import java.util.* +import kotlin.math.exp class StatFormattingException(message: String) : Exception(message) { } -interface StatBase : RealmModel { +class ObjectIdentifier(var id: String, var clazz: Class) { - fun formattedValue(stat: Stat, context: Context): TextFormat +} + +enum class AggregationType { + SESSION, + MONTH, + YEAR, + DURATION; + + val resId: Int + get() { + return when (this) { + SESSION -> R.string.session + MONTH -> R.string.month + YEAR -> R.string.year + DURATION -> R.string.duration + } + } + + val axisFormatting: AxisFormatting + get() { + return when (this) { + DURATION -> AxisFormatting.X_DURATION + else -> AxisFormatting.DEFAULT + } + } } /** * An enum representing all the types of Session statistics */ -enum class Stat(var underlyingClass: Class? = null) : RowRepresentable { +enum class Stat : RowRepresentable { - NETRESULT, + NET_RESULT, + BB_NET_RESULT, HOURLY_RATE, AVERAGE, NUMBER_OF_SETS, NUMBER_OF_GAMES, - DURATION, - AVERAGE_DURATION, + HOURLY_DURATION, + AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS, HOURLY_RATE_BB, AVERAGE_NET_BB, @@ -44,17 +70,17 @@ enum class Stat(var underlyingClass: Class? = null) : RowRepresentabl STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS, - HANDS_PLAYED; - - /** - * Returns whether the stat evolution numericValues requires a distribution sorting - */ - fun hasDistributionSorting(): Boolean { - return when (this) { - STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true - else -> false - } - } + HANDS_PLAYED, + LOCATIONS_PLAYED, + LONGEST_STREAKS, + MAXIMUM_NETRESULT, + MINIMUM_NETRESULT, + MAXIMUM_DURATION, + DAYS_PLAYED, + WINNING_SESSION_COUNT, + BB_SESSION_COUNT, + TOTAL_BUYIN, + ; companion object { @@ -72,18 +98,32 @@ enum class Stat(var underlyingClass: Class? = null) : RowRepresentabl return netBB / numberOfHands * 100 } + 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 ratio = numerator / denominator + return exp(ratio) + + } + } override val resId: Int? get() { return when (this) { - NETRESULT -> R.string.net_result + NET_RESULT -> R.string.net_result + BB_NET_RESULT -> R.string.total_net_result_bb_ HOURLY_RATE -> R.string.average_hour_rate AVERAGE -> R.string.average NUMBER_OF_SETS -> R.string.number_of_sessions NUMBER_OF_GAMES -> R.string.number_of_records - DURATION -> R.string.duration - AVERAGE_DURATION -> R.string.average_hours_played + HOURLY_DURATION -> R.string.duration + AVERAGE_HOURLY_DURATION -> R.string.average_hours_played NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands HOURLY_RATE_BB -> R.string.average_hour_rate_bb_ AVERAGE_NET_BB -> R.string.average_net_result_bb_ @@ -94,6 +134,13 @@ enum class Stat(var underlyingClass: Class? = null) : RowRepresentabl STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands HANDS_PLAYED -> R.string.number_of_hands + LOCATIONS_PLAYED -> R.string.locations_played + LONGEST_STREAKS -> R.string.longest_streaks + MAXIMUM_NETRESULT -> R.string.max_net_result + MINIMUM_NETRESULT -> R.string.min_net_result + MAXIMUM_DURATION -> R.string.longest_session + DAYS_PLAYED -> R.string.days_played + else -> throw IllegalStateException("Stat ${this.name} name required but undefined") } } @@ -101,7 +148,7 @@ enum class Stat(var underlyingClass: Class? = null) : RowRepresentabl /** * Formats the value of the stat to be suitable for display */ - fun format(value: Double, currency: Currency? = null, context: Context): TextFormat { + fun format(value: Double, secondValue: Double? = null, currency: Currency? = null): TextFormat { if (value.isNaN()) { return TextFormat(NULL_TEXT, R.color.white) @@ -109,31 +156,32 @@ enum class Stat(var underlyingClass: Class? = null) : RowRepresentabl when (this) { // Amounts + red/green - Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> { - val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency) + NET_RESULT, HOURLY_RATE, AVERAGE, MAXIMUM_NETRESULT, MINIMUM_NETRESULT -> { val color = if (value >= this.threshold) R.color.green else R.color.red - return TextFormat(numberFormat.format(value), color) + return TextFormat(value.toCurrency(currency), color) } // Red/green numericValues - Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> { + HOURLY_RATE_BB, AVERAGE_NET_BB, NET_BB_PER_100_HANDS -> { val color = if (value >= this.threshold) R.color.green else R.color.red return TextFormat(value.formatted(), color) } // white integers - Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> { + NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED -> { return TextFormat("${value.toInt()}") } // white durations - Stat.DURATION, Stat.AVERAGE_DURATION -> { + HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> { return TextFormat(value.formattedHourlyDuration()) } // red/green percentages - Stat.WIN_RATIO, Stat.ROI -> { + 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 - Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY, - Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { - val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency) - return TextFormat(numberFormat.format(value)) + AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, + STANDARD_DEVIATION_BB_PER_100_HANDS -> { + return TextFormat(value.toCurrency(currency)) + } + LONGEST_STREAKS -> { + return TextFormat("${value.toInt()}W / ${secondValue!!.toInt()}L") } else -> throw FormattingException("Stat formatting of ${this.name} not handled") } @@ -148,14 +196,17 @@ enum class Stat(var underlyingClass: Class? = null) : RowRepresentabl } - fun cumulativeLabelResId(context: Context) : String { + fun cumulativeLabelResId(context: Context): String { val resId = when (this) { - AVERAGE, AVERAGE_DURATION, NET_BB_PER_100_HANDS, - HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, WIN_RATIO, HOURLY_RATE -> R.string.average - NETRESULT, DURATION -> R.string.total + AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS, + HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, HOURLY_RATE -> R.string.average + NUMBER_OF_SETS -> R.string.number_of_sessions + NUMBER_OF_GAMES -> R.string.number_of_records + NET_RESULT -> R.string.total STANDARD_DEVIATION -> R.string.net_result STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands + WIN_RATIO, HOURLY_DURATION -> return this.localizedTitle(context) else -> null } resId?.let { @@ -165,13 +216,67 @@ enum class Stat(var underlyingClass: Class? = null) : RowRepresentabl } } + val aggregationTypes: List + get() { + return when (this) { + NET_RESULT -> listOf( + AggregationType.SESSION, + AggregationType.MONTH, + AggregationType.YEAR, + AggregationType.DURATION + ) + NUMBER_OF_GAMES, NUMBER_OF_SETS -> listOf(AggregationType.MONTH, AggregationType.YEAR) + else -> listOf(AggregationType.SESSION, AggregationType.MONTH, AggregationType.YEAR) + } + } + + val hasEvolutionGraph: Boolean + get() { + return when (this) { + HOURLY_DURATION, AVERAGE_HOURLY_DURATION -> false + else -> true + } + } + + val significantIndividualValue: Boolean + get() { + return when (this) { + WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false + else -> true + } + } + + val shouldShowNumberOfSessions: Boolean + get() { + return when (this) { + NUMBER_OF_GAMES, NUMBER_OF_SETS -> false + else -> true + } + } + + val showXAxisZero: Boolean + get() { + return when (this) { + HOURLY_DURATION -> true + else -> false + } + } + + val showYAxisZero: Boolean + get() { + return when (this) { + HOURLY_DURATION -> true + else -> false + } + } + 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 currency: Currency? = null) { +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) { @@ -180,22 +285,20 @@ class ComputedStat(var stat: Stat, var value: Double, var currency: Currency? = } /** - * The variation of the stat + * The value used to get evolution dataset */ - var variation: Double? = null + var progressValue: Double? = null /** - * Formats the value of the stat to be suitable for display + * The variation of the stat */ - fun format(context: Context): TextFormat { - return this.stat.format(this.value, this.currency, context) - } + var variation: Double? = null /** - * Returns a TextFormat instance for an evolution value located at the specified [index] + * Formats the value of the stat to be suitable for display */ - fun evolutionValueFormat(index: Int): TextFormat { - return TextFormat("undef ${index}") + fun format(): TextFormat { + return this.stat.format(this.value, this.secondValue, this.currency) } } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Format.kt b/app/src/main/java/net/pokeranalytics/android/calculus/TextFormat.kt similarity index 100% rename from app/src/main/java/net/pokeranalytics/android/calculus/Format.kt rename to app/src/main/java/net/pokeranalytics/android/calculus/TextFormat.kt 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 79c81f85..e56689c2 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 @@ -1,8 +1,11 @@ package net.pokeranalytics.android.calculus.bankroll import io.realm.Realm -import net.pokeranalytics.android.model.realm.Session -import net.pokeranalytics.android.model.realm.Transaction +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.model.realm.* class BankrollCalculator { @@ -13,30 +16,80 @@ class BankrollCalculator { val realm = Realm.getDefaultInstance() val report = BankrollReport(setup) + val bankrolls: List = if (setup.bankroll != null) listOf(setup.bankroll) else realm.where(Bankroll::class.java).findAll() - val sessionQuery = realm.where(Session::class.java) - if (setup.bankroll != null) { - sessionQuery.equalTo("bankroll.id", setup.bankroll.id) - } -// val sessions = sessionQuery.findAll() - val transactionQuery = realm.where(Transaction::class.java) - if (setup.bankroll != null) { - transactionQuery.equalTo("bankroll.id", setup.bankroll.id).findAll() + var initialValue = 0.0 + var transactionNet = 0.0 + + bankrolls.forEach { bankroll -> + + val rate = if (setup.virtualBankroll) bankroll.rate else 1.0 + + initialValue += bankroll.initialValue * rate + transactionNet += bankroll.transactions.sumByDouble { it.amount } * rate } - val transactions = transactionQuery.findAll() -// val sessionsNet = sessions.sum("result.net") - val transactionsNet = transactions.sum("value") + report.transactionsNet = transactionNet + report.initial = initialValue + + val queryConditions = setup.queryConditions + val transactions = Filter.queryOn(realm, queryConditions) + report.addDatedItems(transactions) transactions.forEach { report.addTransaction(it) } + val sessions = Filter.queryOn(realm, queryConditions) + report.addDatedItems(sessions) + + if (setup.virtualBankroll) { + + val options = Calculator.Options(stats = listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY)) + val group = ComputableGroup(conditions = queryConditions) + val result = Calculator.compute(realm, group, options) + result.computedStat(Stat.NET_RESULT)?.let { + report.netResult = it.value + } + this.computeRiskOfRuin(report, result) + + } else { + + val results = Filter.queryOn(realm, queryConditions) + report.netResult = results.sum("net").toDouble() + + } + + val depositType = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm) + report.transactionBuckets[depositType.id]?.let { bucket -> + report.depositTotal = bucket.transactions.sumByDouble { it.amount } + } + + val withdrawalType = TransactionType.getByValue(TransactionType.Value.WITHDRAWAL, realm) + report.transactionBuckets[withdrawalType.id]?.let { bucket -> + report.withdrawalTotal = bucket.transactions.sumByDouble { it.amount } + } + + report.generateGraphPointsIfNecessary() + realm.close() return report } + private fun computeRiskOfRuin(report: BankrollReport, results: ComputedResults) { + + val hourlyRate = results.computedStat(Stat.HOURLY_RATE)?.value + val hourlyStandardDeviation = results.computedStat(Stat.STANDARD_DEVIATION_HOURLY)?.value + + if (hourlyRate != null && hourlyStandardDeviation != null) { + + report.riskOfRuin = Stat.riskOfRuin(hourlyRate, hourlyStandardDeviation, report.total) + + } + + } + } } \ No newline at end of file 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 cd830a50..be3a4b47 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 @@ -1,82 +1,74 @@ package net.pokeranalytics.android.calculus.bankroll -import net.pokeranalytics.android.calculus.interfaces.DatableValue +import android.content.Context +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineDataSet +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.interfaces.DatedValue import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.ui.graph.PALineDataSet import java.util.* import kotlin.collections.HashMap -/** - * A class describing the parameters required to launch a bankroll report - * - */ -class BankrollReportSetup(bankroll: Bankroll?, from: Date? = null, to: Date? = null) { - /** - * The bankroll to compute. If null, the virtual global bankroll - */ - val bankroll = bankroll + +class BankrollReport(setup: BankrollReportSetup) { /** - * The start of the report + * The setup used to compute the report */ - val from = from + var setup: BankrollReportSetup = setup /** - * The end of the report + * The value of the bankroll */ - val to = to - -} - -class TransactionBucket(useRate: Boolean = false) { - var transactions: MutableList = mutableListOf() - private set var total: Double = 0.0 private set - var useRate: Boolean = useRate - private set - fun addTransaction(transaction: Transaction) { + /** + * The initial value of the bankroll, or of all bankrolls if virtual is computed + */ + var initial: Double = 0.0 - this.transactions.add(transaction) - var rate = 1.0 - if (this.useRate) { - rate = transaction.bankroll?.currency?.rate ?: 1.0 + /** + * The net result from poker computables + */ + var netResult: Double = 0.0 + set(value) { + field = value + this.computeBankrollTotal() } - val ratedAmount = rate * transaction.amount - this.total += ratedAmount - - } - -} - -class BRGraphPoint { - - var value: Double = 0.0 - var variation: Double = 0.0 - var date: Date? = null - -} - -class BankrollReport(setup: BankrollReportSetup) { - /** - * The setup used to compute the report + * The net result from transactions */ - var setup: BankrollReportSetup = setup + var transactionsNet: Double = 0.0 + set(value) { + field = value + this.computeBankrollTotal() + } + + fun computeBankrollTotal() { + this.total = this.initial + this.netResult + this.transactionsNet + } /** - * The value of the bankroll + * The sum of all deposits */ - var total: Double = 0.0 - private set + var depositTotal: Double = 0.0 + set(value) { + field = value + this.netBanked = this.depositTotal + this.withdrawalTotal + } /** - * The net result from poker computables + * The sum of all withdrawals */ - var netResult: Double = 0.0 - private set + var withdrawalTotal: Double = 0.0 + set(value) { + field = value + this.netBanked = this.depositTotal + this.withdrawalTotal + } /** * The difference between withdrawals and deposits @@ -87,16 +79,21 @@ class BankrollReport(setup: BankrollReportSetup) { /** * The risk of ruin */ - var riskOfRuin: Double = 0.0 - private set + var riskOfRuin: Double? = null var transactions: List = mutableListOf() private set var transactionBuckets: HashMap = HashMap() + private set - var evolutionPoints: Array = arrayOf() - var evolutionItems: Array = arrayOf() + var evolutionPoints: MutableList = mutableListOf() + var evolutionItems: MutableList = mutableListOf() + private set + + fun addDatedItems(items: Collection) { + this.evolutionItems.addAll(items) + } fun addTransaction(transaction: Transaction) { @@ -105,7 +102,7 @@ class BankrollReport(setup: BankrollReportSetup) { var bucket = this.transactionBuckets[type.id] if (bucket == null) { - val b = TransactionBucket(this.setup.bankroll == null) + val b = TransactionBucket(this.setup.virtualBankroll) this.transactionBuckets[type.id] = b bucket = b } @@ -118,5 +115,93 @@ class BankrollReport(setup: BankrollReportSetup) { } + fun generateGraphPointsIfNecessary() { + + if (!this.setup.virtualBankroll) { + return + } + + this.evolutionItems.sortBy { it.date } + + this.evolutionItems.forEach { + val point = BRGraphPoint(it.amount, it.date, it) + this.evolutionPoints.add(point) + } + + } + + fun lineDataSet(context: Context): LineDataSet { + + val entries = mutableListOf() + this.evolutionPoints.forEach { + val entry = Entry(it.date.time.toFloat(), it.value.toFloat(), it.data) + entries.add(entry) + } + return PALineDataSet(entries, "", context) + } + +} + +/** + * 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) { + + val virtualBankroll: Boolean + get() { + return this.bankroll == null + } -} \ No newline at end of file + val queryConditions: List + get() { + val conditions = mutableListOf() + this.bankroll?.let { + val bankrollCondition = QueryCondition.AnyBankroll(bankroll) + conditions.add(bankrollCondition) + } + this.from?.let { + val fromCondition = QueryCondition.StartedFromDate() + fromCondition.singleValue = it + conditions.add(fromCondition) + } + this.to?.let { + val toCondition = QueryCondition.StartedToDate() + toCondition.singleValue = it + conditions.add(toCondition) + } + + return conditions + } + +} + +class TransactionBucket(useRate: Boolean = false) { + + var transactions: MutableList = mutableListOf() + private set + var total: Double = 0.0 + private set + var useRate: Boolean = useRate + private set + + fun addTransaction(transaction: Transaction) { + + this.transactions.add(transaction) + var rate = 1.0 + if (this.useRate) { + rate = transaction.bankroll?.currency?.rate ?: 1.0 + } + + val ratedAmount = rate * transaction.amount + this.total += ratedAmount + + } + +} + +data class BRGraphPoint(var value: Double, var date: Date, var data: Any? = null) { + + var variation: Double = 0.0 + +} diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Computable.kt b/app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Computable.kt deleted file mode 100644 index 4d1eca7c..00000000 --- a/app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Computable.kt +++ /dev/null @@ -1,17 +0,0 @@ -package net.pokeranalytics.android.calculus.interfaces - -import net.pokeranalytics.android.model.realm.SessionSet - -interface Computable { - - var ratedNet: Double - var bbNet: Double - var hasBigBlind: Int - var isPositive: Int - var ratedBuyin: Double - var estimatedHands: Double - var bbPer100Hands: Double - - var sessionSet: SessionSet? - -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Datable.kt b/app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Datable.kt deleted file mode 100644 index 7e8855f2..00000000 --- a/app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Datable.kt +++ /dev/null @@ -1,11 +0,0 @@ -package net.pokeranalytics.android.calculus.interfaces - -import java.util.* - -interface Datable { - var date: Date -} - -interface DatableValue : Datable { - var value: Double -} \ No newline at end of file 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 f1ff2e51..1eabf395 100644 --- a/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt +++ b/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt @@ -11,13 +11,14 @@ class ConfigurationException(message: String) : Exception(message) sealed class PokerAnalyticsException(message: String) : Exception(message) { object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition") object FilterElementUnknownSectionName: PokerAnalyticsException(message = "No filterElement section name was found to identify the queryCondition") - object FilterMissingEntity: PokerAnalyticsException(message = "This filter has no entity initialized") + object FilterMissingEntity: PokerAnalyticsException(message = "This queryWith has no entity initialized") object FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable") object QueryValueMapUnknown: PokerAnalyticsException(message = "fieldName is missing") - object QueryTypeUnhandled: PokerAnalyticsException(message = "filter type not handled") + object QueryTypeUnhandled: PokerAnalyticsException(message = "queryWith type not handled") object QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected") - object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "filter is empty or null") - data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "filter element '$filterElementRow' type is missing") + object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "queryWith is empty or null") + 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 filter type for $filterElementRow") + data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow") + data class MissingFieldNameForQueryCondition(val name: String) : PokerAnalyticsException(message = "Missing fieldname for QueryCondition ${name}") } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt b/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt new file mode 100644 index 00000000..9df897ab --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt @@ -0,0 +1,191 @@ +package net.pokeranalytics.android.model + +import io.realm.Realm +import io.realm.Sort +import io.realm.kotlin.where +import net.pokeranalytics.android.exceptions.PokerAnalyticsException +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.interfaces.NameManageable +import net.pokeranalytics.android.model.realm.* +import java.util.* +import kotlin.collections.ArrayList + +fun List.combined(): List> { + val comparatorList = ArrayList>() + this.forEach { + comparatorList.add(it.queryConditions) + } + return getCombinations(comparatorList) +} + +fun List>.upToNow(): List> { + val calendar = Calendar.getInstance() + calendar.time = Date() + val currentYear = calendar.get(Calendar.YEAR) + val currentMonth = calendar.get(Calendar.MONTH) + + val toRemove = this.filter { list -> + list.any { it is QueryCondition.AnyYear && it.listOfValues.first() == currentYear } + }.filter { list -> + list.any { + it is QueryCondition.AnyMonthOfYear && it.listOfValues.first() > currentMonth + } + } + + return this.filter{ list -> + var keep = true + toRemove.forEach { + if (list.containsAll(it)) { + keep = false + } + } + keep + } +} + +fun getCombinations(lists: List>): List> { + var combinations: LinkedHashSet> = LinkedHashSet() + var newCombinations: LinkedHashSet> + + var index = 0 + + // extract each of the integers in the first list + // and add each to ints as a new list + if (lists.isNotEmpty()) { + for (i in lists[0]) { + val newList = ArrayList() + newList.add(i) + combinations.add(newList) + } + index++ + } + while (index < lists.size) { + val nextList = lists[index] + newCombinations = LinkedHashSet() + for (first in combinations) { + for (second in nextList) { + val newList = ArrayList() + newList.addAll(first) + newList.add(second) + newCombinations.add(newList) + } + } + combinations = newCombinations + + index++ + } + + return combinations.toList() +} + +sealed class Criteria { + abstract class RealmCriteria : Criteria() { + inline fun comparison(): List { + return compare, T>() + .sorted() + } + } + + abstract class SimpleCriteria(private val conditions:List): Criteria() { + fun comparison(): List { + return conditions + } + } + + abstract class ListCriteria : Criteria() { + inline fun , reified S:Comparable> comparison(): List { + QueryCondition.distinct()?.let { + val values = it.mapNotNull { session -> + when (this) { + is Limits -> if (session.limit is S) { session.limit as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue + is TournamentTypes -> if (session.tournamentType is S) { session.tournamentType as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue + is TableSizes -> if (session.tableSize is S) { session.tableSize as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue + is TournamentFees -> if (session.tournamentEntryFee is S) { session.tournamentEntryFee as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue + is Blinds -> if (session.blinds is S) { session.blinds as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue + else -> null + } + }.distinct() + return compareList(values = values).sorted() + } + return listOf() + } + } + + + object Bankrolls: RealmCriteria() + object Games: RealmCriteria() + object TournamentNames: RealmCriteria() + object Locations: RealmCriteria() + object TournamentFeatures: RealmCriteria() + object Limits: ListCriteria() + object TableSizes: ListCriteria() + object TournamentTypes: ListCriteria() + object MonthsOfYear: SimpleCriteria(List(12) { index -> QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index)} }) + object DaysOfWeek: SimpleCriteria(List(7) { index -> QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) } }) + object SessionTypes: SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament)) + object BankrollTypes: SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline)) + object DayPeriods: SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd)) + object Years: ListCriteria() + object Blinds: ListCriteria() + object TournamentFees: ListCriteria() + object Cash: SimpleCriteria(listOf(QueryCondition.IsCash)) + object Tournament: SimpleCriteria(listOf(QueryCondition.IsTournament)) + + val queryConditions: List + get() { + return when (this) { + is Bankrolls -> comparison() + is Games -> comparison() + is TournamentFeatures -> comparison() + is TournamentNames -> comparison() + is Locations -> comparison() + is SimpleCriteria -> comparison() + is Limits -> comparison() + is TournamentTypes -> comparison() + is TableSizes -> comparison() + is TournamentFees -> comparison() + is Years -> { + val years = arrayListOf() + val calendar = Calendar.getInstance() + calendar.time = Date() + val yearNow = calendar.get(Calendar.YEAR) + val realm = Realm.getDefaultInstance() + realm.where().sort("year", Sort.ASCENDING).findFirst()?.year?.let { + for (index in 0..(yearNow - it)) { + years.add(QueryCondition.AnyYear().apply { + listOfValues = arrayListOf(yearNow - index) + }) + } + } + realm.close() + years.sorted() + } + is Blinds -> comparison() + else -> throw PokerAnalyticsException.QueryTypeUnhandled + } + } + + companion object { + inline fun < reified S : QueryCondition.QueryDataCondition, reified T : NameManageable > compare(): List { + val objects = arrayListOf() + val realm = Realm.getDefaultInstance() + realm.where().findAll().forEach { + objects.add((QueryCondition.getInstance() as S).apply { + setObject(it) + }) + } + realm.close() + return objects + } + + inline fun < reified S : QueryCondition.ListOfValues, T:Any > compareList(values:List): List { + val objects = arrayListOf() + values.forEach { + objects.add((S::class.java.newInstance()).apply { + listOfValues = arrayListOf(it) + }) + } + return objects + } + } +} diff --git a/app/src/main/java/net/pokeranalytics/android/model/StatRepresentable.kt b/app/src/main/java/net/pokeranalytics/android/model/StatRepresentable.kt deleted file mode 100644 index 25829c3a..00000000 --- a/app/src/main/java/net/pokeranalytics/android/model/StatRepresentable.kt +++ /dev/null @@ -1,21 +0,0 @@ -package net.pokeranalytics.android.model - -import net.pokeranalytics.android.calculus.ComputedStat -import net.pokeranalytics.android.calculus.Stat -import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowViewType - - -class StatRepresentable(stat: Stat, computedStat: ComputedStat?, groupName: String = "") : RowRepresentable { - - var stat: Stat = stat - var computedStat: ComputedStat? = computedStat - var groupName: String = groupName - - override val viewType: Int - get() = RowViewType.STAT.ordinal - - override val resId: Int? - get() = this.stat.resId - -} diff --git a/app/src/main/java/net/pokeranalytics/android/model/TableSize.kt b/app/src/main/java/net/pokeranalytics/android/model/TableSize.kt index 165ed238..c1bba39f 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/TableSize.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/TableSize.kt @@ -14,6 +14,14 @@ class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITL } } + override fun getDisplayName(): String { + return if (this.numberOfPlayer == 2) { + return "HU" + } else { + "${this.numberOfPlayer}-max" + } + } + override val resId: Int? get() { return if (this.numberOfPlayer == 2) { diff --git a/app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt b/app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt index c06cfed4..64a22295 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt @@ -23,5 +23,12 @@ enum class TournamentType : RowRepresentable { } } + override fun getDisplayName(): String { + return when (this) { + MTT -> "MTT" + SNG -> "SNG" + } + } + override val viewType: Int = RowViewType.TITLE.ordinal } \ No newline at end of file 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 02d9495b..a6caa598 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 @@ -25,9 +25,6 @@ enum class SessionState { */ fun Session.getState(): SessionState { -// if (timeFrame == null) { -// return SessionState.PENDING -// } val start = this.startDate if (start == null) { return SessionState.PENDING @@ -43,4 +40,59 @@ fun Session.getState(): SessionState { } } +} + +val AbstractList.hourlyDuration: Double + get() { + val intervals = mutableListOf() + this.forEach { + val interval = TimeInterval(it.startDate!!, it.endDate!!, it.breakDuration) + intervals.update(interval) + } + return intervals.sumByDouble { it.hourlyDuration } + } + +class TimeInterval(var start: Date, var end: Date, var breakDuration: Long) { + + val hourlyDuration: Double + get() { + val netDuration = end.time - start.time - breakDuration + return (netDuration / 3600000).toDouble() + } +} + +fun MutableList.update(timeInterval: TimeInterval): MutableList { + + val overlapped = this.filter { + + (it.start.before(timeInterval.start) && it.end.after(timeInterval.start)) || + (it.start.before(timeInterval.end) && it.end.after(timeInterval.end)) || + (it.start.after(timeInterval.start) && it.end.before(timeInterval.end)) + + } + + if (overlapped.size == 0) { // add + this.add(timeInterval) + } else { // update + + var start = timeInterval.start + var end = timeInterval.end + var breakDuration = timeInterval.breakDuration + + overlapped.forEach { + if (it.start.before(start)) { + start = it.start + } + if (it.end.after(end)) { + end = it.end + } + breakDuration = kotlin.math.max(it.breakDuration, breakDuration) + } + + this.removeAll(overlapped) + this.add(TimeInterval(start, end, breakDuration)) + + } + + return this } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt b/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt index a7a7f467..7ace8c8d 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt @@ -1,9 +1,13 @@ package net.pokeranalytics.android.model.filter import io.realm.RealmModel +import io.realm.RealmResults +import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.SessionSet +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.Result /** * We want to be able to store filters in the database: @@ -11,7 +15,7 @@ import net.pokeranalytics.android.model.realm.SessionSet * - filters can be applied to different type of objects: Sessions, Hands, Transactions... * - filters can be applied to a list of different type of objects (feed) * - * A filter is described by the following: + * A queryWith is described by the following: * - a data type: Session, Hands... * - a field: table size of a Session * - an operator: equal, >=, <... @@ -27,7 +31,7 @@ import net.pokeranalytics.android.model.realm.SessionSet * - multiple numericValues as 'OR' * * Also: - * A filter should be able to be converted into a Realm query + * A queryWith should be able to be converted into a Realm query * */ @@ -47,32 +51,46 @@ interface Filterable : RealmModel { } +inline fun RealmResults.filter(conditions: List) : RealmResults { + return conditions.queryWith(this.where()).findAll() +} + class FilterHelper { companion object { - inline fun fieldNameForQueryType(queryCondition: QueryCondition): String? { + inline fun fieldNameForQueryType(queryCondition: Class< out QueryCondition>): String? { - return when (T::class.java) { + val fieldName = when (T::class.java) { Session::class.java -> Session.fieldNameForQueryType(queryCondition) ComputableResult::class.java -> ComputableResult.fieldNameForQueryType(queryCondition) SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition) + Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition) + Result::class.java -> Result.fieldNameForQueryType(queryCondition) else -> { throw UnmanagedFilterField("Filterable type fields are not defined for class ${T::class}") } } + fieldName?.let { + return fieldName + } ?: run { + throw PokerAnalyticsException.MissingFieldNameForQueryCondition(queryCondition.name) + } + } } } + + // -//fun MutableList.filter(filter: FilterCondition) : List { +//fun MutableList.queryWith(queryWith: FilterCondition) : List { // -// return this.filter { f -> -// return@filter true +// return this.queryWith { f -> +// return@queryWith true // } //} diff --git a/app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt b/app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt index 077c866c..0f44e1ae 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt @@ -1,336 +1,605 @@ package net.pokeranalytics.android.model.filter -import io.realm.RealmList +import io.realm.Realm import io.realm.RealmQuery +import io.realm.RealmResults +import io.realm.Sort +import io.realm.kotlin.where +import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.PokerAnalyticsException -import net.pokeranalytics.android.model.realm.FilterCondition -import net.pokeranalytics.android.model.realm.FilterElementBlind -import net.pokeranalytics.android.model.realm.Session +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.interfaces.NameManageable +import net.pokeranalytics.android.model.realm.* +import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType +import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow +import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow +import net.pokeranalytics.android.util.NULL_TEXT +import net.pokeranalytics.android.util.UserDefaults import net.pokeranalytics.android.util.extensions.endOfDay import net.pokeranalytics.android.util.extensions.startOfDay +import net.pokeranalytics.android.util.extensions.toCurrency +import java.text.DateFormatSymbols import java.util.* +import kotlin.collections.ArrayList +fun List.name() : String { + return this.map { it.getDisplayName() }.joinToString(" : ") +} + +//inline fun List.query(realm: Realm): RealmQuery { +// return this.queryWith(realm.where()) +//} + +inline fun List.queryWith(query: RealmQuery): RealmQuery { + var realmQuery = query + this.forEach { + realmQuery = it.queryWith(realmQuery) + } + return realmQuery +} /** * Enum describing the way a query should be handled * Some queries requires a value to be checked upon through equals, in, more, less, between - * To handle that, the enum has a public [valueMap] variable - * A new type should also set the expected numericValues required in the [filterValuesExpectedKeys] */ -enum class QueryCondition(var operator: Operator? = null) { - LIVE, - CASH, - ONLINE, - TOURNAMENT, - BANKROLL, - GAME, - TOURNAMENT_NAME, - ANY_TOURNAMENT_FEATURES, - ALL_TOURNAMENT_FEATURES, - LOCATION, - LIMIT, - TABLE_SIZE, - TOURNAMENT_TYPE, - BLINDS, - LAST_GAMES, - LAST_SESSIONS, - MORE_NUMBER_OF_TABLE(Operator.MORE), - LESS_NUMBER_OF_TABLE(Operator.LESS), - BETWEEN_NUMBER_OF_TABLE(Operator.BETWEEN), - MORE_THAN_NET_RESULT(Operator.MORE), - LESS_THAN_NET_RESULT(Operator.LESS), - MORE_THAN_BUY_IN(Operator.MORE), - LESS_THAN_BUY_IN(Operator.LESS), - MORE_THAN_CASH_OUT(Operator.MORE), - LESS_THAN_CASH_OUT(Operator.LESS), - MORE_THAN_TIPS(Operator.MORE), - LESS_THAN_TIPS(Operator.LESS), - MORE_THAN_NUMBER_OF_PLAYER(Operator.MORE), - LESS_THAN_NUMBER_OF_PLAYER(Operator.LESS), - BETWEEN_NUMBER_OF_PLAYER(Operator.BETWEEN), - MORE_THAN_TOURNAMENT_FEE(Operator.MORE), - LESS_THAN_TOURNAMENT_FEE(Operator.LESS), - BETWEEN_TOURNAMENT_FEE(Operator.BETWEEN), - MIN_RE_BUY(Operator.MORE), - MAX_RE_BUY(Operator.LESS), - - // Dates - STARTED_FROM_DATE, - STARTED_TO_DATE, - ENDED_FROM_DATE, - ENDED_TO_DATE, - DAY_OF_WEEK, - MONTH, - YEAR, - WEEK_DAY, - WEEK_END, - TODAY, - YESTERDAY, - TODAY_AND_YESTERDAY, - THIS_WEEK, - THIS_MONTH, - THIS_YEAR, - PAST_DAYS, - MORE_THAN_DURATION(Operator.MORE), - LESS_THAN_DURATION(Operator.LESS), - - CURRENCY, - CURRENCY_CODE, - BIG_BLIND, - SMALL_BLIND, - COMMENT, - - ; + +sealed class QueryCondition : FilterElementRow { + companion object { + inline fun < reified T:QueryCondition> more():T { return T::class.java.newInstance().apply { this.operator = Operator.MORE } } + inline fun < reified T:QueryCondition> less():T { return T::class.java.newInstance().apply { this.operator = Operator.LESS } } + inline fun < reified T:QueryCondition> moreOrLess():ArrayList { return arrayListOf(more(), less()) } + + fun valueOf(name:String) : T { + val kClass = Class.forName("${QueryCondition::class.qualifiedName}$$name").kotlin + val instance = kClass.objectInstance ?: kClass.java.newInstance() + return instance as T + } + + inline fun getInstance(): QueryCondition { + return when (T::class.java) { + Bankroll::class.java -> AnyBankroll() + Game::class.java -> AnyGame() + Location::class.java -> AnyLocation() + TournamentName::class.java -> AnyTournamentName() + TournamentFeature::class.java -> AnyTournamentFeature() + else -> throw PokerAnalyticsException.QueryTypeUnhandled + } + } + + inline fun < reified T: Filterable, reified S: QueryCondition, reified U:Comparable>distinct(): RealmResults? { + FilterHelper.fieldNameForQueryType(S::class.java)?.let { + val realm = Realm.getDefaultInstance() + + val distincts = when (T::class) { + String::class, Int::class -> realm.where().distinct(it).findAll().sort(it, Sort.ASCENDING) + else -> realm.where().isNotNull(it).findAll().sort(it, Sort.ASCENDING) + } + + realm.close() + return distincts + } + return null + } + } enum class Operator { - BETWEEN, + ANY, + ALL, MORE, - LESS; + LESS, + EQUALS, + BETWEEN, + BETWEEN_RIGHT_EXCLUSIVE, + BETWEEN_LEFT_EXCLUSIVE, + ; } - var valueMap : Map? = null - get() { - this.filterValuesExpectedKeys?.let { valueMapExceptedKeys -> - field?.let { map -> - val missingKeys = map.keys.filter { !valueMapExceptedKeys.contains(it) } - if (map.keys.size == valueMapExceptedKeys.size && missingKeys.isNotEmpty()) { - throw PokerAnalyticsException.QueryValueMapMissingKeys(missingKeys) - } - } ?: run { - throw PokerAnalyticsException.QueryValueMapUnexpectedValue - } - } - return field + val baseId = this::class.simpleName ?: throw PokerAnalyticsException.FilterElementUnknownName + + val id: List get() { + when (this.operator) { + Operator.MORE, Operator.LESS -> return listOf("$baseId+${this.operator.name}") } - private set - private val filterValuesExpectedKeys : Array? - get() { - this.operator?.let { - return when (it) { - Operator.BETWEEN -> arrayOf("leftValue", "rightValue") - else -> arrayOf("value") - } + return when (this) { + is SingleValue<*> -> listOf(baseId) + is ListOfValues<*> -> { + if (listOfValues.isEmpty()) { return listOf(baseId) } + this.listOfValues.map{ "$baseId+$it" } } - return when (this) { - BANKROLL, GAME, LOCATION, ANY_TOURNAMENT_FEATURES, ALL_TOURNAMENT_FEATURES, TOURNAMENT_NAME -> arrayOf("ids") - LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> arrayOf("values") - BLINDS -> arrayOf("blinds") - STARTED_FROM_DATE, STARTED_TO_DATE, ENDED_FROM_DATE, ENDED_TO_DATE -> arrayOf("date") - DAY_OF_WEEK -> arrayOf("dayOfWeek") - MONTH -> arrayOf("month") - YEAR -> arrayOf("year") - else -> null + else -> listOf(baseId) + } + } + + open var operator: Operator = Operator.ANY + + abstract class ListOfValues: QueryCondition(), Comparable> where T:Comparable { + abstract var listOfValues: ArrayList + abstract fun labelForValue(value:T): String + + override fun getDisplayName(): String { + return when (listOfValues.size) { + 0 -> return NULL_TEXT + 1,2 -> listOfValues.map { labelForValue(it) }.joinToString(", ") + else -> "${listOfValues.size} $baseId" } } - /** - * main method of the enum - * providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery] - */ - inline fun filter(realmQuery: RealmQuery): RealmQuery { - when { - this == BLINDS -> { - - val smallBlindFieldName = FilterHelper.fieldNameForQueryType(SMALL_BLIND) - val bigBlindFieldName = FilterHelper.fieldNameForQueryType(BIG_BLIND) - val currencyCodeFieldName = FilterHelper.fieldNameForQueryType(CURRENCY_CODE) - smallBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown - bigBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown - currencyCodeFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown - - val blinds: RealmList by valueMap - blinds.forEachIndexed { index, blind -> - realmQuery - .beginGroup() - - blind.sb?.let { - realmQuery - .equalTo(smallBlindFieldName, it) - .and() - } + override fun compareTo(other: ListOfValues): Int { + return listOfValues.sorted().first().compareTo(other.listOfValues.sorted().first()) + } + } - realmQuery - .equalTo(bigBlindFieldName, blind.bb) - .and() + abstract class SingleValue: ListOfValues() where T:Comparable { + override var listOfValues = ArrayList() + abstract var singleValue : T + } - blind.currencyCode?.let { - realmQuery.equalTo(currencyCodeFieldName, it) - } ?: run { - realmQuery.isNull(currencyCodeFieldName) - } + abstract class ListOfDouble: ListOfValues() { + open var sign: Int = 1 - realmQuery.endGroup() + override var listOfValues = arrayListOf(0.0) + override fun updateValueMap(filterCondition: FilterCondition) { + super.updateValueMap(filterCondition) + listOfValues = filterCondition.getValues() + } + override fun labelForValue(value: Double): String { + return value.toCurrency(UserDefaults.currency) + } + } - if (index < blinds.size - 1) { - realmQuery.or() - } - } - return realmQuery - } - else -> { + abstract class ListOfInt: ListOfValues() { + override var listOfValues = arrayListOf(0) + override fun updateValueMap(filterCondition: FilterCondition) { + super.updateValueMap(filterCondition) + println("<<<< updateValueMap ${filterCondition.intValues}") + listOfValues = filterCondition.getValues() + } + override fun labelForValue(value: Int): String { + return value.toString() + } + } - val fieldName = FilterHelper.fieldNameForQueryType(this) - fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown + abstract class ListOfString: ListOfValues() { + override var listOfValues = ArrayList() + override fun labelForValue(value: String): String { return value } + override fun updateValueMap(filterCondition: FilterCondition) { + super.updateValueMap(filterCondition) + listOfValues = filterCondition.getValues() + } + } - when (operator) { - Operator.LESS -> { - val value: Double by valueMap - return realmQuery.lessThanOrEqualTo(fieldName, value) - } - Operator.MORE -> { - val value: Double by valueMap - return realmQuery.greaterThanOrEqualTo(fieldName, value) - } - Operator.BETWEEN -> { - val leftValue: Double by valueMap - val rightValue: Double by valueMap - return realmQuery.between(fieldName, leftValue, rightValue) - } - } + abstract class SingleDate: SingleValue() { + override fun labelForValue(value: Date): String { + return value.toString() + } - return when (this) { - LIVE, ONLINE -> realmQuery.equalTo(fieldName, this == LIVE) - CASH -> realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal) - TOURNAMENT -> realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal) - ALL_TOURNAMENT_FEATURES -> { - val ids: Array by valueMap - ids.forEach { - realmQuery.equalTo(fieldName, it) - } - realmQuery - } - ANY_TOURNAMENT_FEATURES -> { - val ids: Array by valueMap - realmQuery.`in`(fieldName, ids) - } - BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> { - val ids: Array by valueMap - realmQuery.`in`(fieldName, ids) - } - LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> { - val values: Array? by valueMap - realmQuery.`in`(fieldName, values) - } - STARTED_FROM_DATE -> { - val date: Date by valueMap - realmQuery.greaterThanOrEqualTo(fieldName, date) - } - STARTED_TO_DATE -> { - val date: Date by valueMap - realmQuery.lessThanOrEqualTo(fieldName, date) - } - ENDED_FROM_DATE -> { - val date: Date by valueMap - realmQuery.greaterThanOrEqualTo(fieldName, date) - } - ENDED_TO_DATE -> { - val date: Date by valueMap - realmQuery.lessThanOrEqualTo(fieldName, date) - } - DAY_OF_WEEK -> { - val dayOfWeek: Int by valueMap - realmQuery.equalTo(fieldName, dayOfWeek) - } - MONTH -> { - val month: Int by valueMap - realmQuery.equalTo(fieldName, month) - } - YEAR -> { - val year: Int by valueMap - realmQuery.equalTo(fieldName, year) - } - WEEK_END, WEEK_DAY -> { - var query = realmQuery - if (this == WEEK_DAY) { - query = realmQuery.not() - } - query.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY)) - } - TODAY -> { - val startDate = Date() - realmQuery.between(fieldName, startDate.startOfDay(), startDate.endOfDay()) - } - TODAY_AND_YESTERDAY-> { - val startDate = Date() - val calendar = Calendar.getInstance() - calendar.time = startDate - calendar.add(Calendar.HOUR_OF_DAY, -24) - realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) - } - YESTERDAY -> { - val calendar = Calendar.getInstance() - calendar.time = Date() - calendar.add(Calendar.HOUR_OF_DAY, -24) - realmQuery.between(fieldName, calendar.time.startOfDay(), calendar.time.endOfDay()) - } - THIS_WEEK -> { - val startDate = Date() - val calendar = Calendar.getInstance() - calendar.time = startDate - calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY) - realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) - } - THIS_MONTH -> { - val startDate = Date() - val calendar = Calendar.getInstance() - calendar.time = startDate - calendar.set(Calendar.DAY_OF_MONTH, 1) - realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) - } - THIS_YEAR -> { - val startDate = Date() - val calendar = Calendar.getInstance() - calendar.time = startDate - calendar.set(Calendar.DAY_OF_YEAR, 1) - realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) - } - else -> { - throw PokerAnalyticsException.QueryTypeUnhandled - } + override var singleValue: Date + get() { return listOfValues.firstOrNull() ?: Date() } + set(value) { listOfValues.add(value) } + + override fun updateValueMap(filterCondition: FilterCondition) { + super.updateValueMap(filterCondition) + singleValue = filterCondition.getValue() + } + } + + abstract class SingleInt: SingleValue() { + override fun labelForValue(value: Int): String { + return value.toString() + } + override var singleValue: Int + get() { return listOfValues.firstOrNull() ?: 0 } + set(value) { listOfValues.add(value) } + + override fun updateValueMap(filterCondition: FilterCondition) { + super.updateValueMap(filterCondition) + singleValue = filterCondition.getValue() + } + } + + override fun getDisplayName(): String { return baseId } + + override var filterSectionRow: FilterSectionRow = FilterSectionRow.CASH_TOURNAMENT + + abstract class QueryDataCondition < T: NameManageable > : ListOfString() { + fun setObject(dataObject: T) { + this.listOfValues.removeAll(this.listOfValues) + this.listOfValues.add(dataObject.id) + } + + abstract val entity : Class + + override fun getDisplayName(): String { + val realm = Realm.getDefaultInstance() + val completeLabel = when (listOfValues.size) { + 0 -> return NULL_TEXT + 1,2 -> { + return listOfValues.map { labelForValue(realm, it) }.joinToString(", ") } + else -> "${listOfValues.size} $baseId" } + realm.close() + return completeLabel + } + + private fun labelForValue(realm:Realm, value:String): String { + val query = realm.where(entity) + return query.equalTo("id", value).findFirst()?.name ?: NULL_TEXT + } + } + + + interface DateTime { + val showTime: Boolean + } + + abstract class DateQuery: SingleDate(), DateTime { + override val showTime: Boolean = false + + } + + abstract class TimeQuery: DateQuery() { + override val showTime: Boolean = true + } + + object IsLive : QueryCondition() { + override fun getDisplayName(): String { return "Live" } + } + + object IsCash : QueryCondition() { + override fun getDisplayName(): String { return "Cash" } + } + + object IsOnline : QueryCondition() { + override fun getDisplayName(): String { return "Online" } + } + + object IsTournament : QueryCondition() { + override fun getDisplayName(): String { return "Tournament" } + } + + class AnyBankroll(): QueryDataCondition() { + override var entity: Class = Bankroll::class.java + constructor(bankroll: Bankroll): this() { + this.setObject(bankroll) } + } + class AnyGame(): QueryDataCondition() { + override val entity: Class = Game::class.java + constructor(game: Game): this() { + this.setObject(game) + } + } + class AnyTournamentName(): QueryDataCondition() { + override val entity: Class = TournamentName::class.java + constructor(tournamentName: TournamentName): this() { + this.setObject(tournamentName) + } } - fun updateValueMap(filterCondition: FilterCondition) { - if (filterValuesExpectedKeys == null) { - return + class AnyTournamentFeature(): QueryDataCondition() { + override val entity: Class = TournamentFeature::class.java + constructor(tournamentFeature: TournamentFeature): this() { + this.setObject(tournamentFeature) } + } - this.operator?.let { - valueMap = mapOf("value" to filterCondition.value) - return + class AllTournamentFeature(): QueryDataCondition() { + override var operator = Operator.ALL + override val entity: Class = TournamentFeature::class.java + constructor(tournamentFeature: TournamentFeature): this() { + this.setObject(tournamentFeature) } + } + + class AnyLocation(): QueryDataCondition() { + override val entity: Class = Location::class.java + constructor(location: Location): this() { + this.setObject(location) + } + } + + class AnyLimit: ListOfInt() { + override fun labelForValue(value: Int): String { + return Limit.values()[value].getDisplayName() + } + } + + class AnyTableSize: ListOfInt() { + override fun labelForValue(value: Int): String { + return TableSize(value).getDisplayName() + } + } + + class AnyTournamentType: ListOfInt() { + override fun labelForValue(value: Int): String { + return TournamentType.values()[value].getDisplayName() + } + } + + class AnyBlind: ListOfString() + + class LastGame: SingleInt() + class LastSession: SingleInt() + + class NumberOfTable: ListOfInt() + + open class NetAmountWon: ListOfDouble() + class NetAmountLost: NetAmountWon() { override var sign: Int = -1 } + + class NumberOfPlayer: ListOfInt() + + class StartedFromDate: DateQuery() { override var operator = Operator.MORE } + class StartedToDate: DateQuery() { override var operator = Operator.LESS } + class EndedFromDate: DateQuery() { override var operator = Operator.MORE } + class EndedToDate: DateQuery() { override var operator = Operator.LESS } + + class AnyDayOfWeek: ListOfInt() { + override fun labelForValue(value: Int): String { + return DateFormatSymbols.getInstance(Locale.getDefault()).weekdays[value] + } + } + + class AnyMonthOfYear: ListOfInt() { + override fun labelForValue(value: Int): String { + return DateFormatSymbols.getInstance(Locale.getDefault()).months[value] + } + } + + class AnyYear: ListOfInt() { + override fun labelForValue(value: Int): String { + return "$value" + } + } + + object IsWeekDay: QueryCondition() + object IsWeekEnd: QueryCondition() + object IsToday: QueryCondition() + object WasYesterday: QueryCondition() + object WasTodayAndYesterday: QueryCondition() + object DuringThisWeek: QueryCondition() + object DuringThisMonth: QueryCondition() + object DuringThisYear: QueryCondition() + + class TournamentFee: ListOfDouble() { + override fun labelForValue(value: Double): String { + return value.toCurrency(UserDefaults.currency) + } + } + + class PastDay: SingleInt() { + override val viewType: Int = RowViewType.TITLE_VALUE_CHECK.ordinal + } + + class Duration: SingleInt() { + var minutes:Int + get() { return singleValue } + set(value) { singleValue = value } + + override val viewType: Int = RowViewType.TITLE_VALUE_CHECK.ordinal + override val bottomSheetType: BottomSheetType = BottomSheetType.DOUBLE_EDIT_TEXT + } + + class StartedFromTime: TimeQuery() { + override var operator = Operator.MORE + init { + this.singleValue = Date().startOfDay() + } + } + + class EndedToTime: TimeQuery() { + override var operator = Operator.LESS + init { + this.singleValue = Date().endOfDay() + } + } + + /** + * main method of the enum + * providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery] + */ + inline fun queryWith(realmQuery: RealmQuery): RealmQuery { + val fieldName = FilterHelper.fieldNameForQueryType(this::class.java) + fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown when (this) { - ALL_TOURNAMENT_FEATURES, ANY_TOURNAMENT_FEATURES, BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> { - valueMap = mapOf("ids" to filterCondition.ids) + //is Between -> realmQuery.between(fieldName, leftValue, rightValue) + //is BetweenLeftExclusive -> realmQuery.greaterThan(fieldName, leftValue).and().lessThanOrEqualTo(fieldName, rightValue) + //is BetweenRightExclusive -> realmQuery.greaterThanOrEqualTo(fieldName, leftValue).and().lessThan(fieldName, rightValue) + IsLive, IsOnline -> return realmQuery.equalTo(fieldName, this == IsLive) + IsCash -> return realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal) + IsTournament -> return realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal) + IsWeekEnd, IsWeekDay -> { + var query = realmQuery + if (this == IsWeekDay) { + query = realmQuery.not() + } + return query.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY)) + } + IsToday -> { + val startDate = Date() + return realmQuery.between(fieldName, startDate.startOfDay(), startDate.endOfDay()) + } + WasTodayAndYesterday-> { + val startDate = Date() + val calendar = Calendar.getInstance() + calendar.time = startDate + calendar.add(Calendar.HOUR_OF_DAY, -24) + return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) } - LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> { - valueMap = mapOf("values" to filterCondition.values) + WasYesterday -> { + val calendar = Calendar.getInstance() + calendar.time = Date() + calendar.add(Calendar.HOUR_OF_DAY, -24) + return realmQuery.between(fieldName, calendar.time.startOfDay(), calendar.time.endOfDay()) } - BLINDS -> { - valueMap = mapOf("blinds" to filterCondition.blinds) + DuringThisWeek -> { + val startDate = Date() + val calendar = Calendar.getInstance() + calendar.time = startDate + calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY) + return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) } - STARTED_FROM_DATE, STARTED_TO_DATE, ENDED_FROM_DATE, ENDED_TO_DATE -> { - valueMap = mapOf("date" to filterCondition.date) + DuringThisMonth -> { + val startDate = Date() + val calendar = Calendar.getInstance() + calendar.time = startDate + calendar.set(Calendar.DAY_OF_MONTH, 1) + return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) + } + DuringThisYear -> { + val startDate = Date() + val calendar = Calendar.getInstance() + calendar.time = startDate + calendar.set(Calendar.DAY_OF_YEAR, 1) + return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) + } + } + + return when (operator) { + Operator.EQUALS -> { + when (this) { + is SingleDate -> realmQuery.equalTo(fieldName, singleValue) + is SingleInt -> realmQuery.equalTo(fieldName, singleValue) + is ListOfInt -> realmQuery.equalTo(fieldName, listOfValues.first()) + is ListOfDouble -> realmQuery.equalTo(fieldName, listOfValues.first() * sign) + is ListOfString -> realmQuery.equalTo(fieldName, listOfValues.first()) + else -> realmQuery + } } - DAY_OF_WEEK -> { - valueMap = mapOf("dayOfWeek" to filterCondition.dayOfWeek) + Operator.MORE -> { + when (this) { + is SingleDate -> realmQuery.greaterThanOrEqualTo(fieldName, singleValue) + is SingleInt -> realmQuery.greaterThanOrEqualTo(fieldName, singleValue) + is ListOfInt -> realmQuery.greaterThanOrEqualTo(fieldName, listOfValues.first()) + is ListOfDouble -> realmQuery.greaterThanOrEqualTo(fieldName, listOfValues.first() * sign) + else -> realmQuery + } } - MONTH -> { - valueMap = mapOf("month" to filterCondition.month) + Operator.LESS -> { + when (this) { + is SingleDate -> realmQuery.lessThanOrEqualTo(fieldName, singleValue) + is SingleInt -> realmQuery.lessThanOrEqualTo(fieldName, singleValue) + is ListOfInt -> realmQuery.lessThanOrEqualTo(fieldName, listOfValues.first()) + is ListOfDouble -> realmQuery.lessThanOrEqualTo(fieldName, listOfValues.first() * sign) + else -> realmQuery + } } - YEAR -> { - valueMap = mapOf("year" to filterCondition.year) + Operator.ALL -> { + when (this) { + is ListOfInt -> { + listOfValues.forEach { realmQuery.equalTo(fieldName, it) } + realmQuery + } + is ListOfDouble -> { + listOfValues.forEach { realmQuery.equalTo(fieldName, it * sign) } + realmQuery + } + is ListOfString -> { + listOfValues.forEach { realmQuery.equalTo(fieldName, it) } + realmQuery + } + else -> realmQuery + } } - else -> { - throw PokerAnalyticsException.QueryValueMapUnexpectedValue + Operator.ANY -> { + when (this) { + is ListOfInt -> realmQuery.`in`(fieldName, listOfValues.toTypedArray()) + is ListOfDouble -> realmQuery.`in`(fieldName, listOfValues.toTypedArray()) + is ListOfString -> realmQuery.`in`(fieldName, listOfValues.toTypedArray()) + else -> realmQuery + } } + else -> realmQuery + } + } + + open fun updateValueMap(filterCondition: FilterCondition) { + filterCondition.operator?.let { + this.operator = Operator.values()[it] } } + override val viewType: Int + get() { + return when (this) { + is PastDay -> RowViewType.TITLE_VALUE_CHECK.ordinal + is LastGame -> RowViewType.TITLE_VALUE_CHECK.ordinal + is LastSession -> RowViewType.TITLE_VALUE_CHECK.ordinal + else -> { + when (this.operator) { + Operator.MORE -> RowViewType.TITLE_VALUE_CHECK.ordinal + Operator.LESS -> RowViewType.TITLE_VALUE_CHECK.ordinal + else -> RowViewType.TITLE_CHECK.ordinal + } + } + } + } + + override val bottomSheetType: BottomSheetType + get() { + return when (this) { + is PastDay -> BottomSheetType.EDIT_TEXT + is LastGame -> BottomSheetType.EDIT_TEXT + is LastSession -> BottomSheetType.EDIT_TEXT + else -> { + when (this.operator) { + Operator.MORE -> BottomSheetType.EDIT_TEXT + Operator.LESS -> BottomSheetType.EDIT_TEXT + else -> BottomSheetType.NONE + } + } + } + } + + override val resId: Int? + get() { + return when (this) { + is IsCash -> R.string.cash_game + is IsTournament -> R.string.tournament + is IsToday -> R.string.today + is WasYesterday -> R.string.yesterday + is WasTodayAndYesterday -> R.string.yesterday_and_today + is DuringThisWeek -> R.string.current_week + is DuringThisMonth -> R.string.current_month + is DuringThisYear -> R.string.current_year + is StartedFromTime, is StartedFromDate -> R.string.from + is EndedToDate, is EndedToTime-> R.string.to + is IsLive -> R.string.live + is IsOnline -> R.string.online + is IsWeekDay -> R.string.week_days + is IsWeekEnd -> R.string.weekend + is PastDay -> R.string.period_in_days + is LastGame -> R.string.last_records + is LastSession -> R.string.last_sessions + is NetAmountWon -> { + when (this.operator) { + Operator.MORE -> R.string.won_amount_more_than + Operator.LESS -> R.string.won_amount_less_than + else -> null + } + } + is NetAmountLost -> { + when (this.operator) { + Operator.MORE -> R.string.lost_amount_more_than + Operator.LESS -> R.string.lost_amount_less_than + else -> null + } + } + else -> { + when (this.operator) { + Operator.MORE -> R.string.more_than + Operator.LESS -> R.string.less_than + else -> null + } + } + } + } } \ 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 new file mode 100644 index 00000000..86cea43b --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt @@ -0,0 +1,15 @@ +package net.pokeranalytics.android.model.interfaces + +import java.util.* + +interface Dated { + + var date: Date + +} + +interface DatedValue : Dated { + + var amount: Double + +} \ 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 01431a6e..aa2741dd 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,9 +1,10 @@ package net.pokeranalytics.android.model.interfaces -import net.pokeranalytics.android.calculus.StatBase +import net.pokeranalytics.android.calculus.ObjectIdentifier +import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry import java.util.* -interface Timed : StatBase, Identifiable { +interface Timed : GraphUnderlyingEntry, Identifiable { fun startDate() : Date? @@ -29,4 +30,6 @@ interface Timed : StatBase, 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/PokerAnalyticsMigration.kt b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt index 39f185d1..4a542cc6 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 @@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.migrations import io.realm.DynamicRealm import io.realm.RealmMigration import timber.log.Timber +import java.util.* class PokerAnalyticsMigration : RealmMigration { @@ -35,10 +36,10 @@ class PokerAnalyticsMigration : RealmMigration { // Migrate to version 2 if (currentVersion == 1) { Timber.d("*** Running migration ${currentVersion + 1}") - schema.rename("FilterElement", "FilterCondition") schema.get("Filter")?.let { it.renameField("filterElements", "filterConditions") + it.removeField("entityType") } schema.get("SessionSet")?.let { it.addField("id", String::class.java).setRequired("id", true) @@ -47,6 +48,34 @@ class PokerAnalyticsMigration : RealmMigration { currentVersion++ } + // Migrate to version 2 + if (currentVersion == 2) { + Timber.d("*** Running migration ${currentVersion + 1}") + schema.rename("Report", "ReportSetup") + + schema.get("Session")?.let { + it.addField("blinds", String::class.java) + } + + schema.get("FilterCondition")?.let { + it.removeField("blindValues") + it.addField("operator", String::class.java).setNullable("operator", true) + it.addField("intValue", Int::class.java).setNullable("intValue", true) + it.addField("doubleValue", Double::class.java).setNullable("intValue", true) + it.addField("stringValue", String::class.java).setNullable("stringValue", true) + } + + schema.get("ComputableResult")?.let { + it.removeField("sessionSet") + } + + schema.get("Bankroll")?.let { + it.addField("initialValue", Double::class.java).setRequired("initialValue", true) + } + + 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 b05eb7e2..cd577725 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 @@ -8,29 +8,12 @@ import io.realm.kotlin.where import net.pokeranalytics.android.R 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.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 java.util.* -import kotlin.collections.ArrayList -open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { - - companion object { - val rowRepresentation : List by lazy { - val rows = ArrayList() - rows.add(SimpleRow.NAME) - rows.add(BankrollRow.LIVE) - rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency)) - rows.add(BankrollRow.CURRENCY) - rows - } - } +open class Bankroll() : RealmObject(), NameManageable, RowRepresentable { @PrimaryKey override var id = UUID.randomUUID().toString() @@ -46,37 +29,16 @@ open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDat // The currency of the bankroll var currency: Currency? = null - override fun getDisplayName(): String { - return this.name - } - - // Row Representable Datasource - override fun adapterRows(): List? { - return Bankroll.rowRepresentation - } + // The initial value of the bankroll + var initialValue: Double = 0.0 - override fun stringForRow(row: RowRepresentable): String { - return when (row) { - SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT - else -> return super.stringForRow(row) + val rate: Double + get() { + return this.currency?.rate ?: 1.0 } - } - override fun boolForRow(row: RowRepresentable): Boolean { - return when (row) { - BankrollRow.LIVE -> !this.live - else -> super.boolForRow(row) - } - } - - override fun editDescriptors(row: RowRepresentable): ArrayList? { - return when (row) { - SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name)) - BankrollRow.RATE -> row.editingDescriptors(mapOf()) - else -> { - row.editingDescriptors(mapOf()) - } - } + override fun getDisplayName(): String { + return this.name } override fun updateValue(value: Any?, row: RowRepresentable) { @@ -84,8 +46,12 @@ open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDat SimpleRow.NAME -> this.name = value as String? ?: "" BankrollRow.LIVE -> { this.live = if (value is Boolean) !value else false + } + BankrollRow.INITIAL_VALUE -> { + this.initialValue = value as Double? ?: 0.0 } BankrollRow.CURRENCY -> { + //TODO handle a use default currency option this.currency?.code = value as String? } BankrollRow.RATE -> { 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 fd3aa17a..e23f6045 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 @@ -1,33 +1,32 @@ package net.pokeranalytics.android.model.realm import io.realm.RealmObject -import net.pokeranalytics.android.calculus.interfaces.Computable import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.QueryCondition -open class ComputableResult() : RealmObject(), Computable, Filterable { +open class ComputableResult() : RealmObject(), Filterable { - override var ratedNet: Double = 0.0 + var ratedNet: Double = 0.0 - override var bbNet: BB = 0.0 + var bbNet: BB = 0.0 - override var hasBigBlind: Int = 0 + var hasBigBlind: Int = 0 - override var isPositive: Int = 0 + var isPositive: Int = 0 - override var ratedBuyin: Double = 0.0 + var ratedBuyin: Double = 0.0 - override var estimatedHands: Double = 0.0 + var estimatedHands: Double = 0.0 - override var bbPer100Hands: BB = 0.0 + var bbPer100Hands: BB = 0.0 - override var sessionSet: SessionSet? = null +// var sessionSet: SessionSet? = null var session: Session? = null fun updateWith(session: Session) { - this.sessionSet = session.sessionSet +// this.sessionSet = session.sessionSet val rate = session.bankroll?.currency?.rate ?: 1.0 @@ -56,8 +55,11 @@ open class ComputableResult() : RealmObject(), Computable, Filterable { companion object { - fun fieldNameForQueryType(queryCondition: QueryCondition): String? { - return "session." + Session.fieldNameForQueryType(queryCondition) + fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { + Session.fieldNameForQueryType(queryCondition)?.let { + return "session.$it" + } + return null } } 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 e6b96fca..304f3a2b 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 @@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.realm import io.realm.RealmObject import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey +import net.pokeranalytics.android.util.UserDefaults import java.util.* open class Currency : RealmObject() { @@ -13,7 +14,7 @@ open class Currency : RealmObject() { @PrimaryKey var id = UUID.randomUUID().toString() - /** + /** * The currency code of the currency, i.e. USD, EUR... */ var code: String? = null @@ -37,7 +38,15 @@ open class Currency : RealmObject() { computable.ratedBuyin = it * rate } + computable.session?.bankrollHasBeenUpdated() + } } + + fun hasMainCurrencyCode() : Boolean { + this.code?.let { return it == UserDefaults.currency.currencyCode } + return false + } + } \ No newline at end of file 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 dbbba9bc..d9c2c159 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 @@ -3,13 +3,9 @@ package net.pokeranalytics.android.model.realm import io.realm.* import io.realm.annotations.PrimaryKey import io.realm.kotlin.where -import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow -import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow -import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow -import org.jetbrains.annotations.TestOnly import timber.log.Timber import java.util.* @@ -20,13 +16,6 @@ import java.util.* */ open class Filter : RealmObject() { - private var entityType: Int? = Entity.SESSION.ordinal - - private enum class Entity { - SESSION, - ; - } - companion object { // Create a new instance @@ -35,16 +24,18 @@ open class Filter : RealmObject() { return realm.copyToRealm(filter) } - // Get a filter by its id + // Get a queryWith by its id fun getFilterBydId(realm: Realm, filterId: String): Filter? { return realm.where().equalTo("id", filterId).findFirst() } - @TestOnly - inline fun queryOn(realm: Realm, queries: List): RealmResults { + inline fun queryOn(realm: Realm, queries: List, sortField: String? = null): RealmResults { var realmQuery = realm.where() queries.forEach { - realmQuery = it.filter(realmQuery) + realmQuery = it.queryWith(realmQuery) + } + sortField?.let { + realmQuery.sort(it) } Timber.d(">>> Filter query: ${realmQuery.description}") return realmQuery.findAll() @@ -54,81 +45,76 @@ open class Filter : RealmObject() { @PrimaryKey var id = UUID.randomUUID().toString() - // the filter name + // the queryWith name var name: String = "" - // the number of use of the filter, + // the number of use of the queryWith, // for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0) var filterConditions: RealmList = RealmList() private set - fun createOrUpdateFilterConditions(filterConditionRows: ArrayList) { - filterConditions.clear() - filterConditionRows + 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 .map { - it.filterName + it.filterSectionRow } .distinct() .forEach { filterName-> filterConditionRows .filter { - it.filterName == filterName + it.filterSectionRow == filterName } .apply { - val casted = arrayListOf() + + println("list of querys: ${this.map { it.id }}") + val casted = arrayListOf() casted.addAll(this) - filterConditions.add(FilterCondition(casted)) - } + val newFilterCondition = FilterCondition(casted) + val previousCondition = filterConditions.filter { + it.filterName == newFilterCondition.filterName + } + filterConditions.removeAll(previousCondition) + filterConditions.add(newFilterCondition) + } } } - fun countBy(filterCategoryRow: FilterCategoryRow): Int { - val sections = filterCategoryRow.filterSectionRows - return filterConditions.count { - sections.contains(FilterSectionRow.valueOf(it.sectionName ?: throw PokerAnalyticsException.FilterElementUnknownSectionName)) - } - } + fun remove(filterCategoryRow: FilterCategoryRow) { + val sections = filterCategoryRow.filterSectionRows.map { it.name } + val savedSections = filterConditions.filter { sections.contains(it.sectionName) } + this.filterConditions.removeAll(savedSections) + } - fun contains(filterElementRow: FilterElementRow): Boolean { - val filtered = filterConditions.filter { - it.filterName == filterElementRow.filterName - } - if (filtered.isEmpty()) { - return false - } - return filterElementRow.contains(filtered) + 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 } - /** - * Set the saved value in the filter for the given [filterElementRow] - */ - fun setSavedValueForElement(filterElementRow: FilterElementRow) { - when (filterElementRow) { - is FilterElementRow.PastDays -> { - val values = getSavedValueForElement(filterElementRow) as Array<*> - if (values.isNotEmpty() && values.first() is Int) { - filterElementRow.lastDays = values.first() as Int - } - } - is FilterElementRow.DateFilterElementRow -> filterElementRow.dateValue = getSavedValueForElement(filterElementRow) as Date? ?: Date() - } + 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 } /** * Get the saved value for the given [filterElementRow] */ - private fun getSavedValueForElement(filterElementRow: FilterElementRow): Any? { + fun loadValueForElement(filterElementRow: QueryCondition) { val filtered = filterConditions.filter { - it.filterName == filterElementRow.filterName + it.queryCondition.id == filterElementRow.id } - if (filtered.isNotEmpty()) { - return filtered.first().getFilterConditionValue(filterElementRow) + return filterElementRow.updateValueMap(filtered.first()) } - - return null } inline fun results(): RealmResults { @@ -136,7 +122,7 @@ open class Filter : RealmObject() { this.filterConditions.map { it.queryCondition }.forEach { - realmQuery = it.filter(realmQuery) + realmQuery = it.queryWith(realmQuery) } return realmQuery.findAll() diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt index afada902..4340ed63 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt @@ -4,9 +4,8 @@ import io.realm.RealmList import io.realm.RealmObject import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.model.filter.QueryCondition -import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow -import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow.* import java.util.* +import kotlin.collections.ArrayList open class FilterCondition() : RealmObject() { @@ -15,35 +14,16 @@ open class FilterCondition() : RealmObject() { this.sectionName = sectionName } - constructor(filterElementRows: ArrayList) : this(filterElementRows.first().filterName, filterElementRows.first().filterSectionRow.name) { + constructor(filterElementRows: ArrayList) : this(filterElementRows.first().baseId, filterElementRows.first().filterSectionRow.name) { val row = filterElementRows.first() this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName - + this.operator = row.operator.ordinal when (row) { - is DateFilterElementRow -> { - this.dateValue = row.dateValue - } - is StringFilterElementRow -> { - this.stringValues = RealmList().apply { - this.addAll(filterElementRows.map { - (it as StringFilterElementRow).stringValue - }) - } - } - is NumericFilterElementRow -> { - this.numericValues = RealmList().apply { - this.addAll(filterElementRows.map { - (it as NumericFilterElementRow).doubleValue - }) - } - } - is FilterElementBlind -> { - this.blindValues = RealmList().apply { - this.addAll(filterElementRows.map { - FilterElementBlind((it as FilterElementRow.Blind).sb, it.bb, it.code) - }) - } - } + is QueryCondition.SingleInt -> this.setValue(row.singleValue) + is QueryCondition.SingleDate -> this.setValue(row.singleValue) + is QueryCondition.ListOfDouble -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfDouble).listOfValues }) + is QueryCondition.ListOfInt -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfInt).listOfValues }) + is QueryCondition.ListOfString -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfString).listOfValues }) } } @@ -51,75 +31,62 @@ open class FilterCondition() : RealmObject() { var sectionName: String? = null val queryCondition : QueryCondition - get() = QueryCondition.valueOf(this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName) + get() = QueryCondition.valueOf(this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName) .apply { this.updateValueMap(this@FilterCondition) } - private var numericValues: RealmList? = null - private var dateValue: Date? = null - private var stringValues: RealmList? = null - private var blindValues: RealmList? = null - - val ids: Array - get() = stringValues?.toTypedArray() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing - - val blinds: RealmList - get() { - blindValues?.let { - if (it.isNotEmpty()) { - return it - } else { - throw PokerAnalyticsException.FilterElementExpectedValueMissing - } - } - throw PokerAnalyticsException.FilterElementExpectedValueMissing - } - - - val date: Date - get() = dateValue ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing - - - val values: Array - get() = numericValues?.map { - it.toInt() - }?.toTypedArray() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing - - - val value: Double - get() = numericValues?.first() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing - - - val leftValue: Double - get() = numericValues?.first() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing - - - val rightValue: Double - get() = numericValues?.last() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing - - - val dayOfWeek: Int - get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing - + var doubleValues: RealmList? = null + var intValues: RealmList? = null + var stringValues: RealmList? = null + var dateValue: Date? = null + var doubleValue: Double? = null + var intValue: Int? = null + var stringValue: String? = null + var operator: Int? = null + + inline fun getValues(): ArrayList < T > { + println("<<<< r $stringValues") + return when (T::class) { + Int::class -> ArrayList().apply { intValues?.map { add(it as T) } } + Double::class -> ArrayList().apply { doubleValues?.map { add(it as T) } } + String::class -> ArrayList().apply { stringValues?.map { add(it as T) } } + else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue + } + } - val month: Int - get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing + inline fun getValue(): T { + return when (T::class) { + Int::class -> intValue ?: 0 + Double::class -> doubleValue?: 0.0 + Date::class -> dateValue ?: Date() + String::class -> stringValue ?: "" + else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue + } as T + } + private inline fun setValues(values:List) { + when (T::class) { + Int::class -> intValues = RealmList().apply { values.map { it as Int }.forEach { add(it) } } + Double::class -> doubleValues = RealmList().apply { values.map { it as Double }.forEach { add(it) } } + String::class -> stringValues = RealmList().apply { values.map { it as String }.forEach { add(it) } } + else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue + } + } - val year: Int - get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing + fun setValue(value:Double) { + doubleValue = value + } + fun setValue(value:Date) { + dateValue = value + } - /** - * Return the value associated with the given [filterElementRow] - */ - fun getFilterConditionValue(filterElementRow: FilterElementRow): Any? { - return when (filterElementRow) { - is From, is To -> dateValue //TODO: Probably change by 'date' (doesn't work now because the value isn't correctly saved - is PastDays -> values - else -> throw PokerAnalyticsException.FilterElementTypeMissing(filterElementRow) - } - } + fun setValue(value:Int) { + intValue= value + } + fun setValue(value:String) { + stringValue = value + } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElementBlind.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElementBlind.kt deleted file mode 100644 index ebde6cad..00000000 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElementBlind.kt +++ /dev/null @@ -1,8 +0,0 @@ -package net.pokeranalytics.android.model.realm - -import io.realm.RealmObject - -open class FilterElementBlind(var sb : Double? = null, - var bb : Double? = null, - var currencyCode : String? = null -) : RealmObject() \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Report.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt similarity index 88% rename from app/src/main/java/net/pokeranalytics/android/model/realm/Report.kt rename to app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt index abf711a9..9014a1b9 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Report.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt @@ -11,7 +11,7 @@ enum class ReportDisplay { MAP } -open class Report : RealmObject() { +open class ReportSetup : RealmObject() { @PrimaryKey var id = UUID.randomUUID().toString() @@ -24,7 +24,7 @@ open class Report : RealmObject() { // @todo define the configuration options -// var comparators: List = listOf() +// var criteria: List = listOf() // var stats: List = listOf() // The filters associated with the report 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 e0641348..e784e513 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,16 +6,29 @@ import io.realm.RealmResults import io.realm.annotations.Ignore import io.realm.annotations.LinkingObjects import io.realm.annotations.RealmClass +import net.pokeranalytics.android.model.filter.Filterable +import net.pokeranalytics.android.model.filter.QueryCondition @RealmClass -open class Result : RealmObject() { +open class Result : RealmObject(), Filterable { + companion object { + + fun fieldNameForQueryType(queryCondition: Class < out QueryCondition>): String? { + Session.fieldNameForQueryType(queryCondition)?.let { + return "sessions.$it" + } + return null + } + + } /** * The buyin amount */ var buyin: Double? = null set(value) { field = value + this.computeNumberOfRebuy() this.computeNet() } @@ -36,6 +49,15 @@ open class Result : RealmObject() { */ var netResult: Double? = null set(value) { + + this.session?.bankroll?.let { bankroll -> + if (bankroll.live) { + throw IllegalStateException("Can't set net result on a live bankroll") + } + } ?: run { + throw IllegalStateException("Session doesn't have any bankroll") + } + field = value this.computeNet() if (value != null) { @@ -64,6 +86,9 @@ open class Result : RealmObject() { // The tournament final position, if applicable var tournamentFinalPosition: Int? = null + // Number of rebuys + //var numberOfRebuy: Double? = null + @LinkingObjects("result") private val sessions: RealmResults? = null @@ -96,6 +121,10 @@ open class Result : RealmObject() { this.session?.sessionSet?.computeStats() } + // Computes the number of rebuy + private fun computeNumberOfRebuy() { + } + // @todo tips? } \ No newline at end of file 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 9994fe98..825af61d 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 @@ -1,6 +1,7 @@ package net.pokeranalytics.android.model.realm import android.content.Context +import com.github.mikephil.charting.data.Entry import io.realm.Realm import io.realm.RealmList import io.realm.RealmObject @@ -11,10 +12,7 @@ import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey import io.realm.kotlin.where import net.pokeranalytics.android.R -import net.pokeranalytics.android.calculus.ComputedStat -import net.pokeranalytics.android.calculus.Stat -import net.pokeranalytics.android.calculus.StatFormattingException -import net.pokeranalytics.android.calculus.TextFormat +import net.pokeranalytics.android.calculus.* import net.pokeranalytics.android.exceptions.ModelException import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.LiveData @@ -29,16 +27,15 @@ import net.pokeranalytics.android.model.interfaces.* 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.view.RowRepresentable -import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor -import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.ui.fragment.GraphFragment +import net.pokeranalytics.android.ui.view.* import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable -import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow -import net.pokeranalytics.android.util.CurrencyUtils import net.pokeranalytics.android.util.NULL_TEXT -import net.pokeranalytics.android.util.Preferences +import net.pokeranalytics.android.util.UserDefaults import net.pokeranalytics.android.util.extensions.* +import java.text.DateFormat import java.util.* import java.util.Currency import kotlin.collections.ArrayList @@ -46,8 +43,7 @@ import kotlin.collections.ArrayList typealias BB = Double open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed, - TimeFilterable, Filterable { - + TimeFilterable, Filterable, DatedValue { enum class Type { CASH_GAME, @@ -67,36 +63,29 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat return realm.copyToRealm(session) } - fun fieldNameForQueryType(queryCondition: QueryCondition): String? { - return when (queryCondition) { - LIVE, ONLINE -> "bankroll.live" - CASH, TOURNAMENT -> "type" - BANKROLL -> "bankroll.id" - GAME -> "game.id" - TOURNAMENT_NAME -> "tournamentName.id" - ANY_TOURNAMENT_FEATURES, ALL_TOURNAMENT_FEATURES -> "tournamentFeatures.id" - LOCATION -> "location.id" - LIMIT -> "limit" - TABLE_SIZE -> "tableSize" - TOURNAMENT_TYPE -> "tournamentType" - CURRENCY -> "bankroll.currency" - CURRENCY_CODE -> "bankroll.currency.code" - BIG_BLIND -> "cgBigBlind" - SMALL_BLIND -> "cgSmallBlind" - COMMENT -> "comment" - BETWEEN_NUMBER_OF_TABLE, MORE_NUMBER_OF_TABLE, LESS_NUMBER_OF_TABLE -> "numberOfTable" - MORE_THAN_NET_RESULT, LESS_THAN_NET_RESULT -> "computableResults.ratedNet" - MORE_THAN_BUY_IN, LESS_THAN_BUY_IN -> "result.buyin" - MORE_THAN_CASH_OUT, LESS_THAN_CASH_OUT -> "result.cashout" - MORE_THAN_TIPS, LESS_THAN_TIPS -> "result.tips" - MORE_THAN_NUMBER_OF_PLAYER, LESS_THAN_NUMBER_OF_PLAYER, BETWEEN_NUMBER_OF_PLAYER -> "tournamentNumberOfPlayers" - MORE_THAN_TOURNAMENT_FEE, LESS_THAN_TOURNAMENT_FEE, BETWEEN_TOURNAMENT_FEE -> "tournamentEntryFee" - STARTED_FROM_DATE, STARTED_TO_DATE -> "startDate" - ENDED_FROM_DATE, ENDED_TO_DATE -> "endDate" - DAY_OF_WEEK, WEEK_END, WEEK_DAY -> "dayOfWeek" - MONTH -> "month" - YEAR -> "year" - TODAY, YESTERDAY, TODAY_AND_YESTERDAY, THIS_YEAR, THIS_MONTH, THIS_WEEK -> "startDate" + fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { + return when (queryCondition) { + IsLive::class.java, IsOnline::class.java -> "bankroll.live" + IsCash::class.java, IsTournament::class.java -> "type" + AnyBankroll::class.java -> "bankroll.id" + AnyGame::class.java -> "game.id" + AnyTournamentName::class.java -> "tournamentName.id" + AnyTournamentFeature::class.java, AllTournamentFeature::class.java -> "tournamentFeatures.id" + AnyLocation::class.java -> "location.id" + AnyLimit::class.java -> "limit" + AnyTableSize::class.java -> "tableSize" + AnyTournamentType::class.java -> "tournamentType" + AnyBlind::class.java -> "blinds" + NumberOfTable::class.java -> "numberOfTable" + NetAmountWon::class.java -> "computableResults.ratedNet" + NumberOfPlayer::class.java -> "tournamentNumberOfPlayers" + TournamentFee::class.java -> "tournamentEntryFee" + StartedFromDate::class.java, StartedToDate::class.java -> "startDate" + EndedFromDate::class.java, EndedToDate::class.java -> "endDate" + AnyDayOfWeek::class.java, IsWeekEnd::class.java, IsWeekDay::class.java -> "dayOfWeek" + AnyMonthOfYear::class.java -> "month" + AnyYear::class.java -> "year" + IsToday::class.java, WasYesterday::class.java, WasTodayAndYesterday::class.java, DuringThisYear::class.java, DuringThisMonth::class.java, DuringThisWeek::class.java -> "startDate" else -> null } } @@ -122,7 +111,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat // Timed interface - override var dayOfWeek : Int? = null + override var dayOfWeek: Int? = null override var month: Int? = null override var year: Int? = null override var dayOfMonth: Int? = null @@ -221,14 +210,22 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat // The small blind value var cgSmallBlind: Double? = null + set(value) { + field = value + formatBlinds() + } // The big blind value var cgBigBlind: Double? = null set(value) { field = value this.computeStats() + formatBlinds() } + var blinds: String? = null + private set + // Tournament // The entry fee of the tournament @@ -246,6 +243,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat // The features of the tournament, like Knockout, Shootout, Turbo... var tournamentFeatures: RealmList = RealmList() + fun bankrollHasBeenUpdated() { + formatBlinds() + } + /** * Manages impacts on SessionSets * Should be called when the start / end date are changed @@ -299,11 +300,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat */ val bbNet: BB get() { - val bb = this.cgBigBlind; val result = this.result - if (bb != null && result != null) { - return result.net / bb + val bb = this.cgBigBlind + val result = this.result + return if (bb != null && result != null) { + result.net / bb } else { - return 0.0 + 0.0 } } @@ -318,6 +320,21 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat return noh * hd } + + // DatedValue + + @Ignore + override var date: Date = Date() + get() { + return this.startDate ?: this.creationDate + } + + @Ignore + override var amount: Double = 0.0 + get() { + return this.computableResult?.ratedNet ?: 0.0 + } + /** * Pre-compute various stats */ @@ -353,16 +370,6 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat return playerHandsPerHour / tableSize.toDouble() } - @Ignore - var ratedBuyin: Double = 0.0 - get() { - val rate = this.bankroll?.currency?.rate ?: 1.0 - this.result?.buyin?.let { buyin -> - return buyin * rate - } - return 0.0 - } - val hourlyRate: Double get() { this.result?.let { result -> @@ -483,14 +490,14 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat return NULL_TEXT } - /** - * Return the formatted blinds - */ - fun getBlinds(context: Context): String { - val currencyCode = bankroll?.currency?.code ?: CurrencyUtils.getLocaleCurrency().currencyCode - val currencySymbol = Currency.getInstance(currencyCode).getSymbol(Preferences.getCurrencyLocale(context)) - return if (cgSmallBlind == null) NULL_TEXT else "$currencySymbol ${cgSmallBlind?.formatted()}/${cgBigBlind?.round()}" - } + val currency: Currency + get() { + return bankroll?.currency?.code?.let { + Currency.getInstance(it) + } ?: run { + UserDefaults.currency + } + } /** * Return the game title @@ -509,6 +516,19 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat return if (gameTitle.isNotBlank()) gameTitle else NULL_TEXT } + fun getFormattedBlinds(): String { + return blinds ?: NULL_TEXT + } + + private fun formatBlinds() { + blinds = null + if (cgBigBlind == null) return + cgBigBlind?.let { bb -> + val sb = cgSmallBlind ?: bb / 2.0 + blinds = "${currency.symbol} ${sb.formatted()}/${bb.round()}" + } + } + // LifeCycle /** @@ -547,7 +567,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } @Ignore - private var rowRepresentationForCurrentState : List = mutableListOf() + private var rowRepresentationForCurrentState: List = mutableListOf() private fun updatedRowRepresentationForCurrentState(): List { val rows = ArrayList() @@ -559,34 +579,34 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat CustomizableRowRepresentable( RowViewType.HEADER_TITLE_AMOUNT_BIG, title = getFormattedDuration(), - computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, CurrencyUtils.getCurrency(bankroll)) + computedStat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency) ) ) - rows.add(SeparatorRowRepresentable()) + rows.add(SeparatorRow()) } SessionState.PAUSED -> { rows.add( CustomizableRowRepresentable( RowViewType.HEADER_TITLE_AMOUNT_BIG, resId = R.string.pause, - computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, CurrencyUtils.getCurrency(bankroll)) + computedStat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency) ) ) - rows.add(SeparatorRowRepresentable()) + rows.add(SeparatorRow()) } SessionState.FINISHED -> { rows.add( CustomizableRowRepresentable( RowViewType.HEADER_TITLE_AMOUNT_BIG, title = getFormattedDuration(), - computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, CurrencyUtils.getCurrency(bankroll)) + computedStat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency) ) ) rows.add( CustomizableRowRepresentable( RowViewType.HEADER_TITLE_AMOUNT, resId = R.string.hour_rate_without_pauses, - computedStat = ComputedStat(Stat.HOURLY_RATE, this.hourlyRate, CurrencyUtils.getCurrency(bankroll)) + computedStat = ComputedStat(Stat.HOURLY_RATE, this.hourlyRate, currency = currency) ) ) @@ -599,7 +619,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat // ) // ) // } - rows.add(SeparatorRowRepresentable()) + rows.add(SeparatorRow()) } else -> { } @@ -625,21 +645,21 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat override fun stringForRow(row: RowRepresentable, context: Context): String { return when (row) { SessionRow.BANKROLL -> bankroll?.name ?: NULL_TEXT - SessionRow.BLINDS -> getBlinds(context) + SessionRow.BLINDS -> getFormattedBlinds() SessionRow.BREAK_TIME -> if (this.breakDuration > 0.0) this.breakDuration.toMinutes() else NULL_TEXT - SessionRow.BUY_IN -> this.result?.buyin?.toCurrency(CurrencyUtils.getCurrency(bankroll)) ?: NULL_TEXT - SessionRow.CASHED_OUT, SessionRow.PRIZE -> this.result?.cashout?.toCurrency(CurrencyUtils.getCurrency(bankroll)) ?: NULL_TEXT - SessionRow.NET_RESULT -> this.result?.netResult?.toCurrency(CurrencyUtils.getCurrency(bankroll)) ?: NULL_TEXT + SessionRow.BUY_IN -> this.result?.buyin?.toCurrency(currency) ?: NULL_TEXT + SessionRow.CASHED_OUT, SessionRow.PRIZE -> this.result?.cashout?.toCurrency(currency) ?: NULL_TEXT + SessionRow.NET_RESULT -> this.result?.netResult?.toCurrency(currency) ?: NULL_TEXT SessionRow.COMMENT -> if (this.comment.isNotEmpty()) this.comment else NULL_TEXT SessionRow.END_DATE -> this.endDate?.shortDateTime() ?: NULL_TEXT SessionRow.GAME -> getFormattedGame() - SessionRow.INITIAL_BUY_IN -> tournamentEntryFee?.toCurrency(CurrencyUtils.getCurrency(bankroll)) ?: NULL_TEXT + SessionRow.INITIAL_BUY_IN -> tournamentEntryFee?.toCurrency(currency) ?: NULL_TEXT SessionRow.LOCATION -> location?.name ?: NULL_TEXT SessionRow.PLAYERS -> tournamentNumberOfPlayers?.toString() ?: NULL_TEXT SessionRow.POSITION -> result?.tournamentFinalPosition?.toString() ?: NULL_TEXT SessionRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT SessionRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: NULL_TEXT - SessionRow.TIPS -> result?.tips?.toCurrency(CurrencyUtils.getCurrency(bankroll)) ?: NULL_TEXT + SessionRow.TIPS -> result?.tips?.toCurrency(currency) ?: NULL_TEXT SessionRow.TOURNAMENT_TYPE -> this.tournamentType?.let { TournamentType.values()[it].localizedTitle(context) } ?: run { @@ -647,7 +667,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } SessionRow.TOURNAMENT_FEATURE -> { if (tournamentFeatures.size > 2) { - "${tournamentFeatures.subList(0,2).joinToString { + "${tournamentFeatures.subList(0, 2).joinToString { it.name }}, ..." } else if (tournamentFeatures.size > 0) { @@ -674,56 +694,98 @@ 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 LiveData.BANKROLL.items(realm))) - SessionRow.GAME -> row.editingDescriptors(mapOf( - "limit" to this.limit, - "defaultValue" to this.game, - "data" to LiveData.GAME.items(realm))) - SessionRow.LOCATION -> row.editingDescriptors(mapOf( - "defaultValue" to this.location, - "data" to LiveData.LOCATION.items(realm))) - SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(mapOf( - "defaultValue" to this.tournamentFeatures, - "data" to LiveData.TOURNAMENT_FEATURE.items(realm))) - SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(mapOf( - "defaultValue" to this.tournamentName, - "data" to LiveData.TOURNAMENT_NAME.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.BANKROLL -> row.editingDescriptors( + mapOf( + "defaultValue" to this.bankroll, + "data" to LiveData.BANKROLL.items(realm) + ) + ) + SessionRow.GAME -> row.editingDescriptors( + mapOf( + "limit" to this.limit, + "defaultValue" to this.game, + "data" to LiveData.GAME.items(realm) + ) + ) + SessionRow.LOCATION -> row.editingDescriptors( + mapOf( + "defaultValue" to this.location, + "data" to LiveData.LOCATION.items(realm) + ) + ) + SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors( + mapOf( + "defaultValue" to this.tournamentFeatures, + "data" to LiveData.TOURNAMENT_FEATURE.items(realm) + ) + ) + SessionRow.TOURNAMENT_NAME -> row.editingDescriptors( + mapOf( + "defaultValue" to this.tournamentName, + "data" to LiveData.TOURNAMENT_NAME.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 - )) + 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 + ) + ) else -> null } } @@ -760,13 +822,15 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000 } SessionRow.BUY_IN -> { - val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java) + val localResult = + if (this.result != null) this.result as Result else realm.createObject(Result::class.java) localResult.buyin = value as Double? this.result = localResult this.updateRowRepresentation() } SessionRow.CASHED_OUT, SessionRow.PRIZE -> { - val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java) + val localResult = + if (this.result != null) this.result as Result else realm.createObject(Result::class.java) localResult.cashout = value as Double? @@ -846,14 +910,19 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } - // Stat Base + // Stat Entry - override fun formattedValue(stat: Stat, context: Context) : TextFormat { + override val entryTitle: String + get() { + return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate) + } + + override fun formattedValue(stat: Stat): TextFormat { this.result?.let { result -> val value: Double? = when (stat) { - Stat.NETRESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> result.net + Stat.NET_RESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> result.net Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> 1.0 Stat.AVERAGE_BUYIN -> result.buyin Stat.ROI -> { @@ -864,16 +933,20 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } } Stat.HOURLY_RATE_BB -> this.bbHourlyRate - Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> Stat.netBBPer100Hands(this.bbNet, this.estimatedHands) + Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> Stat.netBBPer100Hands( + this.bbNet, + this.estimatedHands + ) Stat.AVERAGE_NET_BB -> this.bbNet - Stat.DURATION, Stat.AVERAGE_DURATION -> this.netDuration.toDouble() + Stat.HOURLY_DURATION, Stat.AVERAGE_HOURLY_DURATION -> this.netDuration.toDouble() Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate Stat.HANDS_PLAYED -> this.estimatedHands + Stat.WIN_RATIO -> null else -> throw StatFormattingException("format undefined for stat ${stat.name}") } value?.let { - return stat.format(it, CurrencyUtils.getCurrency(this.bankroll), context) + return stat.format(it, currency = currency) } ?: run { return TextFormat(NULL_TEXT) } @@ -884,6 +957,56 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } + override fun legendValues( + stat: Stat, + entry: Entry, + style: GraphFragment.Style, + groupName: String, + context: Context + ) : LegendContent { + + when (style) { + GraphFragment.Style.MULTILINE -> { + + val secondTitle = stat.localizedTitle(context) + val entryValue = this.formattedValue(stat) + val dateValue = TextFormat(this.entryTitle) -} + + return MultilineLegendValues(groupName, secondTitle, entryValue, dateValue) + } + else -> { + + return when (stat) { + Stat.STANDARD_DEVIATION -> { + val left = this.formattedValue(Stat.NET_RESULT) + + val hasMainCurrencyCode = this.bankroll?.currency?.hasMainCurrencyCode() ?: false + var right: TextFormat? = null + + if (!hasMainCurrencyCode) { + this.computableResult?.ratedNet?.let { ratedNet -> + right = Stat.NET_RESULT.format(ratedNet) + } + } + + DefaultLegendValues(this.entryTitle, left, right) + } + else -> { + super.legendValues(stat, entry, style, groupName, context) + } + } + } + + } + + } + + // Timed + + override val objectIdentifier: ObjectIdentifier + get() = ObjectIdentifier(this.id, 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 2e5cd71b..52b998c6 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 @@ -1,18 +1,20 @@ package net.pokeranalytics.android.model.realm -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.ObjectIdentifier import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.StatFormattingException import net.pokeranalytics.android.calculus.TextFormat import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.interfaces.Timed +import net.pokeranalytics.android.util.NULL_TEXT +import java.text.DateFormat import java.util.* @@ -79,17 +81,13 @@ open class SessionSet() : RealmObject(), Timed, Filterable { var bbNet: BB = 0.0 - override fun formattedValue(stat: Stat, context: Context) : TextFormat { - return when (stat) { - Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, null, context) - Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), null, context) - Stat.HOURLY_RATE -> stat.format(this.hourlyRate, null, context) - Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, null, context) - else -> throw StatFormattingException("format undefined for stat ${stat.name}") + val bbHourlyRate: BB + get() { + return this.bbNet / this.hourlyDuration } - } enum class Field(val identifier: String) { + START_DATE("startDate"), RATED_NET("ratedNet"), HOURLY_RATE("hourlyRate"), BB_NET("bbNet"), @@ -104,11 +102,45 @@ open class SessionSet() : RealmObject(), Timed, Filterable { return realm.copyToRealm(sessionSet) } - fun fieldNameForQueryType(queryCondition: QueryCondition): String? { - return "sessions." + Session.fieldNameForQueryType(queryCondition) + fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { + Session.fieldNameForQueryType(queryCondition)?.let { + return "sessions.$it" + } + return null } } + // Stat Base + + override val entryTitle: String + get() { + return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate) + } + + override fun formattedValue(stat: Stat) : TextFormat { + return when (stat) { + Stat.NET_RESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null) + Stat.HOURLY_DURATION, Stat.AVERAGE_HOURLY_DURATION -> stat.format(this.hourlyDuration, currency = null) + Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null) + Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null) + Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, currency = null) + Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { + val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands) + if (netBBPer100Hands != null) { + return stat.format(this.estimatedHands, currency = null) + } else { + return TextFormat(NULL_TEXT) + } + } + else -> throw StatFormattingException("format undefined for stat ${stat.name}") + } + } + + // Timed + + override val objectIdentifier: ObjectIdentifier + get() = ObjectIdentifier(this.id, SessionSet::class.java) + } 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 f53f8583..481a9444 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 @@ -5,6 +5,9 @@ import io.realm.RealmObject import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.filter.Filterable +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.interfaces.DatedValue import net.pokeranalytics.android.model.interfaces.Manageable import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource @@ -15,39 +18,47 @@ import java.util.* import kotlin.collections.ArrayList -open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable { +open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, Filterable, DatedValue { companion object { - val rowRepresentation : List by lazy { + val rowRepresentation: List by lazy { val rows = ArrayList() rows.addAll(TransactionRow.values()) rows } + + fun fieldNameForQueryType(queryCondition: Class): String? { + return when (queryCondition) { + QueryCondition.AnyBankroll::class.java -> "bankroll.id" + QueryCondition.StartedFromDate::class.java, QueryCondition.StartedToDate::class.java -> "date" + else -> null + } + } } - @PrimaryKey + @PrimaryKey override var id = UUID.randomUUID().toString() // The bankroll of the transaction var bankroll: Bankroll? = null - // The amount of the transaction - var amount: Double = 0.0 + // The amount of the transaction + override var amount: Double = 0.0 - // The date of the transaction - var date: Date = Date() + // The date of the transaction + override var date: Date = Date() - // The type of the transaction - var type: TransactionType? = null + // The type of the transaction + var type: TransactionType? = null - // A user comment - var comment: String = "" + // A user comment + var comment: String = "" @Ignore override val viewType: Int = RowViewType.ROW_TRANSACTION.ordinal override fun updateValue(value: Any?, row: RowRepresentable) { - when(row) { + when (row) { TransactionRow.BANKROLL -> bankroll = value as Bankroll? TransactionRow.TYPE -> type = value as TransactionType? TransactionRow.AMOUNT -> amount = if (value == null) 0.0 else (value as String).toDouble() @@ -85,5 +96,5 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo override fun getFailedDeleteMessage(): Int { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } -} +} 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 eba342a5..0a55ef46 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 @@ -14,65 +14,77 @@ import kotlin.collections.ArrayList open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { - companion object { - val rowRepresentation : List by lazy { - val rows = ArrayList() - rows.add(SimpleRow.NAME) - rows.addAll(TransactionTypeRow.values()) - rows - } - } - - @PrimaryKey - override var id = UUID.randomUUID().toString() - - // The name of the transaction type - override var name: String = "" - - // Whether or not the amount is added, or subtracted to the bankroll total - var additive: Boolean = false - - // Whether or not the type can be deleted by the user - var lock: Boolean = false - - // The predefined kind, if necessary, like: Withdrawal, deposit, or tips - var kind: Int? = null - - override fun getDisplayName(): String { - return this.name - } - - override fun adapterRows(): List? { - return TransactionType.rowRepresentation - } - - override fun stringForRow(row: RowRepresentable): String { - return when (row) { - SimpleRow.NAME -> this.name - 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? ?: "" - } - } - - override fun isValidForDelete(realm: Realm): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun getFailedDeleteMessage(): Int { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } -} -enum class TransactionKind { - WITHDRAWAL, - DEPOSIT + enum class Value(val additive: Boolean) { + WITHDRAWAL(false), + DEPOSIT(true), + BONUS(true) + } + + 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() + type?.let { + return it + } + throw IllegalStateException("Transaction type ${value.name} should exist in database!") + } + + } + + @PrimaryKey + override var id = UUID.randomUUID().toString() + + // The name of the transaction type + override var name: String = "" + + // Whether or not the amount is added, or subtracted to the bankroll total + var additive: Boolean = false + + // Whether or not the type can be deleted by the user + var lock: Boolean = false + + // The predefined kind, if necessary, like: Withdrawal, deposit, or tips + var kind: Int? = null + + override fun getDisplayName(): String { + return this.name + } + + override fun adapterRows(): List? { + return rowRepresentation + } + + override fun stringForRow(row: RowRepresentable): String { + return when (row) { + SimpleRow.NAME -> this.name + 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? ?: "" + } + } + + override fun isValidForDelete(realm: Realm): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun getFailedDeleteMessage(): Int { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } } + 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 ecf5fbc7..a0d2d371 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 @@ -3,24 +3,23 @@ package net.pokeranalytics.android.model.utils import android.content.Context import io.realm.Realm import io.realm.kotlin.where -import net.pokeranalytics.android.R import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.Currency -import net.pokeranalytics.android.util.CurrencyUtils -import net.pokeranalytics.android.util.CurrencyUtils.Companion.getLocaleCurrency -import net.pokeranalytics.android.util.Preferences +import net.pokeranalytics.android.util.UserDefaults import java.util.* + class Seed(var context:Context) : Realm.Transaction { override fun execute(realm: Realm) { this.createDefaultGames(realm) this.createDefaultTournamentFeatures(realm) this.createDefaultCurrencyAndBankroll(realm) + this.createDefaultTransactionTypes(realm) } private fun createDefaultTournamentFeatures(realm: Realm) { - context.resources.getStringArray(R.array.seed_tournament_features).forEach { + context.resources.getStringArray(net.pokeranalytics.android.R.array.seed_tournament_features).forEach { val tournamentFeature = TournamentFeature() tournamentFeature.id = UUID.randomUUID().toString() tournamentFeature.name = it @@ -29,23 +28,24 @@ class Seed(var context:Context) : Realm.Transaction { } private fun createDefaultCurrencyAndBankroll(realm: Realm) { + // Currency - val localeCurrency = getLocaleCurrency() + val localeCurrency = UserDefaults.getLocaleCurrency() val defaultCurrency = Currency() defaultCurrency.code = localeCurrency.currencyCode realm.insertOrUpdate(defaultCurrency) // Bankroll val bankroll = Bankroll() - bankroll.name = context.resources.getString(R.string.live) + bankroll.name = context.resources.getString(net.pokeranalytics.android.R.string.live) bankroll.live = true bankroll.currency = realm.where().equalTo("code", localeCurrency.currencyCode).findFirst() realm.insertOrUpdate(bankroll) } private fun createDefaultGames(realm: Realm) { - val gamesName = context.resources.getStringArray(R.array.seed_games) - val gamesShortName = context.resources.getStringArray(R.array.seed_games_short_name) + val gamesName = context.resources.getStringArray(net.pokeranalytics.android.R.array.seed_games) + val gamesShortName = context.resources.getStringArray(net.pokeranalytics.android.R.array.seed_games_short_name) for ((index, name) in gamesName.withIndex()) { val game = Game() game.id = UUID.randomUUID().toString() @@ -54,4 +54,15 @@ class Seed(var context:Context) : Realm.Transaction { realm.insertOrUpdate(game) } } + + private fun createDefaultTransactionTypes(realm: Realm) { + TransactionType.Value.values().forEachIndexed { index, value -> + val type = TransactionType() + 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/ui/activity/BankrollActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt new file mode 100644 index 00000000..e1b6ea05 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.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 BankrollActivity : PokerAnalyticsActivity() { + + companion object { + fun newInstance(context: Context) { + val intent = Intent(context, BankrollActivity::class.java) + context.startActivity(intent) + } + + /** + * Create a new instance for result + */ + fun newInstanceForResult(fragment: Fragment, requestCode: Int) { + val intent = Intent(fragment.requireContext(), BankrollActivity::class.java) + fragment.startActivityForResult(intent, requestCode) + } + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_bankroll) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/CalendarDetailsActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/CalendarDetailsActivity.kt new file mode 100644 index 00000000..53c9cc77 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/CalendarDetailsActivity.kt @@ -0,0 +1,51 @@ +package net.pokeranalytics.android.ui.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import kotlinx.android.synthetic.main.activity_calendar_details.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.ComputedResults +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity +import net.pokeranalytics.android.ui.fragment.CalendarDetailsFragment + + +class CalendarDetailsActivity : PokerAnalyticsActivity() { + + companion object { + + var computedResults: ComputedResults? = null + var sessionTypeCondition: QueryCondition? = null + var detailsTitle: String? = null + + /** + * Default constructor + */ + fun newInstance(context: Context, computedResults: ComputedResults, sessionTypeCondition: QueryCondition?, title: String?) { + this.computedResults = computedResults + this.sessionTypeCondition = sessionTypeCondition + this.detailsTitle = title + val intent = Intent(context, CalendarDetailsActivity::class.java) + context.startActivity(intent) + } + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_calendar_details) + initUI() + } + + /** + * Init UI + */ + private fun initUI() { + + val calendarDetailsFragment = calendarDetailsFragment as CalendarDetailsFragment + calendarDetailsFragment.setData(computedResults, sessionTypeCondition, detailsTitle) + + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/ComparisonChartActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/ComparisonChartActivity.kt new file mode 100644 index 00000000..a38ca702 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/ComparisonChartActivity.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 ComparisonChartActivity : PokerAnalyticsActivity() { + + companion object { + fun newInstance(context: Context) { + val intent = Intent(context, ComparisonChartActivity::class.java) + context.startActivity(intent) + } + + /** + * Create a new instance for result + */ + fun newInstanceForResult(fragment: Fragment, requestCode: Int) { + val intent = Intent(fragment.requireContext(), ComparisonChartActivity::class.java) + fragment.startActivityForResult(intent, requestCode) + } + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_comparison_chart) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt deleted file mode 100644 index 8f815c9c..00000000 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt +++ /dev/null @@ -1,69 +0,0 @@ -package net.pokeranalytics.android.ui.activity - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import com.github.mikephil.charting.data.Entry -import net.pokeranalytics.android.R -import net.pokeranalytics.android.calculus.Stat -import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity -import net.pokeranalytics.android.ui.fragment.GraphFragment - -class GraphParameters(stat: Stat, entries: List) { - var stat: Stat = stat - var entries: List = entries -} - -class GraphActivity : PokerAnalyticsActivity() { - - private enum class IntentKey(val keyName: String) { - STAT("STAT"), - ENTRIES("ENTRIES"), - } - - companion object { - - // Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects - var parameters: GraphParameters? = null - - /** - * Default constructor - */ - fun newInstance(context: Context, stat: Stat, entries: List) { - - GraphActivity.parameters = GraphParameters(stat, entries) - - val intent = Intent(context, GraphActivity::class.java) - context.startActivity(intent) - } - - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_editable_data) - initUI() - } - - /** - * Init UI - */ - private fun initUI() { - - val fragmentManager = supportFragmentManager - val fragmentTransaction = fragmentManager.beginTransaction() - val fragment = GraphFragment() - - fragmentTransaction.add(R.id.container, fragment) - fragmentTransaction.commit() - - GraphActivity.parameters?.let { - fragment.setData(it.stat, it.entries) - GraphActivity.parameters = null - } ?: run { - throw Exception("Missing graph parameters") - } - - } - -} 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 071e2a81..610a948c 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 @@ -32,20 +32,35 @@ class HomeActivity : PokerAnalyticsActivity() { private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item -> when (item.itemId) { - net.pokeranalytics.android.R.id.navigation_history -> { + //CLEAN + /* + R.id.navigation_history -> { displayFragment(0) - return@OnNavigationItemSelectedListener true } - net.pokeranalytics.android.R.id.navigation_stats -> { + R.id.navigation_stats -> { displayFragment(1) - return@OnNavigationItemSelectedListener true } - net.pokeranalytics.android.R.id.navigation_settings -> { + R.id.navigation_settings -> { displayFragment(2) - return@OnNavigationItemSelectedListener true + } + */ + R.id.navigation_history -> { + displayFragment(0) + } + R.id.navigation_stats -> { + displayFragment(1) + } + R.id.navigation_calendar -> { + displayFragment(2) + } + R.id.navigation_reports -> { + displayFragment(3) + } + R.id.navigation_more -> { + displayFragment(4) } } - false + return@OnNavigationItemSelectedListener true } override fun onCreate(savedInstanceState: Bundle?) { @@ -58,10 +73,11 @@ class HomeActivity : PokerAnalyticsActivity() { } override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.home_menu, menu) + menu?.clear() + menuInflater.inflate(R.menu.toolbar_home, menu) this.homeMenu = menu - //TODO: Change filter button visibility - homeMenu?.findItem(R.id.filter)?.isVisible = false + //TODO: Change queryWith button visibility + homeMenu?.findItem(R.id.filter)?.isVisible = true return super.onCreateOptionsMenu(menu) } @@ -97,7 +113,7 @@ class HomeActivity : PokerAnalyticsActivity() { setSupportActionBar(toolbar) navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener) - navigation.selectedItemId = net.pokeranalytics.android.R.id.navigation_history + navigation.selectedItemId = R.id.navigation_history val homePagerAdapter = HomePagerAdapter(supportFragmentManager) viewPager.offscreenPageLimit = 5 @@ -134,17 +150,40 @@ class HomeActivity : PokerAnalyticsActivity() { */ private fun updateToolbar(index: Int) { when (index) { + //CLEAN + /* 0 -> { toolbar.title = getString(R.string.feed) - //TODO: Change filter button visibility - homeMenu?.findItem(R.id.filter)?.isVisible = false + homeMenu?.findItem(R.id.queryWith)?.isVisible = false } 1 -> { toolbar.title = getString(R.string.stats) - homeMenu?.findItem(R.id.filter)?.isVisible = false + homeMenu?.findItem(R.id.queryWith)?.isVisible = false } 2 -> { toolbar.title = getString(R.string.services) + homeMenu?.findItem(R.id.queryWith)?.isVisible = false + } + */ + + 0 -> { + toolbar.title = getString(R.string.feed) + homeMenu?.findItem(R.id.filter)?.isVisible = true + } + 1 -> { + toolbar.title = getString(R.string.stats) + homeMenu?.findItem(R.id.filter)?.isVisible = true + } + 2 -> { + toolbar.title = getString(R.string.calendar) + homeMenu?.findItem(R.id.filter)?.isVisible = false + } + 3 -> { + toolbar.title = getString(R.string.reports) + homeMenu?.findItem(R.id.filter)?.isVisible = false + } + 4 -> { + toolbar.title = getString(R.string.more) homeMenu?.findItem(R.id.filter)?.isVisible = false } } @@ -171,18 +210,15 @@ class HomeActivity : PokerAnalyticsActivity() { .setCancelable(true) .setItems(choices.toTypedArray()) { _, which -> Timber.d("Click on $which") - - when(which) { + when (which) { 0 -> FiltersActivity.newInstance(this@HomeActivity) } - } .setNegativeButton(R.string.cancel) { _, _ -> Timber.d("Click on cancel") } builder.show() - } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/ReportDetailsActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/ReportDetailsActivity.kt new file mode 100644 index 00000000..be1b5dd1 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/ReportDetailsActivity.kt @@ -0,0 +1,55 @@ +package net.pokeranalytics.android.ui.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.Report +import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity +import net.pokeranalytics.android.ui.fragment.ReportDetailsFragment + + + +class ReportDetailsActivity : PokerAnalyticsActivity() { + + companion object { + + // Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects + private var report: Report? = null + private var reportTitle: String = "" + + /** + * Default constructor + */ + fun newInstance(context: Context, report: Report, reportTitle: String) { + //parameters = GraphParameters(stat, group, report) + this.report = report + this.reportTitle = reportTitle + val intent = Intent(context, ReportDetailsActivity::class.java) + context.startActivity(intent) + } + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_report_details) + initUI() + } + + /** + * Init UI + */ + private fun initUI() { + + report?.let { + val fragmentTransaction = supportFragmentManager.beginTransaction() + val reportDetailsFragment = ReportDetailsFragment.newInstance(it, reportTitle) + fragmentTransaction.add(R.id.reportDetailsContainer, reportDetailsFragment) + fragmentTransaction.commit() + + report = null + } + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/SettingsActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/SettingsActivity.kt new file mode 100644 index 00000000..031da0f3 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/SettingsActivity.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 SettingsActivity : PokerAnalyticsActivity() { + + companion object { + fun newInstance(context: Context) { + val intent = Intent(context, SettingsActivity::class.java) + context.startActivity(intent) + } + + /** + * Create a new instance for result + */ + fun newInstanceForResult(fragment: Fragment, requestCode: Int) { + val intent = Intent(fragment.requireContext(), SettingsActivity::class.java) + fragment.startActivityForResult(intent, requestCode) + } + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_settings) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/StatisticDetailsActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/StatisticDetailsActivity.kt new file mode 100644 index 00000000..a5b12c9d --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/StatisticDetailsActivity.kt @@ -0,0 +1,59 @@ +package net.pokeranalytics.android.ui.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.ComputableGroup +import net.pokeranalytics.android.calculus.Report +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity +import net.pokeranalytics.android.ui.fragment.StatisticDetailsFragment + + +class StatisticsDetailsParameters(var stat: Stat, var computableGroup: ComputableGroup, var report: Report) + +class StatisticDetailsActivity : PokerAnalyticsActivity() { + + companion object { + + // Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects + private var parameters: StatisticsDetailsParameters? = null + private var displayAggregationChoices: Boolean = true + + /** + * Default constructor + */ + fun newInstance(context: Context, stat: Stat, group: ComputableGroup, report: Report, displayAggregationChoices: Boolean = true) { + parameters = StatisticsDetailsParameters(stat, group, report) + this.displayAggregationChoices = displayAggregationChoices + val intent = Intent(context, StatisticDetailsActivity::class.java) + context.startActivity(intent) + } + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_statistic_details) + initUI() + } + + /** + * Init UI + */ + private fun initUI() { + + val fragmentTransaction = supportFragmentManager.beginTransaction() + val statisticDetailsFragment = StatisticDetailsFragment() + fragmentTransaction.add(R.id.statisticDetailsContainer, statisticDetailsFragment) + fragmentTransaction.commit() + + parameters?.let { + statisticDetailsFragment.setData(it.stat, it.computableGroup, it.report, displayAggregationChoices) + parameters = null + } + + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/ComparisonChartPagerAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/ComparisonChartPagerAdapter.kt new file mode 100644 index 00000000..2b900480 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/ComparisonChartPagerAdapter.kt @@ -0,0 +1,69 @@ +package net.pokeranalytics.android.ui.adapter + +import android.content.Context +import android.util.SparseArray +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentStatePagerAdapter +import net.pokeranalytics.android.R +import net.pokeranalytics.android.ui.fragment.CalendarFragment +import net.pokeranalytics.android.ui.fragment.GraphFragment +import net.pokeranalytics.android.ui.fragment.HistoryFragment +import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment +import java.lang.ref.WeakReference + +/** + * Comparison Chart Pager Adapter + */ +class ComparisonChartPagerAdapter(val context: Context, fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) { + + var weakReferences = SparseArray>() + + override fun getItem(position: Int): PokerAnalyticsFragment { + return when (position) { + 0 -> GraphFragment() + 1 -> GraphFragment() + 2 -> CalendarFragment.newInstance() + else -> HistoryFragment.newInstance() + } + } + + override fun getCount(): Int { + return 3 + } + + override fun getPageTitle(position: Int): CharSequence? { + return when(position) { + 0 -> context.getString(R.string.bar) + 1 -> context.getString(R.string.line) + 2-> context.getString(R.string.table) + else -> super.getPageTitle(position) + } + } + + override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { + super.destroyItem(container, position, `object`) + weakReferences.remove(position) + } + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val fragment = super.instantiateItem(container, position) as PokerAnalyticsFragment + weakReferences.put(position, WeakReference(fragment)) + return fragment + } + + override fun getItemPosition(obj: Any): Int { + return POSITION_UNCHANGED + } + + /** + * Return the fragment at the position key + */ + fun getFragment(key: Int): PokerAnalyticsFragment? { + if (weakReferences.get(key) != null) { + return weakReferences.get(key).get() + } + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/HistorySessionRowRepresentableAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/HistorySessionRowRepresentableAdapter.kt index d80db269..6e602693 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/HistorySessionRowRepresentableAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/HistorySessionRowRepresentableAdapter.kt @@ -160,9 +160,9 @@ class HistorySessionRowRepresentableAdapter( // Add headers if the date doesn't exist yet for ((index, session) in realmResults.withIndex()) { - calendar.time = session.creationDate + calendar.time = session.startDate ?: session.creationDate if (checkHeaderCondition(calendar, previousYear, previousMonth)) { - headersPositions[index + headersPositions.size + pendingRealmResults.size] = session.creationDate + headersPositions[index + headersPositions.size + pendingRealmResults.size] = session.startDate ?: session.creationDate previousYear = calendar.get(Calendar.YEAR) previousMonth = calendar.get(Calendar.MONTH) } 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 17a16d3a..f749a4db 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 @@ -4,9 +4,7 @@ import android.util.SparseArray import android.view.ViewGroup import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentStatePagerAdapter -import net.pokeranalytics.android.ui.fragment.HistoryFragment -import net.pokeranalytics.android.ui.fragment.SettingsFragment -import net.pokeranalytics.android.ui.fragment.StatsFragment +import net.pokeranalytics.android.ui.fragment.* import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import java.lang.ref.WeakReference @@ -20,14 +18,16 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda override fun getItem(position: Int): PokerAnalyticsFragment { return when (position) { 0 -> HistoryFragment.newInstance() - 1 -> StatsFragment.newInstance() - 2 -> SettingsFragment.newInstance() + 1 -> StatisticsFragment.newInstance() + 2 -> CalendarFragment.newInstance() + 3 -> ReportsFragment.newInstance() + 4 -> MoreFragment.newInstance() else -> HistoryFragment.newInstance() } } override fun getCount(): Int { - return 3 + return 5 } override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { @@ -42,11 +42,12 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda } override fun getItemPosition(obj: Any): Int { - val fragment = obj as PokerAnalyticsFragment - return when (fragment) { + return when (obj) { HistoryFragment::class.java -> 0 - StatsFragment::class.java -> 1 - SettingsFragment::class.java -> 2 + StatisticsFragment::class.java -> 1 + CalendarFragment::class.java -> 2 + ReportsFragment::class.java -> 3 + MoreFragment::class.java -> 4 else -> -1 } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/ReportPagerAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/ReportPagerAdapter.kt new file mode 100644 index 00000000..37440c01 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/ReportPagerAdapter.kt @@ -0,0 +1,78 @@ +package net.pokeranalytics.android.ui.adapter + +import android.content.Context +import android.util.SparseArray +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.viewpager.widget.PagerAdapter +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.Report +import net.pokeranalytics.android.ui.fragment.GraphFragment +import net.pokeranalytics.android.ui.fragment.TableReportFragment +import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment +import java.lang.ref.WeakReference + +/** + * Home Adapter + */ +class ReportPagerAdapter(val context: Context, val fragmentManager: FragmentManager, private val report: Report) : FragmentStatePagerAdapter(fragmentManager) { + + var weakReferences = SparseArray>() + + override fun getItem(position: Int): PokerAnalyticsFragment { + return when (position) { + 0 -> { + val dataSetList = listOf(report.barEntries(null, context)) + GraphFragment.newInstance(barDataSets = dataSetList, style = GraphFragment.Style.BAR) + } + 1 -> { + val dataSetList = report.multiLineEntries(context = context) + GraphFragment.newInstance(lineDataSets = dataSetList, style = GraphFragment.Style.MULTILINE) + } + 2 -> { + TableReportFragment.newInstance(report) + } + else -> PokerAnalyticsFragment() + } + } + + override fun getCount(): Int { + return 3 + } + + override fun getPageTitle(position: Int): CharSequence? { + return when(position) { + 0 -> context.getString(R.string.bar) + 1 -> context.getString(R.string.line) + 2 -> context.getString(R.string.table) + else -> "" + } + } + + override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { + super.destroyItem(container, position, `object`) + weakReferences.remove(position) + } + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val fragment = super.instantiateItem(container, position) as PokerAnalyticsFragment + weakReferences.put(position, WeakReference(fragment)) + return fragment + } + + override fun getItemPosition(obj: Any): Int { + return PagerAdapter.POSITION_UNCHANGED + } + + /** + * Return the fragment at the position key + */ + fun getFragment(key: Int): PokerAnalyticsFragment? { + if (weakReferences.get(key) != null) { + return weakReferences.get(key).get() + } + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/extensions/ChipGroupExtension.kt b/app/src/main/java/net/pokeranalytics/android/ui/extensions/ChipGroupExtension.kt new file mode 100644 index 00000000..e860242e --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/extensions/ChipGroupExtension.kt @@ -0,0 +1,16 @@ +package net.pokeranalytics.android.ui.extensions + +import com.google.android.material.chip.ChipGroup + +class ChipGroupExtension { + + open class SingleSelectionOnCheckedListener : ChipGroup.OnCheckedChangeListener { + override fun onCheckedChanged(group: ChipGroup, checkedId: Int) { + for (i in 0 until group.childCount) { + val chip = group.getChildAt(i) + chip.isClickable = chip.id != group.checkedChipId + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/UIExtensions.kt b/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt similarity index 63% rename from app/src/main/java/net/pokeranalytics/android/util/extensions/UIExtensions.kt rename to app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt index 06f89e3b..10647782 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/extensions/UIExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt @@ -1,20 +1,30 @@ -package net.pokeranalytics.android.util.extensions +package net.pokeranalytics.android.ui.extensions import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.content.res.Resources import android.net.Uri +import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.AppCompatTextView import androidx.browser.customtabs.CustomTabsIntent import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import androidx.core.view.isVisible import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.TextFormat import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.util.DeviceUtils import net.pokeranalytics.android.util.URL +import java.io.File + + + + // Sizes @@ -59,20 +69,33 @@ fun PokerAnalyticsActivity.openPlayStorePage() { } // Open email for "Contact us" -fun PokerAnalyticsActivity.openContactMail(subjectStringRes: Int) { +fun PokerAnalyticsActivity.openContactMail(subjectStringRes: Int, filePath: String?= null) { val info = "v${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE}), Android ${android.os.Build.VERSION.SDK_INT}, ${DeviceUtils.getDeviceName()}" - val intent = Intent(Intent.ACTION_SENDTO) - intent.data = Uri.parse("mailto:${URL.SUPPORT_EMAIL.value}") - intent.putExtra(Intent.EXTRA_SUBJECT, getString(subjectStringRes)) - intent.putExtra(Intent.EXTRA_EMAIL, URL.SUPPORT_EMAIL.value) - intent.putExtra(Intent.EXTRA_TEXT, "\n\n$info") - startActivity(Intent.createChooser(intent, getString(R.string.contact))) + + val emailIntent = Intent(Intent.ACTION_SEND) + + filePath?.let { + val databaseFile = File(it) + val contentUri = FileProvider.getUriForFile(this, "net.pokeranalytics.android.fileprovider", databaseFile) + if (contentUri != null) { + emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + emailIntent.setDataAndType(contentUri, contentResolver.getType(contentUri)) + emailIntent.putExtra(Intent.EXTRA_STREAM, contentUri) + } + } + + emailIntent.type = "message/rfc822" + emailIntent.putExtra(Intent.EXTRA_SUBJECT, getString(subjectStringRes)) + emailIntent.putExtra(Intent.EXTRA_TEXT, "\n\n$info") + emailIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf(URL.SUPPORT_EMAIL.value)) + + startActivity(Intent.createChooser(emailIntent, getString(R.string.contact))) } // Open custom tab fun PokerAnalyticsActivity.openUrl(url: String) { val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder() - builder.setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary)) + builder.setToolbarColor(ContextCompat.getColor(this, net.pokeranalytics.android.R.color.colorPrimary)) val customTabsIntent = builder.build() customTabsIntent.launchUrl(this, Uri.parse(url)) } @@ -98,6 +121,23 @@ fun showAlertDialog(context: Context, title: Int? = null, message: Int? = null) message?.let { builder.setMessage(message) } - builder.setPositiveButton(R.string.ok, null) + builder.setPositiveButton(net.pokeranalytics.android.R.string.ok, null) builder.show() +} + +fun AppCompatTextView.setTextFormat(textFormat: TextFormat, context: Context) { + this.setTextColor(textFormat.getColor(context)) + this.text = textFormat.text +} + +fun View.hideWithAnimation() { + isVisible = true + animate().cancel() + animate().alpha(0f).withEndAction { isVisible = false }.start() +} + +fun View.showWithAnimation() { + isVisible = true + animate().cancel() + animate().alpha(1f).start() } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt index 675e198f..7f15c747 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt @@ -17,9 +17,10 @@ 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.CurrencyUtils import net.pokeranalytics.android.util.NULL_TEXT -import net.pokeranalytics.android.util.Preferences +import net.pokeranalytics.android.util.UserDefaults +import net.pokeranalytics.android.util.extensions.toCurrency +import net.pokeranalytics.android.util.extensions.toRate import retrofit2.Call import retrofit2.Response import java.util.* @@ -94,9 +95,12 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS NULL_TEXT } } + BankrollRow.INITIAL_VALUE -> { + this.bankroll.initialValue.toCurrency() + } BankrollRow.RATE -> { val rate = this.bankroll.currency?.rate ?: 1.0 - CurrencyUtils.getCurrencyRateFormatter().format(rate) + rate.toRate() } else -> super.stringForRow(row) } @@ -113,6 +117,9 @@ 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)) @@ -145,7 +152,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS */ private fun initData() { - defaultCurrency = Currency.getInstance(Preferences.getCurrencyLocale(this.parentActivity)) + defaultCurrency = UserDefaults.currency if (!isUpdating) { bankroll.currency = net.pokeranalytics.android.model.realm.Currency() @@ -162,6 +169,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS rows.clear() rows.add(SimpleRow.NAME) rows.add(BankrollRow.LIVE) + rows.add(BankrollRow.INITIAL_VALUE) rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency)) rows.add(BankrollRow.CURRENCY) if (this.shouldShowCurrencyRate) { 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 new file mode 100644 index 00000000..b0c06521 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt @@ -0,0 +1,73 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.android.synthetic.main.fragment_bankroll.* +import kotlinx.android.synthetic.main.fragment_stats.recyclerView +import net.pokeranalytics.android.R +import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity +import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment + +class BankrollFragment : PokerAnalyticsFragment() { + + companion object { + + /** + * Create new instance + */ + fun newInstance(): BankrollFragment { + val fragment = BankrollFragment() + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + } + + private lateinit var parentActivity: PokerAnalyticsActivity + + // Life Cycle + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_bankroll, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + } + + + // Business + + /** + * Init data + */ + private fun initData() { + + } + + /** + * Init UI + */ + private fun initUI() { + + parentActivity = activity as PokerAnalyticsActivity + + parentActivity.setSupportActionBar(toolbar) + parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) + setHasOptionsMenu(true) + + val viewManager = LinearLayoutManager(requireContext()) + + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + //adapter = statsAdapter + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarDetailsFragment.kt new file mode 100644 index 00000000..f1f89ee5 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarDetailsFragment.kt @@ -0,0 +1,224 @@ +package net.pokeranalytics.android.ui.fragment + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.tabs.TabLayout +import io.realm.Realm +import kotlinx.android.synthetic.main.fragment_calendar_details.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.Calculator +import net.pokeranalytics.android.calculus.ComputedResults +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.model.Criteria +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.ui.activity.StatisticDetailsActivity +import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity +import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter +import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate +import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource +import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow +import net.pokeranalytics.android.ui.view.rowrepresentable.StatDoubleRow +import timber.log.Timber +import java.util.* +import kotlin.collections.ArrayList + +class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { + + companion object { + fun newInstance(context: Context) { + val intent = Intent(context, CalendarDetailsFragment::class.java) + context.startActivity(intent) + } + } + + private lateinit var parentActivity: PokerAnalyticsActivity + private lateinit var statsAdapter: RowRepresentableAdapter + + private var title: String? = "" + private var computedResults: ComputedResults? = null + private var sessionTypeCondition: QueryCondition? = null + private var rowRepresentables: ArrayList = ArrayList() + + //private var stat: Stat = Stat.NET_RESULT + //private var entries: List = ArrayList() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_calendar_details, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initUI() + } + + override fun adapterRows(): List? { + return rowRepresentables + } + + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { + when (row) { + is GraphRow -> { + //TODO: Open graph details + row.report.results.firstOrNull()?.group?.let { computableGroup -> + StatisticDetailsActivity.newInstance(requireContext(), row.stat, computableGroup, row.report, false) + } + } + } + } + + /** + * Init UI + */ + private fun initUI() { + + parentActivity = activity as PokerAnalyticsActivity + + // Avoid a bug during setting the title + toolbar.title = "" + + parentActivity.setSupportActionBar(toolbar) + parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) + setHasOptionsMenu(true) + + + tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + when (tab.position) { + 0 -> sessionTypeCondition = null + 1 -> sessionTypeCondition = QueryCondition.IsCash + 2 -> sessionTypeCondition = QueryCondition.IsTournament + } + launchStatComputation() + } + + override fun onTabUnselected(tab: TabLayout.Tab) { + } + + override fun onTabReselected(tab: TabLayout.Tab) { + } + }) + + statsAdapter = RowRepresentableAdapter(this, this) + + val viewManager = LinearLayoutManager(requireContext()) + + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + adapter = statsAdapter + } + + } + + /** + * Display data + */ + private fun displayData() { + + title?.let { + toolbar.title = it + } + + sessionTypeCondition?.let { + when (it) { + QueryCondition.IsCash -> tabs.getTabAt(1)?.select() + QueryCondition.IsTournament -> tabs.getTabAt(2)?.select() + else -> tabs.getTabAt(0)?.select() + } + } + } + + + /** + * Launch stat computation + */ + private fun launchStatComputation() { + + progressBar.isVisible = true + progressBar.animate().alpha(1f).start() + recyclerView.animate().alpha(0f).start() + + computedResults?.let { computedResults -> + + GlobalScope.launch { + + val startDate = Date() + + val realm = Realm.getDefaultInstance() + val conditions = ArrayList().apply { + addAll(computedResults.group.conditions) + + // Remove session type conditions + removeAll(Criteria.Cash.queryConditions) + removeAll(Criteria.Tournament.queryConditions) + + when (sessionTypeCondition) { + QueryCondition.IsCash -> addAll(Criteria.Cash.queryConditions) + QueryCondition.IsTournament -> addAll(Criteria.Tournament.queryConditions) + } + } + + val requiredStats: List = listOf(Stat.LOCATIONS_PLAYED, Stat.LONGEST_STREAKS, Stat.DAYS_PLAYED, Stat.STANDARD_DEVIATION_HOURLY) + val options = Calculator.Options(evolutionValues = Calculator.Options.EvolutionValues.STANDARD, stats = requiredStats) + val report = Calculator.computeStatsWithComparators(realm, conditions = conditions, options = options) + + Timber.d("Report take: ${System.currentTimeMillis() - startDate.time}ms") + + report.results.firstOrNull()?.let { + + // Create rows + + rowRepresentables.clear() + rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.net_result)) + rowRepresentables.add(GraphRow(report, Stat.NET_RESULT)) + rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.NET_RESULT), it.computedStat(Stat.HOURLY_RATE))) + rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.LOCATIONS_PLAYED), it.computedStat(Stat.LONGEST_STREAKS))) + rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.distribution)) + rowRepresentables.add(GraphRow(report, Stat.STANDARD_DEVIATION)) + rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.WIN_RATIO), it.computedStat(Stat.MAXIMUM_NETRESULT))) + rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.volume)) + rowRepresentables.add(GraphRow(report, Stat.HOURLY_DURATION)) + rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.HOURLY_DURATION), it.computedStat(Stat.AVERAGE_HOURLY_DURATION))) + rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.DAYS_PLAYED), it.computedStat(Stat.MAXIMUM_DURATION))) + } + + + launch(Dispatchers.Main) { + statsAdapter.notifyDataSetChanged() + progressBar.animate().cancel() + progressBar.animate().alpha(0f).withEndAction { progressBar.isVisible = false }.start() + recyclerView.animate().cancel() + recyclerView.animate().alpha(1f).start() + } + } + } + } + + /** + * Set data + */ + fun setData(computedResults: ComputedResults?, sessionTypeCondition: QueryCondition?, title: String?) { + + this.computedResults = computedResults + this.sessionTypeCondition = sessionTypeCondition + this.title = title + + displayData() + launchStatComputation() + + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..e5e54964 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt @@ -0,0 +1,388 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.tabs.TabLayout +import io.realm.Realm +import kotlinx.android.synthetic.main.fragment_calendar.* +import kotlinx.android.synthetic.main.fragment_stats.recyclerView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import net.pokeranalytics.android.calculus.Calculator +import net.pokeranalytics.android.calculus.ComputedResults +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.model.Criteria +import net.pokeranalytics.android.model.combined +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.ui.activity.CalendarDetailsActivity +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.hideWithAnimation +import net.pokeranalytics.android.ui.extensions.showWithAnimation +import net.pokeranalytics.android.ui.fragment.components.SessionObserverFragment +import net.pokeranalytics.android.ui.view.CalendarTabs +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.* +import timber.log.Timber +import java.util.* +import kotlin.coroutines.CoroutineContext + + +class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRepresentableDataSource, RowRepresentableDelegate { + + enum class TimeFilter { + MONTH, YEAR + } + + companion object { + + /** + * Create new instance + */ + fun newInstance(): CalendarFragment { + val fragment = CalendarFragment() + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + } + + private lateinit var calendarAdapter: RowRepresentableAdapter + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + + + private var rows: ArrayList = ArrayList() + private var sortedMonthlyReports: SortedMap = HashMap().toSortedMap() + private var sortedYearlyReports: SortedMap = HashMap().toSortedMap() + private var datesForRows: HashMap = HashMap() + + private var sessionTypeCondition: QueryCondition? = null + private var currentTimeFilter: TimeFilter = TimeFilter.MONTH + private var currentStat = Stat.NET_RESULT + + // Life Cycle + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(net.pokeranalytics.android.R.layout.fragment_calendar, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + + launchStatComputation() + } + + override fun adapterRows(): List? { + return rows + } + + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { + + when (currentTimeFilter) { + TimeFilter.MONTH -> { + val date = datesForRows[row] + sortedMonthlyReports[datesForRows[row]]?.let { + CalendarDetailsActivity.newInstance(requireContext(), it, sessionTypeCondition, date?.getMonthAndYear()) + } + } + TimeFilter.YEAR -> { + val date = datesForRows[row] + sortedYearlyReports[datesForRows[row]]?.let { + CalendarDetailsActivity.newInstance(requireContext(), it, sessionTypeCondition, date?.getDateYear()) + } + } + } + } + + override fun sessionsChanged() { + launchStatComputation() + } + + // Business + + /** + * Init data + */ + private fun initData() { + } + + /** + * Init UI + */ + private fun initUI() { + + CalendarTabs.values().forEach { + val tab = tabs.newTab() + tab.text = getString(it.resId) + tabs.addTab(tab) + } + + tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + when (tab.position) { + 0 -> currentStat = Stat.NET_RESULT + 1 -> currentStat = Stat.HOURLY_RATE + 2 -> currentStat = Stat.NUMBER_OF_GAMES + 3 -> currentStat = Stat.WIN_RATIO + 4 -> currentStat = Stat.STANDARD_DEVIATION_HOURLY + 5 -> currentStat = Stat.AVERAGE + 6 -> currentStat = Stat.AVERAGE_HOURLY_DURATION + 7 -> currentStat = Stat.HOURLY_DURATION + } + displayData() + } + + override fun onTabUnselected(tab: TabLayout.Tab) { + } + + override fun onTabReselected(tab: TabLayout.Tab) { + } + }) + + // Manage session type queryWith + filterSessionAll.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + sessionTypeCondition = null + filterSessionCash.isChecked = false + filterSessionTournament.isChecked = false + launchStatComputation() + } else if (sessionTypeCondition == null) { + filterSessionAll.isChecked = true + } + } + filterSessionCash.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + sessionTypeCondition = QueryCondition.IsCash + filterSessionAll.isChecked = false + filterSessionTournament.isChecked = false + launchStatComputation() + } else if (sessionTypeCondition == QueryCondition.IsCash) { + filterSessionCash.isChecked = true + } + } + filterSessionTournament.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + sessionTypeCondition = QueryCondition.IsTournament + filterSessionAll.isChecked = false + filterSessionCash.isChecked = false + launchStatComputation() + } else if (sessionTypeCondition == QueryCondition.IsTournament) { + filterSessionTournament.isChecked = true + } + } + + // Manage time queryWith + filterTimeMonth.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + currentTimeFilter = TimeFilter.MONTH + filterTimeYear.isChecked = false + displayData() + } else if (currentTimeFilter == TimeFilter.MONTH) { + filterTimeMonth.isChecked = true + } + + } + filterTimeYear.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + currentTimeFilter = TimeFilter.YEAR + filterTimeMonth.isChecked = false + displayData() + } else if (currentTimeFilter == TimeFilter.YEAR) { + filterTimeYear.isChecked = true + } + } + + val viewManager = LinearLayoutManager(requireContext()) + + calendarAdapter = RowRepresentableAdapter(this, this) + + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + adapter = calendarAdapter + } + } + + + /** + * Launch stat computation + */ + private fun launchStatComputation() { + + progressBar?.showWithAnimation() + recyclerView?.hideWithAnimation() + + GlobalScope.launch { + + val calendar = Calendar.getInstance() + calendar.time = Date().startOfMonth() + + val startDate = Date() + val realm = Realm.getDefaultInstance() + + val monthlyReports: HashMap = HashMap() + val yearlyReports: HashMap = HashMap() + + val requiredStats: List = listOf(Stat.LOCATIONS_PLAYED, Stat.LONGEST_STREAKS, Stat.DAYS_PLAYED, Stat.STANDARD_DEVIATION_HOURLY ) + val options = Calculator.Options(evolutionValues = Calculator.Options.EvolutionValues.STANDARD, stats = requiredStats) + + // Compute data per AnyYear and AnyMonthOfYear + + println(">>>> ${Criteria.MonthsOfYear.queryConditions.map { it.id }}") + + val monthConditions = when (sessionTypeCondition) { + QueryCondition.IsCash -> listOf(Criteria.Years, Criteria.MonthsOfYear, Criteria.Cash).combined() + QueryCondition.IsTournament -> listOf(Criteria.Years, Criteria.MonthsOfYear, Criteria.Tournament).combined() + else -> listOf(Criteria.Years, Criteria.MonthsOfYear).combined() + } + + monthConditions.forEach { conditions -> + + val report = Calculator.computeStatsWithComparators(realm, conditions = conditions, options = options) + report.results.forEach { computedResults -> + if (!computedResults.isEmpty) { + // Set date data + conditions.forEach { condition -> + when (condition) { + is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first()) + is QueryCondition.AnyMonthOfYear -> calendar.set(Calendar.MONTH, condition.listOfValues.first()) + } + } + + monthlyReports[calendar.time] = computedResults + } + } + } + + calendar.time = Date().startOfYear() + + // Compute data per AnyYear + val yearConditions = when (sessionTypeCondition) { + QueryCondition.IsCash -> listOf(Criteria.Years, Criteria.Cash).combined() + QueryCondition.IsTournament -> listOf(Criteria.Years, Criteria.Tournament).combined() + else -> listOf(Criteria.Years).combined() + } + + yearConditions.forEach { conditions -> + val report = Calculator.computeStatsWithComparators(realm, conditions = conditions, options = options) + report.results.forEach { computedResults -> + if (!computedResults.isEmpty) { + // Set date data + conditions.forEach { condition -> + when (condition) { + is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first()) + } + } + yearlyReports[calendar.time] = computedResults + } + } + } + + sortedMonthlyReports = monthlyReports.toSortedMap(compareByDescending { it }) + sortedYearlyReports = yearlyReports.toSortedMap(compareByDescending { it }) + + Timber.d("Computation: ${System.currentTimeMillis() - startDate.time}ms") + + // Logs + /* + Timber.d("========== AnyYear x AnyMonthOfYear") + sortedMonthlyReports.keys.forEach { + Timber.d("$it => ${sortedMonthlyReports[it]?.computedStat(Stat.NET_RESULT)?.value}") + } + + Timber.d("========== YEARLY") + sortedYearlyReports.keys.forEach { + Timber.d("$it => ${sortedYearlyReports[it]?.computedStat(Stat.NET_RESULT)?.value}") + } + */ + + realm.close() + + GlobalScope.launch(Dispatchers.Main) { + displayData() + } + } + } + + /** + * Display data + */ + private fun displayData() { + + val startDate = Date() + + datesForRows.clear() + rows.clear() + + when (currentTimeFilter) { + + // Create monthly reports + TimeFilter.MONTH -> { + val years: ArrayList = ArrayList() + sortedMonthlyReports.keys.forEach { date -> + if (!years.contains(date.getDateYear())) { + years.add(date.getDateYear()) + rows.add( + CustomizableRowRepresentable( + customViewType = RowViewType.HEADER_TITLE, + title = date.getDateYear() + ) + ) + } + + sortedMonthlyReports[date]?.computedStat(currentStat)?.let { computedStat -> + + val row = CustomizableRowRepresentable( + customViewType = RowViewType.TITLE_VALUE_ARROW, + title = date.getDateMonth(), + computedStat = computedStat, + isSelectable = true + ) + + rows.add(row) + datesForRows.put(row, date) + } + } + } + + // Create yearly reports + TimeFilter.YEAR -> { + sortedYearlyReports.keys.forEach { date -> + sortedYearlyReports[date]?.computedStat(currentStat)?.let { computedStat -> + val row = CustomizableRowRepresentable( + customViewType = RowViewType.TITLE_VALUE_ARROW, + title = date.getDateYear(), + computedStat = computedStat, + isSelectable = true + ) + + rows.add(row) + datesForRows.put(row, date) + } + } + } + } + + Timber.d("Display data: ${System.currentTimeMillis() - startDate.time}ms") + Timber.d("Rows: ${rows.size}") + + calendarAdapter.notifyDataSetChanged() + + progressBar?.hideWithAnimation() + recyclerView?.showWithAnimation() + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ComparisonChartFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ComparisonChartFragment.kt new file mode 100644 index 00000000..5ae08654 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ComparisonChartFragment.kt @@ -0,0 +1,123 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.* +import kotlinx.android.synthetic.main.fragment_comparison_chart.* +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.components.PokerAnalyticsActivity +import net.pokeranalytics.android.ui.adapter.ComparisonChartPagerAdapter +import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate +import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource +import net.pokeranalytics.android.ui.extensions.toast +import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.MoreTabRow + +class ComparisonChartFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { + + companion object { + + /** + * Create new instance + */ + fun newInstance(): ComparisonChartFragment { + val fragment = ComparisonChartFragment() + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + + val rowRepresentation: List by lazy { + val rows = ArrayList() + rows.addAll(MoreTabRow.values()) + rows + } + + } + + private lateinit var parentActivity: PokerAnalyticsActivity + private lateinit var viewPagerAdapter: ComparisonChartPagerAdapter + private var comparisonChartMenu: Menu? = null + + + // Life Cycle + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_comparison_chart, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + } + + override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { + menu?.clear() + inflater?.inflate(R.menu.toolbar_comparison_chart, menu) + this.comparisonChartMenu = menu + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item!!.itemId) { + R.id.settings -> openChangeStatistics() + } + return true + } + + // Rows + override fun adapterRows(): List? { + return rowRepresentation + } + + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { + super.onRowSelected(position, row, fromAction) + when(row) { + MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext()) + MoreTabRow.SETTINGS -> SettingsActivity.newInstance(requireContext()) + } + } + + // Business + + /** + * Init data + */ + private fun initData() { + } + + /** + * Init UI + */ + private fun initUI() { + + parentActivity = activity as PokerAnalyticsActivity + + toolbar.title = "" + + parentActivity.setSupportActionBar(toolbar) + parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) + setHasOptionsMenu(true) + + toolbar.title = "Comparison chart" + + viewPagerAdapter = ComparisonChartPagerAdapter(requireContext(), parentActivity.supportFragmentManager) + viewPager.adapter = viewPagerAdapter + viewPager.offscreenPageLimit = 2 + + tabs.setupWithViewPager(viewPager) + + } + + /** + * Open change statistics + */ + private fun openChangeStatistics() { + //TODO + toast("Open change statistics") + } + +} \ No newline at end of file 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 1ff8fee5..76d552e3 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 @@ -16,8 +16,7 @@ import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRowRepresentable -import net.pokeranalytics.android.util.Preferences +import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow import java.util.* class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { @@ -29,7 +28,7 @@ class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataS val rowRepresentation : List by lazy { val rows = ArrayList() rows.addAll(mostUsedCurrencies) - rows.add(SeparatorRowRepresentable()) + rows.add(SeparatorRow()) rows.addAll(availableCurrencies) rows } @@ -69,7 +68,7 @@ class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataS } var currencyCode: String = currency.currencyCode - var currencySymbole: String = currency.getSymbol(Preferences.currencyLocale) + var currencySymbole: String = currency.getSymbol(Locale.getDefault()) var currencyCodeAndSymbol: String = "${this.currencyCode} (${this.currencySymbole})" override val viewType: Int = RowViewType.TITLE_VALUE.ordinal diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt index 44e8b118..ba10376a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt @@ -52,7 +52,8 @@ open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDele } override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { - inflater?.inflate(R.menu.editable_data, menu) + menu?.clear() + inflater?.inflate(R.menu.toolbar_editable_data, menu) this.editableMenu = menu updateMenuUI() super.onCreateOptionsMenu(menu, inflater) @@ -158,12 +159,6 @@ open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDele finishActivityWithResult(uniqueIdentifier) } -// if (managedItem is Bankroll) { -// managedItem.currency?.refreshRelatedRatedValues(it) -// } -// -// val uniqueIdentifier = (managedItem as Savable).id -// finishActivityWithResult(uniqueIdentifier) } } else -> { 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 042ecb95..0233ea82 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 @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.fragment_filter_details.* import kotlinx.android.synthetic.main.fragment_filter_details.view.* import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.ui.activity.FilterDetailsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity @@ -27,24 +28,25 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.extensions.shortDate +import net.pokeranalytics.android.util.extensions.shortTime import net.pokeranalytics.android.util.extensions.toMinutes import timber.log.Timber import java.util.* import kotlin.collections.ArrayList -open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { +open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { lateinit var parentActivity: PokerAnalyticsActivity lateinit var rowRepresentableAdapter: RowRepresentableAdapter + private lateinit var primaryKey: String + private lateinit var filterCategoryRow: FilterCategoryRow private var currentFilter: Filter? = null private var rows: ArrayList = ArrayList() private var rowsForFilterSubcategoryRow: HashMap> = HashMap() - private var primaryKey: String? = null private var filterMenu: Menu? = null - private var filterCategoryRow: FilterCategoryRow? = null - private val selectedRows = ArrayList() + private val selectedRows = ArrayList() private var isUpdating = false private var shouldOpenKeyboard = true @@ -68,48 +70,33 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta Timber.d("Row: $row") + if (row.viewType == RowViewType.TITLE_CHECK.ordinal) { + updateRowsSelection(row) + return + } + when (row) { - is FilterElementRow.DateFilterElementRow -> DateTimePickerManager.create(requireContext(), row, this, row.dateValue, onlyDate = true) - is FilterElementRow.PastDays -> { - val pastDays = if (row.lastDays > 0) row.lastDays.toString() else "" - val data = row.editingDescriptors(mapOf("pastDays" to pastDays)) - BottomSheetFragment.create(fragmentManager, row, this, data, true) - } - is FilterElementRow.LastGames -> { - val lastGames = if (row.lastGames > 0) row.lastGames.toString() else "" - val data = row.editingDescriptors(mapOf("lastGames" to lastGames)) - BottomSheetFragment.create(fragmentManager, row, this, data, true) - } - is FilterElementRow.LastSessions -> { - val lastSessions = if (row.lastSessions > 0) row.lastSessions.toString() else "" - val data = row.editingDescriptors(mapOf("lastSessions" to lastSessions)) - BottomSheetFragment.create(fragmentManager, row, this, data, true) - } - is FilterElementRow.DurationFilterElement -> { + is QueryCondition.DateQuery -> DateTimePickerManager.create(requireContext(), row, this, row.singleValue, onlyDate = !row.showTime, onlyTime = row.showTime) + is QueryCondition.Duration -> { val hours = if (row.minutes / 60 > 0) (row.minutes / 60).toString() else "" val minutes = if (row.minutes % 60 > 0) (row.minutes % 60).toString() else "" val data = row.editingDescriptors(mapOf("hours" to hours, "minutes" to minutes)) BottomSheetFragment.create(fragmentManager, row, this, data, true) } - is FilterElementRow.AmountFilterElement -> { - val amount = if (row.amount > 0) row.amount.toString() else "" - val data = row.editingDescriptors(mapOf("amount" to amount)) - BottomSheetFragment.create(fragmentManager, row, this, data, true) - } - else -> { - updateRowsSelection(row) - } + + is QueryCondition.ListOfValues<*> -> { + val valueAsString = row.listOfValues.firstOrNull()?.toString() ?: "" + val data = row.editingDescriptors(mapOf("valueAsString" to valueAsString)) + BottomSheetFragment.create(fragmentManager, row, this, data, true) + } } } override fun stringForRow(row: RowRepresentable): String { return when (row) { - is FilterElementRow.PastDays -> if (row.lastDays > 0) row.lastDays.toString() else NULL_TEXT - is FilterElementRow.LastGames -> if (row.lastGames > 0) row.lastGames.toString() else NULL_TEXT - is FilterElementRow.LastSessions -> if (row.lastSessions > 0) row.lastSessions.toString() else NULL_TEXT - is FilterElementRow.DateFilterElementRow -> row.dateValue.shortDate() - is FilterElementRow.AmountFilterElement -> if (row.amount > 0) row.amount.toString() else NULL_TEXT - is FilterElementRow.DurationFilterElement -> row.minutes.toMinutes(requireContext()) + is QueryCondition.DateQuery -> if (row.showTime) row.singleValue.shortTime() else row.singleValue.shortDate() + is QueryCondition.Duration -> row.minutes.toMinutes(requireContext()) + is QueryCondition.ListOfValues<*> -> row.listOfValues.firstOrNull()?.toString() ?: NULL_TEXT else -> super.stringForRow(row) } } @@ -123,12 +110,8 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta Timber.d("onRowValueChanged: $row $value") when (row) { - is FilterElementRow.DateFilterElementRow -> row.dateValue = if (value != null && value is Date) value else Date() - is FilterElementRow.PastDays -> row.lastDays = if (value != null && value is String) value.toInt() else 0 - is FilterElementRow.LastGames -> row.lastGames = if (value != null && value is String) value.toInt() else 0 - is FilterElementRow.LastSessions -> row.lastSessions = if (value != null && value is String) value.toInt() else 0 - is FilterElementRow.AmountFilterElement -> row.amount = if (value != null && value is String) value.toDouble() else 0.0 - is FilterElementRow.DurationFilterElement -> { + is QueryCondition.DateQuery -> row.singleValue = if (value != null && value is Date) value else Date() + is QueryCondition.Duration -> { if (value is ArrayList<*>) { val hours = try { (value[0] as String? ?: "0").toInt() @@ -146,6 +129,9 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta row.minutes = 0 } } + is QueryCondition.SingleInt -> row.singleValue = if (value != null && value is String) value.toInt() else 0 + is QueryCondition.ListOfDouble-> row.listOfValues = arrayListOf(if (value != null && value is String) value.toDouble() else 0.0) + is QueryCondition.ListOfInt-> row.listOfValues = arrayListOf(if (value != null && value is String) value.toInt() else 0) } // Remove the row before updating the selected rows list @@ -187,28 +173,24 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta */ private fun initData() { - primaryKey?.let { - currentFilter = Filter.getFilterBydId(getRealm(), it) - } + currentFilter = Filter.getFilterBydId(getRealm(), primaryKey) - filterCategoryRow?.let { - this.appBar.toolbar.title = it.localizedTitle(requireContext()) + this.appBar.toolbar.title = filterCategoryRow.localizedTitle(requireContext()) this.rows.clear() this.rowsForFilterSubcategoryRow.clear() - this.rows.addAll(it.filterElements) + this.rows.addAll(filterCategoryRow.filterElements) this.rows.forEach { element -> - if (element is FilterElementRow && currentFilter?.contains(element) == true) { - currentFilter?.setSavedValueForElement(element) + if (element is QueryCondition && currentFilter?.contains(element) == true) { + currentFilter?.loadValueForElement(element) this.selectedRows.add(element) } } this.rowRepresentableAdapter = RowRepresentableAdapter(this, this) this.recyclerView.adapter = rowRepresentableAdapter - } } /** @@ -229,10 +211,12 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta rowRepresentableAdapter.refreshRow(it) } } - selectedRows.add(row) + selectedRows.add(row as QueryCondition) } } + println("list of selected rows : $selectedRows") + // Update UI rowRepresentableAdapter.refreshRow(row) } @@ -242,13 +226,14 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta */ private fun saveData() { //TODO: Save currentFilter details data - Timber.d("Save data for filter: ${currentFilter?.id}") - selectedRows?.forEach { + Timber.d("Save data for queryWith: ${currentFilter?.id}") + selectedRows.forEach { Timber.d("Selected rows: $it") } val realm = getRealm() realm.beginTransaction() + currentFilter?.remove(filterCategoryRow) currentFilter?.createOrUpdateFilterConditions(selectedRows) realm.commitTransaction() 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 8a9915a4..499b52aa 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 @@ -60,7 +60,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat Timber.d("onActivityResult: $requestCode") if (data != null && data.hasExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)) { val filterId = data.getStringExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName) - Timber.d("Updated filter: ${filterId}") + Timber.d("Updated queryWith: ${filterId}") } */ @@ -71,7 +71,8 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat } override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { - inflater?.inflate(R.menu.editable_data, menu) + menu?.clear() + inflater?.inflate(R.menu.toolbar_editable_data, menu) this.filterMenu = menu updateMenuUI() super.onCreateOptionsMenu(menu, inflater) @@ -179,19 +180,19 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat } /** - * Valid the updates of the filter + * Valid the updates of the queryWith */ private fun validUpdates() { - Timber.d("Valid filter updates") + Timber.d("Valid queryWith updates") val filterId = currentFilter?.id ?: "" finishActivityWithResult(filterId) } /** - * Cancel the latest updates of the filter + * Cancel the latest updates of the queryWith */ private fun cancelUpdates() { - Timber.d("Cancel filter updates") + Timber.d("Cancel queryWith updates") val filterId = filterCopy?.id ?: "" @@ -208,7 +209,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat * Delete data */ private fun deleteFilter() { - Timber.d("Delete filter") + Timber.d("Delete queryWith") val realm = getRealm() realm.beginTransaction() currentFilter?.deleteFromRealm() 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 a126610a..0ae4886d 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 @@ -4,38 +4,63 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.github.mikephil.charting.data.Entry -import com.github.mikephil.charting.data.LineData -import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.charts.BarLineChartBase +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.data.* import com.github.mikephil.charting.highlight.Highlight import com.github.mikephil.charting.listener.OnChartValueSelectedListener import kotlinx.android.synthetic.main.fragment_graph.* import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.ObjectIdentifier import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment +import net.pokeranalytics.android.ui.graph.AxisFormatting +import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry import net.pokeranalytics.android.ui.graph.setStyle +import net.pokeranalytics.android.ui.view.LegendView +import net.pokeranalytics.android.ui.view.MultiLineLegendView +import timber.log.Timber -interface GraphDataSource { - - -} class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { - lateinit var dataSource: GraphDataSource - - lateinit var stat: Stat - lateinit var entries: List + enum class Style { + LINE, + BAR, + MULTILINE, + } companion object { + /** + * Create new instance + */ + fun newInstance(lineDataSets: List? = null, barDataSets: List? = null, style: Style = Style.LINE): GraphFragment { + val fragment = GraphFragment() + fragment.style = style + fragment.lineDataSetList = lineDataSets + fragment.barDataSetList = barDataSets + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } } - fun setData(stat: Stat, entries: List) { - this.stat = stat - this.entries = entries - } + private lateinit var parentActivity: PokerAnalyticsActivity + + private var style: Style = Style.LINE + private lateinit var legendView: LegendView + + private var lineDataSetList: List? = null + private var barDataSetList: List? = null + + private var chartView: BarLineChartBase<*>? = null + + private var stat: Stat = Stat.NET_RESULT + private var axisFormatting: AxisFormatting = AxisFormatting.DEFAULT override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_graph, container, false) @@ -44,54 +69,144 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initUI() + loadGraph() } - private fun initUI() { - - val dataSet = LineDataSet(this.entries, this.stat.name) - val colors = arrayOf(R.color.green_light).toIntArray() - dataSet.setColors(colors, context) - val lineData = LineData(listOf(dataSet)) + /** + * Set data + */ + fun setLineData(lineDataSets: List, stat: Stat, axisFormatting: AxisFormatting = AxisFormatting.DEFAULT) { + this.lineDataSetList = lineDataSets + this.stat = stat + this.axisFormatting = axisFormatting - this.chart.setStyle() + if (isAdded && !isDetached) { + loadGraph() + } + } - this.chart.data = lineData - this.chart.setOnChartValueSelectedListener(this) + fun setBarData(barDataSets: List, stat: Stat, axisFormatting: AxisFormatting = AxisFormatting.DEFAULT) { + this.barDataSetList = barDataSets + this.stat = stat + this.axisFormatting = axisFormatting + if (isAdded && !isDetached) { + loadGraph() + } } - // OnChartValueSelectedListener + /** + * Init UI + */ + private fun initUI() { - override fun onNothingSelected() { - // nothing to do + parentActivity = activity as PokerAnalyticsActivity + parentActivity.title = stat.localizedTitle(requireContext()) + + this.legendView = when (this.style) { + Style.MULTILINE -> MultiLineLegendView(requireContext()) + else -> LegendView(requireContext()) + } + + this.legendContainer.addView(this.legendView) } - override fun onValueSelected(e: Entry?, h: Highlight?) { + /** + * Load graph + */ + private fun loadGraph() { - e?.let { entry -> - h?.let { highlight -> + Timber.d("loadGraph") - val id = entry.data as String - val item = getRealm().where(this.stat.underlyingClass).equalTo("id", id).findAll().firstOrNull() - item?.let { + this.chartContainer.removeAllViews() - val date = it.startDate() + var lastEntry: Entry? = null + var groupName: String? = null - val entryStatName = this.stat.localizedTitle(requireContext()) - val entryValue = it.formattedValue(this.stat, requireContext()) + this.lineDataSetList?.let { dataSets -> - val totalStatName = this.stat.cumulativeLabelResId(requireContext()) - val totalStatValue = this.stat.format(e.y.toDouble(), null, requireContext()) + val lineChart = LineChart(context) + val lineData = LineData(dataSets) + lineChart.data = lineData - } + this.chartView = lineChart - this.text.text = "" + dataSets.firstOrNull()?.let { + this.legendView.prepareWithStat(this.stat, it.entryCount, this.style) + lastEntry = it.getEntryForIndex(it.entryCount - 1) + groupName = it.label + } + } + this.barDataSetList?.let { dataSets -> + this.legendView.prepareWithStat(this.stat, style = this.style) + val barChart = BarChart(context) + if (stat.showXAxisZero) { + barChart.xAxis.axisMinimum = 0.0f + } + if (stat.showYAxisZero) { + barChart.axisLeft.axisMinimum = 0.0f } + this.chartView = barChart + + val barData = BarData(dataSets) + barChart.data = barData + + dataSets.firstOrNull()?.let { + lastEntry = it.getEntryForIndex(it.entryCount - 1) + groupName = it.label + } + } + + this.chartContainer.addView(this.chartView) + + this.chartView?.let { + it.setStyle(false, axisFormatting, requireContext()) + it.setOnChartValueSelectedListener(this) + } + + lastEntry?.let { + this.selectValue(it, groupName ?: "") } } + // OnChartValueSelectedListener + override fun onNothingSelected() { + // nothing to do + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + + var groupName = "" + h?.let { highlight -> + this.chartView?.data?.getDataSetByIndex(highlight.dataSetIndex)?.let { + groupName = it.label + } + } + + e?.let { entry -> + this.selectValue(entry, groupName) + } + } + + private fun selectValue(entry: Entry, groupName: String) { + + val statEntry = when (entry.data) { + is ObjectIdentifier -> { + val identifier = entry.data as ObjectIdentifier + getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull() + } + is GraphUnderlyingEntry -> entry.data as GraphUnderlyingEntry? + else -> null + } + + statEntry?.let { + val legendValue = it.legendValues(stat, entry, this.style, groupName, requireContext()) + this.legendView.setItemData(legendValue) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt index 78b61b06..59c01ec9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.core.view.isVisible +import androidx.interpolator.view.animation.FastOutSlowInInterpolator import io.realm.RealmResults import io.realm.Sort import io.realm.kotlin.where @@ -56,12 +57,6 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource realmSessions.removeAllChangeListeners() } - override fun onResume() { - super.onResume() - // Old - //createSessionsHeaders() - } - /** * Init UI */ @@ -96,7 +91,11 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource disclaimerDismiss.setOnClickListener { Preferences.setStopShowingDisclaimer(requireContext()) - disclaimerContainer.isVisible = false + + disclaimerContainer.animate().translationY(disclaimerContainer.height.toFloat()) + .setInterpolator(FastOutSlowInInterpolator()) + .withEndAction { disclaimerContainer?.isVisible = false } + .start() } } 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 new file mode 100644 index 00000000..faa0d030 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/MoreFragment.kt @@ -0,0 +1,93 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.android.synthetic.main.fragment_stats.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.ui.activity.BankrollActivity +import net.pokeranalytics.android.ui.activity.SettingsActivity +import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter +import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate +import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource +import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.MoreTabRow + +class MoreFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { + + companion object { + + /** + * Create new instance + */ + fun newInstance(): MoreFragment { + val fragment = MoreFragment() + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + + val rowRepresentation: List by lazy { + val rows = ArrayList() + rows.addAll(MoreTabRow.values()) + rows + } + + } + + private lateinit var moreAdapter: RowRepresentableAdapter + + + // Life Cycle + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_more, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + } + + // Rows + override fun adapterRows(): List? { + return rowRepresentation + } + + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { + super.onRowSelected(position, row, fromAction) + when(row) { + MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext()) + MoreTabRow.SETTINGS -> SettingsActivity.newInstance(requireContext()) + } + } + + // Business + + /** + * Init data + */ + private fun initData() { + } + + /** + * Init UI + */ + private fun initUI() { + + moreAdapter = RowRepresentableAdapter(this, this) + + val viewManager = LinearLayoutManager(requireContext()) + + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + adapter = moreAdapter + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportDetailsFragment.kt new file mode 100644 index 00000000..64475656 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportDetailsFragment.kt @@ -0,0 +1,92 @@ +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 kotlinx.android.synthetic.main.fragment_report_details.* +import kotlinx.android.synthetic.main.fragment_statistic_details.toolbar +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.AggregationType +import net.pokeranalytics.android.calculus.Report +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity +import net.pokeranalytics.android.ui.adapter.ReportPagerAdapter +import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment + +class ReportDetailsFragment : PokerAnalyticsFragment() { + + companion object { + fun newInstance(report: Report?, reportTitle: String): ReportDetailsFragment { + val fragment = ReportDetailsFragment() + fragment.reportTitle = reportTitle + report?.let { + fragment.selectedReport = it + } + val bundle = Bundle() + fragment.arguments = bundle + return fragment + + } + } + + private lateinit var parentActivity: PokerAnalyticsActivity + private lateinit var selectedReport: Report + + private var reports: MutableMap = hashMapOf() + private var stat: Stat = Stat.NET_RESULT + private var displayAggregationChoices: Boolean = true + private var reportTitle: String = "" + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_report_details, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initUI() + } + + /** + * Init UI + */ + private fun initUI() { + + parentActivity = activity as PokerAnalyticsActivity + + // Avoid a bug during setting the title + toolbar.title = "" + + parentActivity.setSupportActionBar(toolbar) + parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) + setHasOptionsMenu(true) + + toolbar.title = reportTitle + + val reportPagerAdapter = ReportPagerAdapter(requireContext(), parentActivity.supportFragmentManager, selectedReport) + viewPager.adapter = reportPagerAdapter + viewPager.offscreenPageLimit = 3 + + tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + viewPager.setCurrentItem(tab.position, false) + } + + override fun onTabUnselected(tab: TabLayout.Tab) { + } + + override fun onTabReselected(tab: TabLayout.Tab) { + } + }) + } + + + /** + * Set data + */ + fun setData(report: Report) { + this.selectedReport = report + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt new file mode 100644 index 00000000..8e35f4b4 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt @@ -0,0 +1,140 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.recyclerview.widget.LinearLayoutManager +import io.realm.Realm +import kotlinx.android.synthetic.main.fragment_stats.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.Calculator +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.model.Criteria +import net.pokeranalytics.android.model.combined +import net.pokeranalytics.android.ui.activity.ReportDetailsActivity +import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter +import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate +import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource +import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.ReportRow +import timber.log.Timber +import java.util.* +import kotlin.collections.ArrayList + +class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { + + companion object { + + /** + * Create new instance + */ + fun newInstance(): ReportsFragment { + val fragment = ReportsFragment() + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + + val rowRepresentation: List by lazy { + val rows = ArrayList() + rows.addAll(ReportRow.getRows()) + rows + } + } + + private lateinit var reportsAdapter: RowRepresentableAdapter + + + // Life Cycle + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_reports, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + } + + // Rows + + override fun adapterRows(): List? { + return rowRepresentation + } + + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { + super.onRowSelected(position, row, fromAction) + if (row is ReportRow) { + val reportName = row.localizedTitle(requireContext()) + launchComputation(row.criteria, reportName) + } + } + + + // Business + + /** + * Init data + */ + private fun initData() { + } + + /** + * Init UI + */ + private fun initUI() { + + reportsAdapter = RowRepresentableAdapter(this, this) + + val viewManager = LinearLayoutManager(requireContext()) + + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + adapter = reportsAdapter + } + } + + /** + * Launch computation + */ + private fun launchComputation(criteria: List, reportName: String) { + + if (criteria.combined().size < 2) { + Toast.makeText(context, R.string.less_then_2_values_for_comparison, Toast.LENGTH_LONG).show() + return + } + + showLoader() + + GlobalScope.launch { + + val startDate = Date() + val realm = Realm.getDefaultInstance() + + val requiredStats: List = listOf(Stat.NET_RESULT) + val options = Calculator.Options(evolutionValues = Calculator.Options.EvolutionValues.STANDARD, stats = requiredStats) + + val report = Calculator.computeStatsWithComparators(realm, criteria = criteria, options = options) + + Timber.d("launchComputation: ${System.currentTimeMillis() - startDate.time}ms") + + launch(Dispatchers.Main) { + if (!isDetached) { + hideLoader() + ReportDetailsActivity.newInstance(requireContext(), report, reportName) + } + } + realm.close() + } + } + + +} \ No newline at end of file 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 627ba629..69f5115c 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 @@ -6,7 +6,6 @@ import android.view.* import android.view.animation.OvershootInterpolator import android.widget.Toast import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.recyclerview.widget.DiffUtil import io.realm.kotlin.where @@ -28,7 +27,6 @@ import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentableDiffCallback import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow -import net.pokeranalytics.android.util.CurrencyUtils import java.util.* @@ -45,8 +43,9 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate { private val refreshTimer: Runnable = object : Runnable { override fun run() { // Refresh header each 30 seconds + currentSession.updateRowRepresentation() sessionAdapter.notifyItemChanged(0) - handler.postDelayed(this, 30000) + handler.postDelayed(this, 60000) } } @@ -65,7 +64,8 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate { } override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { - inflater?.inflate(R.menu.session_toolbar, menu) + menu?.clear() + inflater?.inflate(R.menu.toolbar_session, menu) this.sessionMenu = menu updateMenuUI() super.onCreateOptionsMenu(menu, inflater) @@ -105,9 +105,9 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate { } SessionRow.BANKROLL -> { - BottomSheetFragment.create(fragmentManager, row, this, data, false, CurrencyUtils.getCurrency(currentSession.bankroll)) + BottomSheetFragment.create(fragmentManager, row, this, data, false, currentSession.currency) } - else -> BottomSheetFragment.create(fragmentManager, row, this, data, currentCurrency = CurrencyUtils.getCurrency(currentSession.bankroll)) + else -> BottomSheetFragment.create(fragmentManager, row, this, data, currentCurrency = currentSession.currency) } } @@ -168,7 +168,6 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate { when (currentSession.getState()) { SessionState.PENDING, SessionState.PLANNED -> { - state.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) sessionMenu?.findItem(R.id.restart)?.isVisible = false floatingActionButton.setImageResource(R.drawable.ic_outline_play) sessionMenu?.findItem(R.id.stop)?.isVisible = false @@ -177,17 +176,15 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate { .setInterpolator(OvershootInterpolator()).start() } SessionState.STARTED -> { - state.setTextColor(ContextCompat.getColor(requireContext(), R.color.green)) sessionMenu?.findItem(R.id.restart)?.isVisible = true floatingActionButton.setImageResource(R.drawable.ic_outline_pause) sessionMenu?.findItem(R.id.stop)?.isVisible = true floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f) .setDuration(animationDuration) .setInterpolator(OvershootInterpolator()).start() - handler.postDelayed(refreshTimer, 30000) + handler.postDelayed(refreshTimer, 60000) } SessionState.PAUSED -> { - state.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue)) sessionMenu?.findItem(R.id.restart)?.isVisible = true floatingActionButton.setImageResource(R.drawable.ic_outline_play) sessionMenu?.findItem(R.id.stop)?.isVisible = true @@ -196,7 +193,6 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate { .setInterpolator(OvershootInterpolator()).start() } SessionState.FINISHED -> { - state.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) sessionMenu?.findItem(R.id.restart)?.isVisible = true sessionMenu?.findItem(R.id.stop)?.isVisible = false floatingActionButton.animate().scaleX(0f).scaleY(0f).alpha(0f) 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 70a22ecc..b396874f 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 @@ -7,9 +7,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager +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.Session import net.pokeranalytics.android.ui.activity.CurrenciesActivity import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.activity.GDPRActivity @@ -17,20 +19,20 @@ import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource +import net.pokeranalytics.android.ui.extensions.openContactMail +import net.pokeranalytics.android.ui.extensions.openPlayStorePage +import net.pokeranalytics.android.ui.extensions.openUrl import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.URL -import net.pokeranalytics.android.util.extensions.openContactMail -import net.pokeranalytics.android.util.extensions.openPlayStorePage -import net.pokeranalytics.android.util.extensions.openUrl +import net.pokeranalytics.android.util.UserDefaults import java.util.* class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource { - private lateinit var parentActivity: PokerAnalyticsActivity companion object { @@ -53,49 +55,53 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta const val REQUEST_CODE_CURRENCY: Int = 100 } - override fun stringForRow(row: RowRepresentable): String { - return when (row) { - SettingRow.VERSION -> BuildConfig.VERSION_NAME + if (BuildConfig.DEBUG) " (${BuildConfig.VERSION_CODE}) DEBUG" else "" - SettingRow.CURRENCY -> { - val locale = Preferences.getCurrencyLocale(this.parentActivity) - Currency.getInstance(locale).getSymbol(locale) - } - else -> "" - } - } - private lateinit var settingsAdapterRow: RowRepresentableAdapter - + private lateinit var parentActivity: PokerAnalyticsActivity override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(net.pokeranalytics.android.R.layout.fragment_settings, container, false) + return inflater.inflate(R.layout.fragment_settings, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initData() + initUI() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == SettingsFragment.REQUEST_CODE_CURRENCY && resultCode == Activity.RESULT_OK) { + 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) } } } override fun adapterRows(): List? { - return SettingsFragment.rowRepresentation + return rowRepresentation } - override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { + override fun stringForRow(row: RowRepresentable): String { + return when (row) { + SettingRow.VERSION -> BuildConfig.VERSION_NAME + if (BuildConfig.DEBUG) " (${BuildConfig.VERSION_CODE}) DEBUG" else "" + SettingRow.CURRENCY -> UserDefaults.currency.symbol + else -> "" + } + } + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { when (row) { SettingRow.RATE_APP -> parentActivity.openPlayStorePage() SettingRow.CONTACT_US -> parentActivity.openContactMail(R.string.contact) - SettingRow.BUG_REPORT -> parentActivity.openContactMail(R.string.bug_report_subject) - SettingRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@SettingsFragment, SettingsFragment.REQUEST_CODE_CURRENCY) + SettingRow.BUG_REPORT -> parentActivity.openContactMail(R.string.bug_report_subject, Realm.getDefaultInstance().path) + SettingRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@SettingsFragment, REQUEST_CODE_CURRENCY) SettingRow.FOLLOW_US -> { when (position) { 0 -> parentActivity.openUrl(URL.BLOG.value) @@ -115,24 +121,35 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta } /** - * Init data + * Init UI */ - private fun initData() { + private fun initUI() { parentActivity = activity as PokerAnalyticsActivity + parentActivity.setSupportActionBar(toolbar) + parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) + setHasOptionsMenu(true) + val viewManager = LinearLayoutManager(requireContext()) settingsAdapterRow = RowRepresentableAdapter( this, this ) - customRecyclerView.apply { + recyclerView.apply { setHasFixedSize(true) layoutManager = viewManager adapter = settingsAdapterRow } } + /** + * Init data + */ + private fun initData() { + + } + /** * Open GDPR Activity */ diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticDetailsFragment.kt new file mode 100644 index 00000000..3ed90ebe --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticDetailsFragment.kt @@ -0,0 +1,197 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.LineDataSet +import com.google.android.material.chip.Chip +import com.google.android.material.chip.ChipGroup +import io.realm.Realm +import kotlinx.android.synthetic.main.fragment_statistic_details.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.* +import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity +import net.pokeranalytics.android.ui.extensions.ChipGroupExtension +import net.pokeranalytics.android.ui.extensions.hideWithAnimation +import net.pokeranalytics.android.ui.extensions.px +import net.pokeranalytics.android.ui.extensions.showWithAnimation +import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment +import timber.log.Timber +import java.util.* + + +class StatisticDetailsFragment : PokerAnalyticsFragment() { + + companion object { + + /** + * Create new instance + */ + fun newInstance(): StatisticDetailsFragment { + val fragment = StatisticDetailsFragment() + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + } + + private lateinit var parentActivity: PokerAnalyticsActivity + private lateinit var computableGroup: ComputableGroup + private lateinit var graphFragment: GraphFragment + private lateinit var selectedReport: Report + + private var reports: MutableMap = hashMapOf() + private var stat: Stat = Stat.NET_RESULT + private var displayAggregationChoices: Boolean = true + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_statistic_details, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initUI() + } + + /** + * Init UI + */ + private fun initUI() { + + parentActivity = activity as PokerAnalyticsActivity + + // Avoid a bug during setting the title + toolbar.title = "" + + parentActivity.setSupportActionBar(toolbar) + parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) + setHasOptionsMenu(true) + + + val fragmentManager = parentActivity.supportFragmentManager + val fragmentTransaction = fragmentManager.beginTransaction() + graphFragment = GraphFragment() + + fragmentTransaction.add(R.id.graphContainer, graphFragment) + fragmentTransaction.commit() + + stat.aggregationTypes.firstOrNull()?.let { aggregationType -> + reports[aggregationType]?.let { report -> + setGraphData(report, aggregationType) + } + } + + toolbar.title = stat.localizedTitle(requireContext()) + val aggregationTypes = stat.aggregationTypes + + aggregationTypes.forEachIndexed { index, type -> + val chip = Chip(requireContext()) + chip.id = index + chip.text = requireContext().getString(type.resId) + chip.chipStartPadding = 8f.px + chip.chipEndPadding = 8f.px + this.chipGroup.addView(chip) + } + + this.chipGroup.isVisible = displayAggregationChoices + this.chipGroup.check(0) + + this.chipGroup.setOnCheckedChangeListener(object : ChipGroupExtension.SingleSelectionOnCheckedListener() { + override fun onCheckedChanged(group: ChipGroup, checkedId: Int) { + super.onCheckedChanged(group, checkedId) + val aggregationType = aggregationTypes[checkedId] + + reports[aggregationType]?.let { report -> + setGraphData(report, aggregationType) + } ?: run { + launchStatComputation(aggregationType) + } + + } + }) + } + + /** + * Launch stat computation + */ + private fun launchStatComputation(aggregationType: AggregationType) { + + graphContainer.hideWithAnimation() + progressBar.showWithAnimation() + + GlobalScope.launch { + + val s = Date() + Timber.d(">>> start...") + + val realm = Realm.getDefaultInstance() + + val report = Calculator.computeStatsWithEvolutionByAggregationType(realm, stat, computableGroup, aggregationType) + reports[aggregationType] = report + + realm.close() + + val e = Date() + val duration = (e.time - s.time) / 1000.0 + Timber.d(">>> ended in $duration seconds") + + launch(Dispatchers.Main) { + setGraphData(report, aggregationType) + progressBar.hideWithAnimation() + graphContainer.showWithAnimation() + } + } + } + + /** + * Set the graph data set + */ + private fun setGraphData(report: Report, aggregationType: AggregationType) { + + val dataSet = when (aggregationType) { + AggregationType.SESSION -> report.results.firstOrNull()?.defaultStatEntries(stat, requireContext()) + AggregationType.DURATION -> { + report.results.firstOrNull()?.durationEntries(stat, requireContext()) + } + AggregationType.MONTH, AggregationType.YEAR -> { + when (this.stat) { + Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> report.barEntries(this.stat, requireContext()) + else -> report.lineEntries(this.stat, requireContext()) + } + } + } + + dataSet?.let { ds -> + if (ds is LineDataSet) { + graphFragment.setLineData(listOf(ds), stat, aggregationType.axisFormatting) + } + if (ds is BarDataSet) { + graphFragment.setBarData(listOf(ds), stat, aggregationType.axisFormatting) + } + + } + + } + + + /** + * Set data + */ + fun setData(stat: Stat, computableGroup: ComputableGroup, report: Report, displayAggregationChoices: Boolean) { + this.stat = stat + this.computableGroup = computableGroup + this.selectedReport = report + this.displayAggregationChoices = displayAggregationChoices + + stat.aggregationTypes.firstOrNull()?.let { + reports[it] = report + } + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..5fedf62d --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt @@ -0,0 +1,145 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.View +import io.realm.Realm +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.calculus.Calculator +import net.pokeranalytics.android.calculus.ComputableGroup +import net.pokeranalytics.android.calculus.Report +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.StatRow +import timber.log.Timber +import java.util.* +import kotlin.coroutines.CoroutineContext + +class StatisticsFragment : TableReportFragment() { + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + + private var stringAll = "" + private var stringCashGame = "" + private var stringTournament = "" + + companion object { + + /** + * Create new instance + */ + fun newInstance(report: Report? = null): StatisticsFragment { + val fragment = StatisticsFragment() + report?.let { + fragment.report = it + } + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + } + + // Life Cycle + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + launchStatComputation() + } + + override fun sessionsChanged() { + this.launchStatComputation() + this.statsAdapter?.notifyDataSetChanged() + } + + override fun initData() { + super.initData() + + this.stringAll = getString(R.string.all) + this.stringCashGame = getString(R.string.cash_game) + this.stringTournament = getString(R.string.tournament) + } + + override fun convertReportIntoRepresentables(report: Report): ArrayList { + val rows: ArrayList = ArrayList() + report.results.forEach { result -> + rows.add(CustomizableRowRepresentable(title = result.group.name)) + result.group.stats?.forEach { stat -> + rows.add(StatRow(stat, result.computedStat(stat), result.group.name)) + } + } + return rows + } + + + // Business + + /** + * Launch stat computation + */ + private fun launchStatComputation() { + + GlobalScope.launch(coroutineContext) { + + val test = GlobalScope.async { + val s = Date() + Timber.d(">>> start...") + + val realm = Realm.getDefaultInstance() + report = createSessionGroupsAndStartCompute(realm) + realm.close() + + val e = Date() + val duration = (e.time - s.time) / 1000.0 + Timber.d(">>> ended in $duration seconds") + + } + test.await() + + if (!isDetached) { + showResults() + } + } + } + + /** + * Create session groups and start computations + */ + private fun createSessionGroupsAndStartCompute(realm: Realm): Report { + + val allStats: List = listOf( + Stat.NET_RESULT, + Stat.HOURLY_RATE, + Stat.AVERAGE, + Stat.NUMBER_OF_SETS, + Stat.AVERAGE_HOURLY_DURATION, + Stat.HOURLY_DURATION + ) + val allSessionGroup = ComputableGroup(stringAll, listOf(), allStats) + val cgStats: List = listOf( + Stat.NET_RESULT, + Stat.HOURLY_RATE, + Stat.NET_BB_PER_100_HANDS, + Stat.HOURLY_RATE_BB, + Stat.AVERAGE, + Stat.STANDARD_DEVIATION_HOURLY, + Stat.WIN_RATIO, + Stat.NUMBER_OF_GAMES, + Stat.AVERAGE_BUYIN + ) + val cgSessionGroup = ComputableGroup(stringCashGame, listOf(QueryCondition.IsCash), cgStats) + val tStats: List = + listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) + val tSessionGroup = ComputableGroup(stringTournament, listOf(QueryCondition.IsTournament), tStats) + + Timber.d(">>>>> Start computations...") + + return Calculator.computeGroups(realm, listOf(allSessionGroup, cgSessionGroup, tSessionGroup), Calculator.Options()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt deleted file mode 100644 index 4f112698..00000000 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt +++ /dev/null @@ -1,247 +0,0 @@ -package net.pokeranalytics.android.ui.fragment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.LinearLayoutManager -import io.realm.Realm -import kotlinx.android.synthetic.main.fragment_stats.* -import kotlinx.coroutines.* -import net.pokeranalytics.android.R -import net.pokeranalytics.android.calculus.* -import net.pokeranalytics.android.model.StatRepresentable -import net.pokeranalytics.android.model.filter.QueryCondition -import net.pokeranalytics.android.ui.activity.GraphActivity -import net.pokeranalytics.android.ui.adapter.DisplayDescriptor -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.SessionObserverFragment -import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable -import net.pokeranalytics.android.util.NULL_TEXT -import timber.log.Timber -import java.util.* -import kotlin.coroutines.CoroutineContext - -class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope, - RowRepresentableDelegate { - - override val coroutineContext: CoroutineContext - get() = Dispatchers.Main - - private var rowRepresentables: ArrayList = ArrayList() - private var stringAll = "" - private var stringCashGame = "" - private var stringTournament = "" - - private lateinit var statsAdapter: RowRepresentableAdapter - private var computedResults : List? = null - - companion object { - - /** - * Create new instance - */ - fun newInstance(): StatsFragment { - val fragment = StatsFragment() - val bundle = Bundle() - fragment.arguments = bundle - return fragment - } - } - - // Life Cycle - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_stats, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initData() - initUI() - launchStatComputation() - } - - // Row Representable DS - - override fun adapterRows(): List? { - return this.rowRepresentables - } - - override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? { - val dc = DisplayDescriptor() - dc.textFormat = TextFormat(NULL_TEXT) - if (row is StatRepresentable) { - context?.let { context -> - row.computedStat?.let { - dc.textFormat = it.format(context) - } - } - } - return dc - } - - override fun statFormatForRow(row: RowRepresentable): TextFormat { - if (row is StatRepresentable) { - context?.let { context -> - row.computedStat?.let { return it.format(context) } - } - } - return TextFormat(NULL_TEXT) - } - - override fun onResume() { - super.onResume() - statsAdapter.notifyDataSetChanged() - } - - // Override - - override fun sessionsChanged() { - this.launchStatComputation() - this.statsAdapter.notifyDataSetChanged() - } - - // Business - - /** - * Init data - */ - private fun initData() { - - this.stringAll = getString(R.string.all) - this.stringCashGame = getString(R.string.cash_game) - this.stringTournament = getString(R.string.tournament) - - this.statsAdapter = RowRepresentableAdapter(this, this) - } - - /** - * Init UI - */ - private fun initUI() { - - val viewManager = LinearLayoutManager(requireContext()) - - recyclerView.apply { - setHasFixedSize(true) - layoutManager = viewManager - adapter = statsAdapter - } - } - - private fun launchStatComputation() { - - GlobalScope.launch(coroutineContext) { - - var results = listOf() - val test = GlobalScope.async { - val s = Date() - Timber.d(">>> start...") - - val realm = Realm.getDefaultInstance() - results = createSessionGroupsAndStartCompute(realm) - computedResults = results - realm.close() - - val e = Date() - val duration = (e.time - s.time) / 1000.0 - Timber.d(">>> ended in ${duration} seconds") - - } - test.await() - - if (!isDetached) { - showResults(results) - } - } - - } - - private fun createSessionGroupsAndStartCompute(realm: Realm) : List { - - val allStats: List = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE, Stat.NUMBER_OF_SETS, Stat.AVERAGE_DURATION, Stat.DURATION) - val allSessionGroup = ComputableGroup(stringAll, listOf(), allStats) - val cgStats: List = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.NET_BB_PER_100_HANDS, Stat.HOURLY_RATE_BB, Stat.AVERAGE, Stat.STANDARD_DEVIATION_HOURLY, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) - val cgSessionGroup = ComputableGroup(stringCashGame, listOf(QueryCondition.CASH), cgStats) - val tStats: List = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) - val tSessionGroup = ComputableGroup(stringTournament, listOf(QueryCondition.TOURNAMENT), tStats) - - Timber.d(">>>>> Start computations...") - - return Calculator.computeGroups(realm, listOf(allSessionGroup, cgSessionGroup, tSessionGroup), Calculator.Options()) - - } - - private fun showResults(results: List) { - this.rowRepresentables = this.convertResultsIntoRepresentables(results) - statsAdapter.notifyDataSetChanged() - } - - private fun convertResultsIntoRepresentables(results: List) : ArrayList { - - val rows: ArrayList = ArrayList() - - results.forEach { result -> - rows.add(CustomizableRowRepresentable(title = result.group.name)) - result.group.stats?.forEach { stat -> - rows.add(StatRepresentable(stat, result.computedStat(stat), result.group.name)) - } - } - - return rows - } - - // RowRepresentableDelegate - - override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { - -// if (row is StatRepresentable) { -// -// // filter groups -// val groupResults = this.computedResults?.filter { -// it.group.name == row.groupName -// } -// -// groupResults?.firstOrNull()?.let { -// this.launchStatComputationWithEvolution(row.stat, it.group) -// } -// } - - } - - private fun launchStatComputationWithEvolution(stat: Stat, computableGroup: ComputableGroup) { - - GlobalScope.launch(coroutineContext) { - - var results = listOf() - val test = GlobalScope.async { - val s = Date() - Timber.d(">>> start...") - - val realm = Realm.getDefaultInstance() - val options = Calculator.Options() - options.evolutionValues = Calculator.Options.EvolutionValues.STANDARD - results = Calculator.computeGroups(realm, listOf(computableGroup), options) - realm.close() - - val e = Date() - val duration = (e.time - s.time) / 1000.0 - Timber.d(">>> ended in ${duration} seconds") - - } - test.await() - - if (!isDetached) { - results.firstOrNull()?.defaultStatEntries(stat)?.let { entries -> - GraphActivity.newInstance(requireContext(), stat, entries) - } - } - } - - } - -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/TableReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/TableReportFragment.kt new file mode 100644 index 00000000..07cdc63c --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/TableReportFragment.kt @@ -0,0 +1,199 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import io.realm.Realm +import kotlinx.android.synthetic.main.fragment_stats.* +import kotlinx.coroutines.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.* +import net.pokeranalytics.android.ui.activity.StatisticDetailsActivity +import net.pokeranalytics.android.ui.adapter.DisplayDescriptor +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.SessionObserverFragment +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.StatRow +import net.pokeranalytics.android.util.NULL_TEXT +import timber.log.Timber +import java.util.* +import kotlin.coroutines.CoroutineContext + +open class TableReportFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope, + RowRepresentableDelegate { + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + + private var rowRepresentables: ArrayList = ArrayList() + + var statsAdapter: RowRepresentableAdapter? = null + var report : Report? = null + + companion object { + + /** + * Create new instance + */ + fun newInstance(report: Report? = null): TableReportFragment { + val fragment = TableReportFragment() + report?.let { + fragment.report = it + } + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + } + + // Life Cycle + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_stats, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + + report?.let { + showResults() + } + } + + // Row Representable DS + + override fun adapterRows(): List? { + return this.rowRepresentables + } + + override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? { + val dc = DisplayDescriptor() + dc.textFormat = TextFormat(NULL_TEXT) + if (row is StatRow) { + context?.let { _ -> + row.computedStat?.let { + dc.textFormat = it.format() + } + } + } + return dc + } + + override fun statFormatForRow(row: RowRepresentable): TextFormat { + if (row is StatRow) { + context?.let { _ -> + row.computedStat?.let { return it.format() } + } + } + return TextFormat(NULL_TEXT) + } + + override fun onResume() { + super.onResume() + statsAdapter?.notifyDataSetChanged() + } + + // Business + + /** + * Init data + */ + open fun initData() { + this.statsAdapter = RowRepresentableAdapter(this, this) + } + + /** + * Init UI + */ + open fun initUI() { + val viewManager = LinearLayoutManager(requireContext()) + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + adapter = statsAdapter + } + } + + /** + * Show results + */ + fun showResults() { + report?.let { + this.rowRepresentables = this.convertReportIntoRepresentables(it) + statsAdapter?.notifyDataSetChanged() + } + } + + open fun convertReportIntoRepresentables(report: Report): ArrayList { + val rows: ArrayList = ArrayList() + report.options.displayedStats.forEach {stat -> + rows.add(CustomizableRowRepresentable(title = stat.localizedTitle(requireContext()))) + report.results.forEach { + val title = it.group.name + rows.add(StatRow(stat, it.computedStat(stat), it.group.name, title)) + } + + } + return rows + } + + // RowRepresentableDelegate + + override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { + + if (row is StatRow && row.stat.hasEvolutionGraph) { + + // queryWith groups + val groupResults = this.report?.results?.filter { + it.group.name == row.groupName + } + + groupResults?.firstOrNull()?.let { + this.launchStatComputationWithEvolution(row.stat, it.group) + } + } + + } + + private fun launchStatComputationWithEvolution(stat: Stat, computableGroup: ComputableGroup) { + + showLoader() + + GlobalScope.launch(coroutineContext) { + + var report: Report? = null + val test = GlobalScope.async { + val s = Date() + Timber.d(">>> start...") + + val realm = Realm.getDefaultInstance() + + val aggregationType = stat.aggregationTypes.first() + report = Calculator.computeStatsWithEvolutionByAggregationType(realm, stat, computableGroup, aggregationType) + + realm.close() + + val e = Date() + val duration = (e.time - s.time) / 1000.0 + Timber.d(">>> ended in $duration seconds") + + } + test.await() + + if (!isDetached) { + hideLoader() + report?.let { + StatisticDetailsActivity.newInstance(requireContext(), stat, computableGroup, it) + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/LoaderDialogFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/LoaderDialogFragment.kt new file mode 100644 index 00000000..82de63d6 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/LoaderDialogFragment.kt @@ -0,0 +1,53 @@ +package net.pokeranalytics.android.ui.fragment.components + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import kotlinx.android.synthetic.main.fragment_loader.* +import net.pokeranalytics.android.R + + +class LoaderDialogFragment: DialogFragment() { + + companion object { + + const val ARGUMENT_MESSAGE_RES_ID = "ARGUMENT_MESSAGE_RES_ID" + + /** + * Create new instance + */ + fun newInstance(resId: Int? = null, isCancelable: Boolean = false): LoaderDialogFragment { + val fragment = LoaderDialogFragment() + fragment.isCancelable = isCancelable + val bundle = Bundle() + resId?.let { + bundle.putInt(ARGUMENT_MESSAGE_RES_ID, resId) + } + fragment.arguments = bundle + return fragment + } + + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_loader, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + arguments?.let {bundle -> + if (bundle.containsKey(ARGUMENT_MESSAGE_RES_ID)) { + loadingMessage.text = getString(bundle.getInt(ARGUMENT_MESSAGE_RES_ID)) + } + } + } + + override fun onStart() { + super.onStart() + val window = dialog.window + window?.setBackgroundDrawableResource(android.R.color.transparent) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt index e69ce9f6..11ebca17 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt @@ -7,6 +7,7 @@ import io.realm.Realm open class PokerAnalyticsFragment: Fragment() { private var realm: Realm? = null + private var loaderDialogFragment: LoaderDialogFragment? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -36,4 +37,22 @@ open class PokerAnalyticsFragment: Fragment() { */ open fun onBackPressed(){} + /** + * Show the loader + */ + fun showLoader(resId: Int? = null, cancelable: Boolean? = false) { + loaderDialogFragment = LoaderDialogFragment.newInstance(resId, false) + activity?.let { + loaderDialogFragment?.show(it.supportFragmentManager, "loader") + } + } + + /** + * Hide the loader + */ + fun hideLoader() { + loaderDialogFragment?.dismiss() + loaderDialogFragment = null + } + } \ No newline at end of file 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/bottomsheet/BottomSheetFragment.kt index 57c17392..f883c7f2 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/bottomsheet/BottomSheetFragment.kt @@ -97,7 +97,7 @@ open class BottomSheetFragment : BottomSheetDialogFragment() { private fun initUI() { row.let { bottomSheetToolbar.title = row.localizedTitle(requireContext()) - bottomSheetToolbar.inflateMenu(R.menu.bottom_sheet_menu) + bottomSheetToolbar.inflateMenu(R.menu.toolbar_bottom_sheet) bottomSheetToolbar.setOnMenuItemClickListener { false } 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/bottomsheet/BottomSheetListGameFragment.kt index 919acd05..3782f246 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/bottomsheet/BottomSheetListGameFragment.kt @@ -14,7 +14,7 @@ import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorExcep import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.util.extensions.px +import net.pokeranalytics.android.ui.extensions.px /** * Bottom Sheet List Game Fragment 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/bottomsheet/BottomSheetTableSizeGridFragment.kt index e2a7ac37..7c199c09 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/bottomsheet/BottomSheetTableSizeGridFragment.kt @@ -13,7 +13,7 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.view.GridSpacingItemDecoration import net.pokeranalytics.android.ui.view.RowRepresentable -import net.pokeranalytics.android.util.extensions.px +import net.pokeranalytics.android.ui.extensions.px class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/graph/AxisFormatter.kt b/app/src/main/java/net/pokeranalytics/android/ui/graph/AxisFormatter.kt new file mode 100644 index 00000000..8b9673c9 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/graph/AxisFormatter.kt @@ -0,0 +1,32 @@ +package net.pokeranalytics.android.ui.graph + +import com.github.mikephil.charting.components.AxisBase +import com.github.mikephil.charting.formatter.ValueFormatter +import net.pokeranalytics.android.util.extensions.kmbFormatted +import kotlin.math.roundToInt + +class LargeNumberFormatter : ValueFormatter() { + + override fun getFormattedValue(value: Float): String { + return value.kmbFormatted + } + + override fun getAxisLabel(value: Float, axis: AxisBase?): String { + return value.roundToInt().kmbFormatted + } + +} + +class HourFormatter : ValueFormatter() { + + override fun getFormattedValue(value: Float): String { + return value.kmbFormatted + "H" + } + + override fun getAxisLabel(value: Float, axis: AxisBase?): String { + val test = value.kmbFormatted + "H" + return test + } + +} + diff --git a/app/src/main/java/net/pokeranalytics/android/ui/graph/ChartDataSet.kt b/app/src/main/java/net/pokeranalytics/android/ui/graph/ChartDataSet.kt new file mode 100644 index 00000000..2338ad35 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/graph/ChartDataSet.kt @@ -0,0 +1,30 @@ +package net.pokeranalytics.android.ui.graph + +import android.content.Context +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineDataSet +import net.pokeranalytics.android.R + +class PALineDataSet(yVals: List, label: String, context: Context) : LineDataSet(yVals, label) { + + init { + this.highLightColor = context.getColor(R.color.chart_highlight_indicator) + this.setDrawValues(false) + this.setDrawCircles(false) + + val colors = arrayOf(R.color.green_light).toIntArray() + this.setColors(colors, context) + + } + +} + + + +//class PABarDataSet(yVals: List, label: String, context: Context) : BarDataSet(yVals, label) { +// +// init { +// this.highLightColor = context.getColor(R.color.chart_highlight_indicator) +// } +// +//} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt index 56cd4dda..f2e4e2ba 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt @@ -1,66 +1,83 @@ package net.pokeranalytics.android.ui.graph +import android.content.Context +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat import com.github.mikephil.charting.charts.BarChart import com.github.mikephil.charting.charts.BarLineChartBase -import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import net.pokeranalytics.android.R +import net.pokeranalytics.android.ui.extensions.px -fun BarChart.setStyle() { - GraphHelper.setStyle(this) +enum class AxisFormatting { + DEFAULT, + X_DURATION, + Y_DURATION, } -fun LineChart.setStyle() { - GraphHelper.setStyle(this) -} +fun BarLineChartBase<*>.setStyle( + small: Boolean, + axisFormatting: AxisFormatting = AxisFormatting.DEFAULT, + context: Context +) { + + this.legend.isEnabled = false + this.description.isEnabled = false -class GraphHelper { + // X Axis + this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.chart_default) + this.xAxis.enableGridDashedLine(3.0f.px, 5.0f.px, 1.0f.px) + this.xAxis.position = XAxis.XAxisPosition.BOTTOM + this.xAxis.setDrawGridLines(true) + this.xAxis.isGranularityEnabled = true + this.xAxis.granularity = 1.0f + this.xAxis.textColor = ContextCompat.getColor(context, R.color.chart_default) + this.xAxis.typeface = ResourcesCompat.getFont(context, R.font.roboto_medium) + this.xAxis.labelCount = 4 + this.xAxis.textSize = 12f + this.xAxis.isEnabled = true - companion object { + when (this) { + is BarChart -> { + this.xAxis.setDrawLabels(false) + } + else -> { + this.xAxis.setDrawLabels(true) + } + } - fun setStyle(chart: BarLineChartBase<*>) { + // Y Axis + this.axisLeft.setDrawAxisLine(false) + this.axisLeft.setDrawGridLines(true) + this.axisLeft.enableGridDashedLine(3.0f.px, 5.0f.px, 1.0f.px) -// this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.) ChartAppearance.defaultColor -// this.xAxis.axisLineWidth = ChartAppearance.lineWidth -// this.xAxis.enableGridDashedLine(3.0f, 5.0f, 1.0f) -// -// this.xAxis.labelTextColor = ChartAppearance.defaultColor -// this.xAxis.labelFont = Fonts.graphAxis -// this.xAxis.labelCount = 4 -// this.xAxis.labelPosition = .bottom -// -// this.xAxis.drawLabelsEnabled = true -// this.xAxis.drawGridLinesEnabled = true -// this.xAxis.granularity = 1.0 -// this.xAxis.granularityEnabled = true -// this.xAxis.enabled = true -// -// // Y Axis -// this.leftAxis.drawAxisLineEnabled = false -// this.leftAxis.drawGridLinesEnabled = true -// this.leftAxis.gridLineDashLengths = [3.0, 5.0] -// -// this.leftAxis.drawZeroLineEnabled = true -// this.leftAxis.zeroLineWidth = ChartAppearance.lineWidth -// this.leftAxis.zeroLineColor = ChartAppearance.defaultColor -// -// this.leftAxis.granularityEnabled = true -// this.leftAxis.granularity = 1.0 -// -// this.leftAxis.labelTextColor = ChartAppearance.defaultColor -// this.leftAxis.labelFont = Fonts.graphAxis -// this.leftAxis.labelCount = small ? 1 : 7 // @todo not great if interval is [0..2] for number of records as we get decimals -// -// if timeYAxis { -// this.leftAxis.valueFormatter = HourValueFormatter() -// } else { -// this.leftAxis.valueFormatter = LargeNumberFormatter() -// } -// -// this.rightAxis.enabled = false -// -// this.legend.enabled = false + this.axisLeft.setDrawZeroLine(true) + this.axisLeft.zeroLineColor = ContextCompat.getColor(context, R.color.chart_default) - } + this.axisLeft.isGranularityEnabled = true + this.axisLeft.granularity = 1.0f + this.axisLeft.textColor = ContextCompat.getColor(context, R.color.chart_default) + this.axisLeft.typeface = ResourcesCompat.getFont(context, R.font.roboto_medium) + this.axisLeft.labelCount = + if (small) 1 else 7 // @todo not great if interval is [0..2] for number of records as we get decimals + this.axisLeft.textSize = 12f + this.axisLeft.valueFormatter = LargeNumberFormatter() + + this.axisRight.isEnabled = false + + this.data?.isHighlightEnabled = !small + + when (axisFormatting) { + AxisFormatting.DEFAULT -> { + this.axisLeft.valueFormatter = LargeNumberFormatter() + } + AxisFormatting.X_DURATION -> { + this.xAxis.valueFormatter = HourFormatter() + } + AxisFormatting.Y_DURATION -> { + this.axisLeft.valueFormatter = HourFormatter() + } } } 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 new file mode 100644 index 00000000..432d713b --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt @@ -0,0 +1,38 @@ +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.calculus.TextFormat +import net.pokeranalytics.android.ui.fragment.GraphFragment +import net.pokeranalytics.android.ui.view.DefaultLegendValues +import net.pokeranalytics.android.ui.view.LegendContent + +interface GraphUnderlyingEntry { + + val entryTitle: String + fun formattedValue(stat: Stat): TextFormat + + fun legendValues( + stat: Stat, + entry: Entry, + style: GraphFragment.Style, + groupName: String, + context: Context + ): LegendContent { + + return when (stat) { + Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { + val totalStatValue = stat.format(entry.y.toDouble(), currency = null) + DefaultLegendValues(this.entryTitle, totalStatValue) + } + else -> { + val entryValue = this.formattedValue(stat) + val totalStatValue = stat.format(entry.y.toDouble(), currency = null) + DefaultLegendValues(this.entryTitle, entryValue, totalStatValue) + } + } + + } + +} \ No newline at end of file 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 eea9bd32..d5e26d36 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 @@ -23,6 +23,7 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener, private lateinit var calendar: Calendar private var minimumDate: Date? = null private var onlyDate: Boolean = false + private var onlyTime: Boolean = false private var isClearable: Boolean = true companion object { @@ -33,6 +34,7 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener, date: Date?, minimumDate: Date? = null, onlyDate: Boolean? = false, + onlyTime: Boolean? = false, isClearable: Boolean? = true ): DateTimePickerManager { @@ -46,9 +48,14 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener, dateTimePickerManager.calendar = calendar dateTimePickerManager.minimumDate = minimumDate dateTimePickerManager.onlyDate = onlyDate ?: false + dateTimePickerManager.onlyTime = onlyTime ?: false dateTimePickerManager.isClearable = isClearable ?: true - dateTimePickerManager.showDatePicker() + if (dateTimePickerManager.onlyTime) { + dateTimePickerManager.showTimePicker() + } else { + dateTimePickerManager.showDatePicker() + } return dateTimePickerManager } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/CalendarTabs.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/CalendarTabs.kt new file mode 100644 index 00000000..677e3a0f --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/CalendarTabs.kt @@ -0,0 +1,29 @@ +package net.pokeranalytics.android.ui.view + +import net.pokeranalytics.android.R + + +enum class CalendarTabs : Displayable { + NET_RESULTS, + NET_HOURLY_RATE, + NUMBER_OF_GAMES, + WIN_RATIO, + STANDARD_DEVIATION_PER_HOUR, + AVERAGE_NET_RESULT, + AVERAGE_DURATION, + DURATION_OF_PLAY; + + override val resId: Int + get() { + return when (this) { + NET_RESULTS -> R.string.net_result + NET_HOURLY_RATE -> R.string.hour_rate_without_pauses + NUMBER_OF_GAMES -> R.string.number_of_records + WIN_RATIO -> R.string.win_ratio + STANDARD_DEVIATION_PER_HOUR -> R.string.standard_deviation_per_hour + AVERAGE_NET_RESULT -> R.string.average_net_result + AVERAGE_DURATION -> R.string.average_hours_played + DURATION_OF_PLAY -> R.string.total_hours_played + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..e67def78 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/LegendView.kt @@ -0,0 +1,119 @@ +package net.pokeranalytics.android.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import kotlinx.android.synthetic.main.layout_legend_default.view.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.calculus.TextFormat +import net.pokeranalytics.android.ui.extensions.setTextFormat +import net.pokeranalytics.android.ui.fragment.GraphFragment + +interface LegendContent + +data class DefaultLegendValues( + var title: String, + var leftFormat: TextFormat, + var rightFormat: TextFormat? = null +) : LegendContent + +/** + * Display a row session + */ +open class LegendView : FrameLayout { + +// open class Values(var title: String, var leftFormat: TextFormat, var rightFormat: TextFormat? = null) +// class MultiLineValues( +// var firstTitle: String, +// var secondTitle: String, +// leftFormat: TextFormat, +// rightFormat: TextFormat? = null +// ) : Values("", leftFormat, rightFormat) + + private lateinit var legendLayout: ConstraintLayout + + /** + * Constructors + */ + constructor(context: Context) : super(context) { + init() + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + init() + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init() + } + + open protected fun getResourceLayout(): Int { + return R.layout.layout_legend_default + } + + /** + * Init + */ + private fun init() { + val layoutInflater = LayoutInflater.from(context) + legendLayout = layoutInflater.inflate(this.getResourceLayout(), this, false) as ConstraintLayout + val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + addView(legendLayout, layoutParams) + } + + /** + * Set the stat data to the view + */ + open fun prepareWithStat(stat: Stat, counter: Int? = null, style: GraphFragment.Style) { + + when (style) { + GraphFragment.Style.BAR -> { + this.stat1Name.text = stat.localizedTitle(context) + this.stat2Name.text = context.getString(R.string.sessions) + this.counter.isVisible = false + } + GraphFragment.Style.LINE -> { + if (stat.significantIndividualValue) { + this.stat1Name.text = stat.localizedTitle(context) + this.stat2Name.text = stat.cumulativeLabelResId(context) + } else { + this.stat1Name.text = stat.cumulativeLabelResId(context) + this.stat2Name.isVisible = false + } + + counter?.let { + val counterText = "$it ${context.getString(R.string.sessions)}" + this.counter.text = counterText + this.counter.isVisible = stat.shouldShowNumberOfSessions + } + } + else -> { + } + } + + } + + /** + * + */ + open fun setItemData(content: LegendContent) { + + if (content is DefaultLegendValues) { + + this.title.text = content.title + + this.stat1Value.setTextFormat(content.leftFormat, context) + content.rightFormat?.let { + this.stat2Value.setTextFormat(it, context) + } + + } + + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/MultiLineLegendView.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/MultiLineLegendView.kt new file mode 100644 index 00000000..79c84b2c --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/MultiLineLegendView.kt @@ -0,0 +1,40 @@ +package net.pokeranalytics.android.ui.view + +import android.content.Context +import kotlinx.android.synthetic.main.layout_legend_default.view.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.calculus.TextFormat +import net.pokeranalytics.android.ui.extensions.setTextFormat +import net.pokeranalytics.android.ui.fragment.GraphFragment + +data class MultilineLegendValues( + var firstTitle: String, + var secondTitle: String, + var leftFormat: TextFormat, + var rightFormat: TextFormat +) : LegendContent + +class MultiLineLegendView(context: Context) : LegendView(context = context) { + + override fun getResourceLayout(): Int { + return R.layout.layout_legend_color + } + + override fun prepareWithStat(stat: Stat, counter: Int?, style: GraphFragment.Style) { + } + + override fun setItemData(content: LegendContent) { + + if (content is MultilineLegendValues) { + + this.stat1Name.text = content.firstTitle + this.stat2Name.text = content.secondTitle + + this.stat1Value.setTextFormat(content.leftFormat, context) + this.stat2Value.setTextFormat(content.rightFormat, context) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/HomeViewPager.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/NoPagingViewPager.kt similarity index 81% rename from app/src/main/java/net/pokeranalytics/android/ui/view/HomeViewPager.kt rename to app/src/main/java/net/pokeranalytics/android/ui/view/NoPagingViewPager.kt index 2802f2fb..bb7eac22 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/HomeViewPager.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/NoPagingViewPager.kt @@ -6,9 +6,9 @@ import android.view.MotionEvent import androidx.viewpager.widget.ViewPager /** - * Poker Analytics ViewPager + * ViewPager with paging disabled */ -class HomeViewPager(context: Context, attrs: AttributeSet) : ViewPager(context, attrs) { +class NoPagingViewPager(context: Context, attrs: AttributeSet) : ViewPager(context, attrs) { var enablePaging: Boolean = false 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 ba8b6b90..0aa25f23 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 @@ -8,7 +8,7 @@ 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 { +interface RowRepresentable : Displayable, EditDataSource, ImageDecorator { fun getDisplayName(): String { return NULL_TEXT @@ -28,6 +28,17 @@ interface DefaultEditDataSource : EditDataSource, Localizable { } } +/** + * An interface to add an image to a row + */ +interface ImageDecorator { + + val imageRes: Int? + get() { + return null + } +} + /** * An interface used so that enums numericValues can be represented visually * as rows in RecyclerViews 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 c887ff71..a76a4d83 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 @@ -3,6 +3,7 @@ package net.pokeranalytics.android.ui.view import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.SwitchCompat @@ -10,13 +11,25 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible 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 kotlinx.android.synthetic.main.row_history_session.view.* import kotlinx.android.synthetic.main.row_transaction.view.* import net.pokeranalytics.android.R import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Transaction import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter +import net.pokeranalytics.android.ui.extensions.setTextFormat +import net.pokeranalytics.android.ui.graph.AxisFormatting +import net.pokeranalytics.android.ui.graph.setStyle import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable +import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow +import net.pokeranalytics.android.ui.view.rowrepresentable.StatDoubleRow +import net.pokeranalytics.android.ui.view.rowrepresentable.StatRow /** * An interface used to factor the configuration of RecyclerView.ViewHolder @@ -43,6 +56,7 @@ enum class RowViewType(private var layoutRes: Int) { // Row TITLE(R.layout.row_title), TITLE_ARROW(R.layout.row_title_arrow), + TITLE_ICON_ARROW(R.layout.row_title_icon_arrow), TITLE_VALUE(R.layout.row_title_value), TITLE_VALUE_ARROW(R.layout.row_title_value_arrow), TITLE_SWITCH(R.layout.row_title_switch), @@ -58,7 +72,9 @@ enum class RowViewType(private var layoutRes: Int) { ROW_TRANSACTION(R.layout.row_transaction), ROW_BUTTON(R.layout.row_button), ROW_FOLLOW_US(R.layout.row_follow_us), - STAT(R.layout.row_stats_title_value), + STATS(R.layout.row_stats_title_value), + STATS_DOUBLE(R.layout.row_stats_double), + GRAPH(R.layout.row_graph), // Separator SEPARATOR(R.layout.row_separator); @@ -73,9 +89,9 @@ enum class RowViewType(private var layoutRes: Int) { return when (this) { // Row View Holder - HEADER_TITLE, HEADER_TITLE_VALUE, HEADER_TITLE_AMOUNT, HEADER_TITLE_AMOUNT_BIG, - LOCATION_TITLE, INFO, - TITLE, TITLE_ARROW, TITLE_VALUE, TITLE_VALUE_ARROW, TITLE_GRID, TITLE_SWITCH, TITLE_CHECK, TITLE_VALUE_CHECK, + HEADER_TITLE, HEADER_TITLE_VALUE, HEADER_TITLE_AMOUNT, HEADER_TITLE_AMOUNT_BIG, LOCATION_TITLE, + INFO, TITLE, TITLE_ARROW, TITLE_ICON_ARROW, TITLE_VALUE, TITLE_VALUE_ARROW, TITLE_GRID, + TITLE_SWITCH, TITLE_CHECK, TITLE_VALUE_CHECK, DATA, BOTTOM_SHEET_DATA, LOADER -> RowViewHolder(layout) // Row Session @@ -90,8 +106,10 @@ enum class RowViewType(private var layoutRes: Int) { // Row Follow Us ROW_FOLLOW_US -> RowFollowUsViewHolder(layout) - // Row Stat - STAT -> StatsTitleValueViewHolder(layout) + // Row Stats + STATS -> StatsTitleValueViewHolder(layout) + STATS_DOUBLE -> StatsDoubleViewHolder(layout) + GRAPH -> GraphViewHolder(layout) // Separator SEPARATOR -> SeparatorViewHolder(layout) @@ -119,9 +137,8 @@ enum class RowViewType(private var layoutRes: Int) { // Value itemView.findViewById(R.id.value)?.let { if (row.computedStat != null) { - val format = row.computedStat!!.format(itemView.context) - it.setTextColor(format.getColor(itemView.context)) - it.text = format.text + val format = row.computedStat!!.format() + it.setTextFormat(format, itemView.context) } else if (row.value != null) { it.text = row.value } @@ -133,9 +150,7 @@ enum class RowViewType(private var layoutRes: Int) { val listener = View.OnClickListener { adapter.delegate?.onRowSelected(position, row) } - itemView.findViewById(R.id.container)?.let { - it.setOnClickListener(listener) - } + itemView.findViewById(R.id.container)?.setOnClickListener(listener) } } @@ -157,6 +172,13 @@ enum class RowViewType(private var layoutRes: Int) { it.text = adapter.dataSource.stringForRow(row, itemView.context) } + // Icon + itemView.findViewById(R.id.icon)?.let { imageView -> + row.imageRes?.let { imageRes -> + imageView.setImageResource(imageRes) + } + } + // Listener val listener = View.OnClickListener { itemView.findViewById(R.id.switchView)?.let { @@ -166,9 +188,7 @@ enum class RowViewType(private var layoutRes: Int) { } } - itemView.findViewById(R.id.container)?.let { - it.setOnClickListener(listener) - } + itemView.findViewById(R.id.container)?.setOnClickListener(listener) } // Switch @@ -193,16 +213,16 @@ enum class RowViewType(private var layoutRes: Int) { */ inner class RowFollowUsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { - itemView.findViewById(R.id.icon1).setOnClickListener { + itemView.findViewById(R.id.icon1)?.setOnClickListener { adapter.delegate?.onRowSelected(0, row) } - itemView.findViewById(R.id.icon2).setOnClickListener { + itemView.findViewById(R.id.icon2)?.setOnClickListener { adapter.delegate?.onRowSelected(1, row) } - itemView.findViewById(R.id.icon3).setOnClickListener { + itemView.findViewById(R.id.icon3)?.setOnClickListener { adapter.delegate?.onRowSelected(2, row) } - itemView.findViewById(R.id.icon4).setOnClickListener { + itemView.findViewById(R.id.icon4)?.setOnClickListener { adapter.delegate?.onRowSelected(3, row) } } @@ -221,8 +241,59 @@ enum class RowViewType(private var layoutRes: Int) { // Value itemView.findViewById(R.id.value)?.let { view -> adapter.dataSource.contentDescriptorForRow(row)?.textFormat?.let { - view.text = it.text - view.setTextColor(it.getColor(itemView.context)) + view.setTextFormat(it, itemView.context) + } + } + + if (row is StatRow) { + itemView.findViewById(R.id.nextArrow)?.isVisible = row.stat.hasEvolutionGraph + } + + // Listener + val listener = View.OnClickListener { + adapter.delegate?.onRowSelected(position, row) + } + itemView.findViewById(R.id.container)?.setOnClickListener(listener) + } + } + + /** + * Display a stat + */ + inner class StatsDoubleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), + BindableHolder { + override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { + + if (row is StatDoubleRow) { + + // Stat 1 + itemView.findViewById(R.id.stat1Name)?.let { view -> + view.text = "" + row.computedStat1?.stat?.resId?.let { + view.text = view.context.getString(it) + } + } + + itemView.findViewById(R.id.stat1Value)?.let { view -> + view.text = "" + row.computedStat1?.format()?.let { + view.setTextFormat(it, itemView.context) + } + } + + // Stat 2 + itemView.findViewById(R.id.stat2Name)?.let { view -> + view.text = "" + row.computedStat2?.stat?.resId?.let { + view.text = view.context.getString(it) + } + } + + itemView.findViewById(R.id.stat2Value)?.let { view -> + view.text = "" + row.computedStat2?.format()?.let { + view.setTextFormat(it, itemView.context) + } } } @@ -234,6 +305,57 @@ enum class RowViewType(private var layoutRes: Int) { } } + /** + * Display a stat + */ + inner class GraphViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), + BindableHolder { + override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { + + if (row is GraphRow) { + + row.report.results.firstOrNull()?.defaultStatEntries(row.stat, itemView.context)?.let { 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 + } + + itemView.findViewById(R.id.chartContainer)?.let { + it.removeAllViews() + it.addView(chartView) + } + + chartView?.let { + chartView.setStyle(true, AxisFormatting.DEFAULT, context) + chartView.setTouchEnabled(false) + } + +// chartView.highlightValue((entries.size - 1).toFloat(), 0) + } + + } + + // Listener + val listener = View.OnClickListener { + adapter.delegate?.onRowSelected(position, row) + } + itemView.findViewById(R.id.chartContainer)?.setOnClickListener(listener) + } + } + + /** * Display a button in a row */ 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 9a0c441c..9920cbee 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 @@ -15,7 +15,7 @@ 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.util.CurrencyUtils +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 @@ -68,7 +68,7 @@ class SessionRowView : FrameLayout { if (session.isTournament()) { session.tournamentEntryFee?.let { - parameters.add(it.toCurrency(CurrencyUtils.getCurrency(session.bankroll))) + parameters.add(it.toCurrency(session.currency)) } session.tournamentName?.let { @@ -85,7 +85,7 @@ class SessionRowView : FrameLayout { } } else { if (session.cgSmallBlind != null && session.cgBigBlind != null) { - parameters.add(session.getBlinds(context)) + parameters.add(session.getFormattedBlinds()) } session.game?.let { parameters.add(session.getFormattedGame()) @@ -149,9 +149,8 @@ class SessionRowView : FrameLayout { rowHistorySession.infoTitle.isVisible = false val result = session.result?.net ?: 0.0 - val formattedStat = ComputedStat(Stat.NETRESULT, result, currency = CurrencyUtils.getCurrency(session.bankroll)).format(context) - rowHistorySession.gameResult.setTextColor(formattedStat.getColor(context)) - rowHistorySession.gameResult.text = formattedStat.text + val formattedStat = ComputedStat(Stat.NET_RESULT, result, currency = session.currency).format() + rowHistorySession.gameResult.setTextFormat(formattedStat, context) } } 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 ab45b09c..f9971876 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 @@ -10,6 +10,7 @@ import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.ComputedStat import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.ui.extensions.setTextFormat import net.pokeranalytics.android.util.extensions.getDayNumber import net.pokeranalytics.android.util.extensions.getShortDayName @@ -62,9 +63,8 @@ class TransactionRowView : FrameLayout { rowTransaction.transactionSubtitle.text = subtitle // Amount - val formattedStat = ComputedStat(Stat.NETRESULT, transaction.amount).format(context) - rowTransaction.transactionAmount.setTextColor(formattedStat.getColor(context)) - rowTransaction.transactionAmount.text = formattedStat.text + val formattedStat = ComputedStat(Stat.NET_RESULT, transaction.amount).format() + rowTransaction.transactionAmount.setTextFormat(formattedStat, context) } 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 f5cfdc6a..1e3b3152 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 @@ -11,6 +11,7 @@ import net.pokeranalytics.android.ui.view.RowViewType enum class BankrollRow : RowRepresentable, DefaultEditDataSource { LIVE, + INITIAL_VALUE, CURRENCY, RATE, REFRESH_RATE; @@ -19,6 +20,7 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { get() { return when (this) { LIVE -> R.string.online + INITIAL_VALUE -> R.string.initial_value CURRENCY -> R.string.currency RATE -> R.string.rate REFRESH_RATE -> R.string.refresh_rate @@ -29,6 +31,7 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { get() { return when (this) { LIVE -> RowViewType.TITLE_SWITCH.ordinal + INITIAL_VALUE -> RowViewType.TITLE_VALUE.ordinal CURRENCY -> RowViewType.TITLE_VALUE_ARROW.ordinal RATE -> RowViewType.TITLE_VALUE.ordinal REFRESH_RATE -> RowViewType.ROW_BUTTON.ordinal @@ -39,6 +42,7 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { get() { return when (this) { LIVE -> BottomSheetType.NONE + INITIAL_VALUE -> BottomSheetType.NUMERIC_TEXT CURRENCY -> BottomSheetType.NONE RATE -> BottomSheetType.NUMERIC_TEXT REFRESH_RATE -> BottomSheetType.NONE @@ -47,6 +51,13 @@ enum class BankrollRow : RowRepresentable, DefaultEditDataSource { 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( diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt index e9ac2482..5bc99a77 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt @@ -33,7 +33,8 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In GENERAL -> arrayListOf( CASH_TOURNAMENT, LIVE_ONLINE, - GAME, LIMIT_TYPE, + GAME, + LIMIT_TYPE, TABLE_SIZE ) DATE -> arrayListOf( @@ -47,23 +48,21 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In ) TIME_FRAME -> arrayListOf( SESSION_DURATION, - RANGE + TIME_FRAME_RANGE ) SESSIONS -> arrayListOf(FilterSectionRow.SESSIONS) BANKROLLS -> arrayListOf( BANKROLL ) CASH -> arrayListOf( - BLINDS, - CASH_RE_BUY_COUNT + BLIND ) TOURNAMENT -> arrayListOf( TOURNAMENT_TYPE, - COMPLETION_PERCENTAGE, - PLACE, - PLAYERS_COUNT, - TOURNAMENT_RE_BUY_COUNT, - BUY_IN + TOURNAMENT_NAME, + TOURNAMENT_FEATURE, + ENTRY_FEE, + NUMBER_OF_PLAYERS ) ONLINE -> arrayListOf( MULTI_TABLING @@ -72,7 +71,6 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In LOCATION ) PLAYERS -> arrayListOf( - NUMBER_OF_PLAYERS, MULTI_PLAYER ) RESULT -> arrayListOf( 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 d1c6a567..3e8b783e 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,351 +1,50 @@ package net.pokeranalytics.android.ui.view.rowrepresentable -import android.content.Context import android.text.InputType import net.pokeranalytics.android.R -import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.model.filter.QueryCondition -import net.pokeranalytics.android.model.interfaces.Manageable -import net.pokeranalytics.android.model.realm.FilterCondition import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.RowViewType -import net.pokeranalytics.android.util.NULL_TEXT -import net.pokeranalytics.android.util.extensions.formatted -import net.pokeranalytics.android.util.extensions.round -import java.text.DateFormatSymbols import java.util.* -sealed class FilterElementRow : RowRepresentable { - - interface Duration { - var minutes: Int - } - - interface Operator - interface MoreOperator : Operator - interface LessOperator : Operator - - open class BoolFilterElementRow : FilterElementRow() - open class DateFilterElementRow(var dateValue: Date = Date()) : FilterElementRow() - open class NumericFilterElementRow(open val doubleValue: Double = 0.0) : FilterElementRow() - open class StringFilterElementRow(val stringValue: String = "") : FilterElementRow() - - // Subclasses - open class SingleValueFilterElementRow(open val intValue: Int) : NumericFilterElementRow() { - override val doubleValue: Double - get() { - return intValue.toDouble() - } - } - - open class DataFilterElementRow(data: Manageable) : StringFilterElementRow(data.id) { - val id: String = data.id - val name: String = (data as RowRepresentable).getDisplayName() - } - - open class StaticDataFilterElementRow(val row: RowRepresentable, val id: Int) : NumericFilterElementRow(id.toDouble()) { - - override val resId: Int? = row.resId - val name: String = row.getDisplayName() - - fun getDataLocalizedTitle(context: Context): String { - return row.localizedTitle(context) - } - } - - open class DurationFilterElement : NumericFilterElementRow(), Duration { - override var minutes: Int = 0 - override val doubleValue: Double - get() { - return minutes.toDouble() - } - } - - open class AmountFilterElement : NumericFilterElementRow() { - var amount: Double = 0.0 - override val doubleValue: Double - get() { - return amount - } - } - - object Cash : BoolFilterElementRow() - object Tournament : BoolFilterElementRow() - object Live : BoolFilterElementRow() - object Online : BoolFilterElementRow() - object Today : BoolFilterElementRow() - object Yesterday : BoolFilterElementRow() - object TodayAndYesterday : BoolFilterElementRow() - object CurrentWeek : BoolFilterElementRow() - object CurrentMonth : BoolFilterElementRow() - object CurrentYear : BoolFilterElementRow() - object Weekday : BoolFilterElementRow() - object Weekend : BoolFilterElementRow() - object From : DateFilterElementRow() - object To : DateFilterElementRow() - - // Data classes - holding value - object ResultMoreThan : AmountFilterElement(), MoreOperator - - object ResultLessThan : AmountFilterElement(), LessOperator - object DurationMoreThan : DurationFilterElement(), MoreOperator - object DurationLessThan : DurationFilterElement(), LessOperator - object ReBuyMoreThan : AmountFilterElement(), MoreOperator - object ReBuyLessThan : AmountFilterElement(), LessOperator - - - data class Year(val year: Int) : SingleValueFilterElementRow(year) - data class Month(val month: Int) : SingleValueFilterElementRow(month) - data class Day(val day: Int) : SingleValueFilterElementRow(day) - - data class Blind(var sb: Double? = null, var bb: Double? = null, var code: String? = null) : FilterElementRow() { - val name: String - get() { - val currencyCode = code ?: Currency.getInstance(Locale.getDefault()).currencyCode - val currencySymbol = Currency.getInstance(currencyCode).symbol - return if (sb == null) NULL_TEXT else "$currencySymbol ${sb?.formatted()}/${bb?.round()}" - } - } - - //TODO: Refactor? - data class PastDays(var lastDays: Int = 0) : SingleValueFilterElementRow(lastDays) { - override val intValue: Int - get() { - return lastDays - } - } - - data class LastGames(var lastGames: Int) : SingleValueFilterElementRow(lastGames) { - override val intValue: Int - get() { - return lastGames - } - } - - data class LastSessions(var lastSessions: Int) : SingleValueFilterElementRow(lastSessions) { - override val intValue: Int - get() { - return lastSessions - } - } - - data class Limit(val limit: net.pokeranalytics.android.model.Limit) : StaticDataFilterElementRow(limit, limit.ordinal) - data class TableSize(val tableSize: net.pokeranalytics.android.model.TableSize) : StaticDataFilterElementRow(tableSize, tableSize.numberOfPlayer) - - data class Bankroll(val bankroll: Manageable) : DataFilterElementRow(bankroll) - data class Game(val game: Manageable) : DataFilterElementRow(game) - data class Location(val location: Manageable) : DataFilterElementRow(location) - data class TournamentName(val tournamentName: Manageable) : DataFilterElementRow(tournamentName) - data class AllTournamentFeature(val tournamentFeature: Manageable) : DataFilterElementRow(tournamentFeature) - data class AnyTournamentFeature(val tournamentFeature: Manageable) : DataFilterElementRow(tournamentFeature) - - lateinit var filterSectionRow: FilterSectionRow - - val filterName: String = this.queryCondition.name - - private val queryCondition: QueryCondition - get() { - return when (this) { - is Cash -> QueryCondition.CASH - is Tournament -> QueryCondition.TOURNAMENT - is Blind -> QueryCondition.BLINDS - is From -> QueryCondition.STARTED_FROM_DATE - is To -> QueryCondition.ENDED_TO_DATE - is Month -> QueryCondition.MONTH - is Day -> QueryCondition.DAY_OF_WEEK - is Year -> QueryCondition.YEAR - is Live -> QueryCondition.LIVE - is Online -> QueryCondition.ONLINE - is Weekday -> QueryCondition.WEEK_DAY - is Weekend -> QueryCondition.WEEK_END - is Today -> QueryCondition.TODAY - is Yesterday -> QueryCondition.YESTERDAY - is TodayAndYesterday -> QueryCondition.TODAY_AND_YESTERDAY - is CurrentWeek -> QueryCondition.THIS_WEEK - is CurrentMonth -> QueryCondition.THIS_MONTH - is CurrentYear -> QueryCondition.THIS_YEAR - is PastDays -> QueryCondition.PAST_DAYS - is Limit -> QueryCondition.LIMIT - is TableSize -> QueryCondition.TABLE_SIZE - is Game -> QueryCondition.GAME - is Bankroll -> QueryCondition.BANKROLL - is Location -> QueryCondition.LOCATION - is TournamentName -> QueryCondition.TOURNAMENT_NAME - is AnyTournamentFeature -> QueryCondition.ANY_TOURNAMENT_FEATURES - is AllTournamentFeature -> QueryCondition.ALL_TOURNAMENT_FEATURES - is ResultMoreThan -> QueryCondition.MORE_THAN_NET_RESULT - is ResultLessThan -> QueryCondition.LESS_THAN_NET_RESULT - is DurationMoreThan -> QueryCondition.MORE_THAN_DURATION - is DurationLessThan -> QueryCondition.LESS_THAN_DURATION - - - //TODO: Check the conditions - is LastGames -> QueryCondition.LAST_GAMES - is LastSessions -> QueryCondition.LAST_SESSIONS - is ReBuyMoreThan -> QueryCondition.MIN_RE_BUY - is ReBuyLessThan -> QueryCondition.MAX_RE_BUY - - else -> throw PokerAnalyticsException.UnknownQueryTypeForRow(this) - } - } - - fun contains(filterConditions: List): Boolean { - return when (this) { - is SingleValueFilterElementRow -> filterConditions.any { - it.values.contains(this.intValue) - } - is DataFilterElementRow -> filterConditions.any { - it.ids.contains(this.id) - } - else -> true - } - } - - - override val resId: Int? - get() { - return when (this) { - is Cash -> R.string.cash_game - is Tournament -> R.string.tournament - is Today -> R.string.today - is Yesterday -> R.string.yesterday - is TodayAndYesterday -> R.string.yesterday_and_today - is CurrentWeek -> R.string.current_week - is CurrentMonth -> R.string.current_month - is CurrentYear -> R.string.current_year - is From -> R.string.from - is To -> R.string.to - is Live -> R.string.live - is Online -> R.string.online - is Weekday -> R.string.week_days - is Weekend -> R.string.weekend - is PastDays -> R.string.period_in_days - is LastGames -> R.string.last_records - is LastSessions -> R.string.last_sessions - is ReBuyMoreThan -> R.string.minimum - is ReBuyLessThan -> R.string.maximum - is MoreOperator -> R.string.more_than - is LessOperator -> R.string.less_than - else -> null - } - } - - override val viewType: Int - get() { - return when (this) { - is PastDays, is From, is To, is LastGames, is LastSessions, is ReBuyMoreThan, is ReBuyLessThan, - is DurationMoreThan, is DurationLessThan -> RowViewType.TITLE_VALUE_CHECK.ordinal - else -> RowViewType.TITLE_CHECK.ordinal - } - } - - override val bottomSheetType: BottomSheetType - get() { - return when (this) { - is PastDays, is LastGames, is LastSessions, is ReBuyMoreThan, is ReBuyLessThan -> BottomSheetType.EDIT_TEXT - is DurationMoreThan, is DurationLessThan -> BottomSheetType.DOUBLE_EDIT_TEXT - else -> BottomSheetType.NONE - } - } - - override fun editingDescriptors(map: Map): ArrayList? { - return when (this) { - is PastDays -> { - val pastDays: String? by map - arrayListOf( - RowRepresentableEditDescriptor(pastDays, R.string.period_in_days, inputType = InputType.TYPE_CLASS_NUMBER) - ) - } - is LastGames -> { - val lastGames: String? by map - arrayListOf( - RowRepresentableEditDescriptor(lastGames, R.string.last_records, inputType = InputType.TYPE_CLASS_NUMBER) - ) - } - is LastSessions -> { - val lastSessions: String? by map - arrayListOf( - RowRepresentableEditDescriptor(lastSessions, R.string.last_sessions, inputType = InputType.TYPE_CLASS_NUMBER) - ) - } - - //TODO: Refactor that - is AmountFilterElement -> { - val amount: String? by map - arrayListOf( - RowRepresentableEditDescriptor(amount, R.string.amount, inputType = InputType.TYPE_CLASS_NUMBER) - ) - } - - is DurationFilterElement -> { - 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) - ) - } - else -> super.editingDescriptors(map) - } - } - - override fun getDisplayName(): String { - return when (this) { - is SingleValueFilterElementRow -> { - when (this) { - is Day -> DateFormatSymbols.getInstance(Locale.getDefault()).weekdays[this.intValue] - is Month -> DateFormatSymbols.getInstance(Locale.getDefault()).months[this.intValue] - else -> "${this.intValue}" - } - } - is DataFilterElementRow -> this.name - is StaticDataFilterElementRow -> this.name - is Blind -> this.name - else -> super.getDisplayName() - } - } - - override fun localizedTitle(context: Context): String { - return when (this) { - is StaticDataFilterElementRow -> this.getDataLocalizedTitle(context) - else -> super.localizedTitle(context) - } - } - - val sectionToExclude: List? - get() { - val excluded = arrayListOf() - if (!this.filterSectionRow.allowMultiSelection) { - excluded.add(this.filterSectionRow) - } - this.filterSectionRow.exclusiveWith?.let { exclusives -> - excluded.addAll(exclusives) - } - - if (excluded.size > 0) { - return excluded - } - return null - } - - /* - override fun editingDescriptors(map: Map): ArrayList? { - when (this) { - PAST_DAYS -> { - val defaultValue: String? by map - val data = arrayListOf() - data.add( - RowRepresentableEditDescriptor( - defaultValue, - inputType = InputType.TYPE_CLASS_NUMBER - ) - ) - } - } - - return super.editingDescriptors(map) - } - */ +interface FilterElementRow : RowRepresentable { + override fun editingDescriptors(map: Map): ArrayList? { + return when (this) { + 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) + ) + } + is QueryCondition.ListOfValues<*> -> { + val valueAsString: String? by map + arrayListOf( + RowRepresentableEditDescriptor(valueAsString, this.resId, inputType = InputType.TYPE_CLASS_NUMBER) + ) + } + else -> super.editingDescriptors(map) + } + } + + var filterSectionRow: FilterSectionRow + + val sectionToExclude: List? + get() { + val excluded = arrayListOf() + if (!this.filterSectionRow.allowMultiSelection) { + excluded.add(this.filterSectionRow) + } + this.filterSectionRow.exclusiveWith?.let { exclusives -> + excluded.addAll(exclusives) + } + + if (excluded.size > 0) { + return excluded + } + return null + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt index c198a5f4..f4bd2859 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt @@ -1,49 +1,44 @@ package net.pokeranalytics.android.ui.view.rowrepresentable -import io.realm.Realm -import io.realm.Sort -import io.realm.kotlin.where import net.pokeranalytics.android.R -import net.pokeranalytics.android.model.LiveData -import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.model.Criteria +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.filter.QueryCondition.NumberOfTable.* import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow.* -import java.text.DateFormatSymbols -import java.util.* enum class FilterSectionRow(override val resId: Int?) : RowRepresentable { - CASH_TOURNAMENT(net.pokeranalytics.android.R.string.cash_or_tournament), - LIVE_ONLINE(net.pokeranalytics.android.R.string.live_or_online), - GAME(net.pokeranalytics.android.R.string.games), - LIMIT_TYPE(net.pokeranalytics.android.R.string.limits), - TABLE_SIZE(net.pokeranalytics.android.R.string.table_sizes), - DYNAMIC_DATE(net.pokeranalytics.android.R.string.dynamic_date), - FIXED_DATE(net.pokeranalytics.android.R.string.fixed_date), - DURATION(net.pokeranalytics.android.R.string.duration), - YEAR(net.pokeranalytics.android.R.string.year), - WEEKDAYS_OR_WEEKEND(net.pokeranalytics.android.R.string.weekdays_or_weekend), - DAY_OF_WEEK(net.pokeranalytics.android.R.string.day_of_the_week), - MONTH_OF_YEAR(net.pokeranalytics.android.R.string.month_of_the_year), - SESSION_DURATION(net.pokeranalytics.android.R.string.session_duration), - RANGE(net.pokeranalytics.android.R.string.hour_slot), + CASH_TOURNAMENT(R.string.cash_or_tournament), + LIVE_ONLINE(R.string.live_or_online), + GAME(R.string.games), + LIMIT_TYPE(R.string.limits), + TABLE_SIZE(R.string.table_sizes), + DYNAMIC_DATE(R.string.dynamic_date), + FIXED_DATE(R.string.fixed_date), + DURATION(R.string.duration), + YEAR(R.string.year), + WEEKDAYS_OR_WEEKEND(R.string.weekdays_or_weekend), + DAY_OF_WEEK(R.string.day_of_the_week), + MONTH_OF_YEAR(R.string.month_of_the_year), + SESSION_DURATION(R.string.session_duration), + TIME_FRAME_RANGE(R.string.hour_slot), SESSIONS(R.string.sessions), - BLINDS(net.pokeranalytics.android.R.string.blinds), - CASH_RE_BUY_COUNT(net.pokeranalytics.android.R.string.rebuy_count), - TOURNAMENT_TYPE(net.pokeranalytics.android.R.string.tournament_types), - TOURNAMENT_NAME(net.pokeranalytics.android.R.string.tournament_name), - TOURNAMENT_FEATURE(net.pokeranalytics.android.R.string.tournament_feature), - COMPLETION_PERCENTAGE(net.pokeranalytics.android.R.string.tournament_completion_percentage_interval), - PLACE(net.pokeranalytics.android.R.string.final_position), - PLAYERS_COUNT(net.pokeranalytics.android.R.string.players_count), - TOURNAMENT_RE_BUY_COUNT(net.pokeranalytics.android.R.string.rebuy_count), - BUY_IN(net.pokeranalytics.android.R.string.buyin), - MULTI_TABLING(net.pokeranalytics.android.R.string.multi_tabling), - VALUE(net.pokeranalytics.android.R.string.value), - LOCATION(net.pokeranalytics.android.R.string.locations), - BANKROLL(net.pokeranalytics.android.R.string.bankrolls), - NUMBER_OF_PLAYERS(net.pokeranalytics.android.R.string.number_of_players), - MULTI_PLAYER(net.pokeranalytics.android.R.string.multiplayer), + BLIND(R.string.blinds), + CASH_RE_BUY_COUNT(R.string.rebuy_count), + TOURNAMENT_TYPE(R.string.tournament_types), + TOURNAMENT_NAME(R.string.tournament_name), + TOURNAMENT_FEATURE(R.string.tournament_feature), + COMPLETION_PERCENTAGE(R.string.tournament_completion_percentage_interval), + PLACE(R.string.final_position), + PLAYERS_COUNT(R.string.players_count), + TOURNAMENT_RE_BUY_COUNT(R.string.rebuy_count), + ENTRY_FEE(R.string.buyin), + MULTI_TABLING(R.string.multi_tabling), + VALUE(R.string.value), + LOCATION(R.string.locations), + BANKROLL(R.string.bankrolls), + NUMBER_OF_PLAYERS(R.string.number_of_players), + MULTI_PLAYER(R.string.multiplayer), ; private enum class SelectionType { @@ -64,129 +59,59 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable { when (this@FilterSectionRow) { // General - CASH_TOURNAMENT -> arrayListOf(Cash, Tournament) - LIVE_ONLINE -> arrayListOf(Live, Online) - GAME -> { - val games = arrayListOf() - val realm = Realm.getDefaultInstance() - LiveData.GAME.items(realm).forEach { - val game = Game(it as net.pokeranalytics.android.model.realm.Game) - games.add(game) - } - realm.close() - games - } - LIMIT_TYPE -> { - val limits = arrayListOf() - net.pokeranalytics.android.model.Limit.values().forEach { - limits.add(Limit(it)) - } - limits - } - TABLE_SIZE -> { - val tableSizes = arrayListOf() - val realm = Realm.getDefaultInstance() - val distinctTableSizes = realm.where().distinct("tableSize").findAll().sort("tableSize", Sort.ASCENDING) - distinctTableSizes.forEach { session -> - session.tableSize?.let { tableSize -> - tableSizes.add(TableSize(net.pokeranalytics.android.model.TableSize(tableSize))) - } - } - realm.close() - tableSizes - } - + CASH_TOURNAMENT -> Criteria.SessionTypes.queryConditions + LIVE_ONLINE -> Criteria.BankrollTypes.queryConditions + GAME -> Criteria.Games.queryConditions + LIMIT_TYPE -> Criteria.Limits.queryConditions + TABLE_SIZE -> Criteria.TableSizes.queryConditions // Date DYNAMIC_DATE -> arrayListOf( - Today, - Yesterday, - TodayAndYesterday, - CurrentWeek, - CurrentMonth, - CurrentYear + QueryCondition.IsToday, + QueryCondition.WasYesterday, + QueryCondition.WasTodayAndYesterday, + QueryCondition.DuringThisWeek, + QueryCondition.DuringThisMonth, + QueryCondition.DuringThisYear ) - FIXED_DATE -> arrayListOf(From, To) - DURATION -> arrayListOf(PastDays(0)) - YEAR -> { - val years = arrayListOf() - val realm = Realm.getDefaultInstance() - val distinctYears = realm.where().distinct("year").findAll().sort("year", Sort.DESCENDING) - distinctYears.forEach { session -> - session.year?.let { year -> - years.add(Year(year)) - } - } - realm.close() - years - } - WEEKDAYS_OR_WEEKEND -> arrayListOf(Weekday, Weekend) - DAY_OF_WEEK -> { - val daysOfWeek = arrayListOf() - DateFormatSymbols.getInstance(Locale.getDefault()).weekdays.forEachIndexed { index, day -> - if (day.isNotEmpty()) { - daysOfWeek.add(Day(index)) - } - } - daysOfWeek - } - MONTH_OF_YEAR -> { - val months = arrayListOf() - DateFormatSymbols.getInstance(Locale.getDefault()).months.forEachIndexed { index, month -> - if (month.isNotEmpty()) { - months.add(Month(index)) - } - } - months - } + FIXED_DATE -> arrayListOf(QueryCondition.StartedFromDate(), QueryCondition.EndedToDate()) + DURATION -> arrayListOf(QueryCondition.PastDay()) + WEEKDAYS_OR_WEEKEND -> arrayListOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd) + YEAR -> Criteria.Years.queryConditions + DAY_OF_WEEK -> Criteria.DaysOfWeek.queryConditions + MONTH_OF_YEAR -> Criteria.MonthsOfYear.queryConditions // Duration - SESSION_DURATION -> arrayListOf(DurationMoreThan as FilterElementRow, DurationLessThan as FilterElementRow) - RANGE -> arrayListOf(From, To) + SESSION_DURATION -> QueryCondition.moreOrLess() + TIME_FRAME_RANGE -> arrayListOf(QueryCondition.StartedFromTime(), QueryCondition.EndedToTime()) // Sessions - SESSIONS -> arrayListOf(LastGames(0), LastSessions(0)) + SESSIONS -> arrayListOf(QueryCondition.LastGame(), QueryCondition.LastSession()) // Cash - BLINDS -> { - - // TODO: Improve the way we get the blinds distinctly - val blinds = arrayListOf() - val realm = Realm.getDefaultInstance() - val sessions = realm.where().findAll().sort("cgSmallBlind", Sort.ASCENDING) - - val distinctBlinds: ArrayList = ArrayList() - val blindsHashMap: ArrayList = ArrayList() - sessions.forEach { - } - - distinctBlinds.forEach { session -> - blinds.add(Blind(session.cgSmallBlind, session.cgBigBlind, session.bankroll?.currency?.code)) - } - realm.close() - - blinds - } - CASH_RE_BUY_COUNT -> arrayListOf(ReBuyMoreThan as FilterElementRow, ReBuyLessThan as FilterElementRow) + BLIND -> Criteria.Blinds.queryConditions +// CASH_RE_BUY_COUNT -> QueryCondition.moreOrLess() // Tournament - TOURNAMENT_TYPE -> arrayListOf() - COMPLETION_PERCENTAGE -> arrayListOf() - PLACE -> arrayListOf() - PLAYERS_COUNT -> arrayListOf() - TOURNAMENT_RE_BUY_COUNT -> arrayListOf() - BUY_IN -> arrayListOf() - - - TOURNAMENT_NAME -> arrayListOf() - TOURNAMENT_FEATURE -> arrayListOf() - LOCATION -> arrayListOf() - BANKROLL -> arrayListOf() - MULTI_TABLING -> arrayListOf() - NUMBER_OF_PLAYERS -> arrayListOf() - MULTI_PLAYER -> arrayListOf() - - VALUE -> arrayListOf() - + TOURNAMENT_TYPE -> Criteria.TournamentTypes.queryConditions +// COMPLETION_PERCENTAGE -> arrayListOf() +// PLACE -> QueryCondition.moreOrLess() + PLAYERS_COUNT -> QueryCondition.moreOrLess() +// TOURNAMENT_RE_BUY_COUNT -> QueryCondition.moreOrLess() + ENTRY_FEE -> Criteria.TournamentFees.queryConditions + + TOURNAMENT_NAME -> Criteria.TournamentNames.queryConditions + TOURNAMENT_FEATURE -> Criteria.TournamentFeatures.queryConditions + LOCATION -> Criteria.Locations.queryConditions + BANKROLL -> Criteria.Bankrolls.queryConditions + MULTI_TABLING -> QueryCondition.moreOrLess() +// NUMBER_OF_PLAYERS -> QueryCondition.moreOrLess() +// MULTI_PLAYER -> arrayListOf() + + VALUE -> arrayListOf().apply { + addAll(QueryCondition.moreOrLess()) + addAll(QueryCondition.moreOrLess()) + } + else -> arrayListOf() }.apply { this.forEach { it.filterSectionRow = this@FilterSectionRow 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 new file mode 100644 index 00000000..5803cc84 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/GraphRow.kt @@ -0,0 +1,14 @@ +package net.pokeranalytics.android.ui.view.rowrepresentable + +import net.pokeranalytics.android.calculus.Report +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType + + +class GraphRow(var report: Report, var stat: Stat) : RowRepresentable { + + override val viewType: Int + get() = RowViewType.GRAPH.ordinal + +} 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 new file mode 100644 index 00000000..4e2fe8bc --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/MoreTabRow.kt @@ -0,0 +1,31 @@ +package net.pokeranalytics.android.ui.view.rowrepresentable + +import net.pokeranalytics.android.R +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType + +/** + * An enum managing the rows in the more tabs + */ +enum class MoreTabRow : RowRepresentable { + BANKROLL, + SETTINGS; + + override val resId: Int? + get() { + return when(this) { + BANKROLL -> R.string.bankroll + SETTINGS -> R.string.services + } + } + + override val imageRes: Int? + get() { + return when(this) { + BANKROLL -> R.drawable.ic_outline_lock + SETTINGS -> R.drawable.ic_outline_settings + } + } + + override val viewType: Int = RowViewType.TITLE_ICON_ARROW.ordinal +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/ReportRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/ReportRow.kt new file mode 100644 index 00000000..0d10b538 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/ReportRow.kt @@ -0,0 +1,64 @@ +package net.pokeranalytics.android.ui.view.rowrepresentable + +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.Criteria +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType + +/** + * An enum managing the report rows + */ +enum class ReportRow : RowRepresentable { + BLINDS, + BUY_IN, + DAY_OF_WEEKS, + GENERAL, + LOCATIONS, + NUMBER_OF_TABLES, + TOURNAMENT_TYPES, + GAME; + + + companion object { + /** + * Return the report rows + */ + fun getRows(): ArrayList { + val rows = ArrayList() + rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.comparison)) + rows.addAll(values()) + return rows + } + } + + override val resId: Int? + get() { + return when (this) { + BLINDS -> R.string.blinds + BUY_IN -> R.string.buyin + DAY_OF_WEEKS -> R.string.day_of_the_week + GENERAL -> R.string.general + LOCATIONS -> R.string.locations + NUMBER_OF_TABLES -> R.string.number_of_tables + TOURNAMENT_TYPES -> R.string.tournament_type_complete + GAME -> R.string.game + } + } + + override val viewType: Int = RowViewType.TITLE_ARROW.ordinal + + val criteria: List + get() { + return when (this) { + BLINDS -> listOf(Criteria.Blinds) + BUY_IN -> listOf(Criteria.TournamentFees) + DAY_OF_WEEKS -> listOf(Criteria.DaysOfWeek) + GENERAL -> listOf(Criteria.SessionTypes, Criteria.BankrollTypes) + LOCATIONS -> listOf(Criteria.Locations) + NUMBER_OF_TABLES -> listOf() //TODO + TOURNAMENT_TYPES -> listOf(Criteria.TournamentTypes) + GAME -> listOf(Criteria.Games) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SeparatorRowRepresentable.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SeparatorRow.kt similarity index 75% rename from app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SeparatorRowRepresentable.kt rename to app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SeparatorRow.kt index 7497d908..a45bfb04 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SeparatorRowRepresentable.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SeparatorRow.kt @@ -6,6 +6,6 @@ import net.pokeranalytics.android.ui.view.RowViewType /** * A class to display separator as row representable */ -class SeparatorRowRepresentable(customViewType: RowViewType? = RowViewType.SEPARATOR) : RowRepresentable { +class SeparatorRow(customViewType: RowViewType? = RowViewType.SEPARATOR) : RowRepresentable { override val viewType: Int = customViewType?.ordinal ?: RowViewType.SEPARATOR.ordinal } 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 27b2c73f..40a49efe 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 @@ -68,7 +68,7 @@ enum class SessionRow : RowRepresentable { POSITION, PLAYERS, TIPS, - SeparatorRowRepresentable(), + SeparatorRow(), GAME, INITIAL_BUY_IN, LOCATION, @@ -77,7 +77,7 @@ enum class SessionRow : RowRepresentable { TOURNAMENT_TYPE, TOURNAMENT_NAME, TOURNAMENT_FEATURE, - SeparatorRowRepresentable(), + SeparatorRow(), START_DATE, END_DATE, BREAK_TIME, @@ -98,7 +98,7 @@ enum class SessionRow : RowRepresentable { CASHED_OUT, BUY_IN, TIPS, - SeparatorRowRepresentable(), + SeparatorRow(), GAME, BLINDS, LOCATION, @@ -112,7 +112,7 @@ enum class SessionRow : RowRepresentable { } else { arrayListOf( NET_RESULT, - SeparatorRowRepresentable(), + SeparatorRow(), GAME, BLINDS, LOCATION, diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/StatDoubleRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/StatDoubleRow.kt new file mode 100644 index 00000000..6c27eadd --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/StatDoubleRow.kt @@ -0,0 +1,13 @@ +package net.pokeranalytics.android.ui.view.rowrepresentable + +import net.pokeranalytics.android.calculus.ComputedStat +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType + + +class StatDoubleRow(var computedStat1: ComputedStat? = null, var computedStat2: ComputedStat? = null) : RowRepresentable { + + override val viewType: Int + get() = RowViewType.STATS_DOUBLE.ordinal + +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/StatRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/StatRow.kt new file mode 100644 index 00000000..390d0656 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/StatRow.kt @@ -0,0 +1,34 @@ +package net.pokeranalytics.android.ui.view.rowrepresentable + +import android.content.Context +import net.pokeranalytics.android.calculus.ComputedStat +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.ui.view.RowRepresentable +import net.pokeranalytics.android.ui.view.RowViewType + + +class StatRow(stat: Stat, computedStat: ComputedStat?, groupName: String = "", var title: String? = null) : RowRepresentable { + + var stat: Stat = stat + var computedStat: ComputedStat? = computedStat + var groupName: String = groupName + + override val viewType: Int + get() = RowViewType.STATS.ordinal + + override val resId: Int? + get() = this.stat.resId + + + override fun localizedTitle(context: Context): String { + + this.title?.let { + return it + } + this.resId?.let { + return context.getString(it) + } + return "LOCALISATION NOT FOUND" + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/util/ColorUtils.kt b/app/src/main/java/net/pokeranalytics/android/util/ColorUtils.kt new file mode 100644 index 00000000..62ed25f3 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/ColorUtils.kt @@ -0,0 +1,35 @@ +package net.pokeranalytics.android.util + +import android.content.Context +import android.graphics.Color +import net.pokeranalytics.android.R +import java.util.* + +class ColorUtils { + + companion object { + + fun almostRandomColor(index: Int, context: Context) : Int { + + return when (index) { + 0 -> context.getColor(R.color.green_light) + 1 -> context.getColor(R.color.blue) + 2 -> context.getColor(R.color.red) + 3 -> context.getColor(R.color.purple) + 4 -> Color.CYAN + 5 -> Color.MAGENTA + 6 -> Color.YELLOW + else -> { + val rd = Random() + val r = 128 + rd.nextInt(127) + val g = 128 + rd.nextInt(127) + val b = 128 + rd.nextInt(127) + Color.argb(255, r, g, b) + } + } + + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/CurrencyUtils.kt b/app/src/main/java/net/pokeranalytics/android/util/CurrencyUtils.kt deleted file mode 100644 index c3fc3dd2..00000000 --- a/app/src/main/java/net/pokeranalytics/android/util/CurrencyUtils.kt +++ /dev/null @@ -1,63 +0,0 @@ -package net.pokeranalytics.android.util - -import android.content.Context -import net.pokeranalytics.android.model.realm.Bankroll -import java.text.NumberFormat -import java.util.* - -class CurrencyUtils { - - companion object { - - /** - * return the currency associated with this bankroll - */ - fun getCurrency(bankroll: Bankroll? = null) : Currency { - val currencyCode = bankroll?.currency?.code ?: CurrencyUtils.getLocaleCurrency().currencyCode - return Currency.getInstance(currencyCode) - } - - /** - * Get a currency formatter - */ - fun getCurrencyFormatter(context: Context, currency: Currency? = null) : NumberFormat { - val currencyFormatter = NumberFormat.getCurrencyInstance(Preferences.getCurrencyLocale(context)) - currency?.let { - currencyFormatter.currency = it - } - currencyFormatter.minimumFractionDigits = 0 - currencyFormatter.maximumFractionDigits = 2 - return currencyFormatter - } - - /** - * Get a currency rate formatter - */ - fun getCurrencyRateFormatter() : NumberFormat { - val currencyFormatter = NumberFormat.getInstance() - currencyFormatter.minimumFractionDigits = 0 - currencyFormatter.maximumFractionDigits = 6 - return currencyFormatter - } - - /** - * Return the locale currency, or en_US if there - */ - fun getLocaleCurrency() : java.util.Currency { - return try { - java.util.Currency.getInstance(Locale.getDefault()) - } catch (ex: Exception) { - when (Locale.getDefault().language) { - "en" -> java.util.Currency.getInstance(Locale("en", "US")) - "fr" -> java.util.Currency.getInstance(Locale("fr", "FR")) - "es" -> java.util.Currency.getInstance(Locale("es", "ES")) - "de" -> java.util.Currency.getInstance(Locale("de", "DE")) - "ja" -> java.util.Currency.getInstance(Locale("ja", "JP")) - "zh" -> java.util.Currency.getInstance(Locale("zh", "CN")) - else -> java.util.Currency.getInstance(Locale("en", "US")) - } - } - } - } - -} \ No newline at end of file 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 d11daa9a..604b19f7 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt @@ -20,7 +20,7 @@ class FakeDataManager { val buyinList = arrayListOf(100.0, 200.0, 300.0, 500.0, 1000.0, 2000.0) val resultsList = arrayListOf( - -2500.0, -2000.0, -1500.0, -1000.0, -500.0, 200.0, 1000.0, 1500.0, 2500.0 + -2500.0, -2000.0, -1500.0, -1000.0, -500.0, 200.0, 1000.0, 1500.0, 2500.0, 3000.0 ) val commitFrequency = 100 @@ -39,7 +39,7 @@ class FakeDataManager { realm.beginTransaction() - for (index in 0..numberOfSessions) { + for (index in 1..numberOfSessions) { if (index % commitFrequency == 0) { Timber.d("****** committing at ${index} computables...") @@ -54,13 +54,10 @@ class FakeDataManager { session.cgSmallBlind = bigBlind / 2.0 val calendar = Calendar.getInstance() - calendar.set( - (2016..2018).random(), - (0..11).random(), - (0..28).random(), - (0..23).random(), - (0..59).random() - ) + val twoDaysBetweenEachSession: Long = (2 * numberOfSessions) * 24 * 60 * 60 * 1000L // approx! + val randomTime = calendar.time.time - (0..twoDaysBetweenEachSession).random() + + calendar.timeInMillis = randomTime val startDate = calendar.time calendar.add(Calendar.HOUR_OF_DAY, (2..12).random()) @@ -75,8 +72,9 @@ class FakeDataManager { session.game = games.random() session.result?.let { result -> + val buyin = buyinList.random() result.buyin = buyinList.random() - result.netResult = resultsList.random() + result.cashout = resultsList.random() + buyin } } 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 a0808da5..cfc53b7f 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt @@ -16,8 +16,6 @@ class Preferences { companion object { - var currencyLocale : Locale? = null - fun setString(key: Keys, value: String, context: Context) { val preferences = PreferenceManager.getDefaultSharedPreferences(context) val editor = preferences.edit() @@ -43,65 +41,102 @@ class Preferences { } fun setCurrencyCode(currencyCode: String, context: Context) { - Preferences.setString(Keys.CURRENCY_CODE, currencyCode, context) - currencyLocale = null + setString(Keys.CURRENCY_CODE, currencyCode, context) + UserDefaults.setCurrencyValues(context) } private fun getCurrencyCode(context: Context) : String? { - return Preferences.getString(Keys.CURRENCY_CODE, context) + return getString(Keys.CURRENCY_CODE, context) } - fun getCurrencyLocale(context : Context) : Locale { - - currencyLocale?. let { - return it - } - - Preferences.getCurrencyCode(context)?.let { currencyCode -> - Locale.getAvailableLocales().filter{ + private fun getCurrencyLocale(context : Context) : Locale? { + getCurrencyCode(context)?.let { currencyCode -> + Locale.getAvailableLocales().filter { try { Currency.getInstance(it).currencyCode == currencyCode } catch (e: Exception) { false } - }.first().let { - currencyLocale = it + }.firstOrNull()?.let { return it } } + return null + } - val defaultLocale = Locale.getDefault() - val defaultCurrency = try { - Currency.getInstance(defaultLocale) - } catch (ex: Exception) { - null - } - - currencyLocale = defaultCurrency?.let { - defaultLocale - } ?: run { - when (Locale.getDefault().language) { - "en" -> Locale("en", "US") - "fr" -> Locale("fr", "FR") - "es" -> Locale("es", "ES") - "de" -> Locale("de", "DE") - "ja" -> Locale("ja", "JP") - "zh" -> Locale("zh", "CN") - else -> Locale("en", "US") - } + fun getDefaultCurrency(context: Context) : Currency? { + getCurrencyLocale(context)?.let { + return Currency.getInstance(it) } - - return currencyLocale!! + return null } fun setStopShowingDisclaimer(context: Context) { - Preferences.setBoolean(Keys.STOP_SHOWING_DISCLAIMER, true, context) + setBoolean(Keys.STOP_SHOWING_DISCLAIMER, true, context) } fun shouldShowDisclaimer(context: Context) : Boolean { - return !Preferences.getBoolean(Keys.STOP_SHOWING_DISCLAIMER, context) + return !getBoolean(Keys.STOP_SHOWING_DISCLAIMER, 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")) + } + } + } + } +} + + +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 + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt index 6a5a6d10..b60ba293 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt @@ -79,6 +79,14 @@ fun Date.getDayNumber() : String { fun Date.getShortDayName() : String { return SimpleDateFormat("EEE", Locale.getDefault()).format(this) } +// Return the month of the date +fun Date.getDateMonth(): String { + return SimpleDateFormat("MMMM", Locale.getDefault()).format(this).capitalize() +} +// Return the year of the date +fun Date.getDateYear(): String { + return SimpleDateFormat("yyyy", Locale.getDefault()).format(this).capitalize() +} // Return the month & year of the date fun Date.getMonthAndYear(): String { return SimpleDateFormat("MMMM yyyy", Locale.getDefault()).format(this).capitalize() @@ -117,4 +125,20 @@ fun Date.endOfDay() : Date { calendar.set(Calendar.SECOND, 59) calendar.set(Calendar.MILLISECOND, 999) return calendar.time +} + +// Return the date of the beginning of the current month +fun Date.startOfMonth() : Date { + val calendar = Calendar.getInstance() + calendar.time = this.startOfDay() + calendar.set(Calendar.DAY_OF_MONTH, 1) + return calendar.time +} + +// Return the date of the beginning of the current year +fun Date.startOfYear() : Date { + val calendar = Calendar.getInstance() + calendar.time = this.startOfMonth() + calendar.set(Calendar.MONTH, 0) + return calendar.time } \ No newline at end of file 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 26c9bfc6..9566e07a 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,11 +2,31 @@ package net.pokeranalytics.android.util.extensions import android.content.Context import net.pokeranalytics.android.R -import net.pokeranalytics.android.util.Preferences +import java.lang.Math.abs import java.text.DecimalFormat import java.text.NumberFormat import java.util.* +val Number.kmbFormatted: String + get() { + var thousandsExponent = 0 + var v = this.toDouble() + while (abs(v) >= 10000 && thousandsExponent < 3) { + v /= 1000 + thousandsExponent++ + } + + val unit = when(thousandsExponent) { + 0 -> "" + 1 -> "K" + 2 -> "M" + 3 -> "B" + else -> "B+" // shouldn't happen + } + + val formatter = NumberFormat.getInstance() + return formatter.format(v) + unit + } // Double @@ -24,7 +44,7 @@ fun Double.formatted(): String { fun Double.toCurrency(currency: Currency? = null): String { - val currencyFormatter = NumberFormat.getCurrencyInstance(Preferences.currencyLocale) + val currencyFormatter = NumberFormat.getCurrencyInstance() currency?.let { currencyFormatter.currency = currency } @@ -34,6 +54,14 @@ fun Double.toCurrency(currency: Currency? = null): String { return currencyFormatter.format(this) } + +fun Double.toRate(): String { + val currencyFormatter = NumberFormat.getInstance() + currencyFormatter.minimumFractionDigits = 0 + currencyFormatter.maximumFractionDigits = 6 + return currencyFormatter.format(this) +} + fun Double.formattedHourlyDuration() : String { return (this * 1000 * 3600).toLong().toMinutes() } diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 6348baae..00000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/circle_green.xml b/app/src/main/res/drawable/circle_green.xml new file mode 100644 index 00000000..93111933 --- /dev/null +++ b/app/src/main/res/drawable/circle_green.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index a0ad202f..00000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_outline_calendar.xml b/app/src/main/res/drawable/ic_outline_calendar.xml new file mode 100644 index 00000000..9307b8fd --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_calendar.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_chart.xml b/app/src/main/res/drawable/ic_outline_chart.xml new file mode 100644 index 00000000..0d4f8c3f --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_chart.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_lock.xml b/app/src/main/res/drawable/ic_outline_lock.xml new file mode 100644 index 00000000..d4968533 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_lock.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_more.xml b/app/src/main/res/drawable/ic_outline_more.xml new file mode 100644 index 00000000..64325fda --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/rectangle_rounded_dark.xml b/app/src/main/res/drawable/rectangle_rounded_dark.xml new file mode 100644 index 00000000..7812ee4b --- /dev/null +++ b/app/src/main/res/drawable/rectangle_rounded_dark.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw320dp/fragment_calendar.xml b/app/src/main/res/layout-sw320dp/fragment_calendar.xml new file mode 100644 index 00000000..7bb22310 --- /dev/null +++ b/app/src/main/res/layout-sw320dp/fragment_calendar.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw400dp/fragment_calendar.xml b/app/src/main/res/layout-sw400dp/fragment_calendar.xml new file mode 100644 index 00000000..7bc77927 --- /dev/null +++ b/app/src/main/res/layout-sw400dp/fragment_calendar.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_bankroll.xml b/app/src/main/res/layout/activity_bankroll.xml new file mode 100644 index 00000000..eb1d8184 --- /dev/null +++ b/app/src/main/res/layout/activity_bankroll.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_calendar_details.xml b/app/src/main/res/layout/activity_calendar_details.xml new file mode 100644 index 00000000..5085b07e --- /dev/null +++ b/app/src/main/res/layout/activity_calendar_details.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_comparison_chart.xml b/app/src/main/res/layout/activity_comparison_chart.xml new file mode 100644 index 00000000..9ef4638e --- /dev/null +++ b/app/src/main/res/layout/activity_comparison_chart.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_graph.xml b/app/src/main/res/layout/activity_graph.xml index 70454fb9..44e1e283 100644 --- a/app/src/main/res/layout/activity_graph.xml +++ b/app/src/main/res/layout/activity_graph.xml @@ -1,7 +1,26 @@ - - \ No newline at end of file + + + + + diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 09f1bbc4..4f76f96f 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -15,17 +15,7 @@ app:layout_constraintTop_toTopOf="parent" app:title="@string/app_name" /> - - - + app:menu="@menu/navigation_home" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_report_details.xml b/app/src/main/res/layout/activity_report_details.xml new file mode 100644 index 00000000..46088f9f --- /dev/null +++ b/app/src/main/res/layout/activity_report_details.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 00000000..2b3a7a4e --- /dev/null +++ b/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_statistic_details.xml b/app/src/main/res/layout/activity_statistic_details.xml new file mode 100644 index 00000000..401d6f55 --- /dev/null +++ b/app/src/main/res/layout/activity_statistic_details.xml @@ -0,0 +1,7 @@ + + + + \ 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 new file mode 100644 index 00000000..0cf1c9b1 --- /dev/null +++ b/app/src/main/res/layout/fragment_bankroll.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_calendar.xml b/app/src/main/res/layout/fragment_calendar.xml new file mode 100644 index 00000000..642db7b7 --- /dev/null +++ b/app/src/main/res/layout/fragment_calendar.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_calendar_details.xml b/app/src/main/res/layout/fragment_calendar_details.xml new file mode 100644 index 00000000..cd88754a --- /dev/null +++ b/app/src/main/res/layout/fragment_calendar_details.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_comparison_chart.xml b/app/src/main/res/layout/fragment_comparison_chart.xml new file mode 100644 index 00000000..290c62ac --- /dev/null +++ b/app/src/main/res/layout/fragment_comparison_chart.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_graph.xml b/app/src/main/res/layout/fragment_graph.xml index cf58c3d3..39d8185c 100644 --- a/app/src/main/res/layout/fragment_graph.xml +++ b/app/src/main/res/layout/fragment_graph.xml @@ -1,25 +1,26 @@ - + - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index 317fb5a3..6177771c 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -53,51 +53,53 @@ + app:layout_constraintVertical_bias="0.5" + tools:visibility="visible" /> + android:id="@+id/disclaimerContainer" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:background="@color/green_darker" + android:orientation="vertical" + android:padding="24dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:layout_marginBottom="8dp" + android:text="@string/disclaimer" + android:textSize="18sp" + tools:visibility="visible" /> + android:id="@+id/disclaimerDismiss" + style="@style/PokerAnalyticsTheme.Button" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:text="@string/iunderstand" /> diff --git a/app/src/main/res/layout/fragment_loader.xml b/app/src/main/res/layout/fragment_loader.xml new file mode 100644 index 00000000..f4249f8b --- /dev/null +++ b/app/src/main/res/layout/fragment_loader.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_more.xml b/app/src/main/res/layout/fragment_more.xml new file mode 100644 index 00000000..aaa1d704 --- /dev/null +++ b/app/src/main/res/layout/fragment_more.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_report_details.xml b/app/src/main/res/layout/fragment_report_details.xml new file mode 100644 index 00000000..73ad6108 --- /dev/null +++ b/app/src/main/res/layout/fragment_report_details.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_reports.xml b/app/src/main/res/layout/fragment_reports.xml new file mode 100644 index 00000000..aaa1d704 --- /dev/null +++ b/app/src/main/res/layout/fragment_reports.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_session.xml b/app/src/main/res/layout/fragment_session.xml index 53457fd3..872fc2f6 100644 --- a/app/src/main/res/layout/fragment_session.xml +++ b/app/src/main/res/layout/fragment_session.xml @@ -11,102 +11,18 @@ android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - - - - - - - - - - - - - - - + android:paddingBottom="96dp" /> - + diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 8b6825d0..8542d548 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -1,17 +1,57 @@ - - + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_statistic_details.xml b/app/src/main/res/layout/fragment_statistic_details.xml new file mode 100644 index 00000000..952dc2da --- /dev/null +++ b/app/src/main/res/layout/fragment_statistic_details.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_legend_color.xml b/app/src/main/res/layout/layout_legend_color.xml new file mode 100644 index 00000000..b51c93be --- /dev/null +++ b/app/src/main/res/layout/layout_legend_color.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_legend_default.xml b/app/src/main/res/layout/layout_legend_default.xml new file mode 100644 index 00000000..71594785 --- /dev/null +++ b/app/src/main/res/layout/layout_legend_default.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_graph.xml b/app/src/main/res/layout/row_graph.xml new file mode 100644 index 00000000..799076ec --- /dev/null +++ b/app/src/main/res/layout/row_graph.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_stats_double.xml b/app/src/main/res/layout/row_stats_double.xml new file mode 100644 index 00000000..9fd36e44 --- /dev/null +++ b/app/src/main/res/layout/row_stats_double.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_title_arrow.xml b/app/src/main/res/layout/row_title_arrow.xml index a1d94800..1d73205b 100644 --- a/app/src/main/res/layout/row_title_arrow.xml +++ b/app/src/main/res/layout/row_title_arrow.xml @@ -12,10 +12,11 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:textSize="16sp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@+id/guidelineEnd" + app:layout_constraintEnd_toStartOf="@+id/nextArrow" app:layout_constraintStart_toStartOf="@+id/guidelineStart" app:layout_constraintTop_toTopOf="parent" tools:text="Data Type Title" /> diff --git a/app/src/main/res/layout/row_title_icon_arrow.xml b/app/src/main/res/layout/row_title_icon_arrow.xml new file mode 100644 index 00000000..950ad915 --- /dev/null +++ b/app/src/main/res/layout/row_title_icon_arrow.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/navigation.xml b/app/src/main/res/menu/navigation.xml deleted file mode 100644 index 762f9cdb..00000000 --- a/app/src/main/res/menu/navigation.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/menu/navigation_home.xml b/app/src/main/res/menu/navigation_home.xml new file mode 100644 index 00000000..6ac0f33b --- /dev/null +++ b/app/src/main/res/menu/navigation_home.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/bottom_sheet_menu.xml b/app/src/main/res/menu/toolbar_bottom_sheet.xml similarity index 100% rename from app/src/main/res/menu/bottom_sheet_menu.xml rename to app/src/main/res/menu/toolbar_bottom_sheet.xml diff --git a/app/src/main/res/menu/toolbar_comparison_chart.xml b/app/src/main/res/menu/toolbar_comparison_chart.xml new file mode 100644 index 00000000..2132026e --- /dev/null +++ b/app/src/main/res/menu/toolbar_comparison_chart.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/editable_data.xml b/app/src/main/res/menu/toolbar_editable_data.xml similarity index 100% rename from app/src/main/res/menu/editable_data.xml rename to app/src/main/res/menu/toolbar_editable_data.xml diff --git a/app/src/main/res/menu/home_menu.xml b/app/src/main/res/menu/toolbar_home.xml similarity index 100% rename from app/src/main/res/menu/home_menu.xml rename to app/src/main/res/menu/toolbar_home.xml diff --git a/app/src/main/res/menu/session_toolbar.xml b/app/src/main/res/menu/toolbar_session.xml similarity index 100% rename from app/src/main/res/menu/session_toolbar.xml rename to app/src/main/res/menu/toolbar_session.xml diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e9db407a..aaee282c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -95,7 +95,7 @@ Leeren, um einen automatisch generierten Titel zu verwenden Pik ♣︎ – Klassisches Grün Kommentar - Vergleichselemente + Vergleichselemente Vergleich Die eingegrenzte Suche ergab keine Ergebnisse Bestätigung diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index fde8c339..9fb44600 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -95,7 +95,7 @@ Borrar para utilizar título generado automáticamente Trébol ♣︎: verde clásico Comentarios - Comparadores + Comparadores Comparación Los filtros aún no dieron ningún resultado. Confirmación @@ -281,7 +281,7 @@ m Menos Modificar… - mes + Mes Mes del año meses más infos diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e1b0c1f1..d163091b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -112,7 +112,7 @@ Effacer pour utiliser un titre généré automatiquement Trèfle ♣︎ - Vert classique Commentaire - Comparateurs + Comparateurs Comparaison Le filtre ne retourne aucun résultat Confirmation @@ -302,7 +302,7 @@ mins Moins Modifier… - mois + Mois Mois de l\'année mois plus d\'infos diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index fd7ca81a..93db9b16 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -95,7 +95,7 @@ स्वतः उत्पन्न शीर्षक उपयोक करने के लिए साफ करें क्लोवर ♣︎ - क्लासिक हरा टिप्पणी - तुलनायंत्र + तुलनायंत्र तुलना फिल्टर से कोई परिणाम नहीं मिलेे पुष्टीकरण diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index bc2ca2d0..dc9d0d05 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -95,7 +95,7 @@ Cancella per usare un titolo generato automaticamente Fiori ♣︎ - Verde classico Commento - Comparatori + Comparatori Comparazione Il filtraggio non ha dato alcun risultato Conferma @@ -280,7 +280,7 @@ min Meno Modifica… - mese + Mese Mese dell\'anno mesi altre info diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 7ef3ef1c..cf9028b1 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -95,7 +95,7 @@ クリアして自動生成されたタイトルを使用 クローバー ♣︎ - クラシック グリーン コメント - コンパレーター + コンパレーター 比較 フィルタリングでは結果が戻されませんでした 確認 diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index de814433..beb05ff8 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -95,7 +95,7 @@ Limpe para usar o título gerado automaticamente Trevo ♣︎ - Verde clássico Comentário - Comparadores + Comparadores Comparação O filtro não apresentou nenhum resultado Confirmação @@ -280,7 +280,7 @@ mín. Menos Modificar… - mês + Mês Mês do ano meses mais informações diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8107607e..61afe97f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -95,7 +95,7 @@ Очистите для автоматического именования Клевер ♣︎ - Классическое Комментарий - Разделители + Разделители Разделение Не найдено результатов после фильтрации Подтверждение diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 794da5e7..ef0a25ac 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -83,7 +83,7 @@ 清理 清理可使用自动生成标题 评论 - 比较器 + 比较器 比较 确认 联系我们 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b0cd3cbe..16a219a4 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -37,4 +37,7 @@ #8e35c8 + #5c7258 + #f8ffe5 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5e551563..286f075c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,11 @@ Please set a start date for the session Hour Minute + More + Variant + Line + Initial Value + There is less than two values to compare! Please change your habits :) @@ -119,7 +124,7 @@ Clear to use automatically generated title Clover ♣︎ - Classic green Comment - Comparators + Comparators Comparison Filtering did not yield any results Confirmation @@ -304,7 +309,7 @@ mins Minus Modify… - month + Month Month of the year months more infos diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 43246d76..d58f7de3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -10,6 +10,7 @@ @color/gray_dark @color/colorPrimary @color/white + @color/white @style/PokerAnalyticsTheme.BottomNavigationView @style/PokerAnalyticsTheme.Toolbar @@ -18,19 +19,24 @@ @style/PokerAnalyticsTheme.TextView @style/PokerAnalyticsTheme.AlertDialog @style/PokerAnalyticsTheme.Chip + @style/PokerAnalyticsTheme.TabLayout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +