Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	app/src/main/java/net/pokeranalytics/android/model/comparison/Comparator.kt
#	app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
#	app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
feature/top10
Razmig Sarkissian 7 years ago
commit 79835c3cd9
  1. 44
      app/src/androidTest/java/net/pokeranalytics/android/components/SessionInstrumentedUnitTest.kt
  2. 34
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatPerformanceUnitTest.kt
  3. 161
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt
  4. 179
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  5. 34
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  6. 5
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  7. 10
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  8. 9
      app/src/main/java/net/pokeranalytics/android/model/comparison/Comparator.kt
  9. 58
      app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt
  10. 17
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  11. 17
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  12. 4
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  13. 12
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  14. 239
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  15. 46
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  16. 8
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  17. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt
  18. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt
  19. 12
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt
  20. 79
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  21. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt
  22. 10
      app/src/main/java/net/pokeranalytics/android/ui/graph/ChartDataSet.kt

@ -0,0 +1,44 @@
package net.pokeranalytics.android.components
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.RealmList
import net.pokeranalytics.android.model.realm.*
import org.junit.runner.RunWith
import java.util.*
@RunWith(AndroidJUnit4::class)
open class SessionInstrumentedUnitTest : RealmInstrumentedUnitTest() {
// convenience extension
fun Session.Companion.testInstance(
netResult: Double = 0.0,
isTournament: Boolean = false,
startDate: Date = Date(),
endDate: Int = 1,
bankroll: Bankroll? = null,
game: Game? = null,
location: Location? = null,
tournamentName: TournamentName? = null,
tournamentFeatures: RealmList<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,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
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.RealmList
import io.realm.RealmResults
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.components.RealmInstrumentedUnitTest
import net.pokeranalytics.android.components.SessionInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.Currency
import org.junit.Assert
@ -23,62 +23,10 @@ import java.util.*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
// convenience extension
private fun Session.Companion.testInstance(
netResult: Double = 0.0,
isTournament: Boolean = false,
startDate: Date = Date(),
endDate: Int = 1,
bankroll: Bankroll? = null,
game: Game? = null,
location: Location? = null,
tournamentName: TournamentName? = null,
tournamentFeatures: RealmList<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")
}
class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
@Test
fun testSessionStats() {
fun testAllSessionStats() {
val realm = this.mockRealm
@ -124,8 +72,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
println(">>>>>> rated net = ${it.ratedNet} ")
}
val stats: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE)
val group = ComputableGroup("test", listOf(), stats)
val group = ComputableGroup("test")
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION,
@ -510,12 +457,12 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
val sets = realm.where(SessionSet::class.java).findAll()
Assert.assertEquals(1, sets.size)
assertEquals(1, sets.size)
val set = sets.first()
if (set != null) {
Assert.assertEquals(sd1.time, set.startDate.time)
Assert.assertEquals(ed1.time, set.endDate.time)
assertEquals(sd1.time, set.startDate.time)
assertEquals(ed1.time, set.endDate.time)
} else {
Assert.fail("No set")
}
@ -712,7 +659,6 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
}
@Test
fun testDaysPlayed() {
@ -748,4 +694,95 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
}
}
@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.CASH))
val options = Calculator.Options()
options.displayedStats = listOf(Stat.DURATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01
val duration = results.computedStat(Stat.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.CASH))
val options = Calculator.Options()
options.displayedStats = listOf(Stat.DURATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01
val duration = results.computedStat(Stat.DURATION)
if (duration != null) {
assertEquals(4.0, duration.value, delta)
} else {
Assert.fail("No Net result stat")
}
}
}

@ -4,7 +4,9 @@ import io.realm.Realm
import net.pokeranalytics.android.calculus.Stat.*
import net.pokeranalytics.android.model.comparison.Comparator
import net.pokeranalytics.android.model.comparison.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.SessionSet
@ -65,9 +67,18 @@ class Calculator {
return false
}
val computeLongestStreak = this.displayedStats.contains(LONGEST_STREAKS)
val computeLocationsPlayed = this.displayedStats.contains(LOCATIONS_PLAYED)
val computeDaysPlayed = this.displayedStats.contains(DAYS_PLAYED)
val computeLongestStreak: Boolean
get() {
return this.displayedStats.contains(LONGEST_STREAKS)
}
val computeLocationsPlayed: Boolean
get() {
return this.displayedStats.contains(LOCATIONS_PLAYED)
}
val computeDaysPlayed: Boolean
get() {
return this.displayedStats.contains(DAYS_PLAYED)
}
}
@ -137,12 +148,12 @@ class Calculator {
group.cleanup()
// Computes actual sessionGroup stats
val results: ComputedResults = Calculator.compute(realm, group, options = options)
val results: ComputedResults = this.compute(realm, group, options = options)
// Computes the compared sessionGroup if existing
val comparedGroup = group.comparedComputables
if (comparedGroup != null) {
val comparedResults = Calculator.compute(realm, comparedGroup, options = options)
val comparedResults = this.compute(realm, comparedGroup, options = options)
group.comparedComputedResults = comparedResults
results.computeStatVariations(comparedResults)
}
@ -200,7 +211,9 @@ class Calculator {
var tWinningSessionCount = 0
var tBuyinSum = 0.0
var tHands = 0.0
var longestWinStreak = 0; var longestLoseStreak = 0; var currentStreak = 0
var longestWinStreak = 0;
var longestLoseStreak = 0;
var currentStreak = 0
computables.forEach { computable ->
index++
@ -263,39 +276,49 @@ class Calculator {
val sessionSets = computableGroup.sessionSets(realm)
// Compute for each serie
val gHourlyDuration =
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours)
val gSum = sessionSets.sum(SessionSet.Field.RATED_NET.identifier).toDouble()
val gTotalHands = sessionSets.sum(SessionSet.Field.ESTIMATED_HANDS.identifier).toDouble()
val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
val maxDuration = sessionSets.max(SessionSet.Field.NET_DURATION.identifier)?.toDouble()
var gHourlyDuration: Double? = null
var gBBSum: Double? = null
var maxDuration: Double? = null
val hourlyRate = gSum / gHourlyDuration
// var bbHourlyRate = gBBSum / gDuration
if (computableGroup.conditions.size == 0) { // SessionSets are fine
gHourlyDuration =
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours)
gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
val shouldIterateOverSets = (options.evolutionValues != Options.EvolutionValues.NONE || options.computeDaysPlayed)
sessionSets.max(SessionSet.Field.NET_DURATION.identifier)?.let {
maxDuration = it.toDouble() / 3600000
}
}
val shouldIterateOverSets = computableGroup.conditions.size > 0 ||
options.evolutionValues != Options.EvolutionValues.NONE ||
options.computeDaysPlayed
if (shouldIterateOverSets) {
var tHourlyDuration = 0.0
var tIndex = 0
var tSum = 0.0
var tTotalHands = 0.0
var tRatedNetSum = 0.0
var tBBSum = 0.0
var tTotalHands = 0.0
var tHourlyRate = 0.0
var tHourlyRateBB = 0.0
val daysSet = mutableSetOf<Date>()
var tMaxDuration = 0.0
sessionSets.forEach { sessionSet ->
tIndex++
tHourlyDuration += sessionSet.hourlyDuration
tSum += sessionSet.ratedNet
tTotalHands += sessionSet.estimatedHands
tBBSum += sessionSet.bbNet
tHourlyRate = gSum / tHourlyDuration
tHourlyRateBB = gBBSum / tHourlyDuration
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) {
@ -314,13 +337,13 @@ class Calculator {
)
results.addEvolutionValue(tHourlyRateBB, stat = HOURLY_RATE_BB, data = sessionSet)
Stat.netBBPer100Hands(gBBSum, gTotalHands)?.let { netBB100 ->
Stat.netBBPer100Hands(tBBSum, tTotalHands)?.let { netBB100 ->
results.addEvolutionValue(netBB100, stat = NET_BB_PER_100_HANDS, data = sessionSet)
}
}
Options.EvolutionValues.TIMED -> {
results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, sessionSet)
results.addEvolutionValue(tRatedNetSum, tHourlyDuration, NETRESULT, sessionSet)
results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue(
tIndex.toDouble(),
@ -342,7 +365,7 @@ class Calculator {
)
results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet)
Stat.netBBPer100Hands(gBBSum, gTotalHands)?.let { netBB100 ->
Stat.netBBPer100Hands(tBBSum, tTotalHands)?.let { netBB100 ->
results.addEvolutionValue(
netBB100,
tHourlyDuration,
@ -351,14 +374,24 @@ class Calculator {
)
}
}
else -> {
// nothing
}
}
results.addStat(DAYS_PLAYED, daysSet.size.toDouble())
}
gHourlyDuration = tHourlyDuration
gBBSum = tBBSum
maxDuration = tMaxDuration
}
var average = 0.0
var hourlyRate = 0.0
if (computables.size > 0) {
average = sum / computables.size.toDouble()
val winRatio = winningSessionCount.toDouble() / computables.size.toDouble()
@ -373,25 +406,30 @@ class Calculator {
)
}
if (sessionSets.size > 0) {
val avgDuration = gHourlyDuration / sessionSets.size
results.addStats(
setOf(
ComputedStat(HOURLY_RATE, hourlyRate),
ComputedStat(AVERAGE_DURATION, avgDuration)
)
)
if (gHourlyDuration != null) {
hourlyRate = sum / gHourlyDuration
if (sessionSets.size > 0) {
val avgDuration = gHourlyDuration / sessionSets.size
results.addStat(HOURLY_RATE, hourlyRate)
results.addStat(AVERAGE_DURATION, avgDuration)
}
results.addStat(DURATION, gHourlyDuration)
}
if (gBBSum != null) {
if (gHourlyDuration != null) {
results.addStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration)
}
results.addStat(AVERAGE_NET_BB, gBBSum / bbSessionCount)
}
// Create stats
results.addStats(
setOf(
ComputedStat(NETRESULT, sum),
ComputedStat(DURATION, gHourlyDuration),
ComputedStat(NUMBER_OF_SETS, sessionSets.size.toDouble()),
ComputedStat(NUMBER_OF_GAMES, computables.size.toDouble()),
ComputedStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration),
ComputedStat(AVERAGE_NET_BB, gBBSum / bbSessionCount),
ComputedStat(HANDS_PLAYED, totalHands)
)
)
@ -410,7 +448,7 @@ class Calculator {
results.addStat(MINIMUM_NETRESULT, min)
}
maxDuration?.let { maxd ->
results.addStat(MAXIMUM_DURATION, maxd / 3600000) // (milliseconds to hours)
results.addStat(MAXIMUM_DURATION, maxd) // (milliseconds to hours)
}
val bbPer100Hands = bbSum / totalHands * 100
@ -428,20 +466,22 @@ class Calculator {
val standardDeviation = Math.sqrt(stdSum / computables.size)
val standardDeviationBBper100Hands = Math.sqrt(stdBBper100HandsSum / computables.size)
results.addStat(STANDARD_DEVIATION, standardDeviation)
results.addStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands)
// Session Set
var hourlyStdSum = 0.0
sessionSets.forEach { set ->
hourlyStdSum += Math.pow(set.hourlyRate - hourlyRate, 2.0)
if (gHourlyDuration != null) {
var hourlyStdSum = 0.0
sessionSets.forEach { set ->
val ssStats = SSStats(set, computableGroup.conditions)
val sHourlyRate = ssStats.hourlyRate
hourlyStdSum += Math.pow(sHourlyRate - hourlyRate, 2.0)
}
val hourlyStandardDeviation = Math.sqrt(hourlyStdSum / sessionSets.size)
results.addStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation)
}
val hourlyStandardDeviation = Math.sqrt(hourlyStdSum / sessionSets.size)
results.addStats(
setOf(
ComputedStat(STANDARD_DEVIATION, standardDeviation),
ComputedStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation),
ComputedStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands)
)
)
}
return results
@ -450,4 +490,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,5 +1,6 @@
package net.pokeranalytics.android.calculus
import android.content.Context
import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.data.Entry
import io.realm.Realm
@ -35,13 +36,26 @@ class Report() {
this._results.add(result)
}
fun lineEntries(stat: Stat): List<Entry> {
val entries = mutableListOf<Entry>()
this._results.forEachIndexed { index, results ->
val cs = results.computedStat(stat)
cs?.let { computedStat ->
entries.add(Entry(index.toFloat(), computedStat.value.toFloat(), results))
}
}
return entries
}
fun barEntries(stat: Stat): List<Entry> {
val entries = mutableListOf<Entry>()
this._results.forEachIndexed { index, results ->
val cs = results.computedStat(stat)
cs?.let { computedStat ->
entries.add(Entry(index.toFloat(), computedStat.value.toFloat(), results.group))
val barEntry = BarEntry(index.toFloat(), computedStat.value.toFloat(), results)
entries.add(barEntry)
}
}
return entries
@ -61,7 +75,7 @@ class Report() {
/**
* A sessionGroup of computable items identified by a name
*/
class ComputableGroup(name: String, conditions: List<QueryCondition>, stats: List<Stat>? = null) {
class ComputableGroup(name: String, conditions: List<QueryCondition> = listOf(), stats: List<Stat>? = null) {
/**
* The display name of the group
@ -140,9 +154,10 @@ class ComputableGroup(name: String, conditions: List<QueryCondition>, stats: Lis
get() {
return this._computables?.isEmpty() ?: true
}
}
class ComputedResults(group: ComputableGroup) {
class ComputedResults(group: ComputableGroup) : StatEntry {
/**
* The session group used to computed the stats
@ -268,6 +283,19 @@ class ComputedResults(group: ComputableGroup) {
val isEmpty: Boolean = this.group.isEmpty
// Stat Entry
override val entryTitle: String = this.group.name
override fun formattedValue(stat: Stat, context: Context): TextFormat {
this.computedStat(stat)?.let {
return it.format(context)
} ?: run {
throw IllegalStateException("Missing stat in results")
}
}
}
class Point(val x: Double, val y: Double, val data: Any) {

@ -3,7 +3,6 @@ package net.pokeranalytics.android.calculus
import android.content.Context
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FormattingException
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
@ -21,8 +20,10 @@ class ObjectIdentifier(var id: String, var clazz: Class<out Timed>) {
}
interface StatBase : Identifiable {
interface StatEntry {
val entryTitle: String
fun formattedValue(stat: Stat, context: Context): TextFormat
}

@ -11,13 +11,13 @@ class ConfigurationException(message: String) : Exception(message)
sealed class PokerAnalyticsException(message: String) : Exception(message) {
object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition")
object FilterElementUnknownSectionName: PokerAnalyticsException(message = "No filterElement section name was found to identify the queryCondition")
object FilterMissingEntity: PokerAnalyticsException(message = "This filter has no entity initialized")
object FilterMissingEntity: PokerAnalyticsException(message = "This queryWith has no entity initialized")
object FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable")
object QueryValueMapUnknown: PokerAnalyticsException(message = "fieldName is missing")
object QueryTypeUnhandled: PokerAnalyticsException(message = "filter type not handled")
object QueryTypeUnhandled: PokerAnalyticsException(message = "queryWith type not handled")
object QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected")
object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "filter is empty or null")
data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "filter element '$filterElementRow' type is missing")
object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "queryWith is empty or null")
data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "queryWith element '$filterElementRow' type is missing")
data class QueryValueMapMissingKeys(val missingKeys: List<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")
}

@ -1,7 +1,6 @@
package net.pokeranalytics.android.model.comparison
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.Sort
import io.realm.kotlin.where
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
@ -138,14 +137,6 @@ fun List<Comparator>.combined(): List<List<QueryCondition>> {
return getCombinations(comparatorList)
}
inline fun <reified T : Filterable> List<QueryCondition>.query(realm: Realm): RealmQuery<T> {
var realmQuery = realm.where<T>()
this.forEach {
realmQuery = it.filter(realmQuery)
}
return realmQuery
}
fun <T> getCombinations(lists: List<List<T>>): List<List<T>> {
var combinations: MutableSet<List<T>> = LinkedHashSet()
var newCombinations: MutableSet<List<T>>

@ -25,9 +25,6 @@ enum class SessionState {
*/
fun Session.getState(): SessionState {
// if (timeFrame == null) {
// return SessionState.PENDING
// }
val start = this.startDate
if (start == null) {
return SessionState.PENDING
@ -43,4 +40,59 @@ fun Session.getState(): SessionState {
}
}
}
val AbstractList<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,6 +1,7 @@
package net.pokeranalytics.android.model.filter
import io.realm.RealmModel
import io.realm.RealmResults
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
@ -11,7 +12,7 @@ import net.pokeranalytics.android.model.realm.SessionSet
* - filters can be applied to different type of objects: Sessions, Hands, Transactions...
* - filters can be applied to a list of different type of objects (feed)
*
* A filter is described by the following:
* A queryWith is described by the following:
* - a data type: Session, Hands...
* - a field: table size of a Session
* - an operator: equal, >=, <...
@ -27,7 +28,7 @@ import net.pokeranalytics.android.model.realm.SessionSet
* - multiple numericValues as 'OR'
*
* Also:
* A filter should be able to be converted into a Realm query
* A queryWith should be able to be converted into a Realm query
*
*/
@ -47,6 +48,10 @@ interface Filterable : RealmModel {
}
inline fun <reified T : Filterable> RealmResults<T>.filter(conditions: List<QueryCondition>) : RealmResults<T> {
return conditions.queryWith(this.where()).findAll()
}
class FilterHelper {
companion object {
@ -68,11 +73,13 @@ class FilterHelper {
}
//
//fun MutableList<Filterable>.filter(filter: FilterCondition) : List<Filterable> {
//fun MutableList<Filterable>.queryWith(queryWith: FilterCondition) : List<Filterable> {
//
// return this.filter { f ->
// return@filter true
// return this.queryWith { f ->
// return@queryWith true
// }
//}

@ -3,9 +3,8 @@ package net.pokeranalytics.android.model.filter
import io.realm.*
import io.realm.internal.Table
import io.realm.kotlin.where
import io.realm.RealmQuery
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.util.extensions.endOfDay
@ -16,6 +15,18 @@ fun List<QueryCondition>.name() : String {
return this.map { it.name }.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
* Some queries requires a value to be checked upon through equals, in, more, less, between
@ -222,7 +233,7 @@ sealed class QueryCondition(var operator: Operator? = null) {
* 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> filter(realmQuery: RealmQuery<T>): RealmQuery<T> {
inline fun <reified T : Filterable> queryWith(realmQuery: RealmQuery<T>): RealmQuery<T> {
val fieldName = FilterHelper.fieldNameForQueryType<T>(this::class.java)
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
when (operator) {

@ -1,10 +1,10 @@
package net.pokeranalytics.android.model.interfaces
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.StatBase
import net.pokeranalytics.android.calculus.StatEntry
import java.util.*
interface Timed : StatBase {
interface Timed : StatEntry, Identifiable {
fun startDate() : Date?

@ -35,7 +35,7 @@ open class Filter : RealmObject() {
return realm.copyToRealm(filter)
}
// Get a filter by its id
// Get a queryWith by its id
fun getFilterBydId(realm: Realm, filterId: String): Filter? {
return realm.where<Filter>().equalTo("id", filterId).findFirst()
}
@ -44,7 +44,7 @@ open class Filter : RealmObject() {
inline fun <reified T : Filterable> queryOn(realm: Realm, queries: List<QueryCondition>): RealmResults<T> {
var realmQuery = realm.where<T>()
queries.forEach {
realmQuery = it.filter(realmQuery)
realmQuery = it.queryWith(realmQuery)
}
Timber.d(">>> Filter query: ${realmQuery.description}")
return realmQuery.findAll()
@ -54,10 +54,10 @@ open class Filter : RealmObject() {
@PrimaryKey
var id = UUID.randomUUID().toString()
// the filter name
// the queryWith name
var name: String = ""
// the number of use of the filter,
// the number of use of the queryWith,
// for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0)
@ -106,7 +106,7 @@ open class Filter : RealmObject() {
}
/**
* Set the saved value in the filter for the given [filterElementRow]
* Set the saved value in the queryWith for the given [filterElementRow]
*/
fun setSavedValueForElement(filterElementRow: FilterElementRow) {
when (filterElementRow) {
@ -140,7 +140,7 @@ open class Filter : RealmObject() {
this.filterConditions.map {
it.queryCondition
}.forEach {
realmQuery = it.filter(realmQuery)
realmQuery = it.queryWith(realmQuery)
}
return realmQuery.findAll()

@ -35,6 +35,7 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.*
import java.text.DateFormat
import java.util.*
import java.util.Currency
import kotlin.collections.ArrayList
@ -42,7 +43,7 @@ import kotlin.collections.ArrayList
typealias BB = Double
open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed,
TimeFilterable, Filterable {
TimeFilterable, Filterable {
enum class Type {
CASH_GAME,
@ -88,7 +89,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
DAY_OF_WEEK::class, WEEK_END::class, WEEK_DAY::class -> "dayOfWeek"
MONTH::class -> "month"
YEAR::class -> "year"
TODAY::class, YESTERDAY::class, TODAY_AND_YESTERDAY::class, THIS_YEAR::class, THIS_MONTH::class, THIS_WEEK::class -> "startDate"
TODAY::class, YESTERDAY::class, TODAY_AND_YESTERDAY::class, THIS_YEAR::class, THIS_MONTH::class, THIS_WEEK::class -> "startDate"
else -> null
}
}
@ -114,7 +115,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// Timed interface
override var dayOfWeek : Int? = null
override var dayOfWeek: Int? = null
override var month: Int? = null
override var year: Int? = null
override var dayOfMonth: Int? = null
@ -213,21 +214,21 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The small blind value
var cgSmallBlind: Double? = null
set(value) {
field = value
formatBlinds()
}
set(value) {
field = value
formatBlinds()
}
// The big blind value
var cgBigBlind: Double? = null
set(value) {
field = value
this.computeStats()
formatBlinds()
formatBlinds()
}
var blinds: String? = null
private set
var blinds: String? = null
private set
// Tournament
@ -246,9 +247,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The features of the tournament, like Knockout, Shootout, Turbo...
var tournamentFeatures: RealmList<TournamentFeature> = RealmList()
fun bankrollHasBeenUpdated() {
formatBlinds()
}
fun bankrollHasBeenUpdated() {
formatBlinds()
}
/**
* Manages impacts on SessionSets
@ -303,7 +304,8 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/
val bbNet: BB
get() {
val bb = this.cgBigBlind; val result = this.result
val bb = this.cgBigBlind;
val result = this.result
if (bb != null && result != null) {
return result.net / bb
} else {
@ -487,19 +489,19 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return NULL_TEXT
}
val hasDefaultCurrency: Boolean
get() {
return bankroll?.currency?.code == null
}
val hasDefaultCurrency: Boolean
get() {
return bankroll?.currency?.code == null
}
val currency : Currency
get() {
return bankroll?.currency?.code?.let {
Currency.getInstance(it)
} ?: run {
UserDefaults.currency
}
}
val currency: Currency
get() {
return bankroll?.currency?.code?.let {
Currency.getInstance(it)
} ?: run {
UserDefaults.currency
}
}
/**
* Return the game title
@ -518,18 +520,18 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return if (gameTitle.isNotBlank()) gameTitle else NULL_TEXT
}
fun getFormattedBlinds(): String {
return blinds ?: 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()}"
}
}
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
@ -569,7 +571,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
@Ignore
private var rowRepresentationForCurrentState : List<RowRepresentable> = mutableListOf()
private var rowRepresentationForCurrentState: List<RowRepresentable> = mutableListOf()
private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> {
val rows = ArrayList<RowRepresentable>()
@ -669,7 +671,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
SessionRow.TOURNAMENT_FEATURE -> {
if (tournamentFeatures.size > 2) {
"${tournamentFeatures.subList(0,2).joinToString {
"${tournamentFeatures.subList(0, 2).joinToString {
it.name
}}, ..."
} else if (tournamentFeatures.size > 0) {
@ -696,56 +698,98 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) {
SessionRow.BANKROLL -> row.editingDescriptors(mapOf(
"defaultValue" to this.bankroll,
"data" to LiveData.BANKROLL.items(realm)))
SessionRow.GAME -> row.editingDescriptors(mapOf(
"limit" to this.limit,
"defaultValue" to this.game,
"data" to LiveData.GAME.items(realm)))
SessionRow.LOCATION -> row.editingDescriptors(mapOf(
"defaultValue" to this.location,
"data" to LiveData.LOCATION.items(realm)))
SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(mapOf(
"defaultValue" to this.tournamentFeatures,
"data" to LiveData.TOURNAMENT_FEATURE.items(realm)))
SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(mapOf(
"defaultValue" to this.tournamentName,
"data" to LiveData.TOURNAMENT_NAME.items(realm)))
SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors(mapOf(
"defaultValue" to this.tournamentType))
SessionRow.TABLE_SIZE -> row.editingDescriptors(mapOf(
"defaultValue" to this.tableSize))
SessionRow.BLINDS -> row.editingDescriptors(mapOf(
"sb" to cgSmallBlind?.round(),
"bb" to cgBigBlind?.round()
))
SessionRow.BUY_IN -> row.editingDescriptors(mapOf(
"bb" to cgBigBlind,
"fee" to this.tournamentEntryFee,
"ratedBuyin" to result?.buyin
))
SessionRow.BANKROLL -> row.editingDescriptors(
mapOf(
"defaultValue" to this.bankroll,
"data" to LiveData.BANKROLL.items(realm)
)
)
SessionRow.GAME -> row.editingDescriptors(
mapOf(
"limit" to this.limit,
"defaultValue" to this.game,
"data" to LiveData.GAME.items(realm)
)
)
SessionRow.LOCATION -> row.editingDescriptors(
mapOf(
"defaultValue" to this.location,
"data" to LiveData.LOCATION.items(realm)
)
)
SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentFeatures,
"data" to LiveData.TOURNAMENT_FEATURE.items(realm)
)
)
SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentName,
"data" to LiveData.TOURNAMENT_NAME.items(realm)
)
)
SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentType
)
)
SessionRow.TABLE_SIZE -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tableSize
)
)
SessionRow.BLINDS -> row.editingDescriptors(
mapOf(
"sb" to cgSmallBlind?.round(),
"bb" to cgBigBlind?.round()
)
)
SessionRow.BUY_IN -> row.editingDescriptors(
mapOf(
"bb" to cgBigBlind,
"fee" to this.tournamentEntryFee,
"ratedBuyin" to result?.buyin
)
)
SessionRow.BREAK_TIME -> row.editingDescriptors(mapOf())
SessionRow.CASHED_OUT, SessionRow.PRIZE -> row.editingDescriptors(mapOf(
"defaultValue" to result?.cashout
))
SessionRow.NET_RESULT -> row.editingDescriptors(mapOf(
"defaultValue" to result?.netResult
))
SessionRow.COMMENT -> row.editingDescriptors(mapOf(
"defaultValue" to this.comment))
SessionRow.INITIAL_BUY_IN -> row.editingDescriptors(mapOf(
"defaultValue" to this.tournamentEntryFee
))
SessionRow.PLAYERS -> row.editingDescriptors(mapOf(
"defaultValue" to this.tournamentNumberOfPlayers))
SessionRow.POSITION -> row.editingDescriptors(mapOf(
"defaultValue" to this.result?.tournamentFinalPosition))
SessionRow.TIPS -> row.editingDescriptors(mapOf(
"sb" to cgSmallBlind?.round(),
"bb" to cgBigBlind?.round(),
"tips" to result?.tips
))
SessionRow.CASHED_OUT, SessionRow.PRIZE -> row.editingDescriptors(
mapOf(
"defaultValue" to result?.cashout
)
)
SessionRow.NET_RESULT -> row.editingDescriptors(
mapOf(
"defaultValue" to result?.netResult
)
)
SessionRow.COMMENT -> row.editingDescriptors(
mapOf(
"defaultValue" to this.comment
)
)
SessionRow.INITIAL_BUY_IN -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentEntryFee
)
)
SessionRow.PLAYERS -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentNumberOfPlayers
)
)
SessionRow.POSITION -> row.editingDescriptors(
mapOf(
"defaultValue" to this.result?.tournamentFinalPosition
)
)
SessionRow.TIPS -> row.editingDescriptors(
mapOf(
"sb" to cgSmallBlind?.round(),
"bb" to cgBigBlind?.round(),
"tips" to result?.tips
)
)
else -> null
}
}
@ -782,13 +826,15 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000
}
SessionRow.BUY_IN -> {
val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
val localResult =
if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
localResult.buyin = value as Double?
this.result = localResult
this.updateRowRepresentation()
}
SessionRow.CASHED_OUT, SessionRow.PRIZE -> {
val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
val localResult =
if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
localResult.cashout = value as Double?
@ -870,7 +916,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// Stat Base
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, context: Context): TextFormat {
this.result?.let { result ->
@ -886,7 +937,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
}
Stat.HOURLY_RATE_BB -> this.bbHourlyRate
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> Stat.netBBPer100Hands(this.bbNet, this.estimatedHands)
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> Stat.netBBPer100Hands(
this.bbNet,
this.estimatedHands
)
Stat.AVERAGE_NET_BB -> this.bbNet
Stat.DURATION, Stat.AVERAGE_DURATION -> this.netDuration.toDouble()
Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate
@ -914,4 +968,3 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}

@ -15,6 +15,7 @@ import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.util.NULL_TEXT
import java.text.DateFormat
import java.util.*
@ -86,25 +87,6 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
return this.bbNet / this.hourlyDuration
}
override fun formattedValue(stat: Stat, context: Context) : TextFormat {
return when (stat) {
Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null, context = context)
Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), currency = null, context = context)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null, context = context)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null, context = context)
Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, currency = null, context = context)
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, context = context)
} else {
return TextFormat(NULL_TEXT)
}
}
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
}
enum class Field(val identifier: String) {
RATED_NET("ratedNet"),
HOURLY_RATE("hourlyRate"),
@ -126,6 +108,32 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
}
// Stat Base
override val entryTitle: String
get() {
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate)
}
override fun formattedValue(stat: Stat, context: Context) : TextFormat {
return when (stat) {
Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null, context = context)
Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), currency = null, context = context)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null, context = context)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null, context = context)
Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, currency = null, context = context)
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, context = context)
} else {
return TextFormat(NULL_TEXT)
}
}
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
}
// Timed
override val objectIdentifier: ObjectIdentifier

@ -75,7 +75,7 @@ class HomeActivity : PokerAnalyticsActivity() {
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar_home, menu)
this.homeMenu = menu
//TODO: Change filter button visibility
//TODO: Change queryWith button visibility
homeMenu?.findItem(R.id.filter)?.isVisible = true
return super.onCreateOptionsMenu(menu)
}
@ -153,15 +153,15 @@ class HomeActivity : PokerAnalyticsActivity() {
/*
0 -> {
toolbar.title = getString(R.string.feed)
homeMenu?.findItem(R.id.filter)?.isVisible = false
homeMenu?.findItem(R.id.queryWith)?.isVisible = false
}
1 -> {
toolbar.title = getString(R.string.stats)
homeMenu?.findItem(R.id.filter)?.isVisible = false
homeMenu?.findItem(R.id.queryWith)?.isVisible = false
}
2 -> {
toolbar.title = getString(R.string.services)
homeMenu?.findItem(R.id.filter)?.isVisible = false
homeMenu?.findItem(R.id.queryWith)?.isVisible = false
}
*/

@ -149,7 +149,7 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
}
})
// Manage session type filter
// Manage session type queryWith
filterSessionAll.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
sessionTypeCondition = null
@ -181,7 +181,7 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
}
}
// Manage time filter
// Manage time queryWith
filterTimeMonth.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
currentTimeFilter = TimeFilter.MONTH

@ -243,7 +243,7 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
*/
private fun saveData() {
//TODO: Save currentFilter details data
Timber.d("Save data for filter: ${currentFilter?.id}")
Timber.d("Save data for queryWith: ${currentFilter?.id}")
selectedRows.forEach {
Timber.d("Selected rows: $it")
}

@ -60,7 +60,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
Timber.d("onActivityResult: $requestCode")
if (data != null && data.hasExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)) {
val filterId = data.getStringExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)
Timber.d("Updated filter: ${filterId}")
Timber.d("Updated queryWith: ${filterId}")
}
*/
@ -179,19 +179,19 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
}
/**
* Valid the updates of the filter
* Valid the updates of the queryWith
*/
private fun validUpdates() {
Timber.d("Valid filter updates")
Timber.d("Valid queryWith updates")
val filterId = currentFilter?.id ?: ""
finishActivityWithResult(filterId)
}
/**
* Cancel the latest updates of the filter
* Cancel the latest updates of the queryWith
*/
private fun cancelUpdates() {
Timber.d("Cancel filter updates")
Timber.d("Cancel queryWith updates")
val filterId = filterCopy?.id ?: ""
@ -208,7 +208,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
* Delete data
*/
private fun deleteFilter() {
Timber.d("Delete filter")
Timber.d("Delete queryWith")
val realm = getRealm()
realm.beginTransaction()
currentFilter?.deleteFromRealm()

@ -7,8 +7,7 @@ import android.view.ViewGroup
import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.BarLineChartBase
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.*
import com.github.mikephil.charting.highlight.Highlight
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import com.google.android.material.chip.Chip
@ -26,7 +25,6 @@ import net.pokeranalytics.android.ui.graph.PALineDataSet
import net.pokeranalytics.android.ui.graph.setStyle
import net.pokeranalytics.android.ui.view.LegendView
import timber.log.Timber
import java.text.DateFormat
import java.util.*
import kotlin.coroutines.CoroutineContext
@ -86,7 +84,15 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
this.legendView = LegendView(requireContext())
this.legendContainer.addView(this.legendView)
this.loadGraph(this.selectedReport)
this.chartView = when (stat.graphType) {
GraphType.LINE -> LineChart(context)
GraphType.BAR -> BarChart(context)
}
this.chartView.setStyle(false, requireContext())
this.chartContainer.addView(this.chartView)
this.loadGraph(this.aggregationTypes.first(), this.selectedReport)
this.aggregationTypes.forEach { type ->
val chip = Chip(requireContext())
@ -105,9 +111,9 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
val aggregationType = aggregationTypes[checkedId]
reports[aggregationType]?.let {
loadGraph(it)
loadGraph(aggregationType, it)
} ?: run {
launchStatComputation(aggregationType)
}
}
@ -115,7 +121,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
}
private fun launchStatComputation() {
private fun launchStatComputation(aggregationType: AggregationType) {
GlobalScope.launch(coroutineContext) {
@ -126,8 +132,10 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
val realm = Realm.getDefaultInstance()
val aggregationType = stat.aggregationTypes.first()
r = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType)
val report = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType)
reports[aggregationType] = report
r = report
realm.close()
@ -140,40 +148,46 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
if (!isDetached) {
r?.let {
loadGraph(it)
loadGraph(aggregationType, it)
}
}
}
}
fun loadGraph(aggregationType: AggregationType, report: Report) {
fun loadGraph(report: Report) {
val graphEntries = when (aggregationType) {
AggregationType.SESSION, AggregationType.DURATION -> report.results.firstOrNull()?.defaultStatEntries(stat)
AggregationType.MONTH, AggregationType.YEAR -> report.lineEntries(this.stat)
}
report.results.firstOrNull()?.defaultStatEntries(stat)?.let { entries ->
graphEntries?.let { entries ->
this.legendView.prepareWithStat(this.stat, entries.size)
val dataSet = PALineDataSet(entries, this.stat.name, requireContext())
val colors = arrayOf(R.color.green_light).toIntArray()
dataSet.setColors(colors, context)
dataSet.setDrawCircles(false)
val lineData = LineData(listOf(dataSet))
this.chartView = when (stat.graphType) {
when (stat.graphType) {
GraphType.LINE -> {
val lineChart = LineChart(context)
val lineChart: LineChart = this.chartView as LineChart
val dataSet = PALineDataSet(entries, this.stat.name, requireContext())
val colors = arrayOf(R.color.green_light).toIntArray()
dataSet.setColors(colors, context)
dataSet.setDrawCircles(false)
val lineData = LineData(listOf(dataSet))
lineChart.data = lineData
lineChart
}
GraphType.BAR -> {
val barChart = BarChart(context)
barChart
val barChart = this.chartView as BarChart
val dataSet = BarDataSet(entries as List<BarEntry>, this.stat.name)
val colors = arrayOf(R.color.green_light).toIntArray()
dataSet.setColors(colors, context)
val barData = BarData(listOf(dataSet))
barChart.data = barData
}
}
this.chartContainer.addView(this.chartView)
this.chartView.setStyle(false, requireContext())
this.chartView.setOnChartValueSelectedListener(this)
@ -192,11 +206,18 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
e?.let { entry ->
val identifier = entry.data as ObjectIdentifier
val item = getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull()
item?.let {
val statEntry = when (entry.data) {
is ObjectIdentifier -> {
val identifier = entry.data as ObjectIdentifier
getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull()
}
is StatEntry -> entry.data as StatEntry?
else -> null
}
statEntry?.let {
val formattedDate = DateFormat.getDateInstance(DateFormat.SHORT).format(it.startDate())
val formattedDate = it.entryTitle
val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatValue = this.stat.format(e.y.toDouble(), currency = null, context = requireContext())

@ -223,7 +223,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
if (row is StatRow && row.stat.hasEvolutionGraph) {
// filter groups
// queryWith groups
val groupResults = this.report?.results?.filter {
it.group.name == row.groupName
}

@ -9,7 +9,15 @@ class PALineDataSet(yVals: List<Entry>, label: String, context: Context) : LineD
init {
this.highLightColor = context.getColor(R.color.chart_highlight_indicator)
this.setDrawValues(false)
}
}
}
//class PABarDataSet(yVals: List<BarEntry>, label: String, context: Context) : BarDataSet(yVals, label) {
//
// init {
// this.highLightColor = context.getColor(R.color.chart_highlight_indicator)
// }
//
//}
Loading…
Cancel
Save