merge with dev

feature/top10
Laurent 7 years ago
commit af5d40997e
  1. 4
      app/build.gradle
  2. 5
      app/proguard-rules.pro
  3. BIN
      app/src/androidTest/assets/schema_3.realm
  4. 2
      app/src/androidTest/java/net/pokeranalytics/android/components/BaseFilterInstrumentedUnitTest.kt
  5. 2
      app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt
  6. 44
      app/src/androidTest/java/net/pokeranalytics/android/components/SessionInstrumentedUnitTest.kt
  7. 104
      app/src/androidTest/java/net/pokeranalytics/android/model/CriteriaTest.kt
  8. 14
      app/src/androidTest/java/net/pokeranalytics/android/performanceTests/PerfsInstrumentedUnitTest.kt
  9. 125
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt
  10. 2
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt
  11. 34
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatPerformanceUnitTest.kt
  12. 335
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt
  13. 65
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt
  14. 81
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt
  15. 11
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt
  16. 8
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt
  17. 119
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt
  18. 45
      app/src/main/AndroidManifest.xml
  19. 7
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  20. 568
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  21. 218
      app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt
  22. 496
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  23. 199
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  24. 0
      app/src/main/java/net/pokeranalytics/android/calculus/TextFormat.kt
  25. 79
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt
  26. 201
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt
  27. 17
      app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Computable.kt
  28. 11
      app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Datable.kt
  29. 11
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  30. 191
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  31. 21
      app/src/main/java/net/pokeranalytics/android/model/StatRepresentable.kt
  32. 8
      app/src/main/java/net/pokeranalytics/android/model/TableSize.kt
  33. 7
      app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt
  34. 58
      app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt
  35. 32
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  36. 831
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  37. 15
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt
  38. 7
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  39. 31
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  40. 58
      app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt
  41. 28
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  42. 11
      app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt
  43. 104
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  44. 145
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt
  45. 8
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterElementBlind.kt
  46. 4
      app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt
  47. 31
      app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt
  48. 399
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  49. 54
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  50. 37
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  51. 132
      app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt
  52. 29
      app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt
  53. 33
      app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt
  54. 51
      app/src/main/java/net/pokeranalytics/android/ui/activity/CalendarDetailsActivity.kt
  55. 33
      app/src/main/java/net/pokeranalytics/android/ui/activity/ComparisonChartActivity.kt
  56. 69
      app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt
  57. 72
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  58. 55
      app/src/main/java/net/pokeranalytics/android/ui/activity/ReportDetailsActivity.kt
  59. 33
      app/src/main/java/net/pokeranalytics/android/ui/activity/SettingsActivity.kt
  60. 59
      app/src/main/java/net/pokeranalytics/android/ui/activity/StatisticDetailsActivity.kt
  61. 69
      app/src/main/java/net/pokeranalytics/android/ui/adapter/ComparisonChartPagerAdapter.kt
  62. 4
      app/src/main/java/net/pokeranalytics/android/ui/adapter/HistorySessionRowRepresentableAdapter.kt
  63. 21
      app/src/main/java/net/pokeranalytics/android/ui/adapter/HomePagerAdapter.kt
  64. 78
      app/src/main/java/net/pokeranalytics/android/ui/adapter/ReportPagerAdapter.kt
  65. 16
      app/src/main/java/net/pokeranalytics/android/ui/extensions/ChipGroupExtension.kt
  66. 60
      app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
  67. 16
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt
  68. 73
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt
  69. 224
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarDetailsFragment.kt
  70. 388
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt
  71. 123
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ComparisonChartFragment.kt
  72. 7
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CurrenciesFragment.kt
  73. 9
      app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt
  74. 91
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt
  75. 15
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt
  76. 195
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  77. 13
      app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt
  78. 93
      app/src/main/java/net/pokeranalytics/android/ui/fragment/MoreFragment.kt
  79. 92
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportDetailsFragment.kt
  80. 140
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt
  81. 18
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt
  82. 67
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  83. 197
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticDetailsFragment.kt
  84. 145
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt
  85. 247
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt
  86. 199
      app/src/main/java/net/pokeranalytics/android/ui/fragment/TableReportFragment.kt
  87. 53
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/LoaderDialogFragment.kt
  88. 19
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt
  89. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt
  90. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt
  91. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetTableSizeGridFragment.kt
  92. 32
      app/src/main/java/net/pokeranalytics/android/ui/graph/AxisFormatter.kt
  93. 30
      app/src/main/java/net/pokeranalytics/android/ui/graph/ChartDataSet.kt
  94. 117
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt
  95. 38
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt
  96. 9
      app/src/main/java/net/pokeranalytics/android/ui/helpers/DateTimePickerManager.kt
  97. 29
      app/src/main/java/net/pokeranalytics/android/ui/view/CalendarTabs.kt
  98. 119
      app/src/main/java/net/pokeranalytics/android/ui/view/LegendView.kt
  99. 40
      app/src/main/java/net/pokeranalytics/android/ui/view/MultiLineLegendView.kt
  100. 4
      app/src/main/java/net/pokeranalytics/android/ui/view/NoPagingViewPager.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -28,14 +28,14 @@ android {
applicationId "net.pokeranalytics.android" applicationId "net.pokeranalytics.android"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 28 targetSdkVersion 28
versionCode 17 versionCode 16
versionName "1.0" versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
applicationVariants.all { variant -> applicationVariants.all { variant ->
variant.outputs.all { output -> variant.outputs.all { output ->

@ -57,4 +57,7 @@
# Guava # Guava
-dontwarn com.google.j2objc.annotations.** -dontwarn com.google.j2objc.annotations.**
-keep class com.google.j2objc.annotations.** { *; } -keep class com.google.j2objc.annotations.** { *; }
# Enum
-optimizations !class/unboxing/enum

@ -30,7 +30,7 @@ open class BaseFilterInstrumentedUnitTest : RealmInstrumentedUnitTest() {
session.numberOfTables = numberOfTable session.numberOfTables = numberOfTable
session.tableSize = tableSize session.tableSize = tableSize
session.startDate = startDate session.startDate = startDate
session.result?.netResult = netResult session.result?.cashout = netResult
val cal = Calendar.getInstance() // creates calendar val cal = Calendar.getInstance() // creates calendar
cal.time = startDate // sets calendar time/date cal.time = startDate // sets calendar time/date
cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour

@ -11,6 +11,8 @@ import java.util.*
open class RealmInstrumentedUnitTest { open class RealmInstrumentedUnitTest {
val EPSILON = 0.0001
lateinit var mockRealm: Realm lateinit var mockRealm: Realm
companion object { companion object {

@ -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<TournamentFeature> = 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
}
}

@ -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<QueryCondition.AnyYear>
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()}")
}
}
}
}

@ -3,11 +3,11 @@ package net.pokeranalytics.android.performanceTests
import android.content.Context import android.content.Context
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.components.RealmInstrumentedUnitTest
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputableGroup import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.ComputedResults import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Stat 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.ComputableResult
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet import net.pokeranalytics.android.model.realm.SessionSet
@ -53,23 +53,23 @@ class PerfsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
Timber.d("computableResults: ${computableResults.size}") Timber.d("computableResults: ${computableResults.size}")
Timber.d("sets: ${sets.size}") Timber.d("sets: ${sets.size}")
val stats: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE) val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE)
val group = ComputableGroup("test", computableResults, sets, stats) val group = ComputableGroup("test", listOf(), stats)
val options = Calculator.Options() 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)
val results: ComputedResults = Calculator.compute(group, options) val results: ComputedResults = Calculator.compute(realm, group, options)
Timber.d("*** ended in ${System.currentTimeMillis() - start} milliseconds") 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}") Timber.d("*** NET RESULT: ${sum?.value}")
val average = results.computedStat(Stat.AVERAGE) val average = results.computedStat(Stat.AVERAGE)
Timber.d("*** AVERAGE: ${average?.value}") Timber.d("*** AVERAGE: ${average?.value}")
val duration = results.computedStat(Stat.DURATION) val duration = results.computedStat(Stat.HOURLY_DURATION)
Timber.d("*** DURATION: ${duration?.value}") Timber.d("*** HOURLY_DURATION: ${duration?.value}")
} }
} }

@ -1,11 +1,10 @@
package net.pokeranalytics.android.unitTests package net.pokeranalytics.android.unitTests
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.components.RealmInstrumentedUnitTest import io.realm.Realm
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator
import net.pokeranalytics.android.calculus.ComputableGroup import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup
import net.pokeranalytics.android.calculus.ComputedResults import net.pokeranalytics.android.components.SessionInstrumentedUnitTest
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.Currency import net.pokeranalytics.android.model.realm.Currency
import org.junit.Assert import org.junit.Assert
@ -14,7 +13,17 @@ import org.junit.runner.RunWith
import java.util.* import java.util.*
@RunWith(AndroidJUnit4::class) @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 // convenience extension
fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session { fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session {
@ -26,56 +35,88 @@ class BankrollInstrumentedUnitTest : RealmInstrumentedUnitTest() {
} }
@Test @Test
fun testSessionStats() { fun testReport() {
val realm = this.mockRealm val realm = mockRealm
realm.beginTransaction()
val s1 = newSessionInstance(realm) realm.executeTransaction {
val s2 = newSessionInstance(realm)
val br1 = realm.createObject(Bankroll::class.java, "1") this.createDefaultTransactionTypes(realm)
val br2 = realm.createObject(Bankroll::class.java, "2")
val c1 = realm.createObject(Currency::class.java, "1") var br1 = realm.createObject(Bankroll::class.java, "1")
val c2 = realm.createObject(Currency::class.java, "2") var br2 = realm.createObject(Bankroll::class.java, "2")
c1.rate = 0.1
c2.rate = 2.0
br1.currency = c1
br2.currency = c2
s1.bankroll = br1 br1.initialValue = 100.0
s2.bankroll = br2 br2.initialValue = 1000.0
s1.result?.netResult = 100.0 val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString())
s2.result?.netResult = 200.0 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 brSetup1 = BankrollReportSetup(br1)
val sets = realm.where(SessionSet::class.java).findAll() val report1 = BankrollCalculator.computeReport(brSetup1)
val stats: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE) Assert.assertEquals(400.0, report1.total, EPSILON)
val group = ComputableGroup("test", computableResults, sets, stats)
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 brSetupAll = BankrollReportSetup()
val delta = 0.01 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 { @Test
Assert.fail("No AVERAGE stat") 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)
}
} }

@ -18,7 +18,9 @@ class DeleteInstrumentedUnitTest : RealmInstrumentedUnitTest() {
val s2 = newSessionInstance(realm) val s2 = newSessionInstance(realm)
val br1 = realm.createObject(Bankroll::class.java, "1") val br1 = realm.createObject(Bankroll::class.java, "1")
br1.live = false
val br2 = realm.createObject(Bankroll::class.java, "2") val br2 = realm.createObject(Bankroll::class.java, "2")
br2.live = false
val c1 = realm.createObject(Currency::class.java, "1") val c1 = realm.createObject(Currency::class.java, "1")
val c2 = realm.createObject(Currency::class.java, "2") val c2 = realm.createObject(Currency::class.java, "2")

@ -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")
}
}

@ -1,13 +1,13 @@
package net.pokeranalytics.android.unitTests package net.pokeranalytics.android.unitTests
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.RealmList
import io.realm.RealmResults import io.realm.RealmResults
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputableGroup import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.ComputedResults import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Stat 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.*
import net.pokeranalytics.android.model.realm.Currency import net.pokeranalytics.android.model.realm.Currency
import org.junit.Assert import org.junit.Assert
@ -23,62 +23,10 @@ import java.util.*
* See [testing documentation](http://d.android.com/tools/testing). * See [testing documentation](http://d.android.com/tools/testing).
*/ */
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
// 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<TournamentFeature> = 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")
}
@Test @Test
fun testSessionStats() { fun testAllSessionStats() {
val realm = this.mockRealm val realm = this.mockRealm
@ -112,6 +60,10 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
s2.startDate = sd2 s2.startDate = sd2
s2.endDate = ed2 s2.endDate = ed2
val l1 = realm.createObject(Location::class.java, UUID.randomUUID().toString())
s1.location = l1
s2.location = l1
realm.commitTransaction() realm.commitTransaction()
assertEquals(2, computableResults.size) assertEquals(2, computableResults.size)
@ -120,17 +72,16 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
println(">>>>>> rated net = ${it.ratedNet} ") println(">>>>>> rated net = ${it.ratedNet} ")
} }
val sets = realm.where(SessionSet::class.java).findAll() val group = ComputableGroup("test")
val stats: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE)
val group = ComputableGroup("test", computableResults, sets, stats)
val options = Calculator.Options() 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 delta = 0.01
val sum = results.computedStat(Stat.NETRESULT) val sum = results.computedStat(Stat.NET_RESULT)
if (sum != null) { if (sum != null) {
assertEquals(200.0, sum.value, delta) assertEquals(200.0, sum.value, delta)
} else { } else {
@ -144,7 +95,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
Assert.fail("No AVERAGE stat") Assert.fail("No AVERAGE stat")
} }
val duration = results.computedStat(Stat.DURATION) val duration = results.computedStat(Stat.HOURLY_DURATION)
if (duration != null) { if (duration != null) {
assertEquals(4.0, duration.value, delta) assertEquals(4.0, duration.value, delta)
} else { } else {
@ -181,7 +132,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
} else { } else {
Assert.fail("No avgBuyin stat") Assert.fail("No avgBuyin stat")
} }
val avgDuration = results.computedStat(Stat.AVERAGE_DURATION) val avgDuration = results.computedStat(Stat.AVERAGE_HOURLY_DURATION)
if (avgDuration != null) { if (avgDuration != null) {
assertEquals(2.0, avgDuration.value, delta) assertEquals(2.0, avgDuration.value, delta)
} else { } else {
@ -234,6 +185,42 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
Assert.fail("No std100 stat") 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 @Test
@ -263,18 +250,16 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
} }
val computableResults = realm.where(ComputableResult::class.java).findAll() val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE)
val sets = realm.where(SessionSet::class.java).findAll() val group = ComputableGroup("test", listOf(), stats)
val stats: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE)
val group = ComputableGroup("test", computableResults, sets, stats)
val options = Calculator.Options() 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)
val results: ComputedResults = Calculator.compute(group, options) val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01 val delta = 0.01
val duration = results.computedStat(Stat.DURATION) val duration = results.computedStat(Stat.HOURLY_DURATION)
if (duration != null) { if (duration != null) {
assertEquals(3.0, duration.value, delta) assertEquals(3.0, duration.value, delta)
} else { } else {
@ -332,18 +317,16 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val computableResults = realm.where(ComputableResult::class.java).findAll() val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE)
val sets = realm.where(SessionSet::class.java).findAll() val group = ComputableGroup("test", listOf(), stats)
val stats: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE)
val group = ComputableGroup("test", computableResults, sets, stats)
val options = Calculator.Options() 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)
val results: ComputedResults = Calculator.compute(group, options) val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01 val delta = 0.01
val duration = results.computedStat(Stat.DURATION) val duration = results.computedStat(Stat.HOURLY_DURATION)
if (duration != null) { if (duration != null) {
assertEquals(8.0, duration.value, delta) assertEquals(8.0, duration.value, delta)
} else { } else {
@ -417,18 +400,16 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val computableResults = realm.where(ComputableResult::class.java).findAll() val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE)
val sets = realm.where(SessionSet::class.java).findAll() val group = ComputableGroup("test", listOf(), stats)
val stats: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE)
val group = ComputableGroup("test", computableResults, sets, stats)
val options = Calculator.Options() 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)
val results: ComputedResults = Calculator.compute(group, options) val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01 val delta = 0.01
val duration = results.computedStat(Stat.DURATION) val duration = results.computedStat(Stat.HOURLY_DURATION)
if (duration != null) { if (duration != null) {
assertEquals(8.0, duration.value, delta) assertEquals(8.0, duration.value, delta)
} else { } else {
@ -439,14 +420,12 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
s1.deleteFromRealm() s1.deleteFromRealm()
} }
val computableResults2 = realm.where(ComputableResult::class.java).findAll() val stats2: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE)
val sets2 = realm.where(SessionSet::class.java).findAll() val group2 = ComputableGroup("test", listOf(), stats2)
val stats2: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE)
val group2 = ComputableGroup("test", computableResults2, sets2, 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) { if (duration2 != null) {
assertEquals(7.0, duration2.value, delta) assertEquals(7.0, duration2.value, delta)
} else { } else {
@ -478,12 +457,12 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
val sets = realm.where(SessionSet::class.java).findAll() val sets = realm.where(SessionSet::class.java).findAll()
Assert.assertEquals(1, sets.size) assertEquals(1, sets.size)
val set = sets.first() val set = sets.first()
if (set != null) { if (set != null) {
Assert.assertEquals(sd1.time, set.startDate.time) assertEquals(sd1.time, set.startDate.time)
Assert.assertEquals(ed1.time, set.endDate.time) assertEquals(ed1.time, set.endDate.time)
} else { } else {
Assert.fail("No set") Assert.fail("No set")
} }
@ -522,7 +501,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
val sets = realm.where(SessionSet::class.java).findAll() 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 s1.endDate = null
} }
val computableResults = realm.where(ComputableResult::class.java).findAll() val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE)
val sets = realm.where(SessionSet::class.java).findAll() val group = ComputableGroup("test", listOf(), stats)
val stats: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE)
val group = ComputableGroup("test", computableResults, sets, stats)
val options = Calculator.Options() 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)
val results: ComputedResults = Calculator.compute(group, options) val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01 val delta = 0.01
val duration = results.computedStat(Stat.DURATION) val duration = results.computedStat(Stat.HOURLY_DURATION)
if (duration != null) { if (duration != null) {
assertEquals(7.0, duration.value, delta) assertEquals(7.0, duration.value, delta)
} else { } else {
@ -594,7 +571,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
} }
@Test // @Test
fun testRatedNetResultSessions() { fun testRatedNetResultSessions() {
val realm = this.mockRealm val realm = this.mockRealm
@ -620,15 +597,13 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
} }
val computableResults = realm.where(ComputableResult::class.java).findAll() val stats: List<Stat> = listOf(Stat.NET_RESULT)
val sets = realm.where(SessionSet::class.java).findAll() val group = ComputableGroup("test", listOf(), stats)
val stats: List<Stat> = listOf(Stat.NETRESULT)
val group = ComputableGroup("test", computableResults, sets, stats)
val options = Calculator.Options() 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) val netResult = results.computedStat(Stat.NET_RESULT)
Assert.assertEquals(250.0, netResult?.value) assertEquals(250.0, netResult?.value)
} }
// @Test // @Test
@ -658,15 +633,13 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
} }
val computableResults = realm.where(ComputableResult::class.java).findAll() val stats: List<Stat> = listOf(Stat.NET_RESULT)
val sets = realm.where(SessionSet::class.java).findAll() val group = ComputableGroup("test", listOf(), stats)
val stats: List<Stat> = listOf(Stat.NETRESULT)
val group = ComputableGroup("test", computableResults, sets, stats)
val options = Calculator.Options() 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) val netResult = results.computedStat(Stat.NET_RESULT)
Assert.assertEquals(250.0, netResult?.value) assertEquals(250.0, netResult?.value)
println("currency set rate real test starts here") println("currency set rate real test starts here")
@ -679,13 +652,137 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
} }
} }
val updatedComputableResults = realm.where(ComputableResult::class.java).findAll() val updatedGroup = ComputableGroup("test", listOf(), stats)
val updatedSets = realm.where(SessionSet::class.java).findAll() val updatedResults: ComputedResults = Calculator.compute(realm, updatedGroup, options)
val updatedGroup = ComputableGroup("test", updatedComputableResults, updatedSets, stats) val updatedNetResult = updatedResults.computedStat(Stat.NET_RESULT)
val updatedResults: ComputedResults = Calculator.compute(updatedGroup, options) assertEquals(650.0, updatedNetResult?.value)
val updatedNetResult = updatedResults.computedStat(Stat.NETRESULT)
Assert.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")
}
} }
} }

@ -6,7 +6,6 @@ import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.FilterCondition import net.pokeranalytics.android.model.realm.FilterCondition
import net.pokeranalytics.android.model.realm.Session 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.ui.view.rowrepresentable.FilterSectionRow
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
@ -21,7 +20,7 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.beginTransaction() realm.beginTransaction()
val currency = realm.createObject(net.pokeranalytics.android.model.realm.Currency::class.java, "1") 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 b1 = realm.createObject(Bankroll::class.java, "1")
val b2 = realm.createObject(Bankroll::class.java, "2") val b2 = realm.createObject(Bankroll::class.java, "2")
@ -41,14 +40,16 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.AnyBlind()
val filter = QueryCondition.BLINDS val blind = QueryCondition.AnyBlind().apply {
val blind = FilterElementRow.Blind(0.5, 1.0, null) listOfValues = arrayListOf(s1.blinds!!)
blind.filterSectionRow = FilterSectionRow.BLINDS }
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueMap(filterElement)
blind.filterSectionRow = FilterSectionRow.BLIND
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueMap(filterElement)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
@ -86,12 +87,20 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.BLINDS val filter = QueryCondition.AnyBlind()
val blind = FilterElementRow.Blind(null, 1.0, null)
blind.filterSectionRow = FilterSectionRow.BLINDS
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind)) val blind1 = QueryCondition.AnyBlind().apply {
filter.updateValueMap(filterElement) 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<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
@ -108,13 +117,14 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.beginTransaction() realm.beginTransaction()
val currency = realm.createObject(net.pokeranalytics.android.model.realm.Currency::class.java, "1") 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 b1 = realm.createObject(Bankroll::class.java, "1")
val b2 = realm.createObject(Bankroll::class.java, "2") val b2 = realm.createObject(Bankroll::class.java, "2")
b2.currency = currency b2.currency = currency
val s1 = Session.testInstance(100.0, false, Date(), 1, b1) val s1 = Session.testInstance(100.0, false, Date(), 1, b1)
s1.cgBigBlind = 1.0 s1.cgBigBlind = 1.0
s1.cgSmallBlind = 0.5 s1.cgSmallBlind = 0.5
@ -129,13 +139,17 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.BLINDS val filter = QueryCondition.AnyBlind()
val blind = FilterElementRow.Blind(1.0, 2.0, "AUD")
blind.filterSectionRow = FilterSectionRow.BLINDS val blind = QueryCondition.AnyBlind().apply {
listOfValues = arrayListOf(s3.blinds!!)
}
blind.filterSectionRow = FilterSectionRow.BLIND
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind)) val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
println("<<<< ${filter.listOfValues}")
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
@ -172,12 +186,19 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() 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) val filter = QueryCondition.AnyBlind()
blind2.filterSectionRow = FilterSectionRow.BLINDS
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)) val filterElement = FilterCondition(filterElementRows = arrayListOf(blind1, blind2))
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)

@ -6,7 +6,6 @@ import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.FilterCondition import net.pokeranalytics.android.model.realm.FilterCondition
import net.pokeranalytics.android.model.realm.Session 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.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.util.extensions.startOfDay import net.pokeranalytics.android.util.extensions.startOfDay
import org.junit.Assert import org.junit.Assert
@ -30,19 +29,19 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, cal.time) Session.testInstance(100.0, true, cal.time)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.DAY_OF_WEEK val filter = QueryCondition.AnyDayOfWeek()
cal.time = s1.startDate 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 filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
val filterElement = FilterCondition(filterElementRow) val filterElement = FilterCondition(arrayListOf(filterElementRow))
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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) Session.testInstance(100.0, true, cal.time)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.MONTH val filter = QueryCondition.AnyMonthOfYear()
cal.time = s1.startDate 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 filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
val filterElement = FilterCondition(filterElementRow) val filterElement = FilterCondition(arrayListOf(filterElementRow))
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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) Session.testInstance(100.0, true, cal.time)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.YEAR val filter = QueryCondition.AnyYear()
cal.time = s1.startDate 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 filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
val filterElement = FilterCondition(filterElementRow) val filterElement = FilterCondition(arrayListOf(filterElementRow))
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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) Session.testInstance(100.0, true, cal.time)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.WEEK_END)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.IsWeekEnd))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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() realm.commitTransaction()
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.WEEK_DAY)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.IsWeekDay))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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) Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.TODAY)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.IsToday))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -193,7 +192,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2) Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.TODAY)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.IsToday))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -221,7 +220,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3) Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.YESTERDAY)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.WasYesterday))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -248,7 +247,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3) Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.YESTERDAY)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.WasYesterday))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -275,7 +274,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3) Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.TODAY_AND_YESTERDAY)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.WasTodayAndYesterday))
Assert.assertEquals(2, sessions.size) Assert.assertEquals(2, sessions.size)
Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2))) Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2)))
@ -306,7 +305,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 5) Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 5)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.THIS_YEAR)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.DuringThisYear))
Assert.assertEquals(3, sessions.size) Assert.assertEquals(3, sessions.size)
Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2, s3))) 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) Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 5)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.THIS_MONTH)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.DuringThisMonth))
Assert.assertEquals(2, sessions.size) Assert.assertEquals(2, sessions.size)
Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2))) Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2)))
@ -362,7 +361,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2) Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.THIS_WEEK)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.DuringThisWeek))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
Assert.assertTrue(sessions.containsAll(arrayListOf(s1))) Assert.assertTrue(sessions.containsAll(arrayListOf(s1)))
@ -383,16 +382,16 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val s2 = Session.testInstance(100.0, true, cal.time, 1) val s2 = Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.STARTED_FROM_DATE val filter = QueryCondition.StartedFromDate()
val filterElementRow = FilterElementRow.From(s2.startDate!!) val filterElementRow = QueryCondition.StartedFromDate().apply { singleValue = s2.startDate!!}
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterCondition(filterElementRow)) filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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() realm.commitTransaction()
val filter = QueryCondition.STARTED_TO_DATE val filter = QueryCondition.StartedToDate()
val filterElementRow = FilterElementRow.From(s1.startDate!!) val filterElementRow = QueryCondition.StartedToDate().apply { singleValue = s1.startDate!! }
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterCondition(filterElementRow)) filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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() realm.commitTransaction()
val filter = QueryCondition.ENDED_FROM_DATE val filter = QueryCondition.EndedFromDate()
val filterElementRow = FilterElementRow.From(s2.endDate()) val filterElementRow = QueryCondition.EndedFromDate().apply { singleValue = s2.endDate() }
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterCondition(filterElementRow)) filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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() realm.commitTransaction()
val filter = QueryCondition.ENDED_TO_DATE val filter = QueryCondition.EndedToDate()
val filterElementRow = FilterElementRow.From(s1.endDate()) val filterElementRow = QueryCondition.EndedToDate().apply { singleValue = s1.endDate() }
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterCondition(filterElementRow)) filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
Assert.assertEquals(s1.id, (this as Session).id) Assert.assertEquals(s1.id, (this).id)
} }
} }
} }

@ -13,17 +13,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ExceptionFilterInstrumentedTest: BaseFilterInstrumentedUnitTest() { 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<Session>(realm, arrayListOf(filter))
}
@Test(expected = PokerAnalyticsException.FilterElementUnknownName::class) @Test(expected = PokerAnalyticsException.FilterElementUnknownName::class)
fun testFilterException() { fun testFilterException() {
FilterCondition().queryCondition FilterCondition().queryCondition

@ -2,12 +2,10 @@ package net.pokeranalytics.android.unitTests.filter
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow 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 net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
@ -25,7 +23,7 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = Filter() val filter = Filter()
filter.name = "testSaveLoadCashFilter" filter.name = "testSaveLoadCashFilter"
val filterElement = FilterElementRow.Cash val filterElement = QueryCondition.IsCash
filterElement.filterSectionRow = FilterSectionRow.CASH_TOURNAMENT filterElement.filterSectionRow = FilterSectionRow.CASH_TOURNAMENT
filter.createOrUpdateFilterConditions(arrayListOf(filterElement)) filter.createOrUpdateFilterConditions(arrayListOf(filterElement))
@ -38,7 +36,7 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filterComponent = filter.filterConditions.first() val filterComponent = filter.filterConditions.first()
filterComponent?.let { filterComponent?.let {
Assert.assertEquals(QueryCondition.CASH, QueryCondition.valueOf(it.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName)) Assert.assertTrue(it.queryCondition is QueryCondition.IsCash)
} ?: run { } ?: run {
Assert.fail() Assert.fail()
} }
@ -55,7 +53,7 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
Assert.assertEquals(Session.Type.CASH_GAME.ordinal, (this as Session).type) Assert.assertEquals(Session.Type.CASH_GAME.ordinal, this.type)
} ?: run { } ?: run {
Assert.fail() Assert.fail()
} }

@ -3,10 +3,8 @@ package net.pokeranalytics.android.unitTests.filter
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.RealmList import io.realm.RealmList
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
@ -26,11 +24,11 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, Date(), 1) Session.testInstance(100.0, true, Date(), 1)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.CASH)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.IsCash))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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) Session.testInstance(100.0, true, Date(), 1)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.TOURNAMENT)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.IsTournament))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { 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) Session.testInstance(100.0, true, Date(), 1, b2)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.LIVE)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.IsLive))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).bankroll?.run { (sessions[0] as Session).bankroll?.run {
@ -89,7 +87,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, Date(), 1, b2) Session.testInstance(100.0, true, Date(), 1, b2)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.ONLINE)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.IsOnline))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).bankroll?.run { (sessions[0] as Session).bankroll?.run {
@ -109,8 +107,8 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(bankroll = b2) Session.testInstance(bankroll = b2)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.BANKROLL val filter = QueryCondition.AnyBankroll()
val filterElementRow = FilterElementRow.Bankroll(b1) val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) }
filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
@ -140,11 +138,11 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(bankroll = b3) Session.testInstance(bankroll = b3)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.BANKROLL val filter = QueryCondition.AnyBankroll()
val filterElementRow = FilterElementRow.Bankroll(b1) val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) }
filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL
val filterElementRow2 = FilterElementRow.Bankroll(b2) val filterElementRow2 = QueryCondition.AnyBankroll().apply { setObject(b2) }
filterElementRow2.filterSectionRow = FilterSectionRow.BANKROLL filterElementRow2.filterSectionRow = FilterSectionRow.BANKROLL
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
@ -169,12 +167,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(game = g2) Session.testInstance(game = g2)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.GAME val anyGame = QueryCondition.AnyGame(g2)
val filterElementRow = FilterElementRow.Game(g2) val fc = FilterCondition(filterElementRows = arrayListOf(anyGame))
filterElementRow.filterSectionRow = FilterSectionRow.GAME val sessions = Filter.queryOn<Session>(realm, arrayListOf(fc.queryCondition))
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).game?.run { (sessions[0] as Session).game?.run {
@ -200,15 +195,13 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(game = g3) Session.testInstance(game = g3)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.GAME val filterElementRow = QueryCondition.AnyGame().apply { setObject(g2) }
val filterElementRow = FilterElementRow.Game(g2)
filterElementRow.filterSectionRow = FilterSectionRow.GAME filterElementRow.filterSectionRow = FilterSectionRow.GAME
val filterElementRow2 = FilterElementRow.Game(g3) val filterElementRow2 = QueryCondition.AnyGame().apply { setObject(g3) }
filterElementRow2.filterSectionRow = FilterSectionRow.GAME filterElementRow2.filterSectionRow = FilterSectionRow.GAME
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) val filterCondition = FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))
val queryCondition = filterCondition.queryCondition
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(queryCondition))
Assert.assertEquals(6, sessions.size) Assert.assertEquals(6, sessions.size)
@ -229,8 +222,8 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(location = l2) Session.testInstance(location = l2)
realm.commitTransaction() 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 filterElementRow.filterSectionRow = FilterSectionRow.LOCATION
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
@ -260,11 +253,11 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(location = l3) Session.testInstance(location = l3)
realm.commitTransaction() 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 filterElementRow.filterSectionRow = FilterSectionRow.LOCATION
val filterElementRow2 = FilterElementRow.Location(l3) val filterElementRow2 = QueryCondition.AnyLocation().apply { setObject(l3) }
filterElementRow2.filterSectionRow = FilterSectionRow.LOCATION filterElementRow2.filterSectionRow = FilterSectionRow.LOCATION
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
@ -290,9 +283,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentName = t2) Session.testInstance(tournamentName = t2)
realm.commitTransaction() 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 filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
@ -322,10 +315,10 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentName = t3) Session.testInstance(tournamentName = t3)
realm.commitTransaction() 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 filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME
val filterElementRow2 = FilterElementRow.TournamentName(t2) val filterElementRow2 = QueryCondition.AnyTournamentName().apply { setObject(t2) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
@ -358,12 +351,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentFeatures = RealmList(t1)) Session.testInstance(tournamentFeatures = RealmList(t1))
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.ALL_TOURNAMENT_FEATURES val filter = QueryCondition.AllTournamentFeature()
val filterElementRow = FilterElementRow.AllTournamentFeature(t1) val filterElementRow = QueryCondition.AllTournamentFeature().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
val filterElementRow2 = FilterElementRow.AllTournamentFeature(t2) val filterElementRow2 = QueryCondition.AllTournamentFeature().apply { setObject(t2) }
filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
val filterElementRow3 = FilterElementRow.AllTournamentFeature(t4) val filterElementRow3 = QueryCondition.AllTournamentFeature().apply { setObject(t4) }
filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3)))
@ -393,14 +386,14 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentFeatures = RealmList(t1)) Session.testInstance(tournamentFeatures = RealmList(t1))
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.ANY_TOURNAMENT_FEATURES val filter = QueryCondition.AnyTournamentFeature()
val filterElementRow = FilterElementRow.AnyTournamentFeature(t1) val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
val filterElementRow2 = FilterElementRow.AnyTournamentFeature(t2) val filterElementRow2 = QueryCondition.AnyTournamentFeature().apply { setObject(t2) }
filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
val filterElementRow3 = FilterElementRow.AnyTournamentFeature(t3) val filterElementRow3 = QueryCondition.AnyTournamentFeature().apply { setObject(t3) }
filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
val filterElementRow4 = FilterElementRow.AnyTournamentFeature(t4) val filterElementRow4 = QueryCondition.AnyTournamentFeature().apply { setObject(t4) }
filterElementRow4.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow4.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3, filterElementRow4))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3, filterElementRow4)))
@ -427,8 +420,8 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentFeatures = RealmList(t1)) Session.testInstance(tournamentFeatures = RealmList(t1))
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.ANY_TOURNAMENT_FEATURES val filter = QueryCondition.AnyTournamentFeature()
val filterElementRow = FilterElementRow.AnyTournamentFeature(t2) val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t2) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
@ -452,10 +445,10 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tableSize = 10) Session.testInstance(tableSize = 10)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.TABLE_SIZE val filter = QueryCondition.AnyTableSize()
val filterElementRow = FilterElementRow.TableSize(TableSize(2)) val filterElementRow = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(2) }
filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE
val filterElementRow2 = FilterElementRow.TableSize(TableSize(4)) val filterElementRow2 = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(4) }
filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE
filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
@ -479,12 +472,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val s2 = Session.testInstance(netResult = 570.0) val s2 = Session.testInstance(netResult = 570.0)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.MORE_THAN_NET_RESULT val filter = QueryCondition.NetAmountWon()
val filterElementRow = FilterElementRow.ResultMoreThan(204.0) val filterElementRow = QueryCondition.more<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(204.0) }
filterElementRow.filterSectionRow = FilterSectionRow.VALUE filterElementRow.filterSectionRow = FilterSectionRow.VALUE
filter.updateValueMap(FilterCondition(filterElementRow)) filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filterElementRow))
Assert.assertEquals(2, sessions.size) Assert.assertEquals(2, sessions.size)
@ -504,10 +497,10 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(netResult = 570.0) Session.testInstance(netResult = 570.0)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.LESS_THAN_NET_RESULT val filter = QueryCondition.NetAmountWon()
val filterElementRow = FilterElementRow.ResultLessThan(540.0) val filterElementRow = QueryCondition.less<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(540.0) }
filterElementRow.filterSectionRow = FilterSectionRow.VALUE filterElementRow.filterSectionRow = FilterSectionRow.VALUE
filter.updateValueMap(FilterCondition(filterElementRow)) filter.updateValueMap(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
@ -529,15 +522,15 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(netResult = 570.0) Session.testInstance(netResult = 570.0)
realm.commitTransaction() realm.commitTransaction()
val filterMore = QueryCondition.MORE_THAN_NET_RESULT val filterMore = QueryCondition.NetAmountWon()
val filterElementRow = FilterElementRow.ResultMoreThan(200.0) val filterElementRow = QueryCondition.more<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(200.0) }
filterElementRow.filterSectionRow = FilterSectionRow.VALUE filterElementRow.filterSectionRow = FilterSectionRow.VALUE
filterMore.updateValueMap(FilterCondition(filterElementRow)) filterMore.updateValueMap(FilterCondition(arrayListOf(filterElementRow)))
val filterLess = QueryCondition.LESS_THAN_NET_RESULT val filterLess = QueryCondition.NetAmountWon()
val filterElementRow2 = FilterElementRow.ResultLessThan(400.0) val filterElementRow2 = QueryCondition.less<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(400.0) }
filterElementRow2.filterSectionRow = FilterSectionRow.VALUE filterElementRow2.filterSectionRow = FilterSectionRow.VALUE
filterLess.updateValueMap(FilterCondition(filterElementRow2)) filterLess.updateValueMap(FilterCondition(arrayListOf(filterElementRow2)))
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filterMore, filterLess)) val sessions = Filter.queryOn<Session>(realm, arrayListOf(filterMore, filterLess))

@ -34,6 +34,36 @@
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing" /> android:windowSoftInputMode="adjustNothing" />
<activity
android:name=".ui.activity.BankrollActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name=".ui.activity.SettingsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name=".ui.activity.StatisticDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name=".ui.activity.ReportDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name=".ui.activity.CalendarDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name=".ui.activity.ComparisonChartActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity <activity
android:name=".ui.activity.DataListActivity" android:name=".ui.activity.DataListActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
@ -64,15 +94,20 @@
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity
android:name=".ui.activity.GraphActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<meta-data <meta-data
android:name="preloaded_fonts" android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" /> android:resource="@array/preloaded_fonts" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application> </application>
</manifest> </manifest>

@ -15,6 +15,7 @@ import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.utils.Seed import net.pokeranalytics.android.model.utils.Seed
import net.pokeranalytics.android.util.FakeDataManager import net.pokeranalytics.android.util.FakeDataManager
import net.pokeranalytics.android.util.PokerAnalyticsLogs import net.pokeranalytics.android.util.PokerAnalyticsLogs
import net.pokeranalytics.android.util.UserDefaults
import timber.log.Timber import timber.log.Timber
@ -23,12 +24,13 @@ class PokerAnalyticsApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
UserDefaults.init(this)
// Realm // Realm
Realm.init(this) Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder() val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME) .name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(2) .schemaVersion(3)
.migration(PokerAnalyticsMigration()) .migration(PokerAnalyticsMigration())
.initialData(Seed(this)) .initialData(Seed(this))
.build() .build()
@ -49,7 +51,8 @@ class PokerAnalyticsApplication : Application() {
} }
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
// this.createFakeSessions() Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}")
// this.createFakeSessions()
} }
Patcher.patchBreaks() Patcher.patchBreaks()

@ -2,11 +2,20 @@ package net.pokeranalytics.android.calculus
import io.realm.Realm import io.realm.Realm
import net.pokeranalytics.android.calculus.Stat.* 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.ComputableResult
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.SessionSet 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 timber.log.Timber
import java.util.* import java.util.*
import kotlin.math.max
import kotlin.math.min
/** /**
* The class performing stats computation * The class performing stats computation
@ -16,7 +25,12 @@ class Calculator {
/** /**
* The options used for calculations or display * The options used for calculations or display
*/ */
class Options { class Options(
display: Display = Display.TABLE,
evolutionValues: EvolutionValues = EvolutionValues.NONE,
stats: List<Stat> = listOf(),
aggregationType: AggregationType? = null
) {
/** /**
* The way the stats are going to be displayed * The way the stats are going to be displayed
@ -35,49 +49,122 @@ class Calculator {
enum class EvolutionValues { enum class EvolutionValues {
NONE, NONE,
STANDARD, STANDARD,
DATED TIMED
} }
var display: Display = Display.TABLE var display: Display = display
var evolutionValues: EvolutionValues = EvolutionValues.NONE var evolutionValues: EvolutionValues = evolutionValues
var displayedStats: List<Stat> = listOf() var displayedStats: List<Stat> = stats
var aggregationType: AggregationType? = aggregationType
/** /**
* This function determines whether the standard deviation should be computed * This function determines whether the standard deviation should be computed
*/ */
fun shouldComputeStandardDeviation(): Boolean { val computeStandardDeviation: Boolean
this.displayedStats.forEach { stat -> get() {
return when (stat) { this.displayedStats.forEach {
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true if (it == STANDARD_DEVIATION_BB_PER_100_HANDS || it == STANDARD_DEVIATION || it == STANDARD_DEVIATION_HOURLY) {
else -> false 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 { companion object {
fun computeStatsWithFilters(realm: Realm, filters: List<Filter>, options: Options): List<ComputedResults> { fun computeStatsWithEvolutionByAggregationType(
realm: Realm,
stat: Stat,
group: ComputableGroup,
aggregationType: AggregationType,
stats: List<Stat>? = 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<ComputableGroup> = mutableListOf() stats?.let {
filters.forEach { filter -> 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<Criteria> = listOf(Criteria.Years, Criteria.MonthsOfYear)
this.computeStatsWithComparators(realm, criteria, group.conditions, options)
}
AggregationType.YEAR -> {
val criteria: List<Criteria> = listOf(Criteria.Years)
this.computeStatsWithComparators(realm, criteria, group.conditions, options)
}
}
}
fun computeStatsWithComparators(
realm: Realm,
criteria: List<Criteria> = listOf(),
conditions: List<QueryCondition> = listOf(),
options: Options = Options()
): Report {
val computableGroups: MutableList<ComputableGroup> = mutableListOf()
criteria.combined().upToNow().forEach { comparatorConditions ->
val allConditions = mutableListOf<QueryCondition>()
allConditions.addAll(conditions)
allConditions.addAll(comparatorConditions)
val group = ComputableGroup(allConditions.name(), allConditions)
computableGroups.add(group) 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 * Computes all stats for list of Session sessionGroup
*/ */
fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options): List<ComputedResults> { fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options = Options()): Report {
val computedResults = mutableListOf<ComputedResults>() val report = Report(options)
groups.forEach { group -> groups.forEach { group ->
val s = Date() val s = Date()
@ -85,18 +172,22 @@ class Calculator {
group.cleanup() group.cleanup()
// Computes actual sessionGroup stats // 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 // Computes the compared sessionGroup if existing
val comparedGroup = group.comparedComputables val comparedGroup = group.comparedGroup
if (comparedGroup != null) { if (comparedGroup != null) {
val comparedResults = Calculator.compute(realm, comparedGroup, options = options) val comparedResults = this.compute(realm, comparedGroup, options)
group.comparedComputedResults = comparedResults group.comparedComputedResults = comparedResults
results.computeStatVariations(comparedResults) results.computeStatVariations(comparedResults)
} }
if (options.shouldManageMultiGroupProgressValues) {
group.comparedComputedResults = report.results.lastOrNull()
}
results.finalize(options) // later treatment, such as evolution numericValues sorting results.finalize(options) // later treatment, such as evolution numericValues sorting
computedResults.add(results) report.addResults(results)
val e = Date() val e = Date()
val duration = (e.time - s.time) / 1000.0 val duration = (e.time - s.time) / 1000.0
@ -104,128 +195,64 @@ class Calculator {
} }
return computedResults return report
} }
/** /**
* Computes stats for a SessionSet * 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) val results = ComputedResults(computableGroup, options.shouldManageMultiGroupProgressValues)
Timber.d(">>>> Start computing group ${computableGroup.name}, ${computables.size} computables")
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() 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() 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() 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() 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 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 val totalBuyin = computables.sum(ComputableResult.Field.RATED_BUYIN.identifier).toDouble()
results.addStat(TOTAL_BUYIN, totalBuyin)
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)
}
Stat.returnOnInvestment(tSum, tBuyinSum)?.let { roi -> val maxNetResult = computables.max(ComputableResult.Field.RATED_NET.identifier)?.toDouble()
results.addEvolutionValue(roi, ROI, session) maxNetResult?.let {
} results.addStat(MAXIMUM_NETRESULT, it)
}
} val minNetResult = computables.min(ComputableResult.Field.RATED_NET.identifier)?.toDouble()
} minNetResult?.let {
else -> { results.addStat(MINIMUM_NETRESULT, it)
// nothing
}
} }
val sessionSets = computableGroup.sessionSets(realm) Stat.netBBPer100Hands(bbSum, totalHands)?.let { netBB100 ->
results.addStat(NET_BB_PER_100_HANDS, netBB100)
// Compute for each serie }
val gHourlyDuration = Stat.returnOnInvestment(sum, totalBuyin)?.let { roi ->
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours) results.addStat(ROI, roi)
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)
}
} if (options.computeLocationsPlayed) {
} results.addStat(LOCATIONS_PLAYED, computables.distinctBy { it.session?.location?.id }.size.toDouble())
else -> {
// nothing
}
} }
var average = 0.0 var average = 0.0 // also used for standard deviation later
if (computables.size > 0) { if (computables.size > 0) {
average = sum / computables.size.toDouble() average = sum / computables.size.toDouble()
val winRatio = winningSessionCount.toDouble() / computables.size.toDouble() val winRatio = winningSessionCount.toDouble() / computables.size.toDouble()
val avgBuyin = totalBuyin / computables.size val avgBuyin = totalBuyin / computables.size.toDouble()
results.addStats( results.addStats(
setOf( setOf(
@ -236,41 +263,228 @@ class Calculator {
) )
} }
if (sessionSets.size > 0) { val shouldIterateOverComputables =
val avgDuration = gHourlyDuration / sessionSets.size (options.evolutionValues == Options.EvolutionValues.STANDARD || options.computeLongestStreak)
results.addStats(
setOf( // Computable Result
ComputedStat(HOURLY_RATE, hourlyRate), if (shouldIterateOverComputables) {
ComputedStat(AVERAGE_DURATION, avgDuration)
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 val sessionSets = computableGroup.sessionSets(realm, options.shouldSortValues)
results.addStats( results.addStat(NUMBER_OF_SETS, sessionSets.size.toDouble())
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)
) var gHourlyDuration: Double? = null
) var gBBSum: Double? = null
var maxDuration: Double? = null
Stat.returnOnInvestment(sum, totalBuyin)?.let { roi -> if (computableGroup.conditions.size == 0) { // SessionSets are fine
results.addStats(setOf(ComputedStat(ROI, roi))) 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<Date>()
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 val bbPer100Hands = bbSum / totalHands * 100
// Standard Deviation // Standard Deviation
if (options.shouldComputeStandardDeviation()) { if (options.computeStandardDeviation) {
// Session // Session
var stdSum = 0.0 var stdSum = 0.0
@ -282,20 +496,22 @@ class Calculator {
val standardDeviation = Math.sqrt(stdSum / computables.size) val standardDeviation = Math.sqrt(stdSum / computables.size)
val standardDeviationBBper100Hands = Math.sqrt(stdBBper100HandsSum / 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 // Session Set
var hourlyStdSum = 0.0 if (gHourlyDuration != null) {
sessionSets.forEach { set -> var hourlyStdSum = 0.0
hourlyStdSum += Math.pow(set.hourlyRate - hourlyRate, 2.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 return results
@ -303,5 +519,43 @@ class Calculator {
} }
}
class SSStats(sessionSet: SessionSet, conditions: List<QueryCondition>) { // 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
}
} }

@ -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<QueryCondition>, stats: List<Stat>? = null) {
/**
* The display name of the group
*/
var name: String = name
/**
* A list of conditions to get
*/
var conditions: List<QueryCondition> = conditions
/**
* The list of endedSessions to compute
*/
private var _computables: RealmResults<ComputableResult>? = null
/**
* Retrieves the computables on the relative [realm] filtered with the provided [conditions]
*/
fun computables(realm: Realm): RealmResults<ComputableResult> {
// if computables exists and is valid (previous realm not closed)
this._computables?.let {
if (it.isValid) {
return it
}
}
val computables: RealmResults<ComputableResult> = Filter.queryOn(realm, this.conditions)
this._computables = computables
return computables
}
/**
* The list of sets to compute
*/
private var _sessionSets: RealmResults<SessionSet>? = null
/**
* Retrieves the session sets on the relative [realm] filtered with the provided [conditions]
*/
fun sessionSets(realm: Realm): RealmResults<SessionSet> {
// if computables exists and is valid (previous realm not closed)
this._sessionSets?.let {
if (it.isValid) {
return it
}
}
val sets: RealmResults<SessionSet> = Filter.queryOn(realm, this.conditions)
this._sessionSets = sets
return sets
}
/**
* The list of stats to display
*/
var stats: List<Stat>? = 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<Stat, ComputedStat> = mutableMapOf()
// The map containing all evolution numericValues for all stats
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf()
fun allStats(): Collection<ComputedStat> {
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<Point> = mutableListOf(point)
this._evolutionValues[stat] = values
}
}
fun addStats(computedStats: Set<ComputedStat>) {
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<Entry> {
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES -> this.barEntries(stat)
else -> this.singleLineEntries(stat)
}
}
fun singleLineEntries(stat: Stat): List<Entry> {
val entries = mutableListOf<Entry>()
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<Entry> {
val entries = mutableListOf<Entry>()
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<BarEntry> {
val entries = mutableListOf<BarEntry>()
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)
}

@ -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<ComputedResults> = mutableListOf()
val results: List<ComputedResults> = 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<Entry>()
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<BarEntry>()
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<LineDataSet> {
val dataSets = mutableListOf<LineDataSet>()
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<QueryCondition> = listOf(), stats: List<Stat>? = null) {
/**
* The display name of the group
*/
var name: String = name
/**
* A list of conditions to get
*/
var conditions: List<QueryCondition> = conditions
/**
* The list of endedSessions to compute
*/
private var _computables: RealmResults<ComputableResult>? = null
/**
* Retrieves the computables on the relative [realm] filtered with the provided [conditions]
*/
fun computables(realm: Realm, sorted: Boolean = false): RealmResults<ComputableResult> {
// 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<ComputableResult>(realm, this.conditions, sortedField)
this._computables = computables
return computables
}
/**
* The list of sets to compute
*/
private var _sessionSets: RealmResults<SessionSet>? = null
/**
* Retrieves the session sets on the relative [realm] filtered with the provided [conditions]
*/
fun sessionSets(realm: Realm, sorted: Boolean = false): RealmResults<SessionSet> {
// 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<SessionSet>(realm, this.conditions, sortedField)
this._sessionSets = sets
return sets
}
/**
* The list of stats to display
*/
var stats: List<Stat>? = 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<Stat, ComputedStat> = mutableMapOf()
// The map containing all evolution numericValues for all stats
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf()
private var shouldManageMultiGroupProgressValues = shouldManageMultiGroupProgressValues
fun allStats(): Collection<ComputedStat> {
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<Point> = 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<ComputedStat>) {
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<out Entry> {
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<Entry>()
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<Entry>()
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<BarEntry>()
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<Int>()
val entries = mutableListOf<BarEntry>()
this._evolutionValues[stat]?.let { points ->
val negative = mutableListOf<Point>()
val positive = mutableListOf<Point>()
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)
}

@ -1,40 +1,66 @@
package net.pokeranalytics.android.calculus package net.pokeranalytics.android.calculus
import android.content.Context import android.content.Context
import io.realm.RealmModel
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FormattingException import net.pokeranalytics.android.exceptions.FormattingException
import net.pokeranalytics.android.model.interfaces.Timed 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.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType 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.NULL_TEXT
import net.pokeranalytics.android.util.extensions.formatted import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import net.pokeranalytics.android.util.extensions.toCurrency
import java.util.* import java.util.*
import kotlin.math.exp
class StatFormattingException(message: String) : Exception(message) { class StatFormattingException(message: String) : Exception(message) {
} }
interface StatBase : RealmModel { class ObjectIdentifier(var id: String, var clazz: Class<out Timed>) {
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 * An enum representing all the types of Session statistics
*/ */
enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentable { enum class Stat : RowRepresentable {
NETRESULT, NET_RESULT,
BB_NET_RESULT,
HOURLY_RATE, HOURLY_RATE,
AVERAGE, AVERAGE,
NUMBER_OF_SETS, NUMBER_OF_SETS,
NUMBER_OF_GAMES, NUMBER_OF_GAMES,
DURATION, HOURLY_DURATION,
AVERAGE_DURATION, AVERAGE_HOURLY_DURATION,
NET_BB_PER_100_HANDS, NET_BB_PER_100_HANDS,
HOURLY_RATE_BB, HOURLY_RATE_BB,
AVERAGE_NET_BB, AVERAGE_NET_BB,
@ -44,17 +70,17 @@ enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentabl
STANDARD_DEVIATION, STANDARD_DEVIATION,
STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS, STANDARD_DEVIATION_BB_PER_100_HANDS,
HANDS_PLAYED; HANDS_PLAYED,
LOCATIONS_PLAYED,
/** LONGEST_STREAKS,
* Returns whether the stat evolution numericValues requires a distribution sorting MAXIMUM_NETRESULT,
*/ MINIMUM_NETRESULT,
fun hasDistributionSorting(): Boolean { MAXIMUM_DURATION,
return when (this) { DAYS_PLAYED,
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true WINNING_SESSION_COUNT,
else -> false BB_SESSION_COUNT,
} TOTAL_BUYIN,
} ;
companion object { companion object {
@ -72,18 +98,32 @@ enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentabl
return netBB / numberOfHands * 100 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? override val resId: Int?
get() { get() {
return when (this) { 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 HOURLY_RATE -> R.string.average_hour_rate
AVERAGE -> R.string.average AVERAGE -> R.string.average
NUMBER_OF_SETS -> R.string.number_of_sessions NUMBER_OF_SETS -> R.string.number_of_sessions
NUMBER_OF_GAMES -> R.string.number_of_records NUMBER_OF_GAMES -> R.string.number_of_records
DURATION -> R.string.duration HOURLY_DURATION -> R.string.duration
AVERAGE_DURATION -> R.string.average_hours_played AVERAGE_HOURLY_DURATION -> R.string.average_hours_played
NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
HOURLY_RATE_BB -> R.string.average_hour_rate_bb_ HOURLY_RATE_BB -> R.string.average_hour_rate_bb_
AVERAGE_NET_BB -> R.string.average_net_result_bb_ AVERAGE_NET_BB -> R.string.average_net_result_bb_
@ -94,6 +134,13 @@ enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentabl
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands
HANDS_PLAYED -> R.string.number_of_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<out Timed>? = null) : RowRepresentabl
/** /**
* Formats the value of the stat to be suitable for display * 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()) { if (value.isNaN()) {
return TextFormat(NULL_TEXT, R.color.white) return TextFormat(NULL_TEXT, R.color.white)
@ -109,31 +156,32 @@ enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentabl
when (this) { when (this) {
// Amounts + red/green // Amounts + red/green
Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> { NET_RESULT, HOURLY_RATE, AVERAGE, MAXIMUM_NETRESULT, MINIMUM_NETRESULT -> {
val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency)
val color = if (value >= this.threshold) R.color.green else R.color.red 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 // 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 val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(value.formatted(), color) return TextFormat(value.formatted(), color)
} }
// white integers // 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()}") return TextFormat("${value.toInt()}")
} // white durations } // white durations
Stat.DURATION, Stat.AVERAGE_DURATION -> { HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> {
return TextFormat(value.formattedHourlyDuration()) return TextFormat(value.formattedHourlyDuration())
} // red/green percentages } // 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 val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color) return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr } // white amountsr
Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY, AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency) return TextFormat(value.toCurrency(currency))
return TextFormat(numberFormat.format(value)) }
LONGEST_STREAKS -> {
return TextFormat("${value.toInt()}W / ${secondValue!!.toInt()}L")
} }
else -> throw FormattingException("Stat formatting of ${this.name} not handled") else -> throw FormattingException("Stat formatting of ${this.name} not handled")
} }
@ -148,14 +196,17 @@ enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentabl
} }
fun cumulativeLabelResId(context: Context) : String { fun cumulativeLabelResId(context: Context): String {
val resId = when (this) { val resId = when (this) {
AVERAGE, AVERAGE_DURATION, NET_BB_PER_100_HANDS, AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS,
HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, WIN_RATIO, HOURLY_RATE -> R.string.average HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, HOURLY_RATE -> R.string.average
NETRESULT, DURATION -> R.string.total 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 -> R.string.net_result
STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
WIN_RATIO, HOURLY_DURATION -> return this.localizedTitle(context)
else -> null else -> null
} }
resId?.let { resId?.let {
@ -165,13 +216,67 @@ enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentabl
} }
} }
val aggregationTypes: List<AggregationType>
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 override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
} }
/** /**
* ComputedStat contains a [stat] and their associated [value] * 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) { constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) {
if (previousValue != null) { 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 { var variation: Double? = null
return this.stat.format(this.value, this.currency, context)
}
/** /**
* 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 { fun format(): TextFormat {
return TextFormat("undef ${index}") return this.stat.format(this.value, this.secondValue, this.currency)
} }
} }

@ -1,8 +1,11 @@
package net.pokeranalytics.android.calculus.bankroll package net.pokeranalytics.android.calculus.bankroll
import io.realm.Realm import io.realm.Realm
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.model.realm.Transaction 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 { class BankrollCalculator {
@ -13,30 +16,80 @@ class BankrollCalculator {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val report = BankrollReport(setup) val report = BankrollReport(setup)
val bankrolls: List<Bankroll> = if (setup.bankroll != null) listOf(setup.bankroll) else realm.where(Bankroll::class.java).findAll()
val sessionQuery = realm.where(Session::class.java) var initialValue = 0.0
if (setup.bankroll != null) { var transactionNet = 0.0
sessionQuery.equalTo("bankroll.id", setup.bankroll.id)
} bankrolls.forEach { bankroll ->
// val sessions = sessionQuery.findAll()
val transactionQuery = realm.where(Transaction::class.java) val rate = if (setup.virtualBankroll) bankroll.rate else 1.0
if (setup.bankroll != null) {
transactionQuery.equalTo("bankroll.id", setup.bankroll.id).findAll() initialValue += bankroll.initialValue * rate
transactionNet += bankroll.transactions.sumByDouble { it.amount } * rate
} }
val transactions = transactionQuery.findAll()
// val sessionsNet = sessions.sum("result.net") report.transactionsNet = transactionNet
val transactionsNet = transactions.sum("value") report.initial = initialValue
val queryConditions = setup.queryConditions
val transactions = Filter.queryOn<Transaction>(realm, queryConditions)
report.addDatedItems(transactions)
transactions.forEach { transactions.forEach {
report.addTransaction(it) report.addTransaction(it)
} }
val sessions = Filter.queryOn<Session>(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<Result>(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() realm.close()
return report 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)
}
}
} }
} }

@ -1,82 +1,74 @@
package net.pokeranalytics.android.calculus.bankroll 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.Bankroll
import net.pokeranalytics.android.model.realm.Transaction import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.graph.PALineDataSet
import java.util.* import java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
/**
* A class describing the parameters required to launch a bankroll report class BankrollReport(setup: BankrollReportSetup) {
*
*/
class BankrollReportSetup(bankroll: Bankroll?, from: Date? = null, to: Date? = null) {
/**
* The bankroll to compute. If null, the virtual global bankroll
*/
val bankroll = bankroll
/** /**
* 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<Transaction> = mutableListOf()
private set
var total: Double = 0.0 var total: Double = 0.0
private set 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 * The net result from poker computables
if (this.useRate) { */
rate = transaction.bankroll?.currency?.rate ?: 1.0 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 var depositTotal: Double = 0.0
private set 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 var withdrawalTotal: Double = 0.0
private set set(value) {
field = value
this.netBanked = this.depositTotal + this.withdrawalTotal
}
/** /**
* The difference between withdrawals and deposits * The difference between withdrawals and deposits
@ -87,16 +79,21 @@ class BankrollReport(setup: BankrollReportSetup) {
/** /**
* The risk of ruin * The risk of ruin
*/ */
var riskOfRuin: Double = 0.0 var riskOfRuin: Double? = null
private set
var transactions: List<Transaction> = mutableListOf() var transactions: List<Transaction> = mutableListOf()
private set private set
var transactionBuckets: HashMap<String, TransactionBucket> = HashMap() var transactionBuckets: HashMap<String, TransactionBucket> = HashMap()
private set
var evolutionPoints: Array<BRGraphPoint> = arrayOf() var evolutionPoints: MutableList<BRGraphPoint> = mutableListOf()
var evolutionItems: Array<DatableValue> = arrayOf() var evolutionItems: MutableList<DatedValue> = mutableListOf()
private set
fun addDatedItems(items: Collection<DatedValue>) {
this.evolutionItems.addAll(items)
}
fun addTransaction(transaction: Transaction) { fun addTransaction(transaction: Transaction) {
@ -105,7 +102,7 @@ class BankrollReport(setup: BankrollReportSetup) {
var bucket = this.transactionBuckets[type.id] var bucket = this.transactionBuckets[type.id]
if (bucket == null) { if (bucket == null) {
val b = TransactionBucket(this.setup.bankroll == null) val b = TransactionBucket(this.setup.virtualBankroll)
this.transactionBuckets[type.id] = b this.transactionBuckets[type.id] = b
bucket = 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<Entry>()
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
}
} val queryConditions: List<QueryCondition>
get() {
val conditions = mutableListOf<QueryCondition>()
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<Transaction> = 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
}

@ -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?
}

@ -1,11 +0,0 @@
package net.pokeranalytics.android.calculus.interfaces
import java.util.*
interface Datable {
var date: Date
}
interface DatableValue : Datable {
var value: Double
}

@ -11,13 +11,14 @@ class ConfigurationException(message: String) : Exception(message)
sealed class PokerAnalyticsException(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 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 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 FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable")
object QueryValueMapUnknown: PokerAnalyticsException(message = "fieldName is missing") 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 QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected")
object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "filter is empty or null") object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "queryWith is empty or null")
data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "filter element '$filterElementRow' type is missing") data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "queryWith element '$filterElementRow' type is missing")
data class QueryValueMapMissingKeys(val missingKeys: List<String>) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys") data class QueryValueMapMissingKeys(val missingKeys: List<String>) : 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}")
} }

@ -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<Criteria>.combined(): List<List<QueryCondition>> {
val comparatorList = ArrayList<List<QueryCondition>>()
this.forEach {
comparatorList.add(it.queryConditions)
}
return getCombinations(comparatorList)
}
fun List<List<QueryCondition>>.upToNow(): List<List<QueryCondition>> {
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 <T> getCombinations(lists: List<List<T>>): List<List<T>> {
var combinations: LinkedHashSet<List<T>> = LinkedHashSet()
var newCombinations: LinkedHashSet<List<T>>
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<T>()
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<T>()
newList.addAll(first)
newList.add(second)
newCombinations.add(newList)
}
}
combinations = newCombinations
index++
}
return combinations.toList()
}
sealed class Criteria {
abstract class RealmCriteria : Criteria() {
inline fun <reified T: NameManageable> comparison(): List<QueryCondition> {
return compare<QueryCondition.QueryDataCondition<NameManageable>, T>()
.sorted()
}
}
abstract class SimpleCriteria(private val conditions:List<QueryCondition>): Criteria() {
fun comparison(): List<QueryCondition> {
return conditions
}
}
abstract class ListCriteria : Criteria() {
inline fun <reified T:QueryCondition.ListOfValues<S>, reified S:Comparable<S>> comparison(): List<QueryCondition> {
QueryCondition.distinct<Session, T, S>()?.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<T, S>(values = values).sorted()
}
return listOf<T>()
}
}
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<QueryCondition>
get() {
return when (this) {
is Bankrolls -> comparison<Bankroll>()
is Games -> comparison<Game>()
is TournamentFeatures -> comparison<TournamentFeature>()
is TournamentNames -> comparison<TournamentName>()
is Locations -> comparison<Location>()
is SimpleCriteria -> comparison()
is Limits -> comparison<QueryCondition.AnyLimit, Int>()
is TournamentTypes -> comparison<QueryCondition.AnyTournamentType, Int>()
is TableSizes -> comparison<QueryCondition.AnyTableSize, Int>()
is TournamentFees -> comparison<QueryCondition.TournamentFee, Double >()
is Years -> {
val years = arrayListOf<QueryCondition.AnyYear>()
val calendar = Calendar.getInstance()
calendar.time = Date()
val yearNow = calendar.get(Calendar.YEAR)
val realm = Realm.getDefaultInstance()
realm.where<Session>().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<QueryCondition.AnyBlind, String >()
else -> throw PokerAnalyticsException.QueryTypeUnhandled
}
}
companion object {
inline fun < reified S : QueryCondition.QueryDataCondition<NameManageable >, reified T : NameManageable > compare(): List<S> {
val objects = arrayListOf<S>()
val realm = Realm.getDefaultInstance()
realm.where<T>().findAll().forEach {
objects.add((QueryCondition.getInstance<T>() as S).apply {
setObject(it)
})
}
realm.close()
return objects
}
inline fun < reified S : QueryCondition.ListOfValues<T>, T:Any > compareList(values:List<T>): List<S> {
val objects = arrayListOf<S>()
values.forEach {
objects.add((S::class.java.newInstance()).apply {
listOfValues = arrayListOf(it)
})
}
return objects
}
}
}

@ -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
}

@ -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? override val resId: Int?
get() { get() {
return if (this.numberOfPlayer == 2) { return if (this.numberOfPlayer == 2) {

@ -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 override val viewType: Int = RowViewType.TITLE.ordinal
} }

@ -25,9 +25,6 @@ enum class SessionState {
*/ */
fun Session.getState(): SessionState { fun Session.getState(): SessionState {
// if (timeFrame == null) {
// return SessionState.PENDING
// }
val start = this.startDate val start = this.startDate
if (start == null) { if (start == null) {
return SessionState.PENDING return SessionState.PENDING
@ -43,4 +40,59 @@ fun Session.getState(): SessionState {
} }
} }
}
val AbstractList<Session>.hourlyDuration: Double
get() {
val intervals = mutableListOf<TimeInterval>()
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<TimeInterval>.update(timeInterval: TimeInterval): MutableList<TimeInterval> {
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
} }

@ -1,9 +1,13 @@
package net.pokeranalytics.android.model.filter package net.pokeranalytics.android.model.filter
import io.realm.RealmModel 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.ComputableResult
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet 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: * 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 different type of objects: Sessions, Hands, Transactions...
* - filters can be applied to a list of different type of objects (feed) * - 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 data type: Session, Hands...
* - a field: table size of a Session * - a field: table size of a Session
* - an operator: equal, >=, <... * - an operator: equal, >=, <...
@ -27,7 +31,7 @@ import net.pokeranalytics.android.model.realm.SessionSet
* - multiple numericValues as 'OR' * - multiple numericValues as 'OR'
* *
* Also: * 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 <reified T : Filterable> RealmResults<T>.filter(conditions: List<QueryCondition>) : RealmResults<T> {
return conditions.queryWith(this.where()).findAll()
}
class FilterHelper { class FilterHelper {
companion object { companion object {
inline fun <reified T : Filterable> fieldNameForQueryType(queryCondition: QueryCondition): String? { inline fun <reified T : Filterable > fieldNameForQueryType(queryCondition: Class< out QueryCondition>): String? {
return when (T::class.java) { val fieldName = when (T::class.java) {
Session::class.java -> Session.fieldNameForQueryType(queryCondition) Session::class.java -> Session.fieldNameForQueryType(queryCondition)
ComputableResult::class.java -> ComputableResult.fieldNameForQueryType(queryCondition) ComputableResult::class.java -> ComputableResult.fieldNameForQueryType(queryCondition)
SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition) SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition)
Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition)
Result::class.java -> Result.fieldNameForQueryType(queryCondition)
else -> { else -> {
throw UnmanagedFilterField("Filterable type fields are not defined for class ${T::class}") throw UnmanagedFilterField("Filterable type fields are not defined for class ${T::class}")
} }
} }
fieldName?.let {
return fieldName
} ?: run {
throw PokerAnalyticsException.MissingFieldNameForQueryCondition(queryCondition.name)
}
} }
} }
} }
// //
//fun MutableList<Filterable>.filter(filter: FilterCondition) : List<Filterable> { //fun MutableList<Filterable>.queryWith(queryWith: FilterCondition) : List<Filterable> {
// //
// return this.filter { f -> // return this.queryWith { f ->
// return@filter true // return@queryWith true
// } // }
//} //}

@ -1,336 +1,605 @@
package net.pokeranalytics.android.model.filter package net.pokeranalytics.android.model.filter
import io.realm.RealmList import io.realm.Realm
import io.realm.RealmQuery 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.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.realm.FilterCondition import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.realm.FilterElementBlind import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.realm.Session 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.endOfDay
import net.pokeranalytics.android.util.extensions.startOfDay import net.pokeranalytics.android.util.extensions.startOfDay
import net.pokeranalytics.android.util.extensions.toCurrency
import java.text.DateFormatSymbols
import java.util.* import java.util.*
import kotlin.collections.ArrayList
fun List<QueryCondition>.name() : String {
return this.map { it.getDisplayName() }.joinToString(" : ")
}
//inline fun <reified T : Filterable> List<QueryCondition>.query(realm: Realm): RealmQuery<T> {
// return this.queryWith(realm.where())
//}
inline fun <reified T : Filterable> List<QueryCondition>.queryWith(query: RealmQuery<T>): RealmQuery<T> {
var realmQuery = query
this.forEach {
realmQuery = it.queryWith(realmQuery)
}
return realmQuery
}
/** /**
* Enum describing the way a query should be handled * Enum describing the way a query should be handled
* Some queries requires a value to be checked upon through equals, in, more, less, between * 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, sealed class QueryCondition : FilterElementRow {
CASH, companion object {
ONLINE, inline fun < reified T:QueryCondition> more():T { return T::class.java.newInstance().apply { this.operator = Operator.MORE } }
TOURNAMENT, inline fun < reified T:QueryCondition> less():T { return T::class.java.newInstance().apply { this.operator = Operator.LESS } }
BANKROLL, inline fun < reified T:QueryCondition> moreOrLess():ArrayList<T> { return arrayListOf(more(), less()) }
GAME,
TOURNAMENT_NAME, fun <T:QueryCondition> valueOf(name:String) : T {
ANY_TOURNAMENT_FEATURES, val kClass = Class.forName("${QueryCondition::class.qualifiedName}$$name").kotlin
ALL_TOURNAMENT_FEATURES, val instance = kClass.objectInstance ?: kClass.java.newInstance()
LOCATION, return instance as T
LIMIT, }
TABLE_SIZE,
TOURNAMENT_TYPE, inline fun <reified T:Identifiable>getInstance(): QueryCondition {
BLINDS, return when (T::class.java) {
LAST_GAMES, Bankroll::class.java -> AnyBankroll()
LAST_SESSIONS, Game::class.java -> AnyGame()
MORE_NUMBER_OF_TABLE(Operator.MORE), Location::class.java -> AnyLocation()
LESS_NUMBER_OF_TABLE(Operator.LESS), TournamentName::class.java -> AnyTournamentName()
BETWEEN_NUMBER_OF_TABLE(Operator.BETWEEN), TournamentFeature::class.java -> AnyTournamentFeature()
MORE_THAN_NET_RESULT(Operator.MORE), else -> throw PokerAnalyticsException.QueryTypeUnhandled
LESS_THAN_NET_RESULT(Operator.LESS), }
MORE_THAN_BUY_IN(Operator.MORE), }
LESS_THAN_BUY_IN(Operator.LESS),
MORE_THAN_CASH_OUT(Operator.MORE), inline fun < reified T: Filterable, reified S: QueryCondition, reified U:Comparable<U>>distinct(): RealmResults<T>? {
LESS_THAN_CASH_OUT(Operator.LESS), FilterHelper.fieldNameForQueryType<T>(S::class.java)?.let {
MORE_THAN_TIPS(Operator.MORE), val realm = Realm.getDefaultInstance()
LESS_THAN_TIPS(Operator.LESS),
MORE_THAN_NUMBER_OF_PLAYER(Operator.MORE), val distincts = when (T::class) {
LESS_THAN_NUMBER_OF_PLAYER(Operator.LESS), String::class, Int::class -> realm.where<T>().distinct(it).findAll().sort(it, Sort.ASCENDING)
BETWEEN_NUMBER_OF_PLAYER(Operator.BETWEEN), else -> realm.where<T>().isNotNull(it).findAll().sort(it, Sort.ASCENDING)
MORE_THAN_TOURNAMENT_FEE(Operator.MORE), }
LESS_THAN_TOURNAMENT_FEE(Operator.LESS),
BETWEEN_TOURNAMENT_FEE(Operator.BETWEEN), realm.close()
MIN_RE_BUY(Operator.MORE), return distincts
MAX_RE_BUY(Operator.LESS), }
return null
// 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,
;
enum class Operator { enum class Operator {
BETWEEN, ANY,
ALL,
MORE, MORE,
LESS; LESS,
EQUALS,
BETWEEN,
BETWEEN_RIGHT_EXCLUSIVE,
BETWEEN_LEFT_EXCLUSIVE,
;
} }
var valueMap : Map<String, Any?>? = null val baseId = this::class.simpleName ?: throw PokerAnalyticsException.FilterElementUnknownName
get() {
this.filterValuesExpectedKeys?.let { valueMapExceptedKeys -> val id: List<String> get() {
field?.let { map -> when (this.operator) {
val missingKeys = map.keys.filter { !valueMapExceptedKeys.contains(it) } Operator.MORE, Operator.LESS -> return listOf("$baseId+${this.operator.name}")
if (map.keys.size == valueMapExceptedKeys.size && missingKeys.isNotEmpty()) {
throw PokerAnalyticsException.QueryValueMapMissingKeys(missingKeys)
}
} ?: run {
throw PokerAnalyticsException.QueryValueMapUnexpectedValue
}
}
return field
} }
private set
private val filterValuesExpectedKeys : Array<String>? return when (this) {
get() { is SingleValue<*> -> listOf(baseId)
this.operator?.let { is ListOfValues<*> -> {
return when (it) { if (listOfValues.isEmpty()) { return listOf(baseId) }
Operator.BETWEEN -> arrayOf("leftValue", "rightValue") this.listOfValues.map{ "$baseId+$it" }
else -> arrayOf("value")
}
} }
return when (this) { else -> listOf(baseId)
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") open var operator: Operator = Operator.ANY
DAY_OF_WEEK -> arrayOf("dayOfWeek")
MONTH -> arrayOf("month") abstract class ListOfValues<T>: QueryCondition(), Comparable<ListOfValues<T>> where T:Comparable<T> {
YEAR -> arrayOf("year") abstract var listOfValues: ArrayList<T>
else -> null 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"
} }
} }
/** override fun compareTo(other: ListOfValues<T>): Int {
* main method of the enum return listOfValues.sorted().first().compareTo(other.listOfValues.sorted().first())
* providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery] }
*/ }
inline fun <reified T : Filterable> filter(realmQuery: RealmQuery<T>): RealmQuery<T> {
when {
this == BLINDS -> {
val smallBlindFieldName = FilterHelper.fieldNameForQueryType<T>(SMALL_BLIND)
val bigBlindFieldName = FilterHelper.fieldNameForQueryType<T>(BIG_BLIND)
val currencyCodeFieldName = FilterHelper.fieldNameForQueryType<T>(CURRENCY_CODE)
smallBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
bigBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
currencyCodeFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
val blinds: RealmList<FilterElementBlind> by valueMap
blinds.forEachIndexed { index, blind ->
realmQuery
.beginGroup()
blind.sb?.let {
realmQuery
.equalTo(smallBlindFieldName, it)
.and()
}
realmQuery abstract class SingleValue<T>: ListOfValues<T>() where T:Comparable<T> {
.equalTo(bigBlindFieldName, blind.bb) override var listOfValues = ArrayList<T>()
.and() abstract var singleValue : T
}
blind.currencyCode?.let { abstract class ListOfDouble: ListOfValues<Double>() {
realmQuery.equalTo(currencyCodeFieldName, it) open var sign: Int = 1
} ?: run {
realmQuery.isNull(currencyCodeFieldName)
}
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) { abstract class ListOfInt: ListOfValues<Int>() {
realmQuery.or() override var listOfValues = arrayListOf(0)
} override fun updateValueMap(filterCondition: FilterCondition) {
} super.updateValueMap(filterCondition)
return realmQuery println("<<<< updateValueMap ${filterCondition.intValues}")
} listOfValues = filterCondition.getValues()
else -> { }
override fun labelForValue(value: Int): String {
return value.toString()
}
}
val fieldName = FilterHelper.fieldNameForQueryType<T>(this) abstract class ListOfString: ListOfValues<String>() {
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown override var listOfValues = ArrayList<String>()
override fun labelForValue(value: String): String { return value }
override fun updateValueMap(filterCondition: FilterCondition) {
super.updateValueMap(filterCondition)
listOfValues = filterCondition.getValues()
}
}
when (operator) { abstract class SingleDate: SingleValue<Date>() {
Operator.LESS -> { override fun labelForValue(value: Date): String {
val value: Double by valueMap return value.toString()
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)
}
}
return when (this) { override var singleValue: Date
LIVE, ONLINE -> realmQuery.equalTo(fieldName, this == LIVE) get() { return listOfValues.firstOrNull() ?: Date() }
CASH -> realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal) set(value) { listOfValues.add(value) }
TOURNAMENT -> realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal)
ALL_TOURNAMENT_FEATURES -> { override fun updateValueMap(filterCondition: FilterCondition) {
val ids: Array<String> by valueMap super.updateValueMap(filterCondition)
ids.forEach { singleValue = filterCondition.getValue()
realmQuery.equalTo(fieldName, it) }
} }
realmQuery
} abstract class SingleInt: SingleValue<Int>() {
ANY_TOURNAMENT_FEATURES -> { override fun labelForValue(value: Int): String {
val ids: Array<String> by valueMap return value.toString()
realmQuery.`in`(fieldName, ids) }
} override var singleValue: Int
BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> { get() { return listOfValues.firstOrNull() ?: 0 }
val ids: Array<String> by valueMap set(value) { listOfValues.add(value) }
realmQuery.`in`(fieldName, ids)
} override fun updateValueMap(filterCondition: FilterCondition) {
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> { super.updateValueMap(filterCondition)
val values: Array<Int?>? by valueMap singleValue = filterCondition.getValue()
realmQuery.`in`(fieldName, values) }
} }
STARTED_FROM_DATE -> {
val date: Date by valueMap override fun getDisplayName(): String { return baseId }
realmQuery.greaterThanOrEqualTo(fieldName, date)
} override var filterSectionRow: FilterSectionRow = FilterSectionRow.CASH_TOURNAMENT
STARTED_TO_DATE -> {
val date: Date by valueMap abstract class QueryDataCondition < T: NameManageable > : ListOfString() {
realmQuery.lessThanOrEqualTo(fieldName, date) fun setObject(dataObject: T) {
} this.listOfValues.removeAll(this.listOfValues)
ENDED_FROM_DATE -> { this.listOfValues.add(dataObject.id)
val date: Date by valueMap }
realmQuery.greaterThanOrEqualTo(fieldName, date)
} abstract val entity : Class<T>
ENDED_TO_DATE -> {
val date: Date by valueMap override fun getDisplayName(): String {
realmQuery.lessThanOrEqualTo(fieldName, date) val realm = Realm.getDefaultInstance()
} val completeLabel = when (listOfValues.size) {
DAY_OF_WEEK -> { 0 -> return NULL_TEXT
val dayOfWeek: Int by valueMap 1,2 -> {
realmQuery.equalTo(fieldName, dayOfWeek) return listOfValues.map { labelForValue(realm, it) }.joinToString(", ")
}
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
}
} }
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<Bankroll>() {
override var entity: Class<Bankroll> = Bankroll::class.java
constructor(bankroll: Bankroll): this() {
this.setObject(bankroll)
} }
}
class AnyGame(): QueryDataCondition<Game>() {
override val entity: Class<Game> = Game::class.java
constructor(game: Game): this() {
this.setObject(game)
}
}
class AnyTournamentName(): QueryDataCondition<TournamentName>() {
override val entity: Class<TournamentName> = TournamentName::class.java
constructor(tournamentName: TournamentName): this() {
this.setObject(tournamentName)
}
} }
fun updateValueMap(filterCondition: FilterCondition) { class AnyTournamentFeature(): QueryDataCondition<TournamentFeature>() {
if (filterValuesExpectedKeys == null) { override val entity: Class<TournamentFeature> = TournamentFeature::class.java
return constructor(tournamentFeature: TournamentFeature): this() {
this.setObject(tournamentFeature)
} }
}
this.operator?.let { class AllTournamentFeature(): QueryDataCondition<TournamentFeature>() {
valueMap = mapOf("value" to filterCondition.value) override var operator = Operator.ALL
return override val entity: Class<TournamentFeature> = TournamentFeature::class.java
constructor(tournamentFeature: TournamentFeature): this() {
this.setObject(tournamentFeature)
} }
}
class AnyLocation(): QueryDataCondition<Location>() {
override val entity: Class<Location> = 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 <reified T : Filterable> queryWith(realmQuery: RealmQuery<T>): RealmQuery<T> {
val fieldName = FilterHelper.fieldNameForQueryType<T>(this::class.java)
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
when (this) { when (this) {
ALL_TOURNAMENT_FEATURES, ANY_TOURNAMENT_FEATURES, BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> { //is Between -> realmQuery.between(fieldName, leftValue, rightValue)
valueMap = mapOf("ids" to filterCondition.ids) //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 -> { WasYesterday -> {
valueMap = mapOf("values" to filterCondition.values) 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 -> { DuringThisWeek -> {
valueMap = mapOf("blinds" to filterCondition.blinds) 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 -> { DuringThisMonth -> {
valueMap = mapOf("date" to filterCondition.date) 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 -> { Operator.MORE -> {
valueMap = mapOf("dayOfWeek" to filterCondition.dayOfWeek) 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 -> { Operator.LESS -> {
valueMap = mapOf("month" to filterCondition.month) 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 -> { Operator.ALL -> {
valueMap = mapOf("year" to filterCondition.year) 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 -> { Operator.ANY -> {
throw PokerAnalyticsException.QueryValueMapUnexpectedValue 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
}
}
}
}
} }

@ -0,0 +1,15 @@
package net.pokeranalytics.android.model.interfaces
import java.util.*
interface Dated {
var date: Date
}
interface DatedValue : Dated {
var amount: Double
}

@ -1,9 +1,10 @@
package net.pokeranalytics.android.model.interfaces 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.* import java.util.*
interface Timed : StatBase, Identifiable { interface Timed : GraphUnderlyingEntry, Identifiable {
fun startDate() : Date? fun startDate() : Date?
@ -29,4 +30,6 @@ interface Timed : StatBase, Identifiable {
val hourlyDuration: Double val hourlyDuration: Double
get() = this.netDuration / 3600000.0 get() = this.netDuration / 3600000.0
val objectIdentifier : ObjectIdentifier
} }

@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.migrations
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import timber.log.Timber import timber.log.Timber
import java.util.*
class PokerAnalyticsMigration : RealmMigration { class PokerAnalyticsMigration : RealmMigration {
@ -35,10 +36,10 @@ class PokerAnalyticsMigration : RealmMigration {
// Migrate to version 2 // Migrate to version 2
if (currentVersion == 1) { if (currentVersion == 1) {
Timber.d("*** Running migration ${currentVersion + 1}") Timber.d("*** Running migration ${currentVersion + 1}")
schema.rename("FilterElement", "FilterCondition") schema.rename("FilterElement", "FilterCondition")
schema.get("Filter")?.let { schema.get("Filter")?.let {
it.renameField("filterElements", "filterConditions") it.renameField("filterElements", "filterConditions")
it.removeField("entityType")
} }
schema.get("SessionSet")?.let { schema.get("SessionSet")?.let {
it.addField("id", String::class.java).setRequired("id", true) it.addField("id", String::class.java).setRequired("id", true)
@ -47,6 +48,34 @@ class PokerAnalyticsMigration : RealmMigration {
currentVersion++ 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 { override fun equals(other: Any?): Boolean {

@ -8,29 +8,12 @@ import io.realm.kotlin.where
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus 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.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.BankrollRow
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.util.NULL_TEXT
import java.util.* import java.util.*
import kotlin.collections.ArrayList
open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { open class Bankroll() : RealmObject(), NameManageable, RowRepresentable {
companion object {
val rowRepresentation : List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.add(SimpleRow.NAME)
rows.add(BankrollRow.LIVE)
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency))
rows.add(BankrollRow.CURRENCY)
rows
}
}
@PrimaryKey @PrimaryKey
override var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
@ -46,37 +29,16 @@ open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDat
// The currency of the bankroll // The currency of the bankroll
var currency: Currency? = null var currency: Currency? = null
override fun getDisplayName(): String { // The initial value of the bankroll
return this.name var initialValue: Double = 0.0
}
// Row Representable Datasource
override fun adapterRows(): List<RowRepresentable>? {
return Bankroll.rowRepresentation
}
override fun stringForRow(row: RowRepresentable): String { val rate: Double
return when (row) { get() {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT return this.currency?.rate ?: 1.0
else -> return super.stringForRow(row)
} }
}
override fun boolForRow(row: RowRepresentable): Boolean { override fun getDisplayName(): String {
return when (row) { return this.name
BankrollRow.LIVE -> !this.live
else -> super.boolForRow(row)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name))
BankrollRow.RATE -> row.editingDescriptors(mapOf())
else -> {
row.editingDescriptors(mapOf())
}
}
} }
override fun updateValue(value: Any?, row: RowRepresentable) { override fun updateValue(value: Any?, row: RowRepresentable) {
@ -84,8 +46,12 @@ open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDat
SimpleRow.NAME -> this.name = value as String? ?: "" SimpleRow.NAME -> this.name = value as String? ?: ""
BankrollRow.LIVE -> { BankrollRow.LIVE -> {
this.live = if (value is Boolean) !value else false this.live = if (value is Boolean) !value else false
}
BankrollRow.INITIAL_VALUE -> {
this.initialValue = value as Double? ?: 0.0
} }
BankrollRow.CURRENCY -> { BankrollRow.CURRENCY -> {
//TODO handle a use default currency option
this.currency?.code = value as String? this.currency?.code = value as String?
} }
BankrollRow.RATE -> { BankrollRow.RATE -> {

@ -1,33 +1,32 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import io.realm.RealmObject import io.realm.RealmObject
import net.pokeranalytics.android.calculus.interfaces.Computable
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition 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 var session: Session? = null
fun updateWith(session: Session) { fun updateWith(session: Session) {
this.sessionSet = session.sessionSet // this.sessionSet = session.sessionSet
val rate = session.bankroll?.currency?.rate ?: 1.0 val rate = session.bankroll?.currency?.rate ?: 1.0
@ -56,8 +55,11 @@ open class ComputableResult() : RealmObject(), Computable, Filterable {
companion object { companion object {
fun fieldNameForQueryType(queryCondition: QueryCondition): String? { fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? {
return "session." + Session.fieldNameForQueryType(queryCondition) Session.fieldNameForQueryType(queryCondition)?.let {
return "session.$it"
}
return null
} }
} }

@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.util.UserDefaults
import java.util.* import java.util.*
open class Currency : RealmObject() { open class Currency : RealmObject() {
@ -13,7 +14,7 @@ open class Currency : RealmObject() {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() var id = UUID.randomUUID().toString()
/** /**
* The currency code of the currency, i.e. USD, EUR... * The currency code of the currency, i.e. USD, EUR...
*/ */
var code: String? = null var code: String? = null
@ -37,7 +38,15 @@ open class Currency : RealmObject() {
computable.ratedBuyin = it * rate computable.ratedBuyin = it * rate
} }
computable.session?.bankrollHasBeenUpdated()
} }
} }
fun hasMainCurrencyCode() : Boolean {
this.code?.let { return it == UserDefaults.currency.currencyCode }
return false
}
} }

@ -3,13 +3,9 @@ package net.pokeranalytics.android.model.realm
import io.realm.* import io.realm.*
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow 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 timber.log.Timber
import java.util.* import java.util.*
@ -20,13 +16,6 @@ import java.util.*
*/ */
open class Filter : RealmObject() { open class Filter : RealmObject() {
private var entityType: Int? = Entity.SESSION.ordinal
private enum class Entity {
SESSION,
;
}
companion object { companion object {
// Create a new instance // Create a new instance
@ -35,16 +24,18 @@ open class Filter : RealmObject() {
return realm.copyToRealm(filter) return realm.copyToRealm(filter)
} }
// Get a filter by its id // Get a queryWith by its id
fun getFilterBydId(realm: Realm, filterId: String): Filter? { fun getFilterBydId(realm: Realm, filterId: String): Filter? {
return realm.where<Filter>().equalTo("id", filterId).findFirst() return realm.where<Filter>().equalTo("id", filterId).findFirst()
} }
@TestOnly inline fun <reified T : Filterable> queryOn(realm: Realm, queries: List<QueryCondition>, sortField: String? = null): RealmResults<T> {
inline fun <reified T : Filterable> queryOn(realm: Realm, queries: List<QueryCondition>): RealmResults<T> {
var realmQuery = realm.where<T>() var realmQuery = realm.where<T>()
queries.forEach { queries.forEach {
realmQuery = it.filter(realmQuery) realmQuery = it.queryWith(realmQuery)
}
sortField?.let {
realmQuery.sort(it)
} }
Timber.d(">>> Filter query: ${realmQuery.description}") Timber.d(">>> Filter query: ${realmQuery.description}")
return realmQuery.findAll() return realmQuery.findAll()
@ -54,81 +45,76 @@ open class Filter : RealmObject() {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() var id = UUID.randomUUID().toString()
// the filter name // the queryWith name
var name: String = "" 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 // for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0) val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0)
var filterConditions: RealmList<FilterCondition> = RealmList() var filterConditions: RealmList<FilterCondition> = RealmList()
private set private set
fun createOrUpdateFilterConditions(filterConditionRows: ArrayList<FilterElementRow>) { fun createOrUpdateFilterConditions(filterConditionRows: ArrayList<QueryCondition>) {
filterConditions.clear() println("list of querys saving: ${filterConditionRows.map { it.id }}")
filterConditionRows println("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}")
filterConditionRows
.map { .map {
it.filterName it.filterSectionRow
} }
.distinct() .distinct()
.forEach { filterName-> .forEach { filterName->
filterConditionRows filterConditionRows
.filter { .filter {
it.filterName == filterName it.filterSectionRow == filterName
} }
.apply { .apply {
val casted = arrayListOf<FilterElementRow>()
println("list of querys: ${this.map { it.id }}")
val casted = arrayListOf<QueryCondition>()
casted.addAll(this) 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 { fun remove(filterCategoryRow: FilterCategoryRow) {
val sections = filterCategoryRow.filterSectionRows val sections = filterCategoryRow.filterSectionRows.map { it.name }
return filterConditions.count { val savedSections = filterConditions.filter { sections.contains(it.sectionName) }
sections.contains(FilterSectionRow.valueOf(it.sectionName ?: throw PokerAnalyticsException.FilterElementUnknownSectionName)) this.filterConditions.removeAll(savedSections)
} }
}
fun contains(filterElementRow: FilterElementRow): Boolean { fun countBy(filterCategoryRow: FilterCategoryRow): Int {
val filtered = filterConditions.filter { val sections = filterCategoryRow.filterSectionRows.map { it.name }
it.filterName == filterElementRow.filterName println("list of sections $sections")
} val savedSections = filterConditions.filter { sections.contains(it.sectionName) }.flatMap { it.queryCondition.id }
if (filtered.isEmpty()) { println("list of savedSections $savedSections")
return false return savedSections.size
}
return filterElementRow.contains(filtered)
} }
/** fun contains(filterElementRow: QueryCondition): Boolean {
* Set the saved value in the filter for the given [filterElementRow] println("list of saved queries ${filterConditions.map { it.queryCondition.id }}")
*/ println("list of contains ${filterElementRow.id}")
fun setSavedValueForElement(filterElementRow: FilterElementRow) { val contained = filterConditions.flatMap{ it.queryCondition.id }.contains(filterElementRow.id.first())
when (filterElementRow) { println("list of : $contained")
is FilterElementRow.PastDays -> { return contained
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()
}
} }
/** /**
* Get the saved value for the given [filterElementRow] * Get the saved value for the given [filterElementRow]
*/ */
private fun getSavedValueForElement(filterElementRow: FilterElementRow): Any? { fun loadValueForElement(filterElementRow: QueryCondition) {
val filtered = filterConditions.filter { val filtered = filterConditions.filter {
it.filterName == filterElementRow.filterName it.queryCondition.id == filterElementRow.id
} }
if (filtered.isNotEmpty()) { if (filtered.isNotEmpty()) {
return filtered.first().getFilterConditionValue(filterElementRow) return filterElementRow.updateValueMap(filtered.first())
} }
return null
} }
inline fun <reified T : Filterable> results(): RealmResults<T> { inline fun <reified T : Filterable> results(): RealmResults<T> {
@ -136,7 +122,7 @@ open class Filter : RealmObject() {
this.filterConditions.map { this.filterConditions.map {
it.queryCondition it.queryCondition
}.forEach { }.forEach {
realmQuery = it.filter(realmQuery) realmQuery = it.queryWith(realmQuery)
} }
return realmQuery.findAll() return realmQuery.findAll()

@ -4,9 +4,8 @@ import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.filter.QueryCondition 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 java.util.*
import kotlin.collections.ArrayList
open class FilterCondition() : RealmObject() { open class FilterCondition() : RealmObject() {
@ -15,35 +14,16 @@ open class FilterCondition() : RealmObject() {
this.sectionName = sectionName this.sectionName = sectionName
} }
constructor(filterElementRows: ArrayList<FilterElementRow>) : this(filterElementRows.first().filterName, filterElementRows.first().filterSectionRow.name) { constructor(filterElementRows: ArrayList<QueryCondition>) : this(filterElementRows.first().baseId, filterElementRows.first().filterSectionRow.name) {
val row = filterElementRows.first() val row = filterElementRows.first()
this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName
this.operator = row.operator.ordinal
when (row) { when (row) {
is DateFilterElementRow -> { is QueryCondition.SingleInt -> this.setValue(row.singleValue)
this.dateValue = row.dateValue is QueryCondition.SingleDate -> this.setValue(row.singleValue)
} is QueryCondition.ListOfDouble -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfDouble).listOfValues })
is StringFilterElementRow -> { is QueryCondition.ListOfInt -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfInt).listOfValues })
this.stringValues = RealmList<String>().apply { is QueryCondition.ListOfString -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfString).listOfValues })
this.addAll(filterElementRows.map {
(it as StringFilterElementRow).stringValue
})
}
}
is NumericFilterElementRow -> {
this.numericValues = RealmList<Double>().apply {
this.addAll(filterElementRows.map {
(it as NumericFilterElementRow).doubleValue
})
}
}
is FilterElementBlind -> {
this.blindValues = RealmList<FilterElementBlind>().apply {
this.addAll(filterElementRows.map {
FilterElementBlind((it as FilterElementRow.Blind).sb, it.bb, it.code)
})
}
}
} }
} }
@ -51,75 +31,62 @@ open class FilterCondition() : RealmObject() {
var sectionName: String? = null var sectionName: String? = null
val queryCondition : QueryCondition val queryCondition : QueryCondition
get() = QueryCondition.valueOf(this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName) get() = QueryCondition.valueOf<QueryCondition>(this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName)
.apply { .apply {
this.updateValueMap(this@FilterCondition) this.updateValueMap(this@FilterCondition)
} }
private var numericValues: RealmList<Double>? = null var doubleValues: RealmList<Double>? = null
private var dateValue: Date? = null var intValues: RealmList<Int>? = null
private var stringValues: RealmList<String>? = null var stringValues: RealmList<String>? = null
private var blindValues: RealmList<FilterElementBlind>? = null var dateValue: Date? = null
var doubleValue: Double? = null
val ids: Array<String> var intValue: Int? = null
get() = stringValues?.toTypedArray() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing var stringValue: String? = null
var operator: Int? = null
val blinds: RealmList<FilterElementBlind>
get() { inline fun <reified T:Any > getValues(): ArrayList < T > {
blindValues?.let { println("<<<< r $stringValues")
if (it.isNotEmpty()) { return when (T::class) {
return it Int::class -> ArrayList<T>().apply { intValues?.map { add(it as T) } }
} else { Double::class -> ArrayList<T>().apply { doubleValues?.map { add(it as T) } }
throw PokerAnalyticsException.FilterElementExpectedValueMissing String::class -> ArrayList<T>().apply { stringValues?.map { add(it as T) } }
} else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue
} }
throw PokerAnalyticsException.FilterElementExpectedValueMissing }
}
val date: Date
get() = dateValue ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
val values: Array<Int>
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
val month: Int inline fun <reified T:Any > getValue(): T {
get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing 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 <reified T> setValues(values:List<T>) {
when (T::class) {
Int::class -> intValues = RealmList<Int>().apply { values.map { it as Int }.forEach { add(it) } }
Double::class -> doubleValues = RealmList<Double>().apply { values.map { it as Double }.forEach { add(it) } }
String::class -> stringValues = RealmList<String>().apply { values.map { it as String }.forEach { add(it) } }
else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue
}
}
val year: Int fun setValue(value:Double) {
get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing doubleValue = value
}
fun setValue(value:Date) {
dateValue = value
}
/** fun setValue(value:Int) {
* Return the value associated with the given [filterElementRow] intValue= value
*/ }
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:String) {
stringValue = value
}
} }

@ -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()

@ -11,7 +11,7 @@ enum class ReportDisplay {
MAP MAP
} }
open class Report : RealmObject() { open class ReportSetup : RealmObject() {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() var id = UUID.randomUUID().toString()
@ -24,7 +24,7 @@ open class Report : RealmObject() {
// @todo define the configuration options // @todo define the configuration options
// var comparators: List<Int> = listOf() // var criteria: List<Int> = listOf()
// var stats: List<Int> = listOf() // var stats: List<Int> = listOf()
// The filters associated with the report // The filters associated with the report

@ -6,16 +6,29 @@ import io.realm.RealmResults
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import io.realm.annotations.RealmClass import io.realm.annotations.RealmClass
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
@RealmClass @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 * The buyin amount
*/ */
var buyin: Double? = null var buyin: Double? = null
set(value) { set(value) {
field = value field = value
this.computeNumberOfRebuy()
this.computeNet() this.computeNet()
} }
@ -36,6 +49,15 @@ open class Result : RealmObject() {
*/ */
var netResult: Double? = null var netResult: Double? = null
set(value) { 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 field = value
this.computeNet() this.computeNet()
if (value != null) { if (value != null) {
@ -64,6 +86,9 @@ open class Result : RealmObject() {
// The tournament final position, if applicable // The tournament final position, if applicable
var tournamentFinalPosition: Int? = null var tournamentFinalPosition: Int? = null
// Number of rebuys
//var numberOfRebuy: Double? = null
@LinkingObjects("result") @LinkingObjects("result")
private val sessions: RealmResults<Session>? = null private val sessions: RealmResults<Session>? = null
@ -96,6 +121,10 @@ open class Result : RealmObject() {
this.session?.sessionSet?.computeStats() this.session?.sessionSet?.computeStats()
} }
// Computes the number of rebuy
private fun computeNumberOfRebuy() {
}
// @todo tips? // @todo tips?
} }

@ -1,6 +1,7 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import android.content.Context import android.content.Context
import com.github.mikephil.charting.data.Entry
import io.realm.Realm import io.realm.Realm
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
@ -11,10 +12,7 @@ import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedStat import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.exceptions.ModelException import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.LiveData 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.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.*
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable 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.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.CurrencyUtils
import net.pokeranalytics.android.util.NULL_TEXT 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 net.pokeranalytics.android.util.extensions.*
import java.text.DateFormat
import java.util.* import java.util.*
import java.util.Currency import java.util.Currency
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -46,8 +43,7 @@ import kotlin.collections.ArrayList
typealias BB = Double typealias BB = Double
open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed, open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed,
TimeFilterable, Filterable { TimeFilterable, Filterable, DatedValue {
enum class Type { enum class Type {
CASH_GAME, CASH_GAME,
@ -67,36 +63,29 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return realm.copyToRealm(session) return realm.copyToRealm(session)
} }
fun fieldNameForQueryType(queryCondition: QueryCondition): String? { fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? {
return when (queryCondition) { return when (queryCondition) {
LIVE, ONLINE -> "bankroll.live" IsLive::class.java, IsOnline::class.java -> "bankroll.live"
CASH, TOURNAMENT -> "type" IsCash::class.java, IsTournament::class.java -> "type"
BANKROLL -> "bankroll.id" AnyBankroll::class.java -> "bankroll.id"
GAME -> "game.id" AnyGame::class.java -> "game.id"
TOURNAMENT_NAME -> "tournamentName.id" AnyTournamentName::class.java -> "tournamentName.id"
ANY_TOURNAMENT_FEATURES, ALL_TOURNAMENT_FEATURES -> "tournamentFeatures.id" AnyTournamentFeature::class.java, AllTournamentFeature::class.java -> "tournamentFeatures.id"
LOCATION -> "location.id" AnyLocation::class.java -> "location.id"
LIMIT -> "limit" AnyLimit::class.java -> "limit"
TABLE_SIZE -> "tableSize" AnyTableSize::class.java -> "tableSize"
TOURNAMENT_TYPE -> "tournamentType" AnyTournamentType::class.java -> "tournamentType"
CURRENCY -> "bankroll.currency" AnyBlind::class.java -> "blinds"
CURRENCY_CODE -> "bankroll.currency.code" NumberOfTable::class.java -> "numberOfTable"
BIG_BLIND -> "cgBigBlind" NetAmountWon::class.java -> "computableResults.ratedNet"
SMALL_BLIND -> "cgSmallBlind" NumberOfPlayer::class.java -> "tournamentNumberOfPlayers"
COMMENT -> "comment" TournamentFee::class.java -> "tournamentEntryFee"
BETWEEN_NUMBER_OF_TABLE, MORE_NUMBER_OF_TABLE, LESS_NUMBER_OF_TABLE -> "numberOfTable" StartedFromDate::class.java, StartedToDate::class.java -> "startDate"
MORE_THAN_NET_RESULT, LESS_THAN_NET_RESULT -> "computableResults.ratedNet" EndedFromDate::class.java, EndedToDate::class.java -> "endDate"
MORE_THAN_BUY_IN, LESS_THAN_BUY_IN -> "result.buyin" AnyDayOfWeek::class.java, IsWeekEnd::class.java, IsWeekDay::class.java -> "dayOfWeek"
MORE_THAN_CASH_OUT, LESS_THAN_CASH_OUT -> "result.cashout" AnyMonthOfYear::class.java -> "month"
MORE_THAN_TIPS, LESS_THAN_TIPS -> "result.tips" AnyYear::class.java -> "year"
MORE_THAN_NUMBER_OF_PLAYER, LESS_THAN_NUMBER_OF_PLAYER, BETWEEN_NUMBER_OF_PLAYER -> "tournamentNumberOfPlayers" IsToday::class.java, WasYesterday::class.java, WasTodayAndYesterday::class.java, DuringThisYear::class.java, DuringThisMonth::class.java, DuringThisWeek::class.java -> "startDate"
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"
else -> null else -> null
} }
} }
@ -122,7 +111,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// Timed interface // Timed interface
override var dayOfWeek : Int? = null override var dayOfWeek: Int? = null
override var month: Int? = null override var month: Int? = null
override var year: Int? = null override var year: Int? = null
override var dayOfMonth: Int? = null override var dayOfMonth: Int? = null
@ -221,14 +210,22 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The small blind value // The small blind value
var cgSmallBlind: Double? = null var cgSmallBlind: Double? = null
set(value) {
field = value
formatBlinds()
}
// The big blind value // The big blind value
var cgBigBlind: Double? = null var cgBigBlind: Double? = null
set(value) { set(value) {
field = value field = value
this.computeStats() this.computeStats()
formatBlinds()
} }
var blinds: String? = null
private set
// Tournament // Tournament
// The entry fee of the 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... // The features of the tournament, like Knockout, Shootout, Turbo...
var tournamentFeatures: RealmList<TournamentFeature> = RealmList() var tournamentFeatures: RealmList<TournamentFeature> = RealmList()
fun bankrollHasBeenUpdated() {
formatBlinds()
}
/** /**
* Manages impacts on SessionSets * Manages impacts on SessionSets
* Should be called when the start / end date are changed * Should be called when the start / end date are changed
@ -299,11 +300,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/ */
val bbNet: BB val bbNet: BB
get() { get() {
val bb = this.cgBigBlind; val result = this.result val bb = this.cgBigBlind
if (bb != null && result != null) { val result = this.result
return result.net / bb return if (bb != null && result != null) {
result.net / bb
} else { } else {
return 0.0 0.0
} }
} }
@ -318,6 +320,21 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return noh * hd 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 * Pre-compute various stats
*/ */
@ -353,16 +370,6 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return playerHandsPerHour / tableSize.toDouble() 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 val hourlyRate: Double
get() { get() {
this.result?.let { result -> this.result?.let { result ->
@ -483,14 +490,14 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return NULL_TEXT return NULL_TEXT
} }
/** val currency: Currency
* Return the formatted blinds get() {
*/ return bankroll?.currency?.code?.let {
fun getBlinds(context: Context): String { Currency.getInstance(it)
val currencyCode = bankroll?.currency?.code ?: CurrencyUtils.getLocaleCurrency().currencyCode } ?: run {
val currencySymbol = Currency.getInstance(currencyCode).getSymbol(Preferences.getCurrencyLocale(context)) UserDefaults.currency
return if (cgSmallBlind == null) NULL_TEXT else "$currencySymbol ${cgSmallBlind?.formatted()}/${cgBigBlind?.round()}" }
} }
/** /**
* Return the game title * Return the game title
@ -509,6 +516,19 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return if (gameTitle.isNotBlank()) gameTitle else NULL_TEXT 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 // LifeCycle
/** /**
@ -547,7 +567,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
@Ignore @Ignore
private var rowRepresentationForCurrentState : List<RowRepresentable> = mutableListOf() private var rowRepresentationForCurrentState: List<RowRepresentable> = mutableListOf()
private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> { private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
@ -559,34 +579,34 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getFormattedDuration(), 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 -> { SessionState.PAUSED -> {
rows.add( rows.add(
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
resId = R.string.pause, 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 -> { SessionState.FINISHED -> {
rows.add( rows.add(
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getFormattedDuration(), 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( rows.add(
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT, RowViewType.HEADER_TITLE_AMOUNT,
resId = R.string.hour_rate_without_pauses, 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 -> { else -> {
} }
@ -625,21 +645,21 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
override fun stringForRow(row: RowRepresentable, context: Context): String { override fun stringForRow(row: RowRepresentable, context: Context): String {
return when (row) { return when (row) {
SessionRow.BANKROLL -> bankroll?.name ?: NULL_TEXT 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.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.BUY_IN -> this.result?.buyin?.toCurrency(currency) ?: NULL_TEXT
SessionRow.CASHED_OUT, SessionRow.PRIZE -> this.result?.cashout?.toCurrency(CurrencyUtils.getCurrency(bankroll)) ?: NULL_TEXT SessionRow.CASHED_OUT, SessionRow.PRIZE -> this.result?.cashout?.toCurrency(currency) ?: NULL_TEXT
SessionRow.NET_RESULT -> this.result?.netResult?.toCurrency(CurrencyUtils.getCurrency(bankroll)) ?: NULL_TEXT SessionRow.NET_RESULT -> this.result?.netResult?.toCurrency(currency) ?: NULL_TEXT
SessionRow.COMMENT -> if (this.comment.isNotEmpty()) this.comment else NULL_TEXT SessionRow.COMMENT -> if (this.comment.isNotEmpty()) this.comment else NULL_TEXT
SessionRow.END_DATE -> this.endDate?.shortDateTime() ?: NULL_TEXT SessionRow.END_DATE -> this.endDate?.shortDateTime() ?: NULL_TEXT
SessionRow.GAME -> getFormattedGame() 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.LOCATION -> location?.name ?: NULL_TEXT
SessionRow.PLAYERS -> tournamentNumberOfPlayers?.toString() ?: NULL_TEXT SessionRow.PLAYERS -> tournamentNumberOfPlayers?.toString() ?: NULL_TEXT
SessionRow.POSITION -> result?.tournamentFinalPosition?.toString() ?: NULL_TEXT SessionRow.POSITION -> result?.tournamentFinalPosition?.toString() ?: NULL_TEXT
SessionRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT SessionRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT
SessionRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: 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 { SessionRow.TOURNAMENT_TYPE -> this.tournamentType?.let {
TournamentType.values()[it].localizedTitle(context) TournamentType.values()[it].localizedTitle(context)
} ?: run { } ?: run {
@ -647,7 +667,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
SessionRow.TOURNAMENT_FEATURE -> { SessionRow.TOURNAMENT_FEATURE -> {
if (tournamentFeatures.size > 2) { if (tournamentFeatures.size > 2) {
"${tournamentFeatures.subList(0,2).joinToString { "${tournamentFeatures.subList(0, 2).joinToString {
it.name it.name
}}, ..." }}, ..."
} else if (tournamentFeatures.size > 0) { } else if (tournamentFeatures.size > 0) {
@ -674,56 +694,98 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? { override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) { return when (row) {
SessionRow.BANKROLL -> row.editingDescriptors(mapOf( SessionRow.BANKROLL -> row.editingDescriptors(
"defaultValue" to this.bankroll, mapOf(
"data" to LiveData.BANKROLL.items(realm))) "defaultValue" to this.bankroll,
SessionRow.GAME -> row.editingDescriptors(mapOf( "data" to LiveData.BANKROLL.items(realm)
"limit" to this.limit, )
"defaultValue" to this.game, )
"data" to LiveData.GAME.items(realm))) SessionRow.GAME -> row.editingDescriptors(
SessionRow.LOCATION -> row.editingDescriptors(mapOf( mapOf(
"defaultValue" to this.location, "limit" to this.limit,
"data" to LiveData.LOCATION.items(realm))) "defaultValue" to this.game,
SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(mapOf( "data" to LiveData.GAME.items(realm)
"defaultValue" to this.tournamentFeatures, )
"data" to LiveData.TOURNAMENT_FEATURE.items(realm))) )
SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(mapOf( SessionRow.LOCATION -> row.editingDescriptors(
"defaultValue" to this.tournamentName, mapOf(
"data" to LiveData.TOURNAMENT_NAME.items(realm))) "defaultValue" to this.location,
SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors(mapOf( "data" to LiveData.LOCATION.items(realm)
"defaultValue" to this.tournamentType)) )
SessionRow.TABLE_SIZE -> row.editingDescriptors(mapOf( )
"defaultValue" to this.tableSize)) SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(
SessionRow.BLINDS -> row.editingDescriptors(mapOf( mapOf(
"sb" to cgSmallBlind?.round(), "defaultValue" to this.tournamentFeatures,
"bb" to cgBigBlind?.round() "data" to LiveData.TOURNAMENT_FEATURE.items(realm)
)) )
SessionRow.BUY_IN -> row.editingDescriptors(mapOf( )
"bb" to cgBigBlind, SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(
"fee" to this.tournamentEntryFee, mapOf(
"ratedBuyin" to result?.buyin "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.BREAK_TIME -> row.editingDescriptors(mapOf())
SessionRow.CASHED_OUT, SessionRow.PRIZE -> row.editingDescriptors(mapOf( SessionRow.CASHED_OUT, SessionRow.PRIZE -> row.editingDescriptors(
"defaultValue" to result?.cashout mapOf(
)) "defaultValue" to result?.cashout
SessionRow.NET_RESULT -> row.editingDescriptors(mapOf( )
"defaultValue" to result?.netResult )
)) SessionRow.NET_RESULT -> row.editingDescriptors(
SessionRow.COMMENT -> row.editingDescriptors(mapOf( mapOf(
"defaultValue" to this.comment)) "defaultValue" to result?.netResult
SessionRow.INITIAL_BUY_IN -> row.editingDescriptors(mapOf( )
"defaultValue" to this.tournamentEntryFee )
)) SessionRow.COMMENT -> row.editingDescriptors(
SessionRow.PLAYERS -> row.editingDescriptors(mapOf( mapOf(
"defaultValue" to this.tournamentNumberOfPlayers)) "defaultValue" to this.comment
SessionRow.POSITION -> row.editingDescriptors(mapOf( )
"defaultValue" to this.result?.tournamentFinalPosition)) )
SessionRow.TIPS -> row.editingDescriptors(mapOf( SessionRow.INITIAL_BUY_IN -> row.editingDescriptors(
"sb" to cgSmallBlind?.round(), mapOf(
"bb" to cgBigBlind?.round(), "defaultValue" to this.tournamentEntryFee
"tips" to result?.tips )
)) )
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 else -> null
} }
} }
@ -760,13 +822,15 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000 this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000
} }
SessionRow.BUY_IN -> { 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? localResult.buyin = value as Double?
this.result = localResult this.result = localResult
this.updateRowRepresentation() this.updateRowRepresentation()
} }
SessionRow.CASHED_OUT, SessionRow.PRIZE -> { 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? 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 -> this.result?.let { result ->
val value: Double? = when (stat) { 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.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> 1.0
Stat.AVERAGE_BUYIN -> result.buyin Stat.AVERAGE_BUYIN -> result.buyin
Stat.ROI -> { Stat.ROI -> {
@ -864,16 +933,20 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
} }
Stat.HOURLY_RATE_BB -> this.bbHourlyRate 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.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.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate
Stat.HANDS_PLAYED -> this.estimatedHands Stat.HANDS_PLAYED -> this.estimatedHands
Stat.WIN_RATIO -> null
else -> throw StatFormattingException("format undefined for stat ${stat.name}") else -> throw StatFormattingException("format undefined for stat ${stat.name}")
} }
value?.let { value?.let {
return stat.format(it, CurrencyUtils.getCurrency(this.bankroll), context) return stat.format(it, currency = currency)
} ?: run { } ?: run {
return TextFormat(NULL_TEXT) 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)
}

@ -1,18 +1,20 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm import io.realm.Realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.calculus.TextFormat import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.util.NULL_TEXT
import java.text.DateFormat
import java.util.* import java.util.*
@ -79,17 +81,13 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
var bbNet: BB = 0.0 var bbNet: BB = 0.0
override fun formattedValue(stat: Stat, context: Context) : TextFormat { val bbHourlyRate: BB
return when (stat) { get() {
Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, null, context) return this.bbNet / this.hourlyDuration
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}")
} }
}
enum class Field(val identifier: String) { enum class Field(val identifier: String) {
START_DATE("startDate"),
RATED_NET("ratedNet"), RATED_NET("ratedNet"),
HOURLY_RATE("hourlyRate"), HOURLY_RATE("hourlyRate"),
BB_NET("bbNet"), BB_NET("bbNet"),
@ -104,11 +102,45 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
return realm.copyToRealm(sessionSet) return realm.copyToRealm(sessionSet)
} }
fun fieldNameForQueryType(queryCondition: QueryCondition): String? { fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? {
return "sessions." + Session.fieldNameForQueryType(queryCondition) 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)
} }

@ -5,6 +5,9 @@ import io.realm.RealmObject
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.R 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.Manageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -15,39 +18,47 @@ import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable { open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, Filterable, DatedValue {
companion object { companion object {
val rowRepresentation : List<RowRepresentable> by lazy { val rowRepresentation: List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
rows.addAll(TransactionRow.values()) rows.addAll(TransactionRow.values())
rows rows
} }
fun fieldNameForQueryType(queryCondition: Class<out QueryCondition>): 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() override var id = UUID.randomUUID().toString()
// The bankroll of the transaction // The bankroll of the transaction
var bankroll: Bankroll? = null var bankroll: Bankroll? = null
// The amount of the transaction // The amount of the transaction
var amount: Double = 0.0 override var amount: Double = 0.0
// The date of the transaction // The date of the transaction
var date: Date = Date() override var date: Date = Date()
// The type of the transaction // The type of the transaction
var type: TransactionType? = null var type: TransactionType? = null
// A user comment // A user comment
var comment: String = "" var comment: String = ""
@Ignore @Ignore
override val viewType: Int = RowViewType.ROW_TRANSACTION.ordinal override val viewType: Int = RowViewType.ROW_TRANSACTION.ordinal
override fun updateValue(value: Any?, row: RowRepresentable) { override fun updateValue(value: Any?, row: RowRepresentable) {
when(row) { when (row) {
TransactionRow.BANKROLL -> bankroll = value as Bankroll? TransactionRow.BANKROLL -> bankroll = value as Bankroll?
TransactionRow.TYPE -> type = value as TransactionType? TransactionRow.TYPE -> type = value as TransactionType?
TransactionRow.AMOUNT -> amount = if (value == null) 0.0 else (value as String).toDouble() 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 { override fun getFailedDeleteMessage(): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} }
}
}

@ -14,65 +14,77 @@ import kotlin.collections.ArrayList
open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable {
companion object {
val rowRepresentation : List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
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<RowRepresentable>? {
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<RowRepresentableEditDescriptor>? {
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 { enum class Value(val additive: Boolean) {
WITHDRAWAL, WITHDRAWAL(false),
DEPOSIT DEPOSIT(true),
BONUS(true)
}
companion object {
val rowRepresentation: List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
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<RowRepresentable>? {
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<RowRepresentableEditDescriptor>? {
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.
}
} }

@ -3,24 +3,23 @@ package net.pokeranalytics.android.model.utils
import android.content.Context import android.content.Context
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.Currency import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.util.CurrencyUtils import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.CurrencyUtils.Companion.getLocaleCurrency
import net.pokeranalytics.android.util.Preferences
import java.util.* import java.util.*
class Seed(var context:Context) : Realm.Transaction { class Seed(var context:Context) : Realm.Transaction {
override fun execute(realm: Realm) { override fun execute(realm: Realm) {
this.createDefaultGames(realm) this.createDefaultGames(realm)
this.createDefaultTournamentFeatures(realm) this.createDefaultTournamentFeatures(realm)
this.createDefaultCurrencyAndBankroll(realm) this.createDefaultCurrencyAndBankroll(realm)
this.createDefaultTransactionTypes(realm)
} }
private fun createDefaultTournamentFeatures(realm: 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() val tournamentFeature = TournamentFeature()
tournamentFeature.id = UUID.randomUUID().toString() tournamentFeature.id = UUID.randomUUID().toString()
tournamentFeature.name = it tournamentFeature.name = it
@ -29,23 +28,24 @@ class Seed(var context:Context) : Realm.Transaction {
} }
private fun createDefaultCurrencyAndBankroll(realm: Realm) { private fun createDefaultCurrencyAndBankroll(realm: Realm) {
// Currency // Currency
val localeCurrency = getLocaleCurrency() val localeCurrency = UserDefaults.getLocaleCurrency()
val defaultCurrency = Currency() val defaultCurrency = Currency()
defaultCurrency.code = localeCurrency.currencyCode defaultCurrency.code = localeCurrency.currencyCode
realm.insertOrUpdate(defaultCurrency) realm.insertOrUpdate(defaultCurrency)
// Bankroll // Bankroll
val bankroll = 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.live = true
bankroll.currency = realm.where<Currency>().equalTo("code", localeCurrency.currencyCode).findFirst() bankroll.currency = realm.where<Currency>().equalTo("code", localeCurrency.currencyCode).findFirst()
realm.insertOrUpdate(bankroll) realm.insertOrUpdate(bankroll)
} }
private fun createDefaultGames(realm: Realm) { private fun createDefaultGames(realm: Realm) {
val gamesName = context.resources.getStringArray(R.array.seed_games) val gamesName = context.resources.getStringArray(net.pokeranalytics.android.R.array.seed_games)
val gamesShortName = context.resources.getStringArray(R.array.seed_games_short_name) val gamesShortName = context.resources.getStringArray(net.pokeranalytics.android.R.array.seed_games_short_name)
for ((index, name) in gamesName.withIndex()) { for ((index, name) in gamesName.withIndex()) {
val game = Game() val game = Game()
game.id = UUID.randomUUID().toString() game.id = UUID.randomUUID().toString()
@ -54,4 +54,15 @@ class Seed(var context:Context) : Realm.Transaction {
realm.insertOrUpdate(game) 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)
}
}
} }

@ -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)
}
}

@ -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)
}
}

@ -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)
}
}

@ -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<Entry>) {
var stat: Stat = stat
var entries: List<Entry> = 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<Entry>) {
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")
}
}
}

@ -32,20 +32,35 @@ class HomeActivity : PokerAnalyticsActivity() {
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item -> private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) { when (item.itemId) {
net.pokeranalytics.android.R.id.navigation_history -> { //CLEAN
/*
R.id.navigation_history -> {
displayFragment(0) displayFragment(0)
return@OnNavigationItemSelectedListener true
} }
net.pokeranalytics.android.R.id.navigation_stats -> { R.id.navigation_stats -> {
displayFragment(1) displayFragment(1)
return@OnNavigationItemSelectedListener true
} }
net.pokeranalytics.android.R.id.navigation_settings -> { R.id.navigation_settings -> {
displayFragment(2) 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?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -58,10 +73,11 @@ class HomeActivity : PokerAnalyticsActivity() {
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean { 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 this.homeMenu = menu
//TODO: Change filter button visibility //TODO: Change queryWith button visibility
homeMenu?.findItem(R.id.filter)?.isVisible = false homeMenu?.findItem(R.id.filter)?.isVisible = true
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
} }
@ -97,7 +113,7 @@ class HomeActivity : PokerAnalyticsActivity() {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener) navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
navigation.selectedItemId = net.pokeranalytics.android.R.id.navigation_history navigation.selectedItemId = R.id.navigation_history
val homePagerAdapter = HomePagerAdapter(supportFragmentManager) val homePagerAdapter = HomePagerAdapter(supportFragmentManager)
viewPager.offscreenPageLimit = 5 viewPager.offscreenPageLimit = 5
@ -134,17 +150,40 @@ class HomeActivity : PokerAnalyticsActivity() {
*/ */
private fun updateToolbar(index: Int) { private fun updateToolbar(index: Int) {
when (index) { when (index) {
//CLEAN
/*
0 -> { 0 -> {
toolbar.title = getString(R.string.feed) toolbar.title = getString(R.string.feed)
//TODO: Change filter button visibility homeMenu?.findItem(R.id.queryWith)?.isVisible = false
homeMenu?.findItem(R.id.filter)?.isVisible = false
} }
1 -> { 1 -> {
toolbar.title = getString(R.string.stats) toolbar.title = getString(R.string.stats)
homeMenu?.findItem(R.id.filter)?.isVisible = false homeMenu?.findItem(R.id.queryWith)?.isVisible = false
} }
2 -> { 2 -> {
toolbar.title = getString(R.string.services) 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 homeMenu?.findItem(R.id.filter)?.isVisible = false
} }
} }
@ -171,18 +210,15 @@ class HomeActivity : PokerAnalyticsActivity() {
.setCancelable(true) .setCancelable(true)
.setItems(choices.toTypedArray()) { _, which -> .setItems(choices.toTypedArray()) { _, which ->
Timber.d("Click on $which") Timber.d("Click on $which")
when (which) {
when(which) {
0 -> FiltersActivity.newInstance(this@HomeActivity) 0 -> FiltersActivity.newInstance(this@HomeActivity)
} }
} }
.setNegativeButton(R.string.cancel) { _, _ -> .setNegativeButton(R.string.cancel) { _, _ ->
Timber.d("Click on cancel") Timber.d("Click on cancel")
} }
builder.show() builder.show()
} }
} }

@ -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
}
}
}

@ -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)
}
}

@ -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
}
}
}

@ -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<WeakReference<PokerAnalyticsFragment>>()
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
}
}

@ -160,9 +160,9 @@ class HistorySessionRowRepresentableAdapter(
// Add headers if the date doesn't exist yet // Add headers if the date doesn't exist yet
for ((index, session) in realmResults.withIndex()) { for ((index, session) in realmResults.withIndex()) {
calendar.time = session.creationDate calendar.time = session.startDate ?: session.creationDate
if (checkHeaderCondition(calendar, previousYear, previousMonth)) { 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) previousYear = calendar.get(Calendar.YEAR)
previousMonth = calendar.get(Calendar.MONTH) previousMonth = calendar.get(Calendar.MONTH)
} }

@ -4,9 +4,7 @@ import android.util.SparseArray
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter import androidx.fragment.app.FragmentStatePagerAdapter
import net.pokeranalytics.android.ui.fragment.HistoryFragment import net.pokeranalytics.android.ui.fragment.*
import net.pokeranalytics.android.ui.fragment.SettingsFragment
import net.pokeranalytics.android.ui.fragment.StatsFragment
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -20,14 +18,16 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
override fun getItem(position: Int): PokerAnalyticsFragment { override fun getItem(position: Int): PokerAnalyticsFragment {
return when (position) { return when (position) {
0 -> HistoryFragment.newInstance() 0 -> HistoryFragment.newInstance()
1 -> StatsFragment.newInstance() 1 -> StatisticsFragment.newInstance()
2 -> SettingsFragment.newInstance() 2 -> CalendarFragment.newInstance()
3 -> ReportsFragment.newInstance()
4 -> MoreFragment.newInstance()
else -> HistoryFragment.newInstance() else -> HistoryFragment.newInstance()
} }
} }
override fun getCount(): Int { override fun getCount(): Int {
return 3 return 5
} }
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
@ -42,11 +42,12 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
} }
override fun getItemPosition(obj: Any): Int { override fun getItemPosition(obj: Any): Int {
val fragment = obj as PokerAnalyticsFragment return when (obj) {
return when (fragment) {
HistoryFragment::class.java -> 0 HistoryFragment::class.java -> 0
StatsFragment::class.java -> 1 StatisticsFragment::class.java -> 1
SettingsFragment::class.java -> 2 CalendarFragment::class.java -> 2
ReportsFragment::class.java -> 3
MoreFragment::class.java -> 4
else -> -1 else -> -1
} }
} }

@ -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<WeakReference<PokerAnalyticsFragment>>()
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
}
}

@ -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
}
}
}
}

@ -1,20 +1,30 @@
package net.pokeranalytics.android.util.extensions package net.pokeranalytics.android.ui.extensions
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri import android.net.Uri
import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatTextView
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.view.isVisible
import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.util.DeviceUtils import net.pokeranalytics.android.util.DeviceUtils
import net.pokeranalytics.android.util.URL import net.pokeranalytics.android.util.URL
import java.io.File
// Sizes // Sizes
@ -59,20 +69,33 @@ fun PokerAnalyticsActivity.openPlayStorePage() {
} }
// Open email for "Contact us" // 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 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}") val emailIntent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_SUBJECT, getString(subjectStringRes))
intent.putExtra(Intent.EXTRA_EMAIL, URL.SUPPORT_EMAIL.value) filePath?.let {
intent.putExtra(Intent.EXTRA_TEXT, "\n\n$info") val databaseFile = File(it)
startActivity(Intent.createChooser(intent, getString(R.string.contact))) 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 // Open custom tab
fun PokerAnalyticsActivity.openUrl(url: String) { fun PokerAnalyticsActivity.openUrl(url: String) {
val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder() 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() val customTabsIntent = builder.build()
customTabsIntent.launchUrl(this, Uri.parse(url)) customTabsIntent.launchUrl(this, Uri.parse(url))
} }
@ -98,6 +121,23 @@ fun showAlertDialog(context: Context, title: Int? = null, message: Int? = null)
message?.let { message?.let {
builder.setMessage(message) builder.setMessage(message)
} }
builder.setPositiveButton(R.string.ok, null) builder.setPositiveButton(net.pokeranalytics.android.R.string.ok, null)
builder.show() 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()
} }

@ -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.BankrollRow
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow 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.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.Call
import retrofit2.Response import retrofit2.Response
import java.util.* import java.util.*
@ -94,9 +95,12 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
NULL_TEXT NULL_TEXT
} }
} }
BankrollRow.INITIAL_VALUE -> {
this.bankroll.initialValue.toCurrency()
}
BankrollRow.RATE -> { BankrollRow.RATE -> {
val rate = this.bankroll.currency?.rate ?: 1.0 val rate = this.bankroll.currency?.rate ?: 1.0
CurrencyUtils.getCurrencyRateFormatter().format(rate) rate.toRate()
} }
else -> super.stringForRow(row) else -> super.stringForRow(row)
} }
@ -113,6 +117,9 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? { override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) { return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.bankroll.name)) SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.bankroll.name))
BankrollRow.INITIAL_VALUE -> {
row.editingDescriptors(mapOf("defaultValue" to this.bankroll.initialValue))
}
BankrollRow.RATE -> { BankrollRow.RATE -> {
val rate = this.bankroll.currency?.rate val rate = this.bankroll.currency?.rate
row.editingDescriptors(mapOf("defaultValue" to rate)) row.editingDescriptors(mapOf("defaultValue" to rate))
@ -145,7 +152,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
*/ */
private fun initData() { private fun initData() {
defaultCurrency = Currency.getInstance(Preferences.getCurrencyLocale(this.parentActivity)) defaultCurrency = UserDefaults.currency
if (!isUpdating) { if (!isUpdating) {
bankroll.currency = net.pokeranalytics.android.model.realm.Currency() bankroll.currency = net.pokeranalytics.android.model.realm.Currency()
@ -162,6 +169,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
rows.clear() rows.clear()
rows.add(SimpleRow.NAME) rows.add(SimpleRow.NAME)
rows.add(BankrollRow.LIVE) rows.add(BankrollRow.LIVE)
rows.add(BankrollRow.INITIAL_VALUE)
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency)) rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency))
rows.add(BankrollRow.CURRENCY) rows.add(BankrollRow.CURRENCY)
if (this.shouldShowCurrencyRate) { if (this.shouldShowCurrencyRate) {

@ -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
}
}
}

@ -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<RowRepresentable> = ArrayList()
//private var stat: Stat = Stat.NET_RESULT
//private var entries: List<Entry> = 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<RowRepresentable>? {
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<QueryCondition>().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<Stat> = 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()
}
}

@ -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<CustomizableRowRepresentable> = ArrayList()
private var sortedMonthlyReports: SortedMap<Date, ComputedResults> = HashMap<Date, ComputedResults>().toSortedMap()
private var sortedYearlyReports: SortedMap<Date, ComputedResults> = HashMap<Date, ComputedResults>().toSortedMap()
private var datesForRows: HashMap<CustomizableRowRepresentable, Date> = 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<RowRepresentable>? {
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<Date, ComputedResults> = HashMap()
val yearlyReports: HashMap<Date, ComputedResults> = HashMap()
val requiredStats: List<Stat> = 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<String> = 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()
}
}

@ -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<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
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<RowRepresentable>? {
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")
}
}

@ -16,8 +16,7 @@ import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
import net.pokeranalytics.android.util.Preferences
import java.util.* import java.util.*
class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
@ -29,7 +28,7 @@ class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataS
val rowRepresentation : List<RowRepresentable> by lazy { val rowRepresentation : List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
rows.addAll(mostUsedCurrencies) rows.addAll(mostUsedCurrencies)
rows.add(SeparatorRowRepresentable()) rows.add(SeparatorRow())
rows.addAll(availableCurrencies) rows.addAll(availableCurrencies)
rows rows
} }
@ -69,7 +68,7 @@ class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataS
} }
var currencyCode: String = currency.currencyCode 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})" var currencyCodeAndSymbol: String = "${this.currencyCode} (${this.currencySymbole})"
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal override val viewType: Int = RowViewType.TITLE_VALUE.ordinal

@ -52,7 +52,8 @@ open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDele
} }
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { 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 this.editableMenu = menu
updateMenuUI() updateMenuUI()
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
@ -158,12 +159,6 @@ open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDele
finishActivityWithResult(uniqueIdentifier) finishActivityWithResult(uniqueIdentifier)
} }
// if (managedItem is Bankroll) {
// managedItem.currency?.refreshRelatedRatedValues(it)
// }
//
// val uniqueIdentifier = (managedItem as Savable).id
// finishActivityWithResult(uniqueIdentifier)
} }
} }
else -> { else -> {

@ -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.*
import kotlinx.android.synthetic.main.fragment_filter_details.view.* import kotlinx.android.synthetic.main.fragment_filter_details.view.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.FilterDetailsActivity import net.pokeranalytics.android.ui.activity.FilterDetailsActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity 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.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.shortDate import net.pokeranalytics.android.util.extensions.shortDate
import net.pokeranalytics.android.util.extensions.shortTime
import net.pokeranalytics.android.util.extensions.toMinutes import net.pokeranalytics.android.util.extensions.toMinutes
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
lateinit var parentActivity: PokerAnalyticsActivity lateinit var parentActivity: PokerAnalyticsActivity
lateinit var rowRepresentableAdapter: RowRepresentableAdapter lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private lateinit var primaryKey: String
private lateinit var filterCategoryRow: FilterCategoryRow
private var currentFilter: Filter? = null private var currentFilter: Filter? = null
private var rows: ArrayList<RowRepresentable> = ArrayList() private var rows: ArrayList<RowRepresentable> = ArrayList()
private var rowsForFilterSubcategoryRow: HashMap<FilterSectionRow, ArrayList<RowRepresentable>> = HashMap() private var rowsForFilterSubcategoryRow: HashMap<FilterSectionRow, ArrayList<RowRepresentable>> = HashMap()
private var primaryKey: String? = null
private var filterMenu: Menu? = null private var filterMenu: Menu? = null
private var filterCategoryRow: FilterCategoryRow? = null private val selectedRows = ArrayList<QueryCondition>()
private val selectedRows = ArrayList<FilterElementRow>()
private var isUpdating = false private var isUpdating = false
private var shouldOpenKeyboard = true private var shouldOpenKeyboard = true
@ -68,48 +70,33 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
Timber.d("Row: $row") Timber.d("Row: $row")
if (row.viewType == RowViewType.TITLE_CHECK.ordinal) {
updateRowsSelection(row)
return
}
when (row) { when (row) {
is FilterElementRow.DateFilterElementRow -> DateTimePickerManager.create(requireContext(), row, this, row.dateValue, onlyDate = true) is QueryCondition.DateQuery -> DateTimePickerManager.create(requireContext(), row, this, row.singleValue, onlyDate = !row.showTime, onlyTime = row.showTime)
is FilterElementRow.PastDays -> { is QueryCondition.Duration -> {
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 -> {
val hours = if (row.minutes / 60 > 0) (row.minutes / 60).toString() else "" 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 minutes = if (row.minutes % 60 > 0) (row.minutes % 60).toString() else ""
val data = row.editingDescriptors(mapOf("hours" to hours, "minutes" to minutes)) val data = row.editingDescriptors(mapOf("hours" to hours, "minutes" to minutes))
BottomSheetFragment.create(fragmentManager, row, this, data, true) BottomSheetFragment.create(fragmentManager, row, this, data, true)
} }
is FilterElementRow.AmountFilterElement -> {
val amount = if (row.amount > 0) row.amount.toString() else "" is QueryCondition.ListOfValues<*> -> {
val data = row.editingDescriptors(mapOf("amount" to amount)) val valueAsString = row.listOfValues.firstOrNull()?.toString() ?: ""
BottomSheetFragment.create(fragmentManager, row, this, data, true) val data = row.editingDescriptors(mapOf("valueAsString" to valueAsString))
} BottomSheetFragment.create(fragmentManager, row, this, data, true)
else -> { }
updateRowsSelection(row)
}
} }
} }
override fun stringForRow(row: RowRepresentable): String { override fun stringForRow(row: RowRepresentable): String {
return when (row) { return when (row) {
is FilterElementRow.PastDays -> if (row.lastDays > 0) row.lastDays.toString() else NULL_TEXT is QueryCondition.DateQuery -> if (row.showTime) row.singleValue.shortTime() else row.singleValue.shortDate()
is FilterElementRow.LastGames -> if (row.lastGames > 0) row.lastGames.toString() else NULL_TEXT is QueryCondition.Duration -> row.minutes.toMinutes(requireContext())
is FilterElementRow.LastSessions -> if (row.lastSessions > 0) row.lastSessions.toString() else NULL_TEXT is QueryCondition.ListOfValues<*> -> row.listOfValues.firstOrNull()?.toString() ?: 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())
else -> super.stringForRow(row) else -> super.stringForRow(row)
} }
} }
@ -123,12 +110,8 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
Timber.d("onRowValueChanged: $row $value") Timber.d("onRowValueChanged: $row $value")
when (row) { when (row) {
is FilterElementRow.DateFilterElementRow -> row.dateValue = if (value != null && value is Date) value else Date() is QueryCondition.DateQuery -> row.singleValue = 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 QueryCondition.Duration -> {
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 -> {
if (value is ArrayList<*>) { if (value is ArrayList<*>) {
val hours = try { val hours = try {
(value[0] as String? ?: "0").toInt() (value[0] as String? ?: "0").toInt()
@ -146,6 +129,9 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
row.minutes = 0 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 // Remove the row before updating the selected rows list
@ -187,28 +173,24 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
*/ */
private fun initData() { private fun initData() {
primaryKey?.let { currentFilter = Filter.getFilterBydId(getRealm(), primaryKey)
currentFilter = Filter.getFilterBydId(getRealm(), it)
}
filterCategoryRow?.let {
this.appBar.toolbar.title = it.localizedTitle(requireContext()) this.appBar.toolbar.title = filterCategoryRow.localizedTitle(requireContext())
this.rows.clear() this.rows.clear()
this.rowsForFilterSubcategoryRow.clear() this.rowsForFilterSubcategoryRow.clear()
this.rows.addAll(it.filterElements) this.rows.addAll(filterCategoryRow.filterElements)
this.rows.forEach { element -> this.rows.forEach { element ->
if (element is FilterElementRow && currentFilter?.contains(element) == true) { if (element is QueryCondition && currentFilter?.contains(element) == true) {
currentFilter?.setSavedValueForElement(element) currentFilter?.loadValueForElement(element)
this.selectedRows.add(element) this.selectedRows.add(element)
} }
} }
this.rowRepresentableAdapter = RowRepresentableAdapter(this, this) this.rowRepresentableAdapter = RowRepresentableAdapter(this, this)
this.recyclerView.adapter = rowRepresentableAdapter this.recyclerView.adapter = rowRepresentableAdapter
}
} }
/** /**
@ -229,10 +211,12 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
rowRepresentableAdapter.refreshRow(it) rowRepresentableAdapter.refreshRow(it)
} }
} }
selectedRows.add(row) selectedRows.add(row as QueryCondition)
} }
} }
println("list of selected rows : $selectedRows")
// Update UI // Update UI
rowRepresentableAdapter.refreshRow(row) rowRepresentableAdapter.refreshRow(row)
} }
@ -242,13 +226,14 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
*/ */
private fun saveData() { private fun saveData() {
//TODO: Save currentFilter details data //TODO: Save currentFilter details data
Timber.d("Save data for filter: ${currentFilter?.id}") Timber.d("Save data for queryWith: ${currentFilter?.id}")
selectedRows?.forEach { selectedRows.forEach {
Timber.d("Selected rows: $it") Timber.d("Selected rows: $it")
} }
val realm = getRealm() val realm = getRealm()
realm.beginTransaction() realm.beginTransaction()
currentFilter?.remove(filterCategoryRow)
currentFilter?.createOrUpdateFilterConditions(selectedRows) currentFilter?.createOrUpdateFilterConditions(selectedRows)
realm.commitTransaction() realm.commitTransaction()

@ -60,7 +60,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
Timber.d("onActivityResult: $requestCode") Timber.d("onActivityResult: $requestCode")
if (data != null && data.hasExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)) { if (data != null && data.hasExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)) {
val filterId = data.getStringExtra(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?) { 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 this.filterMenu = menu
updateMenuUI() updateMenuUI()
super.onCreateOptionsMenu(menu, inflater) 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() { private fun validUpdates() {
Timber.d("Valid filter updates") Timber.d("Valid queryWith updates")
val filterId = currentFilter?.id ?: "" val filterId = currentFilter?.id ?: ""
finishActivityWithResult(filterId) finishActivityWithResult(filterId)
} }
/** /**
* Cancel the latest updates of the filter * Cancel the latest updates of the queryWith
*/ */
private fun cancelUpdates() { private fun cancelUpdates() {
Timber.d("Cancel filter updates") Timber.d("Cancel queryWith updates")
val filterId = filterCopy?.id ?: "" val filterId = filterCopy?.id ?: ""
@ -208,7 +209,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
* Delete data * Delete data
*/ */
private fun deleteFilter() { private fun deleteFilter() {
Timber.d("Delete filter") Timber.d("Delete queryWith")
val realm = getRealm() val realm = getRealm()
realm.beginTransaction() realm.beginTransaction()
currentFilter?.deleteFromRealm() currentFilter?.deleteFromRealm()

@ -4,38 +4,63 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.charts.BarLineChartBase
import com.github.mikephil.charting.data.LineDataSet 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.highlight.Highlight
import com.github.mikephil.charting.listener.OnChartValueSelectedListener import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import kotlinx.android.synthetic.main.fragment_graph.* import kotlinx.android.synthetic.main.fragment_graph.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.Stat 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.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.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 { class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
lateinit var dataSource: GraphDataSource enum class Style {
LINE,
lateinit var stat: Stat BAR,
lateinit var entries: List<Entry> MULTILINE,
}
companion object { companion object {
/**
* Create new instance
*/
fun newInstance(lineDataSets: List<LineDataSet>? = null, barDataSets: List<BarDataSet>? = 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<Entry>) { private lateinit var parentActivity: PokerAnalyticsActivity
this.stat = stat
this.entries = entries private var style: Style = Style.LINE
} private lateinit var legendView: LegendView
private var lineDataSetList: List<LineDataSet>? = null
private var barDataSetList: List<BarDataSet>? = 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? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_graph, container, false) return inflater.inflate(R.layout.fragment_graph, container, false)
@ -44,54 +69,144 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initUI() initUI()
loadGraph()
} }
private fun initUI() { /**
* Set data
val dataSet = LineDataSet(this.entries, this.stat.name) */
val colors = arrayOf(R.color.green_light).toIntArray() fun setLineData(lineDataSets: List<LineDataSet>, stat: Stat, axisFormatting: AxisFormatting = AxisFormatting.DEFAULT) {
dataSet.setColors(colors, context) this.lineDataSetList = lineDataSets
val lineData = LineData(listOf(dataSet)) this.stat = stat
this.axisFormatting = axisFormatting
this.chart.setStyle() if (isAdded && !isDetached) {
loadGraph()
}
}
this.chart.data = lineData fun setBarData(barDataSets: List<BarDataSet>, stat: Stat, axisFormatting: AxisFormatting = AxisFormatting.DEFAULT) {
this.chart.setOnChartValueSelectedListener(this) this.barDataSetList = barDataSets
this.stat = stat
this.axisFormatting = axisFormatting
if (isAdded && !isDetached) {
loadGraph()
}
} }
// OnChartValueSelectedListener /**
* Init UI
*/
private fun initUI() {
override fun onNothingSelected() { parentActivity = activity as PokerAnalyticsActivity
// nothing to do 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 -> Timber.d("loadGraph")
h?.let { highlight ->
val id = entry.data as String this.chartContainer.removeAllViews()
val item = getRealm().where(this.stat.underlyingClass).equalTo("id", id).findAll().firstOrNull()
item?.let {
val date = it.startDate() var lastEntry: Entry? = null
var groupName: String? = null
val entryStatName = this.stat.localizedTitle(requireContext()) this.lineDataSetList?.let { dataSets ->
val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatName = this.stat.cumulativeLabelResId(requireContext()) val lineChart = LineChart(context)
val totalStatValue = this.stat.format(e.y.toDouble(), null, requireContext()) 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)
}
}
} }

@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
@ -56,12 +57,6 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
realmSessions.removeAllChangeListeners() realmSessions.removeAllChangeListeners()
} }
override fun onResume() {
super.onResume()
// Old
//createSessionsHeaders()
}
/** /**
* Init UI * Init UI
*/ */
@ -96,7 +91,11 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
disclaimerDismiss.setOnClickListener { disclaimerDismiss.setOnClickListener {
Preferences.setStopShowingDisclaimer(requireContext()) Preferences.setStopShowingDisclaimer(requireContext())
disclaimerContainer.isVisible = false
disclaimerContainer.animate().translationY(disclaimerContainer.height.toFloat())
.setInterpolator(FastOutSlowInInterpolator())
.withEndAction { disclaimerContainer?.isVisible = false }
.start()
} }
} }

@ -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<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
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<RowRepresentable>? {
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
}
}
}

@ -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<AggregationType, Report> = 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
}
}

@ -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<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
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<RowRepresentable>? {
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<Criteria>, 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<Stat> = 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()
}
}
}

@ -6,7 +6,6 @@ import android.view.*
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import io.realm.kotlin.where 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.RowRepresentableDiffCallback
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.CurrencyUtils
import java.util.* import java.util.*
@ -45,8 +43,9 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
private val refreshTimer: Runnable = object : Runnable { private val refreshTimer: Runnable = object : Runnable {
override fun run() { override fun run() {
// Refresh header each 30 seconds // Refresh header each 30 seconds
currentSession.updateRowRepresentation()
sessionAdapter.notifyItemChanged(0) 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?) { 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 this.sessionMenu = menu
updateMenuUI() updateMenuUI()
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
@ -105,9 +105,9 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
} }
SessionRow.BANKROLL -> { 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()) { when (currentSession.getState()) {
SessionState.PENDING, SessionState.PLANNED -> { SessionState.PENDING, SessionState.PLANNED -> {
state.setTextColor(ContextCompat.getColor(requireContext(), R.color.white))
sessionMenu?.findItem(R.id.restart)?.isVisible = false sessionMenu?.findItem(R.id.restart)?.isVisible = false
floatingActionButton.setImageResource(R.drawable.ic_outline_play) floatingActionButton.setImageResource(R.drawable.ic_outline_play)
sessionMenu?.findItem(R.id.stop)?.isVisible = false sessionMenu?.findItem(R.id.stop)?.isVisible = false
@ -177,17 +176,15 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
.setInterpolator(OvershootInterpolator()).start() .setInterpolator(OvershootInterpolator()).start()
} }
SessionState.STARTED -> { SessionState.STARTED -> {
state.setTextColor(ContextCompat.getColor(requireContext(), R.color.green))
sessionMenu?.findItem(R.id.restart)?.isVisible = true sessionMenu?.findItem(R.id.restart)?.isVisible = true
floatingActionButton.setImageResource(R.drawable.ic_outline_pause) floatingActionButton.setImageResource(R.drawable.ic_outline_pause)
sessionMenu?.findItem(R.id.stop)?.isVisible = true sessionMenu?.findItem(R.id.stop)?.isVisible = true
floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f) floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f)
.setDuration(animationDuration) .setDuration(animationDuration)
.setInterpolator(OvershootInterpolator()).start() .setInterpolator(OvershootInterpolator()).start()
handler.postDelayed(refreshTimer, 30000) handler.postDelayed(refreshTimer, 60000)
} }
SessionState.PAUSED -> { SessionState.PAUSED -> {
state.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue))
sessionMenu?.findItem(R.id.restart)?.isVisible = true sessionMenu?.findItem(R.id.restart)?.isVisible = true
floatingActionButton.setImageResource(R.drawable.ic_outline_play) floatingActionButton.setImageResource(R.drawable.ic_outline_play)
sessionMenu?.findItem(R.id.stop)?.isVisible = true sessionMenu?.findItem(R.id.stop)?.isVisible = true
@ -196,7 +193,6 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
.setInterpolator(OvershootInterpolator()).start() .setInterpolator(OvershootInterpolator()).start()
} }
SessionState.FINISHED -> { SessionState.FINISHED -> {
state.setTextColor(ContextCompat.getColor(requireContext(), R.color.white))
sessionMenu?.findItem(R.id.restart)?.isVisible = true sessionMenu?.findItem(R.id.restart)?.isVisible = true
sessionMenu?.findItem(R.id.stop)?.isVisible = false sessionMenu?.findItem(R.id.stop)?.isVisible = false
floatingActionButton.animate().scaleX(0f).scaleY(0f).alpha(0f) floatingActionButton.animate().scaleX(0f).scaleY(0f).alpha(0f)

@ -7,9 +7,11 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm
import kotlinx.android.synthetic.main.fragment_settings.* import kotlinx.android.synthetic.main.fragment_settings.*
import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R 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.CurrenciesActivity
import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.GDPRActivity 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.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource 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.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow
import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.URL import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.extensions.openContactMail import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.openPlayStorePage
import net.pokeranalytics.android.util.extensions.openUrl
import java.util.* import java.util.*
class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource { class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource {
private lateinit var parentActivity: PokerAnalyticsActivity
companion object { companion object {
@ -53,49 +55,53 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
const val REQUEST_CODE_CURRENCY: Int = 100 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 settingsAdapterRow: RowRepresentableAdapter
private lateinit var parentActivity: PokerAnalyticsActivity
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initData() initData()
initUI()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 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 { data?.let {
Preferences.setCurrencyCode(data.getStringExtra(CurrenciesFragment.INTENT_CURRENCY_CODE), requireContext()) 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) settingsAdapterRow.refreshRow(SettingRow.CURRENCY)
} }
} }
} }
override fun adapterRows(): List<RowRepresentable>? { override fun adapterRows(): List<RowRepresentable>? {
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) { when (row) {
SettingRow.RATE_APP -> parentActivity.openPlayStorePage() SettingRow.RATE_APP -> parentActivity.openPlayStorePage()
SettingRow.CONTACT_US -> parentActivity.openContactMail(R.string.contact) SettingRow.CONTACT_US -> parentActivity.openContactMail(R.string.contact)
SettingRow.BUG_REPORT -> parentActivity.openContactMail(R.string.bug_report_subject) SettingRow.BUG_REPORT -> parentActivity.openContactMail(R.string.bug_report_subject, Realm.getDefaultInstance().path)
SettingRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@SettingsFragment, SettingsFragment.REQUEST_CODE_CURRENCY) SettingRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@SettingsFragment, REQUEST_CODE_CURRENCY)
SettingRow.FOLLOW_US -> { SettingRow.FOLLOW_US -> {
when (position) { when (position) {
0 -> parentActivity.openUrl(URL.BLOG.value) 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 = activity as PokerAnalyticsActivity
parentActivity.setSupportActionBar(toolbar)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
val viewManager = LinearLayoutManager(requireContext()) val viewManager = LinearLayoutManager(requireContext())
settingsAdapterRow = RowRepresentableAdapter( settingsAdapterRow = RowRepresentableAdapter(
this, this this, this
) )
customRecyclerView.apply { recyclerView.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = viewManager layoutManager = viewManager
adapter = settingsAdapterRow adapter = settingsAdapterRow
} }
} }
/**
* Init data
*/
private fun initData() {
}
/** /**
* Open GDPR Activity * Open GDPR Activity
*/ */

@ -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<AggregationType, Report> = 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
}
}
}

@ -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<RowRepresentable> {
val rows: ArrayList<RowRepresentable> = 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<Stat> = 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<Stat> = 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<Stat> =
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())
}
}

@ -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<RowRepresentable> = ArrayList()
private var stringAll = ""
private var stringCashGame = ""
private var stringTournament = ""
private lateinit var statsAdapter: RowRepresentableAdapter
private var computedResults : List<ComputedResults>? = 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<RowRepresentable>? {
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<ComputedResults>()
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<ComputedResults> {
val allStats: List<Stat> = 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<Stat> = 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<Stat> = 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<ComputedResults>) {
this.rowRepresentables = this.convertResultsIntoRepresentables(results)
statsAdapter.notifyDataSetChanged()
}
private fun convertResultsIntoRepresentables(results: List<ComputedResults>) : ArrayList<RowRepresentable> {
val rows: ArrayList<RowRepresentable> = 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<ComputedResults>()
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)
}
}
}
}
}

@ -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<RowRepresentable> = 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<RowRepresentable>? {
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<RowRepresentable> {
val rows: ArrayList<RowRepresentable> = 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)
}
}
}
}
}

@ -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)
}
}

@ -7,6 +7,7 @@ import io.realm.Realm
open class PokerAnalyticsFragment: Fragment() { open class PokerAnalyticsFragment: Fragment() {
private var realm: Realm? = null private var realm: Realm? = null
private var loaderDialogFragment: LoaderDialogFragment? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -36,4 +37,22 @@ open class PokerAnalyticsFragment: Fragment() {
*/ */
open fun onBackPressed(){} 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
}
} }

@ -97,7 +97,7 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
private fun initUI() { private fun initUI() {
row.let { row.let {
bottomSheetToolbar.title = row.localizedTitle(requireContext()) bottomSheetToolbar.title = row.localizedTitle(requireContext())
bottomSheetToolbar.inflateMenu(R.menu.bottom_sheet_menu) bottomSheetToolbar.inflateMenu(R.menu.toolbar_bottom_sheet)
bottomSheetToolbar.setOnMenuItemClickListener { bottomSheetToolbar.setOnMenuItemClickListener {
false false
} }

@ -14,7 +14,7 @@ import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorExcep
import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.view.RowRepresentable 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 * Bottom Sheet List Game Fragment

@ -13,7 +13,7 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.GridSpacingItemDecoration import net.pokeranalytics.android.ui.view.GridSpacingItemDecoration
import net.pokeranalytics.android.ui.view.RowRepresentable 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 { class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {

@ -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
}
}

@ -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<Entry>, 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<BarEntry>, label: String, context: Context) : BarDataSet(yVals, label) {
//
// init {
// this.highLightColor = context.getColor(R.color.chart_highlight_indicator)
// }
//
//}

@ -1,66 +1,83 @@
package net.pokeranalytics.android.ui.graph 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.BarChart
import com.github.mikephil.charting.charts.BarLineChartBase 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() { enum class AxisFormatting {
GraphHelper.setStyle(this) DEFAULT,
X_DURATION,
Y_DURATION,
} }
fun LineChart.setStyle() { fun BarLineChartBase<*>.setStyle(
GraphHelper.setStyle(this) 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.axisLeft.setDrawZeroLine(true)
// this.xAxis.axisLineWidth = ChartAppearance.lineWidth this.axisLeft.zeroLineColor = ContextCompat.getColor(context, R.color.chart_default)
// 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.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()
}
} }
} }

@ -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)
}
}
}
}

@ -23,6 +23,7 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener,
private lateinit var calendar: Calendar private lateinit var calendar: Calendar
private var minimumDate: Date? = null private var minimumDate: Date? = null
private var onlyDate: Boolean = false private var onlyDate: Boolean = false
private var onlyTime: Boolean = false
private var isClearable: Boolean = true private var isClearable: Boolean = true
companion object { companion object {
@ -33,6 +34,7 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener,
date: Date?, date: Date?,
minimumDate: Date? = null, minimumDate: Date? = null,
onlyDate: Boolean? = false, onlyDate: Boolean? = false,
onlyTime: Boolean? = false,
isClearable: Boolean? = true isClearable: Boolean? = true
): DateTimePickerManager { ): DateTimePickerManager {
@ -46,9 +48,14 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener,
dateTimePickerManager.calendar = calendar dateTimePickerManager.calendar = calendar
dateTimePickerManager.minimumDate = minimumDate dateTimePickerManager.minimumDate = minimumDate
dateTimePickerManager.onlyDate = onlyDate ?: false dateTimePickerManager.onlyDate = onlyDate ?: false
dateTimePickerManager.onlyTime = onlyTime ?: false
dateTimePickerManager.isClearable = isClearable ?: true dateTimePickerManager.isClearable = isClearable ?: true
dateTimePickerManager.showDatePicker() if (dateTimePickerManager.onlyTime) {
dateTimePickerManager.showTimePicker()
} else {
dateTimePickerManager.showDatePicker()
}
return dateTimePickerManager return dateTimePickerManager
} }

@ -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
}
}
}

@ -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)
}
}
}
}

@ -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)
}
}
}

@ -6,9 +6,9 @@ import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager 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 var enablePaging: Boolean = false

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save