Merge optimal duration branch + adds message in session view

hh
Laurent 6 years ago
commit 192cc7c739
  1. 3
      app/build.gradle
  2. 7
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  3. 25
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  4. 16
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  5. 160
      app/src/main/java/net/pokeranalytics/android/calculus/optimalduration/CashGameOptimalDurationCalculator.kt
  6. 61
      app/src/main/java/net/pokeranalytics/android/model/filter/Query.kt
  7. 9
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  8. 27
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  9. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  10. 41
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt

@ -128,6 +128,9 @@ dependencies {
// CSV Parser: https://mvnrepository.com/artifact/org.apache.commons/commons-csv // CSV Parser: https://mvnrepository.com/artifact/org.apache.commons/commons-csv
implementation 'org.apache.commons:commons-csv:1.7' implementation 'org.apache.commons:commons-csv:1.7'
// Polynomial Regression
implementation 'org.apache.commons:commons-math3:3.6.1'
// Instrumented Tests // Instrumented Tests
androidTestImplementation 'androidx.test:core:1.2.0' androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'

@ -79,6 +79,11 @@ class PokerAnalyticsApplication : Application() {
// this.createFakeSessions() // this.createFakeSessions()
} }
// CashGameOptimalDurationCalculator.start(true) {
// val hours = it / 3600 / 1000
// Timber.d("Optimal duration = ${it}, $hours")
// }
Patcher.patchAll(this.applicationContext) Patcher.patchAll(this.applicationContext)
val locale = Locale.getDefault() val locale = Locale.getDefault()
@ -97,7 +102,7 @@ class PokerAnalyticsApplication : Application() {
if (sessionsCount < 10) { if (sessionsCount < 10) {
GlobalScope.launch { GlobalScope.launch {
FakeDataManager.createFakeSessions(200) FakeDataManager.createFakeSessions(400)
} }
} }

@ -21,6 +21,8 @@ import net.pokeranalytics.android.util.extensions.startOfDay
import java.util.* import java.util.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
/** /**
* The class performing statIds computation * The class performing statIds computation
@ -119,7 +121,7 @@ class Calculator {
val computeStandardDeviation: Boolean val computeStandardDeviation: Boolean
get() { get() {
this.stats.forEach { this.stats.forEach {
if (it == STANDARD_DEVIATION_BB_PER_100_HANDS || it == STANDARD_DEVIATION || it == STANDARD_DEVIATION_HOURLY) { if (it.isStandardDeviation) {
return true return true
} }
} }
@ -335,6 +337,11 @@ class Calculator {
) )
) )
} }
var averageBB = 0.0
if (bbSessionCount > 0) {
averageBB = bbSum / bbSessionCount
results.addStat(AVERAGE_NET_BB, averageBB)
}
val shouldIterateOverComputables = val shouldIterateOverComputables =
(options.progressValues == Options.ProgressValues.STANDARD || options.computeLongestStreak) (options.progressValues == Options.ProgressValues.STANDARD || options.computeLongestStreak)
@ -561,15 +568,19 @@ class Calculator {
// Session // Session
var stdSum = 0.0 var stdSum = 0.0
var stdBBSum = 0.0
var stdBBper100HandsSum = 0.0 var stdBBper100HandsSum = 0.0
computables.forEach { session -> computables.forEach { session ->
stdSum += Math.pow(session.ratedNet - average, 2.0) stdSum += (session.ratedNet - average).pow(2.0)
stdBBper100HandsSum += Math.pow(session.bbPer100Hands - bbPer100Hands, 2.0) stdBBSum += (session.bbNet - averageBB).pow(2.0)
stdBBper100HandsSum += (session.bbPer100Hands - bbPer100Hands).pow(2.0)
} }
val standardDeviation = Math.sqrt(stdSum / computables.size) val standardDeviation = sqrt(stdSum / computables.size)
val standardDeviationBBper100Hands = Math.sqrt(stdBBper100HandsSum / computables.size) val standardDeviationBB = sqrt(stdBBSum / computables.size)
val standardDeviationBBper100Hands = sqrt(stdBBper100HandsSum / computables.size)
results.addStat(STANDARD_DEVIATION, standardDeviation) results.addStat(STANDARD_DEVIATION, standardDeviation)
results.addStat(STANDARD_DEVIATION_BB, standardDeviationBB)
results.addStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands) results.addStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands)
// Session Set // Session Set
@ -578,9 +589,9 @@ class Calculator {
sessionSets.forEach { set -> sessionSets.forEach { set ->
val ssStats = SSStats(set, computableGroup.query) val ssStats = SSStats(set, computableGroup.query)
val sHourlyRate = ssStats.hourlyRate val sHourlyRate = ssStats.hourlyRate
hourlyStdSum += Math.pow(sHourlyRate - hourlyRate, 2.0) hourlyStdSum += (sHourlyRate - hourlyRate).pow(2.0)
} }
val hourlyStandardDeviation = Math.sqrt(hourlyStdSum / sessionSets.size) val hourlyStandardDeviation = sqrt(hourlyStdSum / sessionSets.size)
results.addStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) results.addStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation)
} }

@ -52,6 +52,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
BB_SESSION_COUNT(26), BB_SESSION_COUNT(26),
TOTAL_BUYIN(27), TOTAL_BUYIN(27),
RISK_OF_RUIN(28), RISK_OF_RUIN(28),
STANDARD_DEVIATION_BB(29),
; ;
companion object : IntSearchable<Stat> { companion object : IntSearchable<Stat> {
@ -119,6 +120,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
STANDARD_DEVIATION -> R.string.standard_deviation STANDARD_DEVIATION -> R.string.standard_deviation
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
STANDARD_DEVIATION_BB -> R.string.bb_standard_deviation
HANDS_PLAYED -> R.string.number_of_hands HANDS_PLAYED -> R.string.number_of_hands
LOCATIONS_PLAYED -> R.string.locations_played LOCATIONS_PLAYED -> R.string.locations_played
LONGEST_STREAKS -> R.string.longest_streaks LONGEST_STREAKS -> R.string.longest_streaks
@ -169,7 +171,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
} }
// white amountsr // white amountsr
AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS, TOTAL_BUYIN -> { STANDARD_DEVIATION_BB_PER_100_HANDS, STANDARD_DEVIATION_BB, TOTAL_BUYIN -> {
return TextFormat(value.toCurrency(currency)) return TextFormat(value.toCurrency(currency))
} }
LONGEST_STREAKS -> { LONGEST_STREAKS -> {
@ -200,6 +202,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
NUMBER_OF_GAMES -> R.string.number_of_records NUMBER_OF_GAMES -> R.string.number_of_records
NET_RESULT -> R.string.total NET_RESULT -> R.string.total
STANDARD_DEVIATION -> R.string.net_result STANDARD_DEVIATION -> R.string.net_result
STANDARD_DEVIATION_BB -> R.string.average_net_result_bb_
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) WIN_RATIO, HOURLY_DURATION -> return this.localizedTitle(context)
@ -235,13 +238,22 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
val hasProgressGraph: Boolean val hasProgressGraph: Boolean
get() { get() {
return when (this) { return when (this) {
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, HOURLY_DURATION, AVERAGE_HOURLY_DURATION, STANDARD_DEVIATION_BB,
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS -> false STANDARD_DEVIATION_BB_PER_100_HANDS -> false
else -> true else -> true
} }
} }
val isStandardDeviation: Boolean
get() {
return when (this) {
STANDARD_DEVIATION, STANDARD_DEVIATION_BB,
STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true
else -> false
}
}
val legendHideRightValue: Boolean val legendHideRightValue: Boolean
get() { get() {
return when (this) { return when (this) {

@ -0,0 +1,160 @@
package net.pokeranalytics.android.calculus.optimalduration
import io.realm.Realm
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Session
import org.apache.commons.math3.fitting.PolynomialCurveFitter
import org.apache.commons.math3.fitting.WeightedObservedPoints
import java.util.*
import kotlin.math.pow
import kotlin.math.round
/***
* This class attempts to find the optimal game duration,
* meaning the duration where the player will maximize its results, based on his history.
* The results stands for cash game, and are separated between live and online.
* Various reasons can prevent the algorithm to find a duration, see below.
*/
class CashGameOptimalDurationCalculator {
companion object {
private const val bucket = 60 * 60 * 1000L // the duration of bucket
private const val bucketInterval = 4 // number of duration tests inside the bucket to find the best duration
private const val minimumValidityCount = 10 // the number of sessions inside a bucket to start having a reasonable average
private const val intervalValidity = 3 // the minimum number of unit between the shortest & longest valid buckets
private const val polynomialDegree = 7 // the degree of the computed polynomial
/***
* Starts the calculation
* [isLive] is a boolean to indicate if we're looking at live or online games
* return a duration or null if it could not be computed
*/
fun start(isLive: Boolean): Double? {
val realm = Realm.getDefaultInstance()
val query = Query().add(QueryCondition.IsCash) // cash game
query.add(if (isLive) { QueryCondition.IsLive } else { QueryCondition.IsOnline }) // live / online
query.add(QueryCondition.EndDateNotNull) // ended
query.add(QueryCondition.BigBlindNotNull) // has BB value
val sessions = query.queryWith(realm.where(Session::class.java)).findAll()
val sessionsByDuration = sessions.groupBy {
round((it.netDuration / bucket).toDouble()) * bucket
}
// define validity interval
var start: Double? = null
var end: Double? = null
var validBuckets = 0
for (key in sessionsByDuration.keys.sorted()) {
val sessionCount = sessionsByDuration[key]?.size ?: 0
if (start == null && sessionCount >= minimumValidityCount) {
start = key
}
if (sessionCount >= minimumValidityCount) {
end = key
validBuckets++
}
}
if (!(start != null && end != null && (end - start) >= intervalValidity)) {
return null
}
// define if we have enough sessions
if (sessions.size < 50) {
return null
}
val options = Calculator.Options()
options.query = query
val report = Calculator.computeStats(realm, options)
val stdBB = report.results.firstOrNull()?.computedStat(Stat.STANDARD_DEVIATION_BB)?.value
val p = polynomialRegression(sessions, stdBB)
var bestAverage = 0.0
var bestHourlyRate = 0.0
var bestDuration = 0.0
var maxDuration = 0.0
val keys = sessionsByDuration.keys.filter { it >= start && it <= end }.sorted()
for (key in keys) {
val sessionCount = sessionsByDuration[key]?.size ?: 0
if (sessionCount < minimumValidityCount / 2) continue // if too few sessions we don't consider the duration valid
for (i in 0 until bucketInterval) {
val duration = key + i * bucket / bucketInterval
val averageResult = getBB(duration, p)
val hourly = averageResult / duration
if (averageResult > bestAverage && hourly > 2 / 3 * bestHourlyRate) {
bestAverage = averageResult
bestDuration = duration
}
if (duration > 0 && hourly > bestHourlyRate) {
bestHourlyRate = hourly
}
if (duration > maxDuration){
maxDuration = duration
}
}
}
if (bestDuration > 0.0) {
return bestDuration
}
realm.close()
return null
}
private fun getBB(netDuration: Double, polynomial: DoubleArray): Double {
var y = 0.0
for (i in polynomial.indices) {
y += polynomial[i] * netDuration.pow(i)
}
return y
}
private fun polynomialRegression(sessions: List<Session>, bbStandardDeviation: Double?): DoubleArray {
val stdBB = bbStandardDeviation ?: Double.MAX_VALUE
val points = WeightedObservedPoints()
val now = Date().time
sessions.forEach {
var weight = 5.0
val endTime = it.endDate?.time ?: 0L
val age = now - endTime
if (age > 2 * 365 * 24 * 3600 * 1000L) { // if more than 2 years loses 1 point
weight -= 1.0
}
if (it.bbNet > 2 * stdBB) { // if very big result loses 3 points
weight -= 3.0
}
points.add(weight, it.netDuration.toDouble(), it.bbNet)
}
// polynomial of 7 degree, same as iOS
return PolynomialCurveFitter.create(polynomialDegree).fit(points.toList())
}
}
}

@ -12,7 +12,7 @@ fun List<Query>.mapFirstCondition() : List<QueryCondition> {
class Query { class Query {
constructor(vararg elements: QueryCondition) { constructor(vararg elements: QueryCondition) {
if (elements.size > 0) { if (elements.isNotEmpty()) {
this.add(elements.asList()) this.add(elements.asList())
} }
} }
@ -23,22 +23,25 @@ class Query {
return this._conditions return this._conditions
} }
fun add(vararg elements: QueryCondition) { fun add(vararg elements: QueryCondition): Query {
if (elements.size > 0) { if (elements.isNotEmpty()) {
this.add(elements.asList()) this.add(elements.asList())
} }
return this
} }
fun add(queryCondition: QueryCondition) { fun add(queryCondition: QueryCondition): Query {
this._conditions.add(queryCondition) this._conditions.add(queryCondition)
return this
} }
fun remove(queryCondition: QueryCondition) { fun add(queryConditions: List<QueryCondition>): Query{
this._conditions.remove(queryCondition) this._conditions.addAll(queryConditions)
return this
} }
fun add(queryConditions: List<QueryCondition>) { fun remove(queryCondition: QueryCondition) {
this._conditions.addAll(queryConditions) this._conditions.remove(queryCondition)
} }
val defaultName: String val defaultName: String
@ -59,28 +62,32 @@ class Query {
inline fun <reified T : Filterable> queryWith(query: RealmQuery<T>): RealmQuery<T> { inline fun <reified T : Filterable> queryWith(query: RealmQuery<T>): RealmQuery<T> {
var realmQuery = query var realmQuery = query
val queryFromTime = this.conditions.filter { val queryFromTime = this.conditions.firstOrNull {
it is QueryCondition.StartedFromTime it is QueryCondition.StartedFromTime
}.firstOrNull() }
val queryToTime = this.conditions.filter { val queryToTime = this.conditions.firstOrNull {
it is QueryCondition.EndedToTime it is QueryCondition.EndedToTime
}.firstOrNull() }
this.conditions.forEach { this.conditions.forEach {
if (it is QueryCondition.StartedFromTime) { realmQuery = when (it) {
realmQuery = it.queryWith(realmQuery, queryToTime) is QueryCondition.StartedFromTime -> {
} else if (it is QueryCondition.EndedToTime) { it.queryWith(realmQuery, queryToTime)
realmQuery = it.queryWith(realmQuery, queryFromTime) }
} else { is QueryCondition.EndedToTime -> {
realmQuery = it.queryWith(realmQuery) it.queryWith(realmQuery, queryFromTime)
} }
else -> {
it.queryWith(realmQuery)
}
}
} }
// println("<<<<<< ${realmQuery.description}") // println("<<<<<< ${realmQuery.description}")
val queryLast = this.conditions.filter { val queryLast = this.conditions.firstOrNull {
it is QueryCondition.Last it is QueryCondition.Last
}.firstOrNull() }
queryLast?.let {qc -> queryLast?.let {qc ->
(qc as QueryCondition.Last).singleValue?.let { (qc as QueryCondition.Last).singleValue?.let {
return realmQuery.limit(it.toLong()) return realmQuery.limit(it.toLong())
} }

@ -307,6 +307,9 @@ sealed class QueryCondition : FilterElementRow {
abstract class TrueQueryCondition : QueryCondition() { abstract class TrueQueryCondition : QueryCondition() {
override var operator: Operator = Operator.TRUE override var operator: Operator = Operator.TRUE
} }
abstract class NotNullQueryCondition : QueryCondition() {
override var operator: Operator = Operator.NOTNULL
}
object IsLive : TrueQueryCondition() object IsLive : TrueQueryCondition()
@ -602,9 +605,9 @@ sealed class QueryCondition : FilterElementRow {
} }
} }
object DateNotNull : QueryCondition() { object DateNotNull : NotNullQueryCondition()
override var operator = Operator.NOTNULL object EndDateNotNull : NotNullQueryCondition()
} object BigBlindNotNull : NotNullQueryCondition()
class StartedFromTime() : TimeQuery() { class StartedFromTime() : TimeQuery() {
override var operator = Operator.MORE override var operator = Operator.MORE

@ -6,19 +6,19 @@ import net.pokeranalytics.android.model.filter.QueryCondition
open class ComputableResult() : RealmObject(), Filterable { open class ComputableResult() : RealmObject(), Filterable {
var ratedNet: Double = 0.0 var ratedNet: Double = 0.0
var bbNet: BB = 0.0 var bbNet: BB = 0.0
var hasBigBlind: Int = 0 var hasBigBlind: Int = 0
var isPositive: Int = 0 var isPositive: Int = 0
var ratedBuyin: Double = 0.0 var ratedBuyin: Double = 0.0
var estimatedHands: Double = 0.0 var estimatedHands: Double = 0.0
var bbPer100Hands: BB = 0.0 var bbPer100Hands: BB = 0.0
var session: Session? = null var session: Session? = null
@ -35,7 +35,8 @@ open class ComputableResult() : RealmObject(), Filterable {
this.bbNet = session.bbNet this.bbNet = session.bbNet
this.hasBigBlind = if (session.cgBigBlind != null) 1 else 0 this.hasBigBlind = if (session.cgBigBlind != null) 1 else 0
this.estimatedHands = session.estimatedHands this.estimatedHands = session.estimatedHands
this.bbPer100Hands = session.bbNet / (session.numberOfHandsPerHour * session.hourlyDuration) * 100 this.bbPer100Hands =
session.bbNet / (session.numberOfHandsPerHour * session.hourlyDuration) * 100
} }
@ -51,11 +52,11 @@ open class ComputableResult() : RealmObject(), Filterable {
companion object { companion object {
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { fun fieldNameForQueryType(queryCondition: Class<out QueryCondition>): String? {
Session.fieldNameForQueryType(queryCondition)?.let { Session.fieldNameForQueryType(queryCondition)?.let {
return "session.$it" return "session.$it"
} }
return null return null
} }
} }

@ -118,6 +118,8 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomFieldAmountQuery::class.java, CustomFieldNumberQuery::class.java -> "customFieldEntries.numericValue" CustomFieldAmountQuery::class.java, CustomFieldNumberQuery::class.java -> "customFieldEntries.numericValue"
CustomFieldQuery::class.java -> "customFieldEntries.customFields.id" CustomFieldQuery::class.java -> "customFieldEntries.customFields.id"
DateNotNull::class.java -> "startDate" DateNotNull::class.java -> "startDate"
EndDateNotNull::class.java -> "endDate"
BigBlindNotNull::class.java -> "cgBigBlind"
else -> null else -> null
} }
} }

@ -11,8 +11,13 @@ import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import kotlinx.android.synthetic.main.fragment_session.* import kotlinx.android.synthetic.main.fragment_session.*
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.R
import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
import net.pokeranalytics.android.calculus.optimalduration.CashGameOptimalDurationCalculator
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.extensions.SessionState import net.pokeranalytics.android.model.extensions.SessionState
@ -34,8 +39,10 @@ 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.extensions.findById import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import net.pokeranalytics.android.util.extensions.getNextMinuteInMilliseconds import net.pokeranalytics.android.util.extensions.getNextMinuteInMilliseconds
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext
class SessionFragment : RealmFragment(), RowRepresentableDelegate { class SessionFragment : RealmFragment(), RowRepresentableDelegate {
@ -64,6 +71,9 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
} }
} }
private val coroutineContext: CoroutineContext
get() = Dispatchers.Main
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -344,10 +354,17 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
* Update the state of the session (start / pause) * Update the state of the session (start / pause)
*/ */
private fun manageSessionState() { private fun manageSessionState() {
when (currentSession.getState()) { when (val state = currentSession.getState()) {
SessionState.PENDING, SessionState.PLANNED, SessionState.PAUSED -> { SessionState.PENDING, SessionState.PLANNED, SessionState.PAUSED -> {
// if not started computed cash game optimal duration
if (state != SessionState.PAUSED && this.currentSession.isCashGame()) {
computeOptimalDuration()
}
currentSession.startOrContinue() currentSession.startOrContinue()
this.recyclerView.smoothScrollToPosition(0) this.recyclerView.smoothScrollToPosition(0)
} }
SessionState.STARTED -> { SessionState.STARTED -> {
currentSession.pause() currentSession.pause()
@ -358,6 +375,28 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
updateSessionUI() updateSessionUI()
} }
private fun computeOptimalDuration() {
GlobalScope.launch(coroutineContext) {
var optimalDuration: Double? = null
val cr = GlobalScope.async {
optimalDuration = CashGameOptimalDurationCalculator.start(currentSession.isLive)
}
cr.await()
if (!isDetached) {
optimalDuration?.let {
val formattedDuration = it.formattedHourlyDuration()
val message = requireContext().getString(R.string.stop_notification_in_, formattedDuration)
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
}
}
}
}
/** /**
* Stop the current session * Stop the current session
*/ */

Loading…
Cancel
Save