Merge branch 'dev' of gitlab.com:stax-river/poker-analytics into dev

feature/top10
Aurelien Hubert 7 years ago
commit 0de83e179e
  1. 66
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  2. 15
      app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt
  3. 315
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  4. 3
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  5. 1
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  6. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  7. 49
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  8. 18
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  9. 4
      app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt
  10. 2
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  11. 45
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  12. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt
  13. 66
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt
  14. 1
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt
  15. 13
      app/src/main/res/layout/fragment_graph.xml

@ -1,7 +1,5 @@
package net.pokeranalytics.android.calculus package net.pokeranalytics.android.calculus
import android.os.Parcel
import android.os.Parcelable
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.realm.ComputableResult import net.pokeranalytics.android.model.realm.ComputableResult
@ -10,31 +8,6 @@ import net.pokeranalytics.android.model.realm.SessionSet
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
class ParcelableString(var string: String) : Parcelable {
constructor(parcel: Parcel) : this(parcel.readString()) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(string)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ParcelableString> {
override fun createFromParcel(parcel: Parcel): ParcelableString {
return ParcelableString(parcel)
}
override fun newArray(size: Int): Array<ParcelableString?> {
return arrayOfNulls(size)
}
}
}
/** /**
* The class performing stats computation * The class performing stats computation
*/ */
@ -92,8 +65,6 @@ class Calculator {
var computableGroups: MutableList<ComputableGroup> = mutableListOf() var computableGroups: MutableList<ComputableGroup> = mutableListOf()
filters.forEach { filter -> filters.forEach { filter ->
val results = filter.results<ComputableResult>()
val sets = filter.results<SessionSet>()
val group = ComputableGroup(filter.name, filter.filterConditions.map { it.queryCondition }) val group = ComputableGroup(filter.name, filter.filterConditions.map { it.queryCondition })
computableGroups.add(group) computableGroups.add(group)
@ -175,20 +146,20 @@ class Calculator {
tBuyinSum += computable.ratedBuyin tBuyinSum += computable.ratedBuyin
tHands += computable.estimatedHands tHands += computable.estimatedHands
val sessionId = ParcelableString(computable.session?.id ?: throw IllegalStateException("Computing lone ComputableResult")) val session = computable.session ?: throw IllegalStateException("Computing lone ComputableResult")
results.addEvolutionValue(tSum, NETRESULT, sessionId) results.addEvolutionValue(tSum, NETRESULT, session)
results.addEvolutionValue(tSum / index, AVERAGE, sessionId) results.addEvolutionValue(tSum / index, AVERAGE, session)
results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES, sessionId) results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES, session)
results.addEvolutionValue(tBBSum / tBBSessionCount, AVERAGE_NET_BB, sessionId) results.addEvolutionValue(tBBSum / tBBSessionCount, AVERAGE_NET_BB, session)
results.addEvolutionValue((tWinningSessionCount / index).toDouble(), WIN_RATIO, sessionId) results.addEvolutionValue((tWinningSessionCount / index).toDouble(), WIN_RATIO, session)
results.addEvolutionValue(tBuyinSum / index, AVERAGE_BUYIN, sessionId) results.addEvolutionValue(tBuyinSum / index, AVERAGE_BUYIN, session)
Stat.netBBPer100Hands(tBBSum, tHands)?.let { netBB100 -> Stat.netBBPer100Hands(tBBSum, tHands)?.let { netBB100 ->
results.addEvolutionValue(netBB100, NET_BB_PER_100_HANDS, sessionId) results.addEvolutionValue(netBB100, NET_BB_PER_100_HANDS, session)
} }
Stat.returnOnInvestment(tSum, tBuyinSum)?.let { roi -> Stat.returnOnInvestment(tSum, tBuyinSum)?.let { roi ->
results.addEvolutionValue(roi, ROI, sessionId) results.addEvolutionValue(roi, ROI, session)
} }
} }
@ -208,7 +179,7 @@ class Calculator {
val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble() val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
val hourlyRate = gSum / gHourlyDuration val hourlyRate = gSum / gHourlyDuration
// var hourlyRateBB = gBBSum / gDuration // var bbHourlyRate = gBBSum / gDuration
when (options.evolutionValues) { when (options.evolutionValues) {
Options.EvolutionValues.DATED -> { Options.EvolutionValues.DATED -> {
@ -231,17 +202,16 @@ class Calculator {
tHourlyRate = gSum / tHourlyDuration tHourlyRate = gSum / tHourlyDuration
tHourlyRateBB = gBBSum / tHourlyDuration tHourlyRateBB = gBBSum / tHourlyDuration
val id = ParcelableString(sessionSet.id) results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, sessionSet)
results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, id) results.addEvolutionValue(tSum / tHourlyDuration, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue(tSum / tHourlyDuration, tHourlyDuration, HOURLY_RATE, id) results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, id) results.addEvolutionValue(tIndex.toDouble(), tHourlyDuration, NUMBER_OF_SETS, sessionSet)
results.addEvolutionValue(tIndex.toDouble(), tHourlyDuration, NUMBER_OF_SETS, id) results.addEvolutionValue(sessionSet.netDuration.toDouble(), tHourlyDuration, DURATION, sessionSet)
results.addEvolutionValue(sessionSet.netDuration.toDouble(), tHourlyDuration, DURATION, id) results.addEvolutionValue(tHourlyDuration / tIndex, tHourlyDuration, AVERAGE_DURATION, sessionSet)
results.addEvolutionValue(tHourlyDuration / tIndex, tHourlyDuration, AVERAGE_DURATION, id) results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet)
results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, id)
Stat.netBBPer100Hands(gBBSum, gTotalHands)?.let { netBB100 -> Stat.netBBPer100Hands(gBBSum, gTotalHands)?.let { netBB100 ->
results.addEvolutionValue(netBB100, tHourlyDuration, NET_BB_PER_100_HANDS, id) results.addEvolutionValue(netBB100, tHourlyDuration, NET_BB_PER_100_HANDS, sessionSet)
} }
} }

@ -5,6 +5,7 @@ import com.github.mikephil.charting.data.Entry
import io.realm.Realm import io.realm.Realm
import io.realm.RealmResults import io.realm.RealmResults
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.realm.ComputableResult import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.SessionSet import net.pokeranalytics.android.model.realm.SessionSet
@ -113,8 +114,9 @@ class ComputedResults(group: ComputableGroup) {
this._addEvolutionValue(Point(value, data), stat = stat) this._addEvolutionValue(Point(value, data), stat = stat)
} }
fun addEvolutionValue(value: Double, duration: Double, stat: Stat, data: Any) { fun addEvolutionValue(value: Double, duration: Double, stat: Stat, data: Timed) {
this._addEvolutionValue(Point(value, y = duration, data = data), stat = stat) stat.underlyingClass = data::class.java
this._addEvolutionValue(Point(value, y = duration, data = data.id), stat = stat)
} }
private fun _addEvolutionValue(point: Point, stat: Stat) { private fun _addEvolutionValue(point: Point, stat: Stat) {
@ -169,7 +171,7 @@ class ComputedResults(group: ComputableGroup) {
// MPAndroidChart // MPAndroidChart
fun defaultStatEntries(stat: Stat): List<out Entry> { fun defaultStatEntries(stat: Stat): List<Entry> {
return when (stat) { return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES -> this.barEntries(stat) Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES -> this.barEntries(stat)
else -> this.singleLineEntries(stat) else -> this.singleLineEntries(stat)
@ -177,7 +179,7 @@ class ComputedResults(group: ComputableGroup) {
} }
fun singleLineEntries(stat: Stat): List<Entry> { fun singleLineEntries(stat: Stat): List<Entry> {
var entries = mutableListOf<Entry>() val entries = mutableListOf<Entry>()
this._evolutionValues[stat]?.let { points -> this._evolutionValues[stat]?.let { points ->
points.forEachIndexed { index, p -> points.forEachIndexed { index, p ->
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data))
@ -187,7 +189,7 @@ class ComputedResults(group: ComputableGroup) {
} }
fun durationEntries(stat: Stat): List<Entry> { fun durationEntries(stat: Stat): List<Entry> {
var entries = mutableListOf<Entry>() val entries = mutableListOf<Entry>()
this._evolutionValues[stat]?.let { points -> this._evolutionValues[stat]?.let { points ->
points.forEach { p -> points.forEach { p ->
entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data))
@ -198,8 +200,7 @@ class ComputedResults(group: ComputableGroup) {
fun barEntries(stat: Stat): List<BarEntry> { fun barEntries(stat: Stat): List<BarEntry> {
var entries = mutableListOf<BarEntry>() val entries = mutableListOf<BarEntry>()
this._evolutionValues[stat]?.let { points -> this._evolutionValues[stat]?.let { points ->
points.forEach { p -> points.forEach { p ->
entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data))

@ -1,8 +1,10 @@
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.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.CurrencyUtils
@ -11,90 +13,159 @@ import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import java.util.* import java.util.*
class StatFormattingException(message: String) : Exception(message) {
}
interface StatBase : RealmModel {
fun formattedValue(stat: Stat, context: Context): TextFormat
}
/** /**
* An enum representing all the types of Session statistics * An enum representing all the types of Session statistics
*/ */
enum class Stat : RowRepresentable { enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentable {
NETRESULT, NETRESULT,
HOURLY_RATE, HOURLY_RATE,
AVERAGE, AVERAGE,
NUMBER_OF_SETS, NUMBER_OF_SETS,
NUMBER_OF_GAMES, NUMBER_OF_GAMES,
DURATION, DURATION,
AVERAGE_DURATION, AVERAGE_DURATION,
NET_BB_PER_100_HANDS, NET_BB_PER_100_HANDS,
HOURLY_RATE_BB, HOURLY_RATE_BB,
AVERAGE_NET_BB, AVERAGE_NET_BB,
WIN_RATIO, WIN_RATIO,
AVERAGE_BUYIN, AVERAGE_BUYIN,
ROI, ROI,
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;
/** /**
* Returns whether the stat evolution numericValues requires a distribution sorting * Returns whether the stat evolution numericValues requires a distribution sorting
*/ */
fun hasDistributionSorting() : Boolean { fun hasDistributionSorting(): Boolean {
return when (this) { return when (this) {
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true
else -> false else -> false
} }
} }
companion object { companion object {
fun returnOnInvestment(netResult: Double, buyin: Double) : Double? { fun returnOnInvestment(netResult: Double, buyin: Double): Double? {
if (buyin == 0.0) { if (buyin == 0.0) {
return null return null
} }
return netResult / buyin return netResult / buyin
} }
fun netBBPer100Hands(netBB: Double, numberOfHands: Double) : Double? { fun netBBPer100Hands(netBB: Double, numberOfHands: Double): Double? {
if (numberOfHands == 0.0) { if (numberOfHands == 0.0) {
return null return null
} }
return netBB / numberOfHands * 100 return netBB / numberOfHands * 100
} }
} }
override val resId: Int? override val resId: Int?
get() { get() {
return when (this) { return when (this) {
NETRESULT -> R.string.net_result NETRESULT -> R.string.net_result
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 DURATION -> R.string.duration
AVERAGE_DURATION -> R.string.average_hours_played AVERAGE_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_
WIN_RATIO -> R.string.win_ratio WIN_RATIO -> R.string.win_ratio
AVERAGE_BUYIN -> R.string.average_buyin AVERAGE_BUYIN -> R.string.average_buyin
ROI -> R.string.tournament_roi ROI -> R.string.tournament_roi
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
HANDS_PLAYED -> R.string.number_of_hands HANDS_PLAYED -> R.string.number_of_hands
} }
} }
val threshold: Double
get() { /**
return when (this) { * Formats the value of the stat to be suitable for display
WIN_RATIO -> 50.0 */
else -> 0.0 fun format(value: Double, currency: Currency? = null, context: Context): TextFormat {
}
if (value.isNaN()) {
} return TextFormat(NULL_TEXT, R.color.white)
}
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
when (this) {
// Amounts + red/green
Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> {
val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency)
val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(numberFormat.format(value), color)
}
// Red/green numericValues
Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> {
val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(value.formatted(), color)
}
// white integers
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> {
return TextFormat("${value.toInt()}")
} // white durations
Stat.DURATION, Stat.AVERAGE_DURATION -> {
return TextFormat(value.formattedHourlyDuration())
} // red/green percentages
Stat.WIN_RATIO, Stat.ROI -> {
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr
Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY,
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency)
return TextFormat(numberFormat.format(value))
}
else -> throw FormattingException("Stat formatting of ${this.name} not handled")
}
}
val threshold: Double
get() {
return when (this) {
WIN_RATIO -> 50.0
else -> 0.0
}
}
fun cumulativeLabelResId(context: Context) : String {
val resId = when (this) {
AVERAGE, AVERAGE_DURATION, NETRESULT, NET_BB_PER_100_HANDS,
HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, WIN_RATIO, HOURLY_RATE -> R.string.average
NETRESULT, DURATION -> R.string.total
STANDARD_DEVIATION -> R.string.net_result
STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
else -> null
}
resId?.let {
return context.getString(it)
} ?: run {
return NULL_TEXT
}
}
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
} }
/** /**
@ -102,63 +173,29 @@ enum class Stat : RowRepresentable {
*/ */
class ComputedStat(var stat: Stat, var value: Double, var currency: Currency? = null) { class ComputedStat(var stat: Stat, var value: Double, 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) {
this.variation = (value - previousValue) / previousValue this.variation = (value - previousValue) / previousValue
} }
} }
/** /**
* The variation of the stat * The variation of the stat
*/ */
var variation: Double? = null var variation: Double? = null
/** /**
* Formats the value of the stat to be suitable for display * Formats the value of the stat to be suitable for display
*/ */
fun format(context: Context): TextFormat { fun format(context: Context): TextFormat {
return this.stat.format(this.value, this.currency, context)
if (this.value.isNaN()) { }
return TextFormat(NULL_TEXT, R.color.white)
} /**
* Returns a TextFormat instance for an evolution value located at the specified [index]
when (this.stat) { */
// Amounts + red/green fun evolutionValueFormat(index: Int): TextFormat {
Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> { return TextFormat("undef ${index}")
val numberFormat= CurrencyUtils.getCurrencyFormatter(context, currency) }
val color = if (this.value >= this.stat.threshold) R.color.green else R.color.red
return TextFormat(numberFormat.format(this.value), color)
}
// Red/green numericValues
Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> {
val color = if (this.value >= this.stat.threshold) R.color.green else R.color.red
return TextFormat(this.value.formatted(), color)
}
// white integers
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> {
return TextFormat("${value.toInt()}")
} // white durations
Stat.DURATION, Stat.AVERAGE_DURATION -> {
return TextFormat(value.formattedHourlyDuration())
} // red/green percentages
Stat.WIN_RATIO, Stat.ROI -> {
val color = if (value * 100 >= this.stat.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr
Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY,
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val numberFormat= CurrencyUtils.getCurrencyFormatter(context, currency)
return TextFormat(numberFormat.format(this.value))
}
else -> throw FormattingException("Stat formatting of ${this.stat.name} not handled")
}
}
/**
* Returns a TextFormat instance for an evolution value located at the specified [index]
*/
fun evolutionValueFormat(index: Int) : TextFormat {
return TextFormat("undef ${index}")
}
} }

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

@ -43,7 +43,6 @@ class PokerAnalyticsMigration : RealmMigration {
schema.get("SessionSet")?.let { schema.get("SessionSet")?.let {
it.addPrimaryKey("id") it.addPrimaryKey("id")
} }
currentVersion++ currentVersion++
} }

@ -54,7 +54,7 @@ open class ComputableResult() : RealmObject(), Computable, Filterable {
BB_PER100HANDS("bbPer100Hands") BB_PER100HANDS("bbPer100Hands")
} }
companion object : Filterable { companion object {
fun fieldNameForQueryType(queryCondition: QueryCondition): String? { fun fieldNameForQueryType(queryCondition: QueryCondition): String? {
return "session." + Session.fieldNameForQueryType(queryCondition) return "session." + Session.fieldNameForQueryType(queryCondition)

@ -13,6 +13,8 @@ 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.ComputedStat
import net.pokeranalytics.android.calculus.Stat 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
@ -48,7 +50,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
TOURNAMENT TOURNAMENT
} }
companion object : Filterable { companion object {
fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session { fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session {
val session = Session() val session = Session()
session.result = Result() session.result = Result()
@ -365,6 +367,11 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
throw ModelException("Session should have an existing Result relationship") throw ModelException("Session should have an existing Result relationship")
} }
val bbHourlyRate: Double
get() {
return this.bbNet / this.hourlyDuration
}
// Manageable // Manageable
override fun isValidForSave(): Boolean { override fun isValidForSave(): Boolean {
@ -822,5 +829,45 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
} }
// Stat Base
override fun formattedValue(stat: Stat, context: Context) : TextFormat {
this.result?.let { result ->
val value: Double? = when (stat) {
Stat.NETRESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> result.net
Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> 1.0
Stat.AVERAGE_BUYIN -> result.buyin
Stat.ROI -> {
result.buyin?.let {
Stat.returnOnInvestment(result.net, it)
} ?: run {
null
}
}
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.AVERAGE_NET_BB -> this.bbNet
Stat.DURATION, Stat.AVERAGE_DURATION -> this.netDuration.toDouble()
Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate
Stat.HANDS_PLAYED -> this.estimatedHands
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
value?.let {
return stat.format(it, CurrencyUtils.getCurrency(this.bankroll), context)
} ?: run {
return TextFormat(NULL_TEXT)
}
} ?: run {
throw java.lang.IllegalStateException("Asking for stats on Session without Result")
}
}
} }

@ -1,11 +1,15 @@
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.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.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
@ -15,7 +19,7 @@ import java.util.*
open class SessionSet() : RealmObject(), Timed, Filterable { open class SessionSet() : RealmObject(), Timed, Filterable {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
var startDate: Date = Date() var startDate: Date = Date()
set(value) { set(value) {
@ -74,6 +78,16 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
var bbNet: Double = 0.0 var bbNet: Double = 0.0
override fun formattedValue(stat: Stat, context: Context) : TextFormat {
return when (stat) {
Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, null, context)
Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), null, context)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, null, context)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, null, context)
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
}
enum class Field(val identifier: String) { enum class Field(val identifier: String) {
RATED_NET("ratedNet"), RATED_NET("ratedNet"),
HOURLY_RATE("hourlyRate"), HOURLY_RATE("hourlyRate"),
@ -82,7 +96,7 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
NET_DURATION("netDuration") NET_DURATION("netDuration")
} }
companion object : Filterable { companion object {
fun newInstance(realm: Realm) : SessionSet { fun newInstance(realm: Realm) : SessionSet {
val sessionSet = SessionSet() val sessionSet = SessionSet()

@ -9,9 +9,9 @@ import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.GraphFragment import net.pokeranalytics.android.ui.fragment.GraphFragment
class GraphParameters(stat: Stat, entries: List<out Entry>) { class GraphParameters(stat: Stat, entries: List<Entry>) {
var stat: Stat = stat var stat: Stat = stat
var entries: List<out Entry> = entries var entries: List<Entry> = entries
} }
class GraphActivity : PokerAnalyticsActivity() { class GraphActivity : PokerAnalyticsActivity() {

@ -78,7 +78,7 @@ class HomeActivity : PokerAnalyticsActivity() {
// observe currency changes // observe currency changes
this.currencies = realm.where(Currency::class.java).findAll() this.currencies = realm.where(Currency::class.java).findAll()
this.currencies.addChangeListener { t, set -> this.currencies.addChangeListener { t, _ ->
realm.beginTransaction() realm.beginTransaction()
t.forEach { t.forEach {

@ -7,17 +7,20 @@ import android.view.ViewGroup
import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.highlight.Highlight
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.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.graph.setStyle
interface GraphDataSource { interface GraphDataSource {
} }
class GraphFragment : PokerAnalyticsFragment() { class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
lateinit var dataSource: GraphDataSource lateinit var dataSource: GraphDataSource
@ -46,8 +49,48 @@ class GraphFragment : PokerAnalyticsFragment() {
private fun initUI() { private fun initUI() {
val dataSet = LineDataSet(this.entries, this.stat.name) val dataSet = LineDataSet(this.entries, this.stat.name)
val colors = arrayOf(R.color.green_light).toIntArray()
dataSet.setColors(colors, context)
val lineData = LineData(listOf(dataSet)) val lineData = LineData(listOf(dataSet))
this.chart.setStyle()
this.chart.data = lineData this.chart.data = lineData
this.chart.setOnChartValueSelectedListener(this)
}
// OnChartValueSelectedListener
override fun onNothingSelected() {
// nothing to do
}
override fun onValueSelected(e: Entry?, h: Highlight?) {
e?.let { entry ->
h?.let { highlight ->
val id = entry.data as String
val item = getRealm().where(this.stat.underlyingClass).equalTo("id", id).findAll().firstOrNull()
item?.let {
val date = it.startDate()
val entryStatName = this.stat.localizedTitle(requireContext())
val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatName = this.stat.cumulativeLabelResId(requireContext())
val totalStatValue = this.stat.format(e.y.toDouble(), null, requireContext())
}
this.text.text = ""
}
}
} }

@ -112,7 +112,7 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
private fun initData() { private fun initData() {
this.realmSessions = getRealm().where<Session>().findAll().sort("startDate", Sort.DESCENDING) this.realmSessions = getRealm().where<Session>().findAll().sort("startDate", Sort.DESCENDING)
this.realmSessions.addChangeListener { t, changeSet -> this.realmSessions.addChangeListener { _, changeSet ->
this.historyAdapter.refreshData() this.historyAdapter.refreshData()
this.historyAdapter.notifyDataSetChanged() this.historyAdapter.notifyDataSetChanged()
} }

@ -0,0 +1,66 @@
package net.pokeranalytics.android.ui.graph
import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.BarLineChartBase
import com.github.mikephil.charting.charts.LineChart
fun BarChart.setStyle() {
GraphHelper.setStyle(this)
}
fun LineChart.setStyle() {
GraphHelper.setStyle(this)
}
class GraphHelper {
companion object {
fun setStyle(chart: BarLineChartBase<*>) {
// this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.) ChartAppearance.defaultColor
// this.xAxis.axisLineWidth = ChartAppearance.lineWidth
// this.xAxis.enableGridDashedLine(3.0f, 5.0f, 1.0f)
//
// this.xAxis.labelTextColor = ChartAppearance.defaultColor
// this.xAxis.labelFont = Fonts.graphAxis
// this.xAxis.labelCount = 4
// this.xAxis.labelPosition = .bottom
//
// this.xAxis.drawLabelsEnabled = true
// this.xAxis.drawGridLinesEnabled = true
// this.xAxis.granularity = 1.0
// this.xAxis.granularityEnabled = true
// this.xAxis.enabled = true
//
// // Y Axis
// this.leftAxis.drawAxisLineEnabled = false
// this.leftAxis.drawGridLinesEnabled = true
// this.leftAxis.gridLineDashLengths = [3.0, 5.0]
//
// this.leftAxis.drawZeroLineEnabled = true
// this.leftAxis.zeroLineWidth = ChartAppearance.lineWidth
// this.leftAxis.zeroLineColor = ChartAppearance.defaultColor
//
// this.leftAxis.granularityEnabled = true
// this.leftAxis.granularity = 1.0
//
// this.leftAxis.labelTextColor = ChartAppearance.defaultColor
// this.leftAxis.labelFont = Fonts.graphAxis
// this.leftAxis.labelCount = small ? 1 : 7 // @todo not great if interval is [0..2] for number of records as we get decimals
//
// if timeYAxis {
// this.leftAxis.valueFormatter = HourValueFormatter()
// } else {
// this.leftAxis.valueFormatter = LargeNumberFormatter()
// }
//
// this.rightAxis.enabled = false
//
// this.legend.enabled = false
}
}
}

@ -62,7 +62,6 @@ enum class SessionRow : RowRepresentable {
START_DATE, END_DATE, BREAK_TIME, COMMENT START_DATE, END_DATE, BREAK_TIME, COMMENT
) )
} }
else -> arrayListOf()
} }
} }
Session.Type.CASH_GAME.ordinal -> { Session.Type.CASH_GAME.ordinal -> {

@ -6,9 +6,20 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintTop_toTopOf="@+id/chart"
android:layout_marginTop="8dp"/>
<com.github.mikephil.charting.charts.LineChart <com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart" android:id="@+id/chart"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
Loading…
Cancel
Save