commit
af5d40997e
Binary file not shown.
@ -0,0 +1,44 @@ |
|||||||
|
package net.pokeranalytics.android.components |
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||||
|
import io.realm.RealmList |
||||||
|
import net.pokeranalytics.android.model.realm.* |
||||||
|
import org.junit.runner.RunWith |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class) |
||||||
|
open class SessionInstrumentedUnitTest : RealmInstrumentedUnitTest() { |
||||||
|
|
||||||
|
// convenience extension |
||||||
|
fun Session.Companion.testInstance( |
||||||
|
netResult: Double = 0.0, |
||||||
|
isTournament: Boolean = false, |
||||||
|
startDate: Date = Date(), |
||||||
|
endDate: Int = 1, |
||||||
|
bankroll: Bankroll? = null, |
||||||
|
game: Game? = null, |
||||||
|
location: Location? = null, |
||||||
|
tournamentName: TournamentName? = null, |
||||||
|
tournamentFeatures: RealmList<TournamentFeature> = RealmList(), |
||||||
|
numberOfTable: Int = 1, |
||||||
|
limit: Int? = null, |
||||||
|
tableSize: Int? = null |
||||||
|
): Session { |
||||||
|
val session: Session = Session.newInstance(super.mockRealm, isTournament, bankroll) |
||||||
|
session.game = game |
||||||
|
session.location = location |
||||||
|
session.tournamentFeatures = tournamentFeatures |
||||||
|
session.tournamentName = tournamentName |
||||||
|
session.limit = limit |
||||||
|
session.numberOfTables = numberOfTable |
||||||
|
session.tableSize = tableSize |
||||||
|
session.startDate = startDate |
||||||
|
session.result?.netResult = netResult |
||||||
|
val cal = Calendar.getInstance() // creates calendar |
||||||
|
cal.time = startDate // sets calendar time/date |
||||||
|
cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour |
||||||
|
session.endDate = cal.time // returns new date object, one hour in the future |
||||||
|
return session |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,104 @@ |
|||||||
|
package net.pokeranalytics.android.model |
||||||
|
|
||||||
|
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
||||||
|
import net.pokeranalytics.android.components.RealmInstrumentedUnitTest |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.model.realm.Filter |
||||||
|
import net.pokeranalytics.android.model.realm.FilterCondition |
||||||
|
import net.pokeranalytics.android.model.realm.Session |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow |
||||||
|
import org.junit.Assert |
||||||
|
import org.junit.Test |
||||||
|
|
||||||
|
import org.junit.Assert.* |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
class CriteriaTest : BaseFilterInstrumentedUnitTest() { |
||||||
|
|
||||||
|
@Test |
||||||
|
fun getQueryConditions() { |
||||||
|
|
||||||
|
val realm = this.mockRealm |
||||||
|
realm.beginTransaction() |
||||||
|
val cal = Calendar.getInstance() |
||||||
|
cal.time = Date() |
||||||
|
val s1 = Session.testInstance(100.0, false, cal.time) |
||||||
|
cal.add(Calendar.YEAR, 1) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
cal.add(Calendar.YEAR, -11) |
||||||
|
val firstValue = cal.get(Calendar.YEAR) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
cal.add(Calendar.YEAR, 7) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
cal.add(Calendar.YEAR, -2) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
cal.add(Calendar.YEAR, 10) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
|
||||||
|
val lastValue = firstValue + 10 |
||||||
|
|
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val years = Criteria.Years.queryConditions as List<QueryCondition.AnyYear> |
||||||
|
println("years = ${years.map { it.getDisplayName() }}") |
||||||
|
|
||||||
|
assertEquals(11, years.size) |
||||||
|
assertEquals(firstValue, years.first().listOfValues.first()) |
||||||
|
assertEquals(lastValue, years.last().listOfValues.first()) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun combined() { |
||||||
|
|
||||||
|
val critierias = listOf(Criteria.MonthsOfYear, Criteria.DaysOfWeek) |
||||||
|
val combined = critierias.combined() |
||||||
|
combined.forEach { |
||||||
|
it.forEach {qc-> |
||||||
|
println(qc.getDisplayName()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun upToNow() { |
||||||
|
val realm = this.mockRealm |
||||||
|
realm.beginTransaction() |
||||||
|
val cal = Calendar.getInstance() |
||||||
|
cal.time = Date() |
||||||
|
val s1 = Session.testInstance(100.0, false, cal.time) |
||||||
|
cal.add(Calendar.YEAR, 1) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
cal.add(Calendar.YEAR, -11) |
||||||
|
val firstValue = cal.get(Calendar.YEAR) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
cal.add(Calendar.YEAR, 7) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
cal.add(Calendar.YEAR, -2) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
cal.add(Calendar.YEAR, 10) |
||||||
|
Session.testInstance(100.0, true, cal.time) |
||||||
|
|
||||||
|
val lastValue = firstValue + 10 |
||||||
|
|
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val critierias = listOf(Criteria.Years, Criteria.MonthsOfYear) |
||||||
|
val combined = critierias.combined() |
||||||
|
combined.forEach { |
||||||
|
it.forEach {qc-> |
||||||
|
println("<<<<< ${qc.getDisplayName()}") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
println("<<<<< reduced") |
||||||
|
|
||||||
|
val reduced= critierias.combined().upToNow() |
||||||
|
reduced.forEach { |
||||||
|
it.forEach {qc-> |
||||||
|
println("<<<<< ${qc.getDisplayName()}") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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,218 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus |
|
||||||
|
|
||||||
import com.github.mikephil.charting.data.BarEntry |
|
||||||
import com.github.mikephil.charting.data.Entry |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmResults |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.interfaces.Timed |
|
||||||
import net.pokeranalytics.android.model.realm.ComputableResult |
|
||||||
import net.pokeranalytics.android.model.realm.Filter |
|
||||||
import net.pokeranalytics.android.model.realm.SessionSet |
|
||||||
|
|
||||||
/** |
|
||||||
* A sessionGroup of computable items identified by a name |
|
||||||
*/ |
|
||||||
class ComputableGroup(name: String, conditions: List<QueryCondition>, stats: List<Stat>? = null) { |
|
||||||
|
|
||||||
/** |
|
||||||
* The display name of the group |
|
||||||
*/ |
|
||||||
var name: String = name |
|
||||||
|
|
||||||
/** |
|
||||||
* A list of conditions to get |
|
||||||
*/ |
|
||||||
var conditions: List<QueryCondition> = conditions |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of endedSessions to compute |
|
||||||
*/ |
|
||||||
private var _computables: RealmResults<ComputableResult>? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Retrieves the computables on the relative [realm] filtered with the provided [conditions] |
|
||||||
*/ |
|
||||||
fun computables(realm: Realm): RealmResults<ComputableResult> { |
|
||||||
|
|
||||||
// if computables exists and is valid (previous realm not closed) |
|
||||||
this._computables?.let { |
|
||||||
if (it.isValid) { |
|
||||||
return it |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val computables: RealmResults<ComputableResult> = Filter.queryOn(realm, this.conditions) |
|
||||||
this._computables = computables |
|
||||||
return computables |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of sets to compute |
|
||||||
*/ |
|
||||||
private var _sessionSets: RealmResults<SessionSet>? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Retrieves the session sets on the relative [realm] filtered with the provided [conditions] |
|
||||||
*/ |
|
||||||
fun sessionSets(realm: Realm): RealmResults<SessionSet> { |
|
||||||
// if computables exists and is valid (previous realm not closed) |
|
||||||
this._sessionSets?.let { |
|
||||||
if (it.isValid) { |
|
||||||
return it |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val sets: RealmResults<SessionSet> = Filter.queryOn(realm, this.conditions) |
|
||||||
this._sessionSets = sets |
|
||||||
return sets |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of stats to display |
|
||||||
*/ |
|
||||||
var stats: List<Stat>? = stats |
|
||||||
|
|
||||||
/** |
|
||||||
* A subgroup used to compute stat variation |
|
||||||
*/ |
|
||||||
var comparedComputables: ComputableGroup? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* The computed stats of the comparable sessionGroup |
|
||||||
*/ |
|
||||||
var comparedComputedResults: ComputedResults? = null |
|
||||||
|
|
||||||
fun cleanup() { |
|
||||||
this._computables = null |
|
||||||
this._sessionSets = null |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
class ComputedResults(group: ComputableGroup) { |
|
||||||
|
|
||||||
/** |
|
||||||
* The session group used to computed the stats |
|
||||||
*/ |
|
||||||
var group: ComputableGroup = group |
|
||||||
|
|
||||||
// The computed stats of the sessionGroup |
|
||||||
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() |
|
||||||
|
|
||||||
// The map containing all evolution numericValues for all stats |
|
||||||
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf() |
|
||||||
|
|
||||||
fun allStats(): Collection<ComputedStat> { |
|
||||||
return this._computedStats.values |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a value to the evolution values |
|
||||||
*/ |
|
||||||
fun addEvolutionValue(value: Double, stat: Stat, data: Any) { |
|
||||||
this._addEvolutionValue(Point(value, data), stat = stat) |
|
||||||
} |
|
||||||
|
|
||||||
fun addEvolutionValue(value: Double, duration: Double, stat: Stat, data: Timed) { |
|
||||||
stat.underlyingClass = data::class.java |
|
||||||
this._addEvolutionValue(Point(value, y = duration, data = data.id), stat = stat) |
|
||||||
} |
|
||||||
|
|
||||||
private fun _addEvolutionValue(point: Point, stat: Stat) { |
|
||||||
val evolutionValues = this._evolutionValues[stat] |
|
||||||
if (evolutionValues != null) { |
|
||||||
evolutionValues.add(point) |
|
||||||
} else { |
|
||||||
val values: MutableList<Point> = mutableListOf(point) |
|
||||||
this._evolutionValues[stat] = values |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun addStats(computedStats: Set<ComputedStat>) { |
|
||||||
computedStats.forEach { |
|
||||||
this._computedStats[it.stat] = it |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun computedStat(stat: Stat): ComputedStat? { |
|
||||||
return this._computedStats[stat] |
|
||||||
} |
|
||||||
|
|
||||||
fun computeStatVariations(resultsToCompare: ComputedResults) { |
|
||||||
this._computedStats.keys.forEach { stat -> |
|
||||||
val computedStat = this.computedStat(stat) |
|
||||||
val comparedStat = resultsToCompare.computedStat(stat) |
|
||||||
if (computedStat != null && comparedStat != null) { |
|
||||||
computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun finalize(options: Calculator.Options) { |
|
||||||
if (options.evolutionValues != Calculator.Options.EvolutionValues.NONE) { |
|
||||||
|
|
||||||
// Sort points as a distribution |
|
||||||
this._computedStats.keys.filter { it.hasDistributionSorting() }.forEach { _ -> |
|
||||||
// @todo sort |
|
||||||
// var evolutionValues = this._evolutionValues[stat] |
|
||||||
// evolutionValues.so |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the number of computed stats |
|
||||||
*/ |
|
||||||
fun numberOfStats(): Int { |
|
||||||
return this._computedStats.size |
|
||||||
} |
|
||||||
|
|
||||||
// MPAndroidChart |
|
||||||
|
|
||||||
fun defaultStatEntries(stat: Stat): List<Entry> { |
|
||||||
return when (stat) { |
|
||||||
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES -> this.barEntries(stat) |
|
||||||
else -> this.singleLineEntries(stat) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun singleLineEntries(stat: Stat): List<Entry> { |
|
||||||
val entries = mutableListOf<Entry>() |
|
||||||
this._evolutionValues[stat]?.let { points -> |
|
||||||
points.forEachIndexed { index, p -> |
|
||||||
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) |
|
||||||
} |
|
||||||
} |
|
||||||
return entries |
|
||||||
} |
|
||||||
|
|
||||||
fun durationEntries(stat: Stat): List<Entry> { |
|
||||||
val entries = mutableListOf<Entry>() |
|
||||||
this._evolutionValues[stat]?.let { points -> |
|
||||||
points.forEach { p -> |
|
||||||
entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) |
|
||||||
} |
|
||||||
} |
|
||||||
return entries |
|
||||||
} |
|
||||||
|
|
||||||
fun barEntries(stat: Stat): List<BarEntry> { |
|
||||||
|
|
||||||
val entries = mutableListOf<BarEntry>() |
|
||||||
this._evolutionValues[stat]?.let { points -> |
|
||||||
points.forEach { p -> |
|
||||||
entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) |
|
||||||
} |
|
||||||
} |
|
||||||
return entries |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
class Point(val x: Double, val y: Double, val data: Any) { |
|
||||||
|
|
||||||
constructor(y: Double, data: Any) : this(0.0, y, data) |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,496 @@ |
|||||||
|
package net.pokeranalytics.android.calculus |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import com.github.mikephil.charting.data.* |
||||||
|
import io.realm.Realm |
||||||
|
import io.realm.RealmResults |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.model.interfaces.Timed |
||||||
|
import net.pokeranalytics.android.model.realm.ComputableResult |
||||||
|
import net.pokeranalytics.android.model.realm.Filter |
||||||
|
import net.pokeranalytics.android.model.realm.SessionSet |
||||||
|
import net.pokeranalytics.android.ui.fragment.GraphFragment |
||||||
|
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry |
||||||
|
import net.pokeranalytics.android.ui.graph.PALineDataSet |
||||||
|
import net.pokeranalytics.android.ui.view.DefaultLegendValues |
||||||
|
import net.pokeranalytics.android.ui.view.LegendContent |
||||||
|
import net.pokeranalytics.android.util.ColorUtils |
||||||
|
import kotlin.math.abs |
||||||
|
|
||||||
|
/** |
||||||
|
* The class returned after performing calculation in the Calculator object |
||||||
|
*/ |
||||||
|
class Report(var options: Calculator.Options) { |
||||||
|
|
||||||
|
/** |
||||||
|
* The mutable list of ComputedResults, one for each group of data |
||||||
|
*/ |
||||||
|
private var _results: MutableList<ComputedResults> = mutableListOf() |
||||||
|
|
||||||
|
val results: List<ComputedResults> = this._results |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a new result to the list of ComputedResults |
||||||
|
*/ |
||||||
|
fun addResults(result: ComputedResults) { |
||||||
|
this._results.add(result) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the list of entries corresponding to the provided [stat] |
||||||
|
* One value will be returned by result |
||||||
|
*/ |
||||||
|
fun lineEntries(stat: Stat? = null, context: Context): LineDataSet { |
||||||
|
val entries = mutableListOf<Entry>() |
||||||
|
val statToUse = stat ?: options.displayedStats.firstOrNull() |
||||||
|
val statName = statToUse?.name ?: "" |
||||||
|
|
||||||
|
statToUse?.let { |
||||||
|
this._results.forEachIndexed { index, results -> |
||||||
|
results.computedStat(it)?.progressValue?.let { progressValue -> |
||||||
|
entries.add(Entry(index.toFloat(), progressValue.toFloat(), results)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return PALineDataSet(entries, statName, context) |
||||||
|
} |
||||||
|
|
||||||
|
fun barEntries(stat: Stat? = null, context: Context): BarDataSet { |
||||||
|
val entries = mutableListOf<BarEntry>() |
||||||
|
val statToUse = stat ?: options.displayedStats.firstOrNull() |
||||||
|
|
||||||
|
statToUse?.let { |
||||||
|
this._results.forEachIndexed { index, results -> |
||||||
|
val cs = results.computedStat(it) |
||||||
|
cs?.let { computedStat -> |
||||||
|
val barEntry = BarEntry(index.toFloat(), computedStat.value.toFloat(), results) |
||||||
|
entries.add(barEntry) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val barDataSet = BarDataSet(entries, statToUse?.name) |
||||||
|
barDataSet.color = context.getColor(R.color.green) |
||||||
|
barDataSet.setDrawValues(false) |
||||||
|
return barDataSet |
||||||
|
} |
||||||
|
|
||||||
|
fun multiLineEntries(context: Context): List<LineDataSet> { |
||||||
|
val dataSets = mutableListOf<LineDataSet>() |
||||||
|
|
||||||
|
options.displayedStats.forEach { stat -> |
||||||
|
this._results.forEachIndexed { index, result -> |
||||||
|
val ds = result.singleLineEntries(stat, context) |
||||||
|
ds.color = ColorUtils.almostRandomColor(index, context) |
||||||
|
dataSets.add(ds) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return dataSets |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* A sessionGroup of computable items identified by a name |
||||||
|
*/ |
||||||
|
class ComputableGroup(name: String = "", conditions: List<QueryCondition> = listOf(), stats: List<Stat>? = null) { |
||||||
|
|
||||||
|
/** |
||||||
|
* The display name of the group |
||||||
|
*/ |
||||||
|
var name: String = name |
||||||
|
|
||||||
|
/** |
||||||
|
* A list of conditions to get |
||||||
|
*/ |
||||||
|
var conditions: List<QueryCondition> = conditions |
||||||
|
|
||||||
|
/** |
||||||
|
* The list of endedSessions to compute |
||||||
|
*/ |
||||||
|
private var _computables: RealmResults<ComputableResult>? = null |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the computables on the relative [realm] filtered with the provided [conditions] |
||||||
|
*/ |
||||||
|
fun computables(realm: Realm, sorted: Boolean = false): RealmResults<ComputableResult> { |
||||||
|
|
||||||
|
// if computables exists and is valid (previous realm not closed) |
||||||
|
this._computables?.let { |
||||||
|
if (it.isValid) { |
||||||
|
return it |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val sortedField = if (sorted) "session.startDate" else null |
||||||
|
val computables = Filter.queryOn<ComputableResult>(realm, this.conditions, sortedField) |
||||||
|
this._computables = computables |
||||||
|
return computables |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The list of sets to compute |
||||||
|
*/ |
||||||
|
private var _sessionSets: RealmResults<SessionSet>? = null |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the session sets on the relative [realm] filtered with the provided [conditions] |
||||||
|
*/ |
||||||
|
fun sessionSets(realm: Realm, sorted: Boolean = false): RealmResults<SessionSet> { |
||||||
|
// if computables exists and is valid (previous realm not closed) |
||||||
|
this._sessionSets?.let { |
||||||
|
if (it.isValid) { |
||||||
|
return it |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val sortedField = if (sorted) SessionSet.Field.START_DATE.identifier else null |
||||||
|
val sets = Filter.queryOn<SessionSet>(realm, this.conditions, sortedField) |
||||||
|
this._sessionSets = sets |
||||||
|
return sets |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The list of stats to display |
||||||
|
*/ |
||||||
|
var stats: List<Stat>? = stats |
||||||
|
|
||||||
|
/** |
||||||
|
* A subgroup used to compute stat variation |
||||||
|
*/ |
||||||
|
var comparedGroup: ComputableGroup? = null |
||||||
|
|
||||||
|
/** |
||||||
|
* The computed stats of the comparable sessionGroup |
||||||
|
*/ |
||||||
|
var comparedComputedResults: ComputedResults? = null |
||||||
|
|
||||||
|
fun cleanup() { |
||||||
|
this._computables = null |
||||||
|
this._sessionSets = null |
||||||
|
} |
||||||
|
|
||||||
|
val isEmpty: Boolean |
||||||
|
get() { |
||||||
|
return this._computables?.isEmpty() ?: true |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) : GraphUnderlyingEntry { |
||||||
|
|
||||||
|
/** |
||||||
|
* The session group used to computed the stats |
||||||
|
*/ |
||||||
|
var group: ComputableGroup = group |
||||||
|
|
||||||
|
// The computed stats of the sessionGroup |
||||||
|
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() |
||||||
|
|
||||||
|
// The map containing all evolution numericValues for all stats |
||||||
|
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf() |
||||||
|
|
||||||
|
private var shouldManageMultiGroupProgressValues = shouldManageMultiGroupProgressValues |
||||||
|
|
||||||
|
fun allStats(): Collection<ComputedStat> { |
||||||
|
return this._computedStats.values |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a value to the evolution values |
||||||
|
*/ |
||||||
|
fun addEvolutionValue(value: Double, duration: Double? = null, stat: Stat, data: Timed) { |
||||||
|
|
||||||
|
val point = if (duration != null) { |
||||||
|
Point(duration, y = value, data = data.objectIdentifier) |
||||||
|
} else { |
||||||
|
Point(value, data = data.objectIdentifier) |
||||||
|
} |
||||||
|
this._addEvolutionValue(point, stat = stat) |
||||||
|
} |
||||||
|
|
||||||
|
private fun _addEvolutionValue(point: Point, stat: Stat) { |
||||||
|
val evolutionValues = this._evolutionValues[stat] |
||||||
|
if (evolutionValues != null) { |
||||||
|
evolutionValues.add(point) |
||||||
|
} else { |
||||||
|
val values: MutableList<Point> = mutableListOf(point) |
||||||
|
this._evolutionValues[stat] = values |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun addStat(stat: Stat, value: Double, secondValue: Double? = null) { |
||||||
|
val computedStat = ComputedStat(stat, value, secondValue = secondValue) |
||||||
|
this.addComputedStat(computedStat) |
||||||
|
} |
||||||
|
|
||||||
|
fun addStats(computedStats: Set<ComputedStat>) { |
||||||
|
computedStats.forEach { |
||||||
|
this.addComputedStat(it) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a [computedStat] to the list of stats |
||||||
|
* Also computes evolution values using the previously computed values |
||||||
|
*/ |
||||||
|
private fun addComputedStat(computedStat: ComputedStat) { |
||||||
|
this._computedStats[computedStat.stat] = computedStat |
||||||
|
} |
||||||
|
|
||||||
|
private fun consolidateProgressStats() { |
||||||
|
|
||||||
|
if (this.shouldManageMultiGroupProgressValues) { |
||||||
|
|
||||||
|
this.group.comparedComputedResults?.let { previousResult -> |
||||||
|
this.allStats().forEach { computedStat -> |
||||||
|
val stat = computedStat.stat |
||||||
|
previousResult.computedStat(stat)?.let { previousComputedStat -> |
||||||
|
when (stat) { |
||||||
|
Stat.NET_RESULT, Stat.HOURLY_DURATION, Stat.BB_NET_RESULT, Stat.BB_SESSION_COUNT, |
||||||
|
Stat.WINNING_SESSION_COUNT, Stat.TOTAL_BUYIN, Stat.HANDS_PLAYED, Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> { |
||||||
|
val previousValue = previousComputedStat.progressValue ?: previousComputedStat.value |
||||||
|
computedStat.progressValue = previousValue + computedStat.value |
||||||
|
} |
||||||
|
else -> {} |
||||||
|
} |
||||||
|
} ?: run { |
||||||
|
computedStat.progressValue = computedStat.value |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val netResult = this.computedStat(Stat.NET_RESULT)?.progressValue |
||||||
|
val bbNetResult = this.computedStat(Stat.BB_NET_RESULT)?.progressValue |
||||||
|
val duration = this.computedStat(Stat.HOURLY_DURATION)?.progressValue |
||||||
|
val numberOfGames = this.computedStat(Stat.NUMBER_OF_GAMES)?.progressValue |
||||||
|
val numberOfSets = this.computedStat(Stat.NUMBER_OF_SETS)?.progressValue |
||||||
|
val handsPlayed = this.computedStat(Stat.HANDS_PLAYED)?.progressValue |
||||||
|
val winningCount = this.computedStat(Stat.WINNING_SESSION_COUNT)?.progressValue |
||||||
|
val bbSessionCount = this.computedStat(Stat.BB_SESSION_COUNT)?.progressValue |
||||||
|
val totalBuyin = this.computedStat(Stat.TOTAL_BUYIN)?.progressValue |
||||||
|
|
||||||
|
this.allStats().forEach { computedStat -> |
||||||
|
when (computedStat.stat) { |
||||||
|
Stat.HOURLY_RATE -> { |
||||||
|
if (netResult != null && duration != null) { |
||||||
|
computedStat.progressValue = netResult / duration |
||||||
|
} |
||||||
|
} |
||||||
|
Stat.AVERAGE -> { |
||||||
|
if (netResult != null && numberOfGames != null) { |
||||||
|
computedStat.progressValue = netResult / numberOfGames |
||||||
|
} |
||||||
|
} |
||||||
|
Stat.AVERAGE_HOURLY_DURATION -> { |
||||||
|
if (duration != null && numberOfSets != null) { |
||||||
|
computedStat.progressValue = duration / numberOfSets |
||||||
|
} |
||||||
|
} |
||||||
|
Stat.NET_BB_PER_100_HANDS -> { |
||||||
|
if (bbNetResult != null && handsPlayed != null) { |
||||||
|
computedStat.progressValue = Stat.netBBPer100Hands(bbNetResult, handsPlayed) |
||||||
|
} |
||||||
|
} |
||||||
|
Stat.HOURLY_RATE_BB -> { |
||||||
|
if (bbNetResult != null && duration != null) { |
||||||
|
computedStat.progressValue = bbNetResult / duration |
||||||
|
} |
||||||
|
} |
||||||
|
Stat.AVERAGE_NET_BB -> { |
||||||
|
if (bbNetResult != null && bbSessionCount != null) { |
||||||
|
computedStat.progressValue = bbNetResult / bbSessionCount |
||||||
|
} |
||||||
|
} |
||||||
|
Stat.WIN_RATIO -> { |
||||||
|
if (winningCount != null && numberOfGames != null) { |
||||||
|
computedStat.progressValue = winningCount / numberOfGames |
||||||
|
} |
||||||
|
} |
||||||
|
Stat.AVERAGE_BUYIN -> { |
||||||
|
if (totalBuyin != null && numberOfGames != null) { |
||||||
|
computedStat.progressValue = totalBuyin / numberOfGames |
||||||
|
} |
||||||
|
} |
||||||
|
Stat.ROI -> { |
||||||
|
if (totalBuyin != null && netResult != null) { |
||||||
|
computedStat.progressValue = Stat.returnOnInvestment(netResult, totalBuyin) |
||||||
|
} |
||||||
|
} |
||||||
|
else -> {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fun computedStat(stat: Stat): ComputedStat? { |
||||||
|
return this._computedStats[stat] |
||||||
|
} |
||||||
|
|
||||||
|
fun computeStatVariations(resultsToCompare: ComputedResults) { |
||||||
|
this._computedStats.keys.forEach { stat -> |
||||||
|
val computedStat = this.computedStat(stat) |
||||||
|
val comparedStat = resultsToCompare.computedStat(stat) |
||||||
|
if (computedStat != null && comparedStat != null) { |
||||||
|
computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun finalize(options: Calculator.Options) { |
||||||
|
|
||||||
|
this.consolidateProgressStats() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// MPAndroidChart |
||||||
|
|
||||||
|
fun defaultStatEntries(stat: Stat, context: Context): DataSet<out Entry> { |
||||||
|
return when (stat) { |
||||||
|
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HOURLY_DURATION -> this.barEntries(stat, context = context) |
||||||
|
Stat.STANDARD_DEVIATION -> this.distributionEntries(stat, context) |
||||||
|
else -> this.singleLineEntries(stat, context) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun singleLineEntries(stat: Stat, context: Context): LineDataSet { |
||||||
|
val entries = mutableListOf<Entry>() |
||||||
|
this._evolutionValues[stat]?.let { points -> |
||||||
|
points.forEachIndexed { index, p -> |
||||||
|
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) |
||||||
|
} |
||||||
|
} |
||||||
|
return PALineDataSet(entries, this.group.name, context) |
||||||
|
} |
||||||
|
|
||||||
|
fun durationEntries(stat: Stat, context: Context): LineDataSet { |
||||||
|
val entries = mutableListOf<Entry>() |
||||||
|
this._evolutionValues[stat]?.let { points -> |
||||||
|
points.forEach { p -> |
||||||
|
entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) |
||||||
|
} |
||||||
|
} |
||||||
|
return PALineDataSet(entries, stat.name, context) |
||||||
|
} |
||||||
|
|
||||||
|
fun barEntries(stat: Stat, context: Context): BarDataSet { |
||||||
|
|
||||||
|
val entries = mutableListOf<BarEntry>() |
||||||
|
this._evolutionValues[stat]?.let { points -> |
||||||
|
points.forEach { p -> |
||||||
|
entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) |
||||||
|
} |
||||||
|
} |
||||||
|
val dataSet = BarDataSet(entries, stat.name) |
||||||
|
dataSet.color = context.getColor(R.color.green) |
||||||
|
// dataSet.barBorderWidth = 1.0f |
||||||
|
dataSet.setDrawValues(false) |
||||||
|
return dataSet |
||||||
|
} |
||||||
|
|
||||||
|
fun distributionEntries(stat: Stat, context: Context): BarDataSet { |
||||||
|
|
||||||
|
val colors = mutableListOf<Int>() |
||||||
|
val entries = mutableListOf<BarEntry>() |
||||||
|
this._evolutionValues[stat]?.let { points -> |
||||||
|
|
||||||
|
val negative = mutableListOf<Point>() |
||||||
|
val positive = mutableListOf<Point>() |
||||||
|
|
||||||
|
points.sortByDescending { it.y } |
||||||
|
points.forEach { |
||||||
|
if (it.y < 0) { |
||||||
|
negative.add(it) |
||||||
|
} else { |
||||||
|
positive.add(it) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
negative.forEachIndexed { index, p -> |
||||||
|
entries.add(BarEntry(index.toFloat(), abs(p.y.toFloat()), p.data)) |
||||||
|
colors.add(context.getColor(R.color.red)) |
||||||
|
} |
||||||
|
positive.forEachIndexed { index, p -> |
||||||
|
val x = negative.size + index.toFloat() |
||||||
|
entries.add(BarEntry(x, p.y.toFloat(), p.data)) |
||||||
|
colors.add(context.getColor(R.color.green)) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
val dataSet = BarDataSet(entries, stat.name) |
||||||
|
dataSet.colors = colors |
||||||
|
dataSet.setDrawValues(false) |
||||||
|
return dataSet |
||||||
|
} |
||||||
|
|
||||||
|
val isEmpty: Boolean |
||||||
|
get() { |
||||||
|
return this.group.isEmpty |
||||||
|
} |
||||||
|
|
||||||
|
// Stat Entry |
||||||
|
|
||||||
|
override val entryTitle: String = this.group.name |
||||||
|
|
||||||
|
override fun formattedValue(stat: Stat): TextFormat { |
||||||
|
this.computedStat(stat)?.let { |
||||||
|
return it.format() |
||||||
|
} ?: run { |
||||||
|
throw IllegalStateException("Missing stat in results") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun legendValues( |
||||||
|
stat: Stat, |
||||||
|
entry: Entry, |
||||||
|
style: GraphFragment.Style, |
||||||
|
groupName: String, |
||||||
|
context: Context |
||||||
|
): LegendContent { |
||||||
|
|
||||||
|
when (style) { |
||||||
|
|
||||||
|
GraphFragment.Style.BAR -> { |
||||||
|
return when (stat) { |
||||||
|
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { |
||||||
|
val totalStatValue = stat.format(entry.y.toDouble(), currency = null) |
||||||
|
DefaultLegendValues(this.entryTitle, totalStatValue) |
||||||
|
} |
||||||
|
else -> { |
||||||
|
val entryValue = this.formattedValue(stat) |
||||||
|
val countValue = this.computedStat(Stat.NUMBER_OF_GAMES)?.format() |
||||||
|
DefaultLegendValues(this.entryTitle, entryValue, countValue) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else -> { |
||||||
|
|
||||||
|
return when (stat) { |
||||||
|
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { |
||||||
|
val totalStatValue = stat.format(entry.y.toDouble(), currency = null) |
||||||
|
DefaultLegendValues(this.entryTitle, totalStatValue) |
||||||
|
} |
||||||
|
else -> { |
||||||
|
val entryValue = this.formattedValue(stat) |
||||||
|
val totalStatValue = stat.format(entry.y.toDouble(), currency = null) |
||||||
|
DefaultLegendValues(this.entryTitle, entryValue, totalStatValue) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class Point(val x: Double, val y: Double, val data: Any) { |
||||||
|
|
||||||
|
constructor(y: Double, data: Any) : this(0.0, y, data) |
||||||
|
|
||||||
|
} |
||||||
@ -1,17 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.interfaces |
|
||||||
|
|
||||||
import net.pokeranalytics.android.model.realm.SessionSet |
|
||||||
|
|
||||||
interface Computable { |
|
||||||
|
|
||||||
var ratedNet: Double |
|
||||||
var bbNet: Double |
|
||||||
var hasBigBlind: Int |
|
||||||
var isPositive: Int |
|
||||||
var ratedBuyin: Double |
|
||||||
var estimatedHands: Double |
|
||||||
var bbPer100Hands: Double |
|
||||||
|
|
||||||
var sessionSet: SessionSet? |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,11 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.interfaces |
|
||||||
|
|
||||||
import java.util.* |
|
||||||
|
|
||||||
interface Datable { |
|
||||||
var date: Date |
|
||||||
} |
|
||||||
|
|
||||||
interface DatableValue : Datable { |
|
||||||
var value: Double |
|
||||||
} |
|
||||||
@ -0,0 +1,191 @@ |
|||||||
|
package net.pokeranalytics.android.model |
||||||
|
|
||||||
|
import io.realm.Realm |
||||||
|
import io.realm.Sort |
||||||
|
import io.realm.kotlin.where |
||||||
|
import net.pokeranalytics.android.exceptions.PokerAnalyticsException |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.model.interfaces.NameManageable |
||||||
|
import net.pokeranalytics.android.model.realm.* |
||||||
|
import java.util.* |
||||||
|
import kotlin.collections.ArrayList |
||||||
|
|
||||||
|
fun List<Criteria>.combined(): List<List<QueryCondition>> { |
||||||
|
val comparatorList = ArrayList<List<QueryCondition>>() |
||||||
|
this.forEach { |
||||||
|
comparatorList.add(it.queryConditions) |
||||||
|
} |
||||||
|
return getCombinations(comparatorList) |
||||||
|
} |
||||||
|
|
||||||
|
fun List<List<QueryCondition>>.upToNow(): List<List<QueryCondition>> { |
||||||
|
val calendar = Calendar.getInstance() |
||||||
|
calendar.time = Date() |
||||||
|
val currentYear = calendar.get(Calendar.YEAR) |
||||||
|
val currentMonth = calendar.get(Calendar.MONTH) |
||||||
|
|
||||||
|
val toRemove = this.filter { list -> |
||||||
|
list.any { it is QueryCondition.AnyYear && it.listOfValues.first() == currentYear } |
||||||
|
}.filter { list -> |
||||||
|
list.any { |
||||||
|
it is QueryCondition.AnyMonthOfYear && it.listOfValues.first() > currentMonth |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return this.filter{ list -> |
||||||
|
var keep = true |
||||||
|
toRemove.forEach { |
||||||
|
if (list.containsAll(it)) { |
||||||
|
keep = false |
||||||
|
} |
||||||
|
} |
||||||
|
keep |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun <T> getCombinations(lists: List<List<T>>): List<List<T>> { |
||||||
|
var combinations: LinkedHashSet<List<T>> = LinkedHashSet() |
||||||
|
var newCombinations: LinkedHashSet<List<T>> |
||||||
|
|
||||||
|
var index = 0 |
||||||
|
|
||||||
|
// extract each of the integers in the first list |
||||||
|
// and add each to ints as a new list |
||||||
|
if (lists.isNotEmpty()) { |
||||||
|
for (i in lists[0]) { |
||||||
|
val newList = ArrayList<T>() |
||||||
|
newList.add(i) |
||||||
|
combinations.add(newList) |
||||||
|
} |
||||||
|
index++ |
||||||
|
} |
||||||
|
while (index < lists.size) { |
||||||
|
val nextList = lists[index] |
||||||
|
newCombinations = LinkedHashSet() |
||||||
|
for (first in combinations) { |
||||||
|
for (second in nextList) { |
||||||
|
val newList = ArrayList<T>() |
||||||
|
newList.addAll(first) |
||||||
|
newList.add(second) |
||||||
|
newCombinations.add(newList) |
||||||
|
} |
||||||
|
} |
||||||
|
combinations = newCombinations |
||||||
|
|
||||||
|
index++ |
||||||
|
} |
||||||
|
|
||||||
|
return combinations.toList() |
||||||
|
} |
||||||
|
|
||||||
|
sealed class Criteria { |
||||||
|
abstract class RealmCriteria : Criteria() { |
||||||
|
inline fun <reified T: NameManageable> comparison(): List<QueryCondition> { |
||||||
|
return compare<QueryCondition.QueryDataCondition<NameManageable>, T>() |
||||||
|
.sorted() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
abstract class SimpleCriteria(private val conditions:List<QueryCondition>): Criteria() { |
||||||
|
fun comparison(): List<QueryCondition> { |
||||||
|
return conditions |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
abstract class ListCriteria : Criteria() { |
||||||
|
inline fun <reified T:QueryCondition.ListOfValues<S>, reified S:Comparable<S>> comparison(): List<QueryCondition> { |
||||||
|
QueryCondition.distinct<Session, T, S>()?.let { |
||||||
|
val values = it.mapNotNull { session -> |
||||||
|
when (this) { |
||||||
|
is Limits -> if (session.limit is S) { session.limit as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
||||||
|
is TournamentTypes -> if (session.tournamentType is S) { session.tournamentType as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
||||||
|
is TableSizes -> if (session.tableSize is S) { session.tableSize as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
||||||
|
is TournamentFees -> if (session.tournamentEntryFee is S) { session.tournamentEntryFee as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
||||||
|
is Blinds -> if (session.blinds is S) { session.blinds as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
||||||
|
else -> null |
||||||
|
} |
||||||
|
}.distinct() |
||||||
|
return compareList<T, S>(values = values).sorted() |
||||||
|
} |
||||||
|
return listOf<T>() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
object Bankrolls: RealmCriteria() |
||||||
|
object Games: RealmCriteria() |
||||||
|
object TournamentNames: RealmCriteria() |
||||||
|
object Locations: RealmCriteria() |
||||||
|
object TournamentFeatures: RealmCriteria() |
||||||
|
object Limits: ListCriteria() |
||||||
|
object TableSizes: ListCriteria() |
||||||
|
object TournamentTypes: ListCriteria() |
||||||
|
object MonthsOfYear: SimpleCriteria(List(12) { index -> QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index)} }) |
||||||
|
object DaysOfWeek: SimpleCriteria(List(7) { index -> QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) } }) |
||||||
|
object SessionTypes: SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament)) |
||||||
|
object BankrollTypes: SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline)) |
||||||
|
object DayPeriods: SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd)) |
||||||
|
object Years: ListCriteria() |
||||||
|
object Blinds: ListCriteria() |
||||||
|
object TournamentFees: ListCriteria() |
||||||
|
object Cash: SimpleCriteria(listOf(QueryCondition.IsCash)) |
||||||
|
object Tournament: SimpleCriteria(listOf(QueryCondition.IsTournament)) |
||||||
|
|
||||||
|
val queryConditions: List<QueryCondition> |
||||||
|
get() { |
||||||
|
return when (this) { |
||||||
|
is Bankrolls -> comparison<Bankroll>() |
||||||
|
is Games -> comparison<Game>() |
||||||
|
is TournamentFeatures -> comparison<TournamentFeature>() |
||||||
|
is TournamentNames -> comparison<TournamentName>() |
||||||
|
is Locations -> comparison<Location>() |
||||||
|
is SimpleCriteria -> comparison() |
||||||
|
is Limits -> comparison<QueryCondition.AnyLimit, Int>() |
||||||
|
is TournamentTypes -> comparison<QueryCondition.AnyTournamentType, Int>() |
||||||
|
is TableSizes -> comparison<QueryCondition.AnyTableSize, Int>() |
||||||
|
is TournamentFees -> comparison<QueryCondition.TournamentFee, Double >() |
||||||
|
is Years -> { |
||||||
|
val years = arrayListOf<QueryCondition.AnyYear>() |
||||||
|
val calendar = Calendar.getInstance() |
||||||
|
calendar.time = Date() |
||||||
|
val yearNow = calendar.get(Calendar.YEAR) |
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
realm.where<Session>().sort("year", Sort.ASCENDING).findFirst()?.year?.let { |
||||||
|
for (index in 0..(yearNow - it)) { |
||||||
|
years.add(QueryCondition.AnyYear().apply { |
||||||
|
listOfValues = arrayListOf(yearNow - index) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
realm.close() |
||||||
|
years.sorted() |
||||||
|
} |
||||||
|
is Blinds -> comparison<QueryCondition.AnyBlind, String >() |
||||||
|
else -> throw PokerAnalyticsException.QueryTypeUnhandled |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
inline fun < reified S : QueryCondition.QueryDataCondition<NameManageable >, reified T : NameManageable > compare(): List<S> { |
||||||
|
val objects = arrayListOf<S>() |
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
realm.where<T>().findAll().forEach { |
||||||
|
objects.add((QueryCondition.getInstance<T>() as S).apply { |
||||||
|
setObject(it) |
||||||
|
}) |
||||||
|
} |
||||||
|
realm.close() |
||||||
|
return objects |
||||||
|
} |
||||||
|
|
||||||
|
inline fun < reified S : QueryCondition.ListOfValues<T>, T:Any > compareList(values:List<T>): List<S> { |
||||||
|
val objects = arrayListOf<S>() |
||||||
|
values.forEach { |
||||||
|
objects.add((S::class.java.newInstance()).apply { |
||||||
|
listOfValues = arrayListOf(it) |
||||||
|
}) |
||||||
|
} |
||||||
|
return objects |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -1,21 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
import net.pokeranalytics.android.calculus.ComputedStat |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
|
|
||||||
|
|
||||||
class StatRepresentable(stat: Stat, computedStat: ComputedStat?, groupName: String = "") : RowRepresentable { |
|
||||||
|
|
||||||
var stat: Stat = stat |
|
||||||
var computedStat: ComputedStat? = computedStat |
|
||||||
var groupName: String = groupName |
|
||||||
|
|
||||||
override val viewType: Int |
|
||||||
get() = RowViewType.STAT.ordinal |
|
||||||
|
|
||||||
override val resId: Int? |
|
||||||
get() = this.stat.resId |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,336 +1,605 @@ |
|||||||
package net.pokeranalytics.android.model.filter |
package net.pokeranalytics.android.model.filter |
||||||
|
|
||||||
import io.realm.RealmList |
import io.realm.Realm |
||||||
import io.realm.RealmQuery |
import io.realm.RealmQuery |
||||||
|
import io.realm.RealmResults |
||||||
|
import io.realm.Sort |
||||||
|
import io.realm.kotlin.where |
||||||
|
import net.pokeranalytics.android.R |
||||||
import net.pokeranalytics.android.exceptions.PokerAnalyticsException |
import net.pokeranalytics.android.exceptions.PokerAnalyticsException |
||||||
import net.pokeranalytics.android.model.realm.FilterCondition |
import net.pokeranalytics.android.model.Limit |
||||||
import net.pokeranalytics.android.model.realm.FilterElementBlind |
import net.pokeranalytics.android.model.TableSize |
||||||
import net.pokeranalytics.android.model.realm.Session |
import net.pokeranalytics.android.model.TournamentType |
||||||
|
import net.pokeranalytics.android.model.interfaces.Identifiable |
||||||
|
import net.pokeranalytics.android.model.interfaces.NameManageable |
||||||
|
import net.pokeranalytics.android.model.realm.* |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType |
||||||
|
import net.pokeranalytics.android.ui.view.RowViewType |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow |
||||||
|
import net.pokeranalytics.android.util.NULL_TEXT |
||||||
|
import net.pokeranalytics.android.util.UserDefaults |
||||||
import net.pokeranalytics.android.util.extensions.endOfDay |
import net.pokeranalytics.android.util.extensions.endOfDay |
||||||
import net.pokeranalytics.android.util.extensions.startOfDay |
import net.pokeranalytics.android.util.extensions.startOfDay |
||||||
|
import net.pokeranalytics.android.util.extensions.toCurrency |
||||||
|
import java.text.DateFormatSymbols |
||||||
import java.util.* |
import java.util.* |
||||||
|
import kotlin.collections.ArrayList |
||||||
|
|
||||||
|
fun List<QueryCondition>.name() : String { |
||||||
|
return this.map { it.getDisplayName() }.joinToString(" : ") |
||||||
|
} |
||||||
|
|
||||||
|
//inline fun <reified T : Filterable> List<QueryCondition>.query(realm: Realm): RealmQuery<T> { |
||||||
|
// return this.queryWith(realm.where()) |
||||||
|
//} |
||||||
|
|
||||||
|
inline fun <reified T : Filterable> List<QueryCondition>.queryWith(query: RealmQuery<T>): RealmQuery<T> { |
||||||
|
var realmQuery = query |
||||||
|
this.forEach { |
||||||
|
realmQuery = it.queryWith(realmQuery) |
||||||
|
} |
||||||
|
return realmQuery |
||||||
|
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Enum describing the way a query should be handled |
* Enum describing the way a query should be handled |
||||||
* Some queries requires a value to be checked upon through equals, in, more, less, between |
* Some queries requires a value to be checked upon through equals, in, more, less, between |
||||||
* To handle that, the enum has a public [valueMap] variable |
|
||||||
* A new type should also set the expected numericValues required in the [filterValuesExpectedKeys] |
|
||||||
*/ |
*/ |
||||||
enum class QueryCondition(var operator: Operator? = null) { |
|
||||||
LIVE, |
sealed class QueryCondition : FilterElementRow { |
||||||
CASH, |
companion object { |
||||||
ONLINE, |
inline fun < reified T:QueryCondition> more():T { return T::class.java.newInstance().apply { this.operator = Operator.MORE } } |
||||||
TOURNAMENT, |
inline fun < reified T:QueryCondition> less():T { return T::class.java.newInstance().apply { this.operator = Operator.LESS } } |
||||||
BANKROLL, |
inline fun < reified T:QueryCondition> moreOrLess():ArrayList<T> { return arrayListOf(more(), less()) } |
||||||
GAME, |
|
||||||
TOURNAMENT_NAME, |
fun <T:QueryCondition> valueOf(name:String) : T { |
||||||
ANY_TOURNAMENT_FEATURES, |
val kClass = Class.forName("${QueryCondition::class.qualifiedName}$$name").kotlin |
||||||
ALL_TOURNAMENT_FEATURES, |
val instance = kClass.objectInstance ?: kClass.java.newInstance() |
||||||
LOCATION, |
return instance as T |
||||||
LIMIT, |
} |
||||||
TABLE_SIZE, |
|
||||||
TOURNAMENT_TYPE, |
inline fun <reified T:Identifiable>getInstance(): QueryCondition { |
||||||
BLINDS, |
return when (T::class.java) { |
||||||
LAST_GAMES, |
Bankroll::class.java -> AnyBankroll() |
||||||
LAST_SESSIONS, |
Game::class.java -> AnyGame() |
||||||
MORE_NUMBER_OF_TABLE(Operator.MORE), |
Location::class.java -> AnyLocation() |
||||||
LESS_NUMBER_OF_TABLE(Operator.LESS), |
TournamentName::class.java -> AnyTournamentName() |
||||||
BETWEEN_NUMBER_OF_TABLE(Operator.BETWEEN), |
TournamentFeature::class.java -> AnyTournamentFeature() |
||||||
MORE_THAN_NET_RESULT(Operator.MORE), |
else -> throw PokerAnalyticsException.QueryTypeUnhandled |
||||||
LESS_THAN_NET_RESULT(Operator.LESS), |
} |
||||||
MORE_THAN_BUY_IN(Operator.MORE), |
} |
||||||
LESS_THAN_BUY_IN(Operator.LESS), |
|
||||||
MORE_THAN_CASH_OUT(Operator.MORE), |
inline fun < reified T: Filterable, reified S: QueryCondition, reified U:Comparable<U>>distinct(): RealmResults<T>? { |
||||||
LESS_THAN_CASH_OUT(Operator.LESS), |
FilterHelper.fieldNameForQueryType<T>(S::class.java)?.let { |
||||||
MORE_THAN_TIPS(Operator.MORE), |
val realm = Realm.getDefaultInstance() |
||||||
LESS_THAN_TIPS(Operator.LESS), |
|
||||||
MORE_THAN_NUMBER_OF_PLAYER(Operator.MORE), |
val distincts = when (T::class) { |
||||||
LESS_THAN_NUMBER_OF_PLAYER(Operator.LESS), |
String::class, Int::class -> realm.where<T>().distinct(it).findAll().sort(it, Sort.ASCENDING) |
||||||
BETWEEN_NUMBER_OF_PLAYER(Operator.BETWEEN), |
else -> realm.where<T>().isNotNull(it).findAll().sort(it, Sort.ASCENDING) |
||||||
MORE_THAN_TOURNAMENT_FEE(Operator.MORE), |
} |
||||||
LESS_THAN_TOURNAMENT_FEE(Operator.LESS), |
|
||||||
BETWEEN_TOURNAMENT_FEE(Operator.BETWEEN), |
realm.close() |
||||||
MIN_RE_BUY(Operator.MORE), |
return distincts |
||||||
MAX_RE_BUY(Operator.LESS), |
} |
||||||
|
return null |
||||||
// Dates |
} |
||||||
STARTED_FROM_DATE, |
} |
||||||
STARTED_TO_DATE, |
|
||||||
ENDED_FROM_DATE, |
|
||||||
ENDED_TO_DATE, |
|
||||||
DAY_OF_WEEK, |
|
||||||
MONTH, |
|
||||||
YEAR, |
|
||||||
WEEK_DAY, |
|
||||||
WEEK_END, |
|
||||||
TODAY, |
|
||||||
YESTERDAY, |
|
||||||
TODAY_AND_YESTERDAY, |
|
||||||
THIS_WEEK, |
|
||||||
THIS_MONTH, |
|
||||||
THIS_YEAR, |
|
||||||
PAST_DAYS, |
|
||||||
MORE_THAN_DURATION(Operator.MORE), |
|
||||||
LESS_THAN_DURATION(Operator.LESS), |
|
||||||
|
|
||||||
CURRENCY, |
|
||||||
CURRENCY_CODE, |
|
||||||
BIG_BLIND, |
|
||||||
SMALL_BLIND, |
|
||||||
COMMENT, |
|
||||||
|
|
||||||
; |
|
||||||
|
|
||||||
enum class Operator { |
enum class Operator { |
||||||
BETWEEN, |
ANY, |
||||||
|
ALL, |
||||||
MORE, |
MORE, |
||||||
LESS; |
LESS, |
||||||
|
EQUALS, |
||||||
|
BETWEEN, |
||||||
|
BETWEEN_RIGHT_EXCLUSIVE, |
||||||
|
BETWEEN_LEFT_EXCLUSIVE, |
||||||
|
; |
||||||
} |
} |
||||||
|
|
||||||
var valueMap : Map<String, Any?>? = null |
val baseId = this::class.simpleName ?: throw PokerAnalyticsException.FilterElementUnknownName |
||||||
get() { |
|
||||||
this.filterValuesExpectedKeys?.let { valueMapExceptedKeys -> |
val id: List<String> get() { |
||||||
field?.let { map -> |
when (this.operator) { |
||||||
val missingKeys = map.keys.filter { !valueMapExceptedKeys.contains(it) } |
Operator.MORE, Operator.LESS -> return listOf("$baseId+${this.operator.name}") |
||||||
if (map.keys.size == valueMapExceptedKeys.size && missingKeys.isNotEmpty()) { |
|
||||||
throw PokerAnalyticsException.QueryValueMapMissingKeys(missingKeys) |
|
||||||
} |
|
||||||
} ?: run { |
|
||||||
throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
} |
|
||||||
} |
|
||||||
return field |
|
||||||
} |
} |
||||||
private set |
|
||||||
|
|
||||||
private val filterValuesExpectedKeys : Array<String>? |
return when (this) { |
||||||
get() { |
is SingleValue<*> -> listOf(baseId) |
||||||
this.operator?.let { |
is ListOfValues<*> -> { |
||||||
return when (it) { |
if (listOfValues.isEmpty()) { return listOf(baseId) } |
||||||
Operator.BETWEEN -> arrayOf("leftValue", "rightValue") |
this.listOfValues.map{ "$baseId+$it" } |
||||||
else -> arrayOf("value") |
|
||||||
} |
|
||||||
} |
} |
||||||
return when (this) { |
else -> listOf(baseId) |
||||||
BANKROLL, GAME, LOCATION, ANY_TOURNAMENT_FEATURES, ALL_TOURNAMENT_FEATURES, TOURNAMENT_NAME -> arrayOf("ids") |
} |
||||||
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> arrayOf("values") |
} |
||||||
BLINDS -> arrayOf("blinds") |
|
||||||
STARTED_FROM_DATE, STARTED_TO_DATE, ENDED_FROM_DATE, ENDED_TO_DATE -> arrayOf("date") |
open var operator: Operator = Operator.ANY |
||||||
DAY_OF_WEEK -> arrayOf("dayOfWeek") |
|
||||||
MONTH -> arrayOf("month") |
abstract class ListOfValues<T>: QueryCondition(), Comparable<ListOfValues<T>> where T:Comparable<T> { |
||||||
YEAR -> arrayOf("year") |
abstract var listOfValues: ArrayList<T> |
||||||
else -> null |
abstract fun labelForValue(value:T): String |
||||||
|
|
||||||
|
override fun getDisplayName(): String { |
||||||
|
return when (listOfValues.size) { |
||||||
|
0 -> return NULL_TEXT |
||||||
|
1,2 -> listOfValues.map { labelForValue(it) }.joinToString(", ") |
||||||
|
else -> "${listOfValues.size} $baseId" |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
/** |
override fun compareTo(other: ListOfValues<T>): Int { |
||||||
* main method of the enum |
return listOfValues.sorted().first().compareTo(other.listOfValues.sorted().first()) |
||||||
* providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery] |
} |
||||||
*/ |
} |
||||||
inline fun <reified T : Filterable> filter(realmQuery: RealmQuery<T>): RealmQuery<T> { |
|
||||||
when { |
|
||||||
this == BLINDS -> { |
|
||||||
|
|
||||||
val smallBlindFieldName = FilterHelper.fieldNameForQueryType<T>(SMALL_BLIND) |
|
||||||
val bigBlindFieldName = FilterHelper.fieldNameForQueryType<T>(BIG_BLIND) |
|
||||||
val currencyCodeFieldName = FilterHelper.fieldNameForQueryType<T>(CURRENCY_CODE) |
|
||||||
smallBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown |
|
||||||
bigBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown |
|
||||||
currencyCodeFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown |
|
||||||
|
|
||||||
val blinds: RealmList<FilterElementBlind> by valueMap |
|
||||||
blinds.forEachIndexed { index, blind -> |
|
||||||
realmQuery |
|
||||||
.beginGroup() |
|
||||||
|
|
||||||
blind.sb?.let { |
|
||||||
realmQuery |
|
||||||
.equalTo(smallBlindFieldName, it) |
|
||||||
.and() |
|
||||||
} |
|
||||||
|
|
||||||
realmQuery |
abstract class SingleValue<T>: ListOfValues<T>() where T:Comparable<T> { |
||||||
.equalTo(bigBlindFieldName, blind.bb) |
override var listOfValues = ArrayList<T>() |
||||||
.and() |
abstract var singleValue : T |
||||||
|
} |
||||||
|
|
||||||
blind.currencyCode?.let { |
abstract class ListOfDouble: ListOfValues<Double>() { |
||||||
realmQuery.equalTo(currencyCodeFieldName, it) |
open var sign: Int = 1 |
||||||
} ?: run { |
|
||||||
realmQuery.isNull(currencyCodeFieldName) |
|
||||||
} |
|
||||||
|
|
||||||
realmQuery.endGroup() |
override var listOfValues = arrayListOf(0.0) |
||||||
|
override fun updateValueMap(filterCondition: FilterCondition) { |
||||||
|
super.updateValueMap(filterCondition) |
||||||
|
listOfValues = filterCondition.getValues() |
||||||
|
} |
||||||
|
override fun labelForValue(value: Double): String { |
||||||
|
return value.toCurrency(UserDefaults.currency) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
if (index < blinds.size - 1) { |
abstract class ListOfInt: ListOfValues<Int>() { |
||||||
realmQuery.or() |
override var listOfValues = arrayListOf(0) |
||||||
} |
override fun updateValueMap(filterCondition: FilterCondition) { |
||||||
} |
super.updateValueMap(filterCondition) |
||||||
return realmQuery |
println("<<<< updateValueMap ${filterCondition.intValues}") |
||||||
} |
listOfValues = filterCondition.getValues() |
||||||
else -> { |
} |
||||||
|
override fun labelForValue(value: Int): String { |
||||||
|
return value.toString() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
abstract class ListOfString: ListOfValues<String>() { |
||||||
|
override var listOfValues = ArrayList<String>() |
||||||
|
override fun labelForValue(value: String): String { return value } |
||||||
|
override fun updateValueMap(filterCondition: FilterCondition) { |
||||||
|
super.updateValueMap(filterCondition) |
||||||
|
listOfValues = filterCondition.getValues() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
val fieldName = FilterHelper.fieldNameForQueryType<T>(this) |
abstract class SingleDate: SingleValue<Date>() { |
||||||
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown |
override fun labelForValue(value: Date): String { |
||||||
|
return value.toString() |
||||||
|
} |
||||||
|
|
||||||
when (operator) { |
override var singleValue: Date |
||||||
Operator.LESS -> { |
get() { return listOfValues.firstOrNull() ?: Date() } |
||||||
val value: Double by valueMap |
set(value) { listOfValues.add(value) } |
||||||
return realmQuery.lessThanOrEqualTo(fieldName, value) |
|
||||||
} |
|
||||||
Operator.MORE -> { |
|
||||||
val value: Double by valueMap |
|
||||||
return realmQuery.greaterThanOrEqualTo(fieldName, value) |
|
||||||
} |
|
||||||
Operator.BETWEEN -> { |
|
||||||
val leftValue: Double by valueMap |
|
||||||
val rightValue: Double by valueMap |
|
||||||
return realmQuery.between(fieldName, leftValue, rightValue) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return when (this) { |
override fun updateValueMap(filterCondition: FilterCondition) { |
||||||
LIVE, ONLINE -> realmQuery.equalTo(fieldName, this == LIVE) |
super.updateValueMap(filterCondition) |
||||||
CASH -> realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal) |
singleValue = filterCondition.getValue() |
||||||
TOURNAMENT -> realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal) |
} |
||||||
ALL_TOURNAMENT_FEATURES -> { |
} |
||||||
val ids: Array<String> by valueMap |
|
||||||
ids.forEach { |
abstract class SingleInt: SingleValue<Int>() { |
||||||
realmQuery.equalTo(fieldName, it) |
override fun labelForValue(value: Int): String { |
||||||
} |
return value.toString() |
||||||
realmQuery |
} |
||||||
} |
override var singleValue: Int |
||||||
ANY_TOURNAMENT_FEATURES -> { |
get() { return listOfValues.firstOrNull() ?: 0 } |
||||||
val ids: Array<String> by valueMap |
set(value) { listOfValues.add(value) } |
||||||
realmQuery.`in`(fieldName, ids) |
|
||||||
} |
override fun updateValueMap(filterCondition: FilterCondition) { |
||||||
BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> { |
super.updateValueMap(filterCondition) |
||||||
val ids: Array<String> by valueMap |
singleValue = filterCondition.getValue() |
||||||
realmQuery.`in`(fieldName, ids) |
} |
||||||
} |
} |
||||||
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> { |
|
||||||
val values: Array<Int?>? by valueMap |
override fun getDisplayName(): String { return baseId } |
||||||
realmQuery.`in`(fieldName, values) |
|
||||||
} |
override var filterSectionRow: FilterSectionRow = FilterSectionRow.CASH_TOURNAMENT |
||||||
STARTED_FROM_DATE -> { |
|
||||||
val date: Date by valueMap |
abstract class QueryDataCondition < T: NameManageable > : ListOfString() { |
||||||
realmQuery.greaterThanOrEqualTo(fieldName, date) |
fun setObject(dataObject: T) { |
||||||
} |
this.listOfValues.removeAll(this.listOfValues) |
||||||
STARTED_TO_DATE -> { |
this.listOfValues.add(dataObject.id) |
||||||
val date: Date by valueMap |
} |
||||||
realmQuery.lessThanOrEqualTo(fieldName, date) |
|
||||||
} |
abstract val entity : Class<T> |
||||||
ENDED_FROM_DATE -> { |
|
||||||
val date: Date by valueMap |
override fun getDisplayName(): String { |
||||||
realmQuery.greaterThanOrEqualTo(fieldName, date) |
val realm = Realm.getDefaultInstance() |
||||||
} |
val completeLabel = when (listOfValues.size) { |
||||||
ENDED_TO_DATE -> { |
0 -> return NULL_TEXT |
||||||
val date: Date by valueMap |
1,2 -> { |
||||||
realmQuery.lessThanOrEqualTo(fieldName, date) |
return listOfValues.map { labelForValue(realm, it) }.joinToString(", ") |
||||||
} |
|
||||||
DAY_OF_WEEK -> { |
|
||||||
val dayOfWeek: Int by valueMap |
|
||||||
realmQuery.equalTo(fieldName, dayOfWeek) |
|
||||||
} |
|
||||||
MONTH -> { |
|
||||||
val month: Int by valueMap |
|
||||||
realmQuery.equalTo(fieldName, month) |
|
||||||
} |
|
||||||
YEAR -> { |
|
||||||
val year: Int by valueMap |
|
||||||
realmQuery.equalTo(fieldName, year) |
|
||||||
} |
|
||||||
WEEK_END, WEEK_DAY -> { |
|
||||||
var query = realmQuery |
|
||||||
if (this == WEEK_DAY) { |
|
||||||
query = realmQuery.not() |
|
||||||
} |
|
||||||
query.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY)) |
|
||||||
} |
|
||||||
TODAY -> { |
|
||||||
val startDate = Date() |
|
||||||
realmQuery.between(fieldName, startDate.startOfDay(), startDate.endOfDay()) |
|
||||||
} |
|
||||||
TODAY_AND_YESTERDAY-> { |
|
||||||
val startDate = Date() |
|
||||||
val calendar = Calendar.getInstance() |
|
||||||
calendar.time = startDate |
|
||||||
calendar.add(Calendar.HOUR_OF_DAY, -24) |
|
||||||
realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
|
||||||
} |
|
||||||
YESTERDAY -> { |
|
||||||
val calendar = Calendar.getInstance() |
|
||||||
calendar.time = Date() |
|
||||||
calendar.add(Calendar.HOUR_OF_DAY, -24) |
|
||||||
realmQuery.between(fieldName, calendar.time.startOfDay(), calendar.time.endOfDay()) |
|
||||||
} |
|
||||||
THIS_WEEK -> { |
|
||||||
val startDate = Date() |
|
||||||
val calendar = Calendar.getInstance() |
|
||||||
calendar.time = startDate |
|
||||||
calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY) |
|
||||||
realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
|
||||||
} |
|
||||||
THIS_MONTH -> { |
|
||||||
val startDate = Date() |
|
||||||
val calendar = Calendar.getInstance() |
|
||||||
calendar.time = startDate |
|
||||||
calendar.set(Calendar.DAY_OF_MONTH, 1) |
|
||||||
realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
|
||||||
} |
|
||||||
THIS_YEAR -> { |
|
||||||
val startDate = Date() |
|
||||||
val calendar = Calendar.getInstance() |
|
||||||
calendar.time = startDate |
|
||||||
calendar.set(Calendar.DAY_OF_YEAR, 1) |
|
||||||
realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
|
||||||
} |
|
||||||
else -> { |
|
||||||
throw PokerAnalyticsException.QueryTypeUnhandled |
|
||||||
} |
|
||||||
} |
} |
||||||
|
else -> "${listOfValues.size} $baseId" |
||||||
} |
} |
||||||
|
realm.close() |
||||||
|
return completeLabel |
||||||
|
} |
||||||
|
|
||||||
|
private fun labelForValue(realm:Realm, value:String): String { |
||||||
|
val query = realm.where(entity) |
||||||
|
return query.equalTo("id", value).findFirst()?.name ?: NULL_TEXT |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
interface DateTime { |
||||||
|
val showTime: Boolean |
||||||
|
} |
||||||
|
|
||||||
|
abstract class DateQuery: SingleDate(), DateTime { |
||||||
|
override val showTime: Boolean = false |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
abstract class TimeQuery: DateQuery() { |
||||||
|
override val showTime: Boolean = true |
||||||
|
} |
||||||
|
|
||||||
|
object IsLive : QueryCondition() { |
||||||
|
override fun getDisplayName(): String { return "Live" } |
||||||
|
} |
||||||
|
|
||||||
|
object IsCash : QueryCondition() { |
||||||
|
override fun getDisplayName(): String { return "Cash" } |
||||||
|
} |
||||||
|
|
||||||
|
object IsOnline : QueryCondition() { |
||||||
|
override fun getDisplayName(): String { return "Online" } |
||||||
|
} |
||||||
|
|
||||||
|
object IsTournament : QueryCondition() { |
||||||
|
override fun getDisplayName(): String { return "Tournament" } |
||||||
|
} |
||||||
|
|
||||||
|
class AnyBankroll(): QueryDataCondition<Bankroll>() { |
||||||
|
override var entity: Class<Bankroll> = Bankroll::class.java |
||||||
|
constructor(bankroll: Bankroll): this() { |
||||||
|
this.setObject(bankroll) |
||||||
|
} |
||||||
|
} |
||||||
|
class AnyGame(): QueryDataCondition<Game>() { |
||||||
|
override val entity: Class<Game> = Game::class.java |
||||||
|
constructor(game: Game): this() { |
||||||
|
this.setObject(game) |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
|
class AnyTournamentName(): QueryDataCondition<TournamentName>() { |
||||||
|
override val entity: Class<TournamentName> = TournamentName::class.java |
||||||
|
constructor(tournamentName: TournamentName): this() { |
||||||
|
this.setObject(tournamentName) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AnyTournamentFeature(): QueryDataCondition<TournamentFeature>() { |
||||||
|
override val entity: Class<TournamentFeature> = TournamentFeature::class.java |
||||||
|
constructor(tournamentFeature: TournamentFeature): this() { |
||||||
|
this.setObject(tournamentFeature) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AllTournamentFeature(): QueryDataCondition<TournamentFeature>() { |
||||||
|
override var operator = Operator.ALL |
||||||
|
override val entity: Class<TournamentFeature> = TournamentFeature::class.java |
||||||
|
constructor(tournamentFeature: TournamentFeature): this() { |
||||||
|
this.setObject(tournamentFeature) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AnyLocation(): QueryDataCondition<Location>() { |
||||||
|
override val entity: Class<Location> = Location::class.java |
||||||
|
constructor(location: Location): this() { |
||||||
|
this.setObject(location) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AnyLimit: ListOfInt() { |
||||||
|
override fun labelForValue(value: Int): String { |
||||||
|
return Limit.values()[value].getDisplayName() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AnyTableSize: ListOfInt() { |
||||||
|
override fun labelForValue(value: Int): String { |
||||||
|
return TableSize(value).getDisplayName() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AnyTournamentType: ListOfInt() { |
||||||
|
override fun labelForValue(value: Int): String { |
||||||
|
return TournamentType.values()[value].getDisplayName() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AnyBlind: ListOfString() |
||||||
|
|
||||||
|
class LastGame: SingleInt() |
||||||
|
class LastSession: SingleInt() |
||||||
|
|
||||||
|
class NumberOfTable: ListOfInt() |
||||||
|
|
||||||
|
open class NetAmountWon: ListOfDouble() |
||||||
|
class NetAmountLost: NetAmountWon() { override var sign: Int = -1 } |
||||||
|
|
||||||
|
class NumberOfPlayer: ListOfInt() |
||||||
|
|
||||||
|
class StartedFromDate: DateQuery() { override var operator = Operator.MORE } |
||||||
|
class StartedToDate: DateQuery() { override var operator = Operator.LESS } |
||||||
|
class EndedFromDate: DateQuery() { override var operator = Operator.MORE } |
||||||
|
class EndedToDate: DateQuery() { override var operator = Operator.LESS } |
||||||
|
|
||||||
|
class AnyDayOfWeek: ListOfInt() { |
||||||
|
override fun labelForValue(value: Int): String { |
||||||
|
return DateFormatSymbols.getInstance(Locale.getDefault()).weekdays[value] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AnyMonthOfYear: ListOfInt() { |
||||||
|
override fun labelForValue(value: Int): String { |
||||||
|
return DateFormatSymbols.getInstance(Locale.getDefault()).months[value] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class AnyYear: ListOfInt() { |
||||||
|
override fun labelForValue(value: Int): String { |
||||||
|
return "$value" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
object IsWeekDay: QueryCondition() |
||||||
|
object IsWeekEnd: QueryCondition() |
||||||
|
object IsToday: QueryCondition() |
||||||
|
object WasYesterday: QueryCondition() |
||||||
|
object WasTodayAndYesterday: QueryCondition() |
||||||
|
object DuringThisWeek: QueryCondition() |
||||||
|
object DuringThisMonth: QueryCondition() |
||||||
|
object DuringThisYear: QueryCondition() |
||||||
|
|
||||||
|
class TournamentFee: ListOfDouble() { |
||||||
|
override fun labelForValue(value: Double): String { |
||||||
|
return value.toCurrency(UserDefaults.currency) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class PastDay: SingleInt() { |
||||||
|
override val viewType: Int = RowViewType.TITLE_VALUE_CHECK.ordinal |
||||||
|
} |
||||||
|
|
||||||
|
class Duration: SingleInt() { |
||||||
|
var minutes:Int |
||||||
|
get() { return singleValue } |
||||||
|
set(value) { singleValue = value } |
||||||
|
|
||||||
|
override val viewType: Int = RowViewType.TITLE_VALUE_CHECK.ordinal |
||||||
|
override val bottomSheetType: BottomSheetType = BottomSheetType.DOUBLE_EDIT_TEXT |
||||||
} |
} |
||||||
|
|
||||||
fun updateValueMap(filterCondition: FilterCondition) { |
class StartedFromTime: TimeQuery() { |
||||||
if (filterValuesExpectedKeys == null) { |
override var operator = Operator.MORE |
||||||
return |
init { |
||||||
|
this.singleValue = Date().startOfDay() |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
this.operator?.let { |
class EndedToTime: TimeQuery() { |
||||||
valueMap = mapOf("value" to filterCondition.value) |
override var operator = Operator.LESS |
||||||
return |
init { |
||||||
|
this.singleValue = Date().endOfDay() |
||||||
} |
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* main method of the enum |
||||||
|
* providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery] |
||||||
|
*/ |
||||||
|
inline fun <reified T : Filterable> queryWith(realmQuery: RealmQuery<T>): RealmQuery<T> { |
||||||
|
val fieldName = FilterHelper.fieldNameForQueryType<T>(this::class.java) |
||||||
|
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown |
||||||
|
|
||||||
when (this) { |
when (this) { |
||||||
ALL_TOURNAMENT_FEATURES, ANY_TOURNAMENT_FEATURES, BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> { |
//is Between -> realmQuery.between(fieldName, leftValue, rightValue) |
||||||
valueMap = mapOf("ids" to filterCondition.ids) |
//is BetweenLeftExclusive -> realmQuery.greaterThan(fieldName, leftValue).and().lessThanOrEqualTo(fieldName, rightValue) |
||||||
|
//is BetweenRightExclusive -> realmQuery.greaterThanOrEqualTo(fieldName, leftValue).and().lessThan(fieldName, rightValue) |
||||||
|
IsLive, IsOnline -> return realmQuery.equalTo(fieldName, this == IsLive) |
||||||
|
IsCash -> return realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal) |
||||||
|
IsTournament -> return realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal) |
||||||
|
IsWeekEnd, IsWeekDay -> { |
||||||
|
var query = realmQuery |
||||||
|
if (this == IsWeekDay) { |
||||||
|
query = realmQuery.not() |
||||||
|
} |
||||||
|
return query.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY)) |
||||||
} |
} |
||||||
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> { |
IsToday -> { |
||||||
valueMap = mapOf("values" to filterCondition.values) |
val startDate = Date() |
||||||
|
return realmQuery.between(fieldName, startDate.startOfDay(), startDate.endOfDay()) |
||||||
} |
} |
||||||
BLINDS -> { |
WasTodayAndYesterday-> { |
||||||
valueMap = mapOf("blinds" to filterCondition.blinds) |
val startDate = Date() |
||||||
|
val calendar = Calendar.getInstance() |
||||||
|
calendar.time = startDate |
||||||
|
calendar.add(Calendar.HOUR_OF_DAY, -24) |
||||||
|
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
||||||
} |
} |
||||||
STARTED_FROM_DATE, STARTED_TO_DATE, ENDED_FROM_DATE, ENDED_TO_DATE -> { |
WasYesterday -> { |
||||||
valueMap = mapOf("date" to filterCondition.date) |
val calendar = Calendar.getInstance() |
||||||
|
calendar.time = Date() |
||||||
|
calendar.add(Calendar.HOUR_OF_DAY, -24) |
||||||
|
return realmQuery.between(fieldName, calendar.time.startOfDay(), calendar.time.endOfDay()) |
||||||
} |
} |
||||||
DAY_OF_WEEK -> { |
DuringThisWeek -> { |
||||||
valueMap = mapOf("dayOfWeek" to filterCondition.dayOfWeek) |
val startDate = Date() |
||||||
|
val calendar = Calendar.getInstance() |
||||||
|
calendar.time = startDate |
||||||
|
calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY) |
||||||
|
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
||||||
} |
} |
||||||
MONTH -> { |
DuringThisMonth -> { |
||||||
valueMap = mapOf("month" to filterCondition.month) |
val startDate = Date() |
||||||
|
val calendar = Calendar.getInstance() |
||||||
|
calendar.time = startDate |
||||||
|
calendar.set(Calendar.DAY_OF_MONTH, 1) |
||||||
|
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
||||||
} |
} |
||||||
YEAR -> { |
DuringThisYear -> { |
||||||
valueMap = mapOf("year" to filterCondition.year) |
val startDate = Date() |
||||||
|
val calendar = Calendar.getInstance() |
||||||
|
calendar.time = startDate |
||||||
|
calendar.set(Calendar.DAY_OF_YEAR, 1) |
||||||
|
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
||||||
} |
} |
||||||
else -> { |
} |
||||||
throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
|
return when (operator) { |
||||||
|
Operator.EQUALS -> { |
||||||
|
when (this) { |
||||||
|
is SingleDate -> realmQuery.equalTo(fieldName, singleValue) |
||||||
|
is SingleInt -> realmQuery.equalTo(fieldName, singleValue) |
||||||
|
is ListOfInt -> realmQuery.equalTo(fieldName, listOfValues.first()) |
||||||
|
is ListOfDouble -> realmQuery.equalTo(fieldName, listOfValues.first() * sign) |
||||||
|
is ListOfString -> realmQuery.equalTo(fieldName, listOfValues.first()) |
||||||
|
else -> realmQuery |
||||||
|
} |
||||||
|
} |
||||||
|
Operator.MORE -> { |
||||||
|
when (this) { |
||||||
|
is SingleDate -> realmQuery.greaterThanOrEqualTo(fieldName, singleValue) |
||||||
|
is SingleInt -> realmQuery.greaterThanOrEqualTo(fieldName, singleValue) |
||||||
|
is ListOfInt -> realmQuery.greaterThanOrEqualTo(fieldName, listOfValues.first()) |
||||||
|
is ListOfDouble -> realmQuery.greaterThanOrEqualTo(fieldName, listOfValues.first() * sign) |
||||||
|
else -> realmQuery |
||||||
|
} |
||||||
} |
} |
||||||
|
Operator.LESS -> { |
||||||
|
when (this) { |
||||||
|
is SingleDate -> realmQuery.lessThanOrEqualTo(fieldName, singleValue) |
||||||
|
is SingleInt -> realmQuery.lessThanOrEqualTo(fieldName, singleValue) |
||||||
|
is ListOfInt -> realmQuery.lessThanOrEqualTo(fieldName, listOfValues.first()) |
||||||
|
is ListOfDouble -> realmQuery.lessThanOrEqualTo(fieldName, listOfValues.first() * sign) |
||||||
|
else -> realmQuery |
||||||
|
} |
||||||
|
} |
||||||
|
Operator.ALL -> { |
||||||
|
when (this) { |
||||||
|
is ListOfInt -> { |
||||||
|
listOfValues.forEach { realmQuery.equalTo(fieldName, it) } |
||||||
|
realmQuery |
||||||
|
} |
||||||
|
is ListOfDouble -> { |
||||||
|
listOfValues.forEach { realmQuery.equalTo(fieldName, it * sign) } |
||||||
|
realmQuery |
||||||
|
} |
||||||
|
is ListOfString -> { |
||||||
|
listOfValues.forEach { realmQuery.equalTo(fieldName, it) } |
||||||
|
realmQuery |
||||||
|
} |
||||||
|
else -> realmQuery |
||||||
|
} |
||||||
|
} |
||||||
|
Operator.ANY -> { |
||||||
|
when (this) { |
||||||
|
is ListOfInt -> realmQuery.`in`(fieldName, listOfValues.toTypedArray()) |
||||||
|
is ListOfDouble -> realmQuery.`in`(fieldName, listOfValues.toTypedArray()) |
||||||
|
is ListOfString -> realmQuery.`in`(fieldName, listOfValues.toTypedArray()) |
||||||
|
else -> realmQuery |
||||||
|
} |
||||||
|
} |
||||||
|
else -> realmQuery |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
|
open fun updateValueMap(filterCondition: FilterCondition) { |
||||||
|
filterCondition.operator?.let { |
||||||
|
this.operator = Operator.values()[it] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override val viewType: Int |
||||||
|
get() { |
||||||
|
return when (this) { |
||||||
|
is PastDay -> RowViewType.TITLE_VALUE_CHECK.ordinal |
||||||
|
is LastGame -> RowViewType.TITLE_VALUE_CHECK.ordinal |
||||||
|
is LastSession -> RowViewType.TITLE_VALUE_CHECK.ordinal |
||||||
|
else -> { |
||||||
|
when (this.operator) { |
||||||
|
Operator.MORE -> RowViewType.TITLE_VALUE_CHECK.ordinal |
||||||
|
Operator.LESS -> RowViewType.TITLE_VALUE_CHECK.ordinal |
||||||
|
else -> RowViewType.TITLE_CHECK.ordinal |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override val bottomSheetType: BottomSheetType |
||||||
|
get() { |
||||||
|
return when (this) { |
||||||
|
is PastDay -> BottomSheetType.EDIT_TEXT |
||||||
|
is LastGame -> BottomSheetType.EDIT_TEXT |
||||||
|
is LastSession -> BottomSheetType.EDIT_TEXT |
||||||
|
else -> { |
||||||
|
when (this.operator) { |
||||||
|
Operator.MORE -> BottomSheetType.EDIT_TEXT |
||||||
|
Operator.LESS -> BottomSheetType.EDIT_TEXT |
||||||
|
else -> BottomSheetType.NONE |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override val resId: Int? |
||||||
|
get() { |
||||||
|
return when (this) { |
||||||
|
is IsCash -> R.string.cash_game |
||||||
|
is IsTournament -> R.string.tournament |
||||||
|
is IsToday -> R.string.today |
||||||
|
is WasYesterday -> R.string.yesterday |
||||||
|
is WasTodayAndYesterday -> R.string.yesterday_and_today |
||||||
|
is DuringThisWeek -> R.string.current_week |
||||||
|
is DuringThisMonth -> R.string.current_month |
||||||
|
is DuringThisYear -> R.string.current_year |
||||||
|
is StartedFromTime, is StartedFromDate -> R.string.from |
||||||
|
is EndedToDate, is EndedToTime-> R.string.to |
||||||
|
is IsLive -> R.string.live |
||||||
|
is IsOnline -> R.string.online |
||||||
|
is IsWeekDay -> R.string.week_days |
||||||
|
is IsWeekEnd -> R.string.weekend |
||||||
|
is PastDay -> R.string.period_in_days |
||||||
|
is LastGame -> R.string.last_records |
||||||
|
is LastSession -> R.string.last_sessions |
||||||
|
is NetAmountWon -> { |
||||||
|
when (this.operator) { |
||||||
|
Operator.MORE -> R.string.won_amount_more_than |
||||||
|
Operator.LESS -> R.string.won_amount_less_than |
||||||
|
else -> null |
||||||
|
} |
||||||
|
} |
||||||
|
is NetAmountLost -> { |
||||||
|
when (this.operator) { |
||||||
|
Operator.MORE -> R.string.lost_amount_more_than |
||||||
|
Operator.LESS -> R.string.lost_amount_less_than |
||||||
|
else -> null |
||||||
|
} |
||||||
|
} |
||||||
|
else -> { |
||||||
|
when (this.operator) { |
||||||
|
Operator.MORE -> R.string.more_than |
||||||
|
Operator.LESS -> R.string.less_than |
||||||
|
else -> null |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
} |
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
package net.pokeranalytics.android.model.interfaces |
||||||
|
|
||||||
|
import java.util.* |
||||||
|
|
||||||
|
interface Dated { |
||||||
|
|
||||||
|
var date: Date |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
interface DatedValue : Dated { |
||||||
|
|
||||||
|
var amount: Double |
||||||
|
|
||||||
|
} |
||||||
@ -1,8 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import io.realm.RealmObject |
|
||||||
|
|
||||||
open class FilterElementBlind(var sb : Double? = null, |
|
||||||
var bb : Double? = null, |
|
||||||
var currencyCode : String? = null |
|
||||||
) : RealmObject() |
|
||||||
@ -0,0 +1,33 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import androidx.fragment.app.Fragment |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
|
||||||
|
class BankrollActivity : PokerAnalyticsActivity() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun newInstance(context: Context) { |
||||||
|
val intent = Intent(context, BankrollActivity::class.java) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new instance for result |
||||||
|
*/ |
||||||
|
fun newInstanceForResult(fragment: Fragment, requestCode: Int) { |
||||||
|
val intent = Intent(fragment.requireContext(), BankrollActivity::class.java) |
||||||
|
fragment.startActivityForResult(intent, requestCode) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_bankroll) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import kotlinx.android.synthetic.main.activity_calendar_details.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.ComputedResults |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
import net.pokeranalytics.android.ui.fragment.CalendarDetailsFragment |
||||||
|
|
||||||
|
|
||||||
|
class CalendarDetailsActivity : PokerAnalyticsActivity() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
var computedResults: ComputedResults? = null |
||||||
|
var sessionTypeCondition: QueryCondition? = null |
||||||
|
var detailsTitle: String? = null |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor |
||||||
|
*/ |
||||||
|
fun newInstance(context: Context, computedResults: ComputedResults, sessionTypeCondition: QueryCondition?, title: String?) { |
||||||
|
this.computedResults = computedResults |
||||||
|
this.sessionTypeCondition = sessionTypeCondition |
||||||
|
this.detailsTitle = title |
||||||
|
val intent = Intent(context, CalendarDetailsActivity::class.java) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_calendar_details) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
val calendarDetailsFragment = calendarDetailsFragment as CalendarDetailsFragment |
||||||
|
calendarDetailsFragment.setData(computedResults, sessionTypeCondition, detailsTitle) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import androidx.fragment.app.Fragment |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
|
||||||
|
class ComparisonChartActivity : PokerAnalyticsActivity() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun newInstance(context: Context) { |
||||||
|
val intent = Intent(context, ComparisonChartActivity::class.java) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new instance for result |
||||||
|
*/ |
||||||
|
fun newInstanceForResult(fragment: Fragment, requestCode: Int) { |
||||||
|
val intent = Intent(fragment.requireContext(), ComparisonChartActivity::class.java) |
||||||
|
fragment.startActivityForResult(intent, requestCode) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_comparison_chart) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,69 +0,0 @@ |
|||||||
package net.pokeranalytics.android.ui.activity |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import android.content.Intent |
|
||||||
import android.os.Bundle |
|
||||||
import com.github.mikephil.charting.data.Entry |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
|
||||||
import net.pokeranalytics.android.ui.fragment.GraphFragment |
|
||||||
|
|
||||||
class GraphParameters(stat: Stat, entries: List<Entry>) { |
|
||||||
var stat: Stat = stat |
|
||||||
var entries: List<Entry> = entries |
|
||||||
} |
|
||||||
|
|
||||||
class GraphActivity : PokerAnalyticsActivity() { |
|
||||||
|
|
||||||
private enum class IntentKey(val keyName: String) { |
|
||||||
STAT("STAT"), |
|
||||||
ENTRIES("ENTRIES"), |
|
||||||
} |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects |
|
||||||
var parameters: GraphParameters? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Default constructor |
|
||||||
*/ |
|
||||||
fun newInstance(context: Context, stat: Stat, entries: List<Entry>) { |
|
||||||
|
|
||||||
GraphActivity.parameters = GraphParameters(stat, entries) |
|
||||||
|
|
||||||
val intent = Intent(context, GraphActivity::class.java) |
|
||||||
context.startActivity(intent) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) { |
|
||||||
super.onCreate(savedInstanceState) |
|
||||||
setContentView(R.layout.activity_editable_data) |
|
||||||
initUI() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Init UI |
|
||||||
*/ |
|
||||||
private fun initUI() { |
|
||||||
|
|
||||||
val fragmentManager = supportFragmentManager |
|
||||||
val fragmentTransaction = fragmentManager.beginTransaction() |
|
||||||
val fragment = GraphFragment() |
|
||||||
|
|
||||||
fragmentTransaction.add(R.id.container, fragment) |
|
||||||
fragmentTransaction.commit() |
|
||||||
|
|
||||||
GraphActivity.parameters?.let { |
|
||||||
fragment.setData(it.stat, it.entries) |
|
||||||
GraphActivity.parameters = null |
|
||||||
} ?: run { |
|
||||||
throw Exception("Missing graph parameters") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,55 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.Report |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
import net.pokeranalytics.android.ui.fragment.ReportDetailsFragment |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ReportDetailsActivity : PokerAnalyticsActivity() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects |
||||||
|
private var report: Report? = null |
||||||
|
private var reportTitle: String = "" |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor |
||||||
|
*/ |
||||||
|
fun newInstance(context: Context, report: Report, reportTitle: String) { |
||||||
|
//parameters = GraphParameters(stat, group, report) |
||||||
|
this.report = report |
||||||
|
this.reportTitle = reportTitle |
||||||
|
val intent = Intent(context, ReportDetailsActivity::class.java) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_report_details) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
report?.let { |
||||||
|
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||||
|
val reportDetailsFragment = ReportDetailsFragment.newInstance(it, reportTitle) |
||||||
|
fragmentTransaction.add(R.id.reportDetailsContainer, reportDetailsFragment) |
||||||
|
fragmentTransaction.commit() |
||||||
|
|
||||||
|
report = null |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import androidx.fragment.app.Fragment |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
|
||||||
|
class SettingsActivity : PokerAnalyticsActivity() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun newInstance(context: Context) { |
||||||
|
val intent = Intent(context, SettingsActivity::class.java) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new instance for result |
||||||
|
*/ |
||||||
|
fun newInstanceForResult(fragment: Fragment, requestCode: Int) { |
||||||
|
val intent = Intent(fragment.requireContext(), SettingsActivity::class.java) |
||||||
|
fragment.startActivityForResult(intent, requestCode) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_settings) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,59 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.ComputableGroup |
||||||
|
import net.pokeranalytics.android.calculus.Report |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
import net.pokeranalytics.android.ui.fragment.StatisticDetailsFragment |
||||||
|
|
||||||
|
|
||||||
|
class StatisticsDetailsParameters(var stat: Stat, var computableGroup: ComputableGroup, var report: Report) |
||||||
|
|
||||||
|
class StatisticDetailsActivity : PokerAnalyticsActivity() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects |
||||||
|
private var parameters: StatisticsDetailsParameters? = null |
||||||
|
private var displayAggregationChoices: Boolean = true |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor |
||||||
|
*/ |
||||||
|
fun newInstance(context: Context, stat: Stat, group: ComputableGroup, report: Report, displayAggregationChoices: Boolean = true) { |
||||||
|
parameters = StatisticsDetailsParameters(stat, group, report) |
||||||
|
this.displayAggregationChoices = displayAggregationChoices |
||||||
|
val intent = Intent(context, StatisticDetailsActivity::class.java) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_statistic_details) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
val fragmentTransaction = supportFragmentManager.beginTransaction() |
||||||
|
val statisticDetailsFragment = StatisticDetailsFragment() |
||||||
|
fragmentTransaction.add(R.id.statisticDetailsContainer, statisticDetailsFragment) |
||||||
|
fragmentTransaction.commit() |
||||||
|
|
||||||
|
parameters?.let { |
||||||
|
statisticDetailsFragment.setData(it.stat, it.computableGroup, it.report, displayAggregationChoices) |
||||||
|
parameters = null |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,69 @@ |
|||||||
|
package net.pokeranalytics.android.ui.adapter |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.util.SparseArray |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.fragment.app.FragmentManager |
||||||
|
import androidx.fragment.app.FragmentStatePagerAdapter |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.fragment.CalendarFragment |
||||||
|
import net.pokeranalytics.android.ui.fragment.GraphFragment |
||||||
|
import net.pokeranalytics.android.ui.fragment.HistoryFragment |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
import java.lang.ref.WeakReference |
||||||
|
|
||||||
|
/** |
||||||
|
* Comparison Chart Pager Adapter |
||||||
|
*/ |
||||||
|
class ComparisonChartPagerAdapter(val context: Context, fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) { |
||||||
|
|
||||||
|
var weakReferences = SparseArray<WeakReference<PokerAnalyticsFragment>>() |
||||||
|
|
||||||
|
override fun getItem(position: Int): PokerAnalyticsFragment { |
||||||
|
return when (position) { |
||||||
|
0 -> GraphFragment() |
||||||
|
1 -> GraphFragment() |
||||||
|
2 -> CalendarFragment.newInstance() |
||||||
|
else -> HistoryFragment.newInstance() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun getCount(): Int { |
||||||
|
return 3 |
||||||
|
} |
||||||
|
|
||||||
|
override fun getPageTitle(position: Int): CharSequence? { |
||||||
|
return when(position) { |
||||||
|
0 -> context.getString(R.string.bar) |
||||||
|
1 -> context.getString(R.string.line) |
||||||
|
2-> context.getString(R.string.table) |
||||||
|
else -> super.getPageTitle(position) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { |
||||||
|
super.destroyItem(container, position, `object`) |
||||||
|
weakReferences.remove(position) |
||||||
|
} |
||||||
|
|
||||||
|
override fun instantiateItem(container: ViewGroup, position: Int): Any { |
||||||
|
val fragment = super.instantiateItem(container, position) as PokerAnalyticsFragment |
||||||
|
weakReferences.put(position, WeakReference(fragment)) |
||||||
|
return fragment |
||||||
|
} |
||||||
|
|
||||||
|
override fun getItemPosition(obj: Any): Int { |
||||||
|
return POSITION_UNCHANGED |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the fragment at the position key |
||||||
|
*/ |
||||||
|
fun getFragment(key: Int): PokerAnalyticsFragment? { |
||||||
|
if (weakReferences.get(key) != null) { |
||||||
|
return weakReferences.get(key).get() |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,78 @@ |
|||||||
|
package net.pokeranalytics.android.ui.adapter |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.util.SparseArray |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.fragment.app.FragmentManager |
||||||
|
import androidx.fragment.app.FragmentStatePagerAdapter |
||||||
|
import androidx.viewpager.widget.PagerAdapter |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.Report |
||||||
|
import net.pokeranalytics.android.ui.fragment.GraphFragment |
||||||
|
import net.pokeranalytics.android.ui.fragment.TableReportFragment |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
import java.lang.ref.WeakReference |
||||||
|
|
||||||
|
/** |
||||||
|
* Home Adapter |
||||||
|
*/ |
||||||
|
class ReportPagerAdapter(val context: Context, val fragmentManager: FragmentManager, private val report: Report) : FragmentStatePagerAdapter(fragmentManager) { |
||||||
|
|
||||||
|
var weakReferences = SparseArray<WeakReference<PokerAnalyticsFragment>>() |
||||||
|
|
||||||
|
override fun getItem(position: Int): PokerAnalyticsFragment { |
||||||
|
return when (position) { |
||||||
|
0 -> { |
||||||
|
val dataSetList = listOf(report.barEntries(null, context)) |
||||||
|
GraphFragment.newInstance(barDataSets = dataSetList, style = GraphFragment.Style.BAR) |
||||||
|
} |
||||||
|
1 -> { |
||||||
|
val dataSetList = report.multiLineEntries(context = context) |
||||||
|
GraphFragment.newInstance(lineDataSets = dataSetList, style = GraphFragment.Style.MULTILINE) |
||||||
|
} |
||||||
|
2 -> { |
||||||
|
TableReportFragment.newInstance(report) |
||||||
|
} |
||||||
|
else -> PokerAnalyticsFragment() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun getCount(): Int { |
||||||
|
return 3 |
||||||
|
} |
||||||
|
|
||||||
|
override fun getPageTitle(position: Int): CharSequence? { |
||||||
|
return when(position) { |
||||||
|
0 -> context.getString(R.string.bar) |
||||||
|
1 -> context.getString(R.string.line) |
||||||
|
2 -> context.getString(R.string.table) |
||||||
|
else -> "" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { |
||||||
|
super.destroyItem(container, position, `object`) |
||||||
|
weakReferences.remove(position) |
||||||
|
} |
||||||
|
|
||||||
|
override fun instantiateItem(container: ViewGroup, position: Int): Any { |
||||||
|
val fragment = super.instantiateItem(container, position) as PokerAnalyticsFragment |
||||||
|
weakReferences.put(position, WeakReference(fragment)) |
||||||
|
return fragment |
||||||
|
} |
||||||
|
|
||||||
|
override fun getItemPosition(obj: Any): Int { |
||||||
|
return PagerAdapter.POSITION_UNCHANGED |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the fragment at the position key |
||||||
|
*/ |
||||||
|
fun getFragment(key: Int): PokerAnalyticsFragment? { |
||||||
|
if (weakReferences.get(key) != null) { |
||||||
|
return weakReferences.get(key).get() |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
package net.pokeranalytics.android.ui.extensions |
||||||
|
|
||||||
|
import com.google.android.material.chip.ChipGroup |
||||||
|
|
||||||
|
class ChipGroupExtension { |
||||||
|
|
||||||
|
open class SingleSelectionOnCheckedListener : ChipGroup.OnCheckedChangeListener { |
||||||
|
override fun onCheckedChanged(group: ChipGroup, checkedId: Int) { |
||||||
|
for (i in 0 until group.childCount) { |
||||||
|
val chip = group.getChildAt(i) |
||||||
|
chip.isClickable = chip.id != group.checkedChipId |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,73 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import kotlinx.android.synthetic.main.fragment_bankroll.* |
||||||
|
import kotlinx.android.synthetic.main.fragment_stats.recyclerView |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
|
||||||
|
class BankrollFragment : PokerAnalyticsFragment() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create new instance |
||||||
|
*/ |
||||||
|
fun newInstance(): BankrollFragment { |
||||||
|
val fragment = BankrollFragment() |
||||||
|
val bundle = Bundle() |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var parentActivity: PokerAnalyticsActivity |
||||||
|
|
||||||
|
// Life Cycle |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_bankroll, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initData() |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Business |
||||||
|
|
||||||
|
/** |
||||||
|
* Init data |
||||||
|
*/ |
||||||
|
private fun initData() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
parentActivity = activity as PokerAnalyticsActivity |
||||||
|
|
||||||
|
parentActivity.setSupportActionBar(toolbar) |
||||||
|
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) |
||||||
|
setHasOptionsMenu(true) |
||||||
|
|
||||||
|
val viewManager = LinearLayoutManager(requireContext()) |
||||||
|
|
||||||
|
recyclerView.apply { |
||||||
|
setHasFixedSize(true) |
||||||
|
layoutManager = viewManager |
||||||
|
//adapter = statsAdapter |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,224 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.core.view.isVisible |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import com.google.android.material.tabs.TabLayout |
||||||
|
import io.realm.Realm |
||||||
|
import kotlinx.android.synthetic.main.fragment_calendar_details.* |
||||||
|
import kotlinx.coroutines.Dispatchers |
||||||
|
import kotlinx.coroutines.GlobalScope |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.Calculator |
||||||
|
import net.pokeranalytics.android.calculus.ComputedResults |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.model.Criteria |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.ui.activity.StatisticDetailsActivity |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate |
||||||
|
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.RowViewType |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.StatDoubleRow |
||||||
|
import timber.log.Timber |
||||||
|
import java.util.* |
||||||
|
import kotlin.collections.ArrayList |
||||||
|
|
||||||
|
class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun newInstance(context: Context) { |
||||||
|
val intent = Intent(context, CalendarDetailsFragment::class.java) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var parentActivity: PokerAnalyticsActivity |
||||||
|
private lateinit var statsAdapter: RowRepresentableAdapter |
||||||
|
|
||||||
|
private var title: String? = "" |
||||||
|
private var computedResults: ComputedResults? = null |
||||||
|
private var sessionTypeCondition: QueryCondition? = null |
||||||
|
private var rowRepresentables: ArrayList<RowRepresentable> = ArrayList() |
||||||
|
|
||||||
|
//private var stat: Stat = Stat.NET_RESULT |
||||||
|
//private var entries: List<Entry> = ArrayList() |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_calendar_details, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
override fun adapterRows(): List<RowRepresentable>? { |
||||||
|
return rowRepresentables |
||||||
|
} |
||||||
|
|
||||||
|
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||||
|
when (row) { |
||||||
|
is GraphRow -> { |
||||||
|
//TODO: Open graph details |
||||||
|
row.report.results.firstOrNull()?.group?.let { computableGroup -> |
||||||
|
StatisticDetailsActivity.newInstance(requireContext(), row.stat, computableGroup, row.report, false) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
parentActivity = activity as PokerAnalyticsActivity |
||||||
|
|
||||||
|
// Avoid a bug during setting the title |
||||||
|
toolbar.title = "" |
||||||
|
|
||||||
|
parentActivity.setSupportActionBar(toolbar) |
||||||
|
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) |
||||||
|
setHasOptionsMenu(true) |
||||||
|
|
||||||
|
|
||||||
|
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { |
||||||
|
override fun onTabSelected(tab: TabLayout.Tab) { |
||||||
|
when (tab.position) { |
||||||
|
0 -> sessionTypeCondition = null |
||||||
|
1 -> sessionTypeCondition = QueryCondition.IsCash |
||||||
|
2 -> sessionTypeCondition = QueryCondition.IsTournament |
||||||
|
} |
||||||
|
launchStatComputation() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onTabUnselected(tab: TabLayout.Tab) { |
||||||
|
} |
||||||
|
|
||||||
|
override fun onTabReselected(tab: TabLayout.Tab) { |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
statsAdapter = RowRepresentableAdapter(this, this) |
||||||
|
|
||||||
|
val viewManager = LinearLayoutManager(requireContext()) |
||||||
|
|
||||||
|
recyclerView.apply { |
||||||
|
setHasFixedSize(true) |
||||||
|
layoutManager = viewManager |
||||||
|
adapter = statsAdapter |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Display data |
||||||
|
*/ |
||||||
|
private fun displayData() { |
||||||
|
|
||||||
|
title?.let { |
||||||
|
toolbar.title = it |
||||||
|
} |
||||||
|
|
||||||
|
sessionTypeCondition?.let { |
||||||
|
when (it) { |
||||||
|
QueryCondition.IsCash -> tabs.getTabAt(1)?.select() |
||||||
|
QueryCondition.IsTournament -> tabs.getTabAt(2)?.select() |
||||||
|
else -> tabs.getTabAt(0)?.select() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Launch stat computation |
||||||
|
*/ |
||||||
|
private fun launchStatComputation() { |
||||||
|
|
||||||
|
progressBar.isVisible = true |
||||||
|
progressBar.animate().alpha(1f).start() |
||||||
|
recyclerView.animate().alpha(0f).start() |
||||||
|
|
||||||
|
computedResults?.let { computedResults -> |
||||||
|
|
||||||
|
GlobalScope.launch { |
||||||
|
|
||||||
|
val startDate = Date() |
||||||
|
|
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
val conditions = ArrayList<QueryCondition>().apply { |
||||||
|
addAll(computedResults.group.conditions) |
||||||
|
|
||||||
|
// Remove session type conditions |
||||||
|
removeAll(Criteria.Cash.queryConditions) |
||||||
|
removeAll(Criteria.Tournament.queryConditions) |
||||||
|
|
||||||
|
when (sessionTypeCondition) { |
||||||
|
QueryCondition.IsCash -> addAll(Criteria.Cash.queryConditions) |
||||||
|
QueryCondition.IsTournament -> addAll(Criteria.Tournament.queryConditions) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val requiredStats: List<Stat> = listOf(Stat.LOCATIONS_PLAYED, Stat.LONGEST_STREAKS, Stat.DAYS_PLAYED, Stat.STANDARD_DEVIATION_HOURLY) |
||||||
|
val options = Calculator.Options(evolutionValues = Calculator.Options.EvolutionValues.STANDARD, stats = requiredStats) |
||||||
|
val report = Calculator.computeStatsWithComparators(realm, conditions = conditions, options = options) |
||||||
|
|
||||||
|
Timber.d("Report take: ${System.currentTimeMillis() - startDate.time}ms") |
||||||
|
|
||||||
|
report.results.firstOrNull()?.let { |
||||||
|
|
||||||
|
// Create rows |
||||||
|
|
||||||
|
rowRepresentables.clear() |
||||||
|
rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.net_result)) |
||||||
|
rowRepresentables.add(GraphRow(report, Stat.NET_RESULT)) |
||||||
|
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.NET_RESULT), it.computedStat(Stat.HOURLY_RATE))) |
||||||
|
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.LOCATIONS_PLAYED), it.computedStat(Stat.LONGEST_STREAKS))) |
||||||
|
rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.distribution)) |
||||||
|
rowRepresentables.add(GraphRow(report, Stat.STANDARD_DEVIATION)) |
||||||
|
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.WIN_RATIO), it.computedStat(Stat.MAXIMUM_NETRESULT))) |
||||||
|
rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.volume)) |
||||||
|
rowRepresentables.add(GraphRow(report, Stat.HOURLY_DURATION)) |
||||||
|
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.HOURLY_DURATION), it.computedStat(Stat.AVERAGE_HOURLY_DURATION))) |
||||||
|
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.DAYS_PLAYED), it.computedStat(Stat.MAXIMUM_DURATION))) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) { |
||||||
|
statsAdapter.notifyDataSetChanged() |
||||||
|
progressBar.animate().cancel() |
||||||
|
progressBar.animate().alpha(0f).withEndAction { progressBar.isVisible = false }.start() |
||||||
|
recyclerView.animate().cancel() |
||||||
|
recyclerView.animate().alpha(1f).start() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set data |
||||||
|
*/ |
||||||
|
fun setData(computedResults: ComputedResults?, sessionTypeCondition: QueryCondition?, title: String?) { |
||||||
|
|
||||||
|
this.computedResults = computedResults |
||||||
|
this.sessionTypeCondition = sessionTypeCondition |
||||||
|
this.title = title |
||||||
|
|
||||||
|
displayData() |
||||||
|
launchStatComputation() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,388 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import com.google.android.material.tabs.TabLayout |
||||||
|
import io.realm.Realm |
||||||
|
import kotlinx.android.synthetic.main.fragment_calendar.* |
||||||
|
import kotlinx.android.synthetic.main.fragment_stats.recyclerView |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import kotlinx.coroutines.Dispatchers |
||||||
|
import kotlinx.coroutines.GlobalScope |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import net.pokeranalytics.android.calculus.Calculator |
||||||
|
import net.pokeranalytics.android.calculus.ComputedResults |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.model.Criteria |
||||||
|
import net.pokeranalytics.android.model.combined |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.ui.activity.CalendarDetailsActivity |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate |
||||||
|
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource |
||||||
|
import net.pokeranalytics.android.ui.extensions.hideWithAnimation |
||||||
|
import net.pokeranalytics.android.ui.extensions.showWithAnimation |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.SessionObserverFragment |
||||||
|
import net.pokeranalytics.android.ui.view.CalendarTabs |
||||||
|
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.RowViewType |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable |
||||||
|
import net.pokeranalytics.android.util.extensions.* |
||||||
|
import timber.log.Timber |
||||||
|
import java.util.* |
||||||
|
import kotlin.coroutines.CoroutineContext |
||||||
|
|
||||||
|
|
||||||
|
class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRepresentableDataSource, RowRepresentableDelegate { |
||||||
|
|
||||||
|
enum class TimeFilter { |
||||||
|
MONTH, YEAR |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create new instance |
||||||
|
*/ |
||||||
|
fun newInstance(): CalendarFragment { |
||||||
|
val fragment = CalendarFragment() |
||||||
|
val bundle = Bundle() |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var calendarAdapter: RowRepresentableAdapter |
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext |
||||||
|
get() = Dispatchers.Main |
||||||
|
|
||||||
|
|
||||||
|
private var rows: ArrayList<CustomizableRowRepresentable> = ArrayList() |
||||||
|
private var sortedMonthlyReports: SortedMap<Date, ComputedResults> = HashMap<Date, ComputedResults>().toSortedMap() |
||||||
|
private var sortedYearlyReports: SortedMap<Date, ComputedResults> = HashMap<Date, ComputedResults>().toSortedMap() |
||||||
|
private var datesForRows: HashMap<CustomizableRowRepresentable, Date> = HashMap() |
||||||
|
|
||||||
|
private var sessionTypeCondition: QueryCondition? = null |
||||||
|
private var currentTimeFilter: TimeFilter = TimeFilter.MONTH |
||||||
|
private var currentStat = Stat.NET_RESULT |
||||||
|
|
||||||
|
// Life Cycle |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(net.pokeranalytics.android.R.layout.fragment_calendar, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initData() |
||||||
|
initUI() |
||||||
|
|
||||||
|
launchStatComputation() |
||||||
|
} |
||||||
|
|
||||||
|
override fun adapterRows(): List<RowRepresentable>? { |
||||||
|
return rows |
||||||
|
} |
||||||
|
|
||||||
|
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||||
|
|
||||||
|
when (currentTimeFilter) { |
||||||
|
TimeFilter.MONTH -> { |
||||||
|
val date = datesForRows[row] |
||||||
|
sortedMonthlyReports[datesForRows[row]]?.let { |
||||||
|
CalendarDetailsActivity.newInstance(requireContext(), it, sessionTypeCondition, date?.getMonthAndYear()) |
||||||
|
} |
||||||
|
} |
||||||
|
TimeFilter.YEAR -> { |
||||||
|
val date = datesForRows[row] |
||||||
|
sortedYearlyReports[datesForRows[row]]?.let { |
||||||
|
CalendarDetailsActivity.newInstance(requireContext(), it, sessionTypeCondition, date?.getDateYear()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun sessionsChanged() { |
||||||
|
launchStatComputation() |
||||||
|
} |
||||||
|
|
||||||
|
// Business |
||||||
|
|
||||||
|
/** |
||||||
|
* Init data |
||||||
|
*/ |
||||||
|
private fun initData() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
CalendarTabs.values().forEach { |
||||||
|
val tab = tabs.newTab() |
||||||
|
tab.text = getString(it.resId) |
||||||
|
tabs.addTab(tab) |
||||||
|
} |
||||||
|
|
||||||
|
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { |
||||||
|
override fun onTabSelected(tab: TabLayout.Tab) { |
||||||
|
when (tab.position) { |
||||||
|
0 -> currentStat = Stat.NET_RESULT |
||||||
|
1 -> currentStat = Stat.HOURLY_RATE |
||||||
|
2 -> currentStat = Stat.NUMBER_OF_GAMES |
||||||
|
3 -> currentStat = Stat.WIN_RATIO |
||||||
|
4 -> currentStat = Stat.STANDARD_DEVIATION_HOURLY |
||||||
|
5 -> currentStat = Stat.AVERAGE |
||||||
|
6 -> currentStat = Stat.AVERAGE_HOURLY_DURATION |
||||||
|
7 -> currentStat = Stat.HOURLY_DURATION |
||||||
|
} |
||||||
|
displayData() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onTabUnselected(tab: TabLayout.Tab) { |
||||||
|
} |
||||||
|
|
||||||
|
override fun onTabReselected(tab: TabLayout.Tab) { |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
// Manage session type queryWith |
||||||
|
filterSessionAll.setOnCheckedChangeListener { _, isChecked -> |
||||||
|
if (isChecked) { |
||||||
|
sessionTypeCondition = null |
||||||
|
filterSessionCash.isChecked = false |
||||||
|
filterSessionTournament.isChecked = false |
||||||
|
launchStatComputation() |
||||||
|
} else if (sessionTypeCondition == null) { |
||||||
|
filterSessionAll.isChecked = true |
||||||
|
} |
||||||
|
} |
||||||
|
filterSessionCash.setOnCheckedChangeListener { _, isChecked -> |
||||||
|
if (isChecked) { |
||||||
|
sessionTypeCondition = QueryCondition.IsCash |
||||||
|
filterSessionAll.isChecked = false |
||||||
|
filterSessionTournament.isChecked = false |
||||||
|
launchStatComputation() |
||||||
|
} else if (sessionTypeCondition == QueryCondition.IsCash) { |
||||||
|
filterSessionCash.isChecked = true |
||||||
|
} |
||||||
|
} |
||||||
|
filterSessionTournament.setOnCheckedChangeListener { _, isChecked -> |
||||||
|
if (isChecked) { |
||||||
|
sessionTypeCondition = QueryCondition.IsTournament |
||||||
|
filterSessionAll.isChecked = false |
||||||
|
filterSessionCash.isChecked = false |
||||||
|
launchStatComputation() |
||||||
|
} else if (sessionTypeCondition == QueryCondition.IsTournament) { |
||||||
|
filterSessionTournament.isChecked = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Manage time queryWith |
||||||
|
filterTimeMonth.setOnCheckedChangeListener { _, isChecked -> |
||||||
|
if (isChecked) { |
||||||
|
currentTimeFilter = TimeFilter.MONTH |
||||||
|
filterTimeYear.isChecked = false |
||||||
|
displayData() |
||||||
|
} else if (currentTimeFilter == TimeFilter.MONTH) { |
||||||
|
filterTimeMonth.isChecked = true |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
filterTimeYear.setOnCheckedChangeListener { _, isChecked -> |
||||||
|
if (isChecked) { |
||||||
|
currentTimeFilter = TimeFilter.YEAR |
||||||
|
filterTimeMonth.isChecked = false |
||||||
|
displayData() |
||||||
|
} else if (currentTimeFilter == TimeFilter.YEAR) { |
||||||
|
filterTimeYear.isChecked = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val viewManager = LinearLayoutManager(requireContext()) |
||||||
|
|
||||||
|
calendarAdapter = RowRepresentableAdapter(this, this) |
||||||
|
|
||||||
|
recyclerView.apply { |
||||||
|
setHasFixedSize(true) |
||||||
|
layoutManager = viewManager |
||||||
|
adapter = calendarAdapter |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Launch stat computation |
||||||
|
*/ |
||||||
|
private fun launchStatComputation() { |
||||||
|
|
||||||
|
progressBar?.showWithAnimation() |
||||||
|
recyclerView?.hideWithAnimation() |
||||||
|
|
||||||
|
GlobalScope.launch { |
||||||
|
|
||||||
|
val calendar = Calendar.getInstance() |
||||||
|
calendar.time = Date().startOfMonth() |
||||||
|
|
||||||
|
val startDate = Date() |
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
|
||||||
|
val monthlyReports: HashMap<Date, ComputedResults> = HashMap() |
||||||
|
val yearlyReports: HashMap<Date, ComputedResults> = HashMap() |
||||||
|
|
||||||
|
val requiredStats: List<Stat> = listOf(Stat.LOCATIONS_PLAYED, Stat.LONGEST_STREAKS, Stat.DAYS_PLAYED, Stat.STANDARD_DEVIATION_HOURLY ) |
||||||
|
val options = Calculator.Options(evolutionValues = Calculator.Options.EvolutionValues.STANDARD, stats = requiredStats) |
||||||
|
|
||||||
|
// Compute data per AnyYear and AnyMonthOfYear |
||||||
|
|
||||||
|
println(">>>> ${Criteria.MonthsOfYear.queryConditions.map { it.id }}") |
||||||
|
|
||||||
|
val monthConditions = when (sessionTypeCondition) { |
||||||
|
QueryCondition.IsCash -> listOf(Criteria.Years, Criteria.MonthsOfYear, Criteria.Cash).combined() |
||||||
|
QueryCondition.IsTournament -> listOf(Criteria.Years, Criteria.MonthsOfYear, Criteria.Tournament).combined() |
||||||
|
else -> listOf(Criteria.Years, Criteria.MonthsOfYear).combined() |
||||||
|
} |
||||||
|
|
||||||
|
monthConditions.forEach { conditions -> |
||||||
|
|
||||||
|
val report = Calculator.computeStatsWithComparators(realm, conditions = conditions, options = options) |
||||||
|
report.results.forEach { computedResults -> |
||||||
|
if (!computedResults.isEmpty) { |
||||||
|
// Set date data |
||||||
|
conditions.forEach { condition -> |
||||||
|
when (condition) { |
||||||
|
is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first()) |
||||||
|
is QueryCondition.AnyMonthOfYear -> calendar.set(Calendar.MONTH, condition.listOfValues.first()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
monthlyReports[calendar.time] = computedResults |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
calendar.time = Date().startOfYear() |
||||||
|
|
||||||
|
// Compute data per AnyYear |
||||||
|
val yearConditions = when (sessionTypeCondition) { |
||||||
|
QueryCondition.IsCash -> listOf(Criteria.Years, Criteria.Cash).combined() |
||||||
|
QueryCondition.IsTournament -> listOf(Criteria.Years, Criteria.Tournament).combined() |
||||||
|
else -> listOf(Criteria.Years).combined() |
||||||
|
} |
||||||
|
|
||||||
|
yearConditions.forEach { conditions -> |
||||||
|
val report = Calculator.computeStatsWithComparators(realm, conditions = conditions, options = options) |
||||||
|
report.results.forEach { computedResults -> |
||||||
|
if (!computedResults.isEmpty) { |
||||||
|
// Set date data |
||||||
|
conditions.forEach { condition -> |
||||||
|
when (condition) { |
||||||
|
is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first()) |
||||||
|
} |
||||||
|
} |
||||||
|
yearlyReports[calendar.time] = computedResults |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sortedMonthlyReports = monthlyReports.toSortedMap(compareByDescending { it }) |
||||||
|
sortedYearlyReports = yearlyReports.toSortedMap(compareByDescending { it }) |
||||||
|
|
||||||
|
Timber.d("Computation: ${System.currentTimeMillis() - startDate.time}ms") |
||||||
|
|
||||||
|
// Logs |
||||||
|
/* |
||||||
|
Timber.d("========== AnyYear x AnyMonthOfYear") |
||||||
|
sortedMonthlyReports.keys.forEach { |
||||||
|
Timber.d("$it => ${sortedMonthlyReports[it]?.computedStat(Stat.NET_RESULT)?.value}") |
||||||
|
} |
||||||
|
|
||||||
|
Timber.d("========== YEARLY") |
||||||
|
sortedYearlyReports.keys.forEach { |
||||||
|
Timber.d("$it => ${sortedYearlyReports[it]?.computedStat(Stat.NET_RESULT)?.value}") |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
realm.close() |
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.Main) { |
||||||
|
displayData() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Display data |
||||||
|
*/ |
||||||
|
private fun displayData() { |
||||||
|
|
||||||
|
val startDate = Date() |
||||||
|
|
||||||
|
datesForRows.clear() |
||||||
|
rows.clear() |
||||||
|
|
||||||
|
when (currentTimeFilter) { |
||||||
|
|
||||||
|
// Create monthly reports |
||||||
|
TimeFilter.MONTH -> { |
||||||
|
val years: ArrayList<String> = ArrayList() |
||||||
|
sortedMonthlyReports.keys.forEach { date -> |
||||||
|
if (!years.contains(date.getDateYear())) { |
||||||
|
years.add(date.getDateYear()) |
||||||
|
rows.add( |
||||||
|
CustomizableRowRepresentable( |
||||||
|
customViewType = RowViewType.HEADER_TITLE, |
||||||
|
title = date.getDateYear() |
||||||
|
) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
sortedMonthlyReports[date]?.computedStat(currentStat)?.let { computedStat -> |
||||||
|
|
||||||
|
val row = CustomizableRowRepresentable( |
||||||
|
customViewType = RowViewType.TITLE_VALUE_ARROW, |
||||||
|
title = date.getDateMonth(), |
||||||
|
computedStat = computedStat, |
||||||
|
isSelectable = true |
||||||
|
) |
||||||
|
|
||||||
|
rows.add(row) |
||||||
|
datesForRows.put(row, date) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Create yearly reports |
||||||
|
TimeFilter.YEAR -> { |
||||||
|
sortedYearlyReports.keys.forEach { date -> |
||||||
|
sortedYearlyReports[date]?.computedStat(currentStat)?.let { computedStat -> |
||||||
|
val row = CustomizableRowRepresentable( |
||||||
|
customViewType = RowViewType.TITLE_VALUE_ARROW, |
||||||
|
title = date.getDateYear(), |
||||||
|
computedStat = computedStat, |
||||||
|
isSelectable = true |
||||||
|
) |
||||||
|
|
||||||
|
rows.add(row) |
||||||
|
datesForRows.put(row, date) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Timber.d("Display data: ${System.currentTimeMillis() - startDate.time}ms") |
||||||
|
Timber.d("Rows: ${rows.size}") |
||||||
|
|
||||||
|
calendarAdapter.notifyDataSetChanged() |
||||||
|
|
||||||
|
progressBar?.hideWithAnimation() |
||||||
|
recyclerView?.showWithAnimation() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,123 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.* |
||||||
|
import kotlinx.android.synthetic.main.fragment_comparison_chart.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.activity.BankrollActivity |
||||||
|
import net.pokeranalytics.android.ui.activity.SettingsActivity |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
import net.pokeranalytics.android.ui.adapter.ComparisonChartPagerAdapter |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate |
||||||
|
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource |
||||||
|
import net.pokeranalytics.android.ui.extensions.toast |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.MoreTabRow |
||||||
|
|
||||||
|
class ComparisonChartFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create new instance |
||||||
|
*/ |
||||||
|
fun newInstance(): ComparisonChartFragment { |
||||||
|
val fragment = ComparisonChartFragment() |
||||||
|
val bundle = Bundle() |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
} |
||||||
|
|
||||||
|
val rowRepresentation: List<RowRepresentable> by lazy { |
||||||
|
val rows = ArrayList<RowRepresentable>() |
||||||
|
rows.addAll(MoreTabRow.values()) |
||||||
|
rows |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var parentActivity: PokerAnalyticsActivity |
||||||
|
private lateinit var viewPagerAdapter: ComparisonChartPagerAdapter |
||||||
|
private var comparisonChartMenu: Menu? = null |
||||||
|
|
||||||
|
|
||||||
|
// Life Cycle |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_comparison_chart, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initData() |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { |
||||||
|
menu?.clear() |
||||||
|
inflater?.inflate(R.menu.toolbar_comparison_chart, menu) |
||||||
|
this.comparisonChartMenu = menu |
||||||
|
super.onCreateOptionsMenu(menu, inflater) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem?): Boolean { |
||||||
|
when (item!!.itemId) { |
||||||
|
R.id.settings -> openChangeStatistics() |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// Rows |
||||||
|
override fun adapterRows(): List<RowRepresentable>? { |
||||||
|
return rowRepresentation |
||||||
|
} |
||||||
|
|
||||||
|
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||||
|
super.onRowSelected(position, row, fromAction) |
||||||
|
when(row) { |
||||||
|
MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext()) |
||||||
|
MoreTabRow.SETTINGS -> SettingsActivity.newInstance(requireContext()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Business |
||||||
|
|
||||||
|
/** |
||||||
|
* Init data |
||||||
|
*/ |
||||||
|
private fun initData() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
parentActivity = activity as PokerAnalyticsActivity |
||||||
|
|
||||||
|
toolbar.title = "" |
||||||
|
|
||||||
|
parentActivity.setSupportActionBar(toolbar) |
||||||
|
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) |
||||||
|
setHasOptionsMenu(true) |
||||||
|
|
||||||
|
toolbar.title = "Comparison chart" |
||||||
|
|
||||||
|
viewPagerAdapter = ComparisonChartPagerAdapter(requireContext(), parentActivity.supportFragmentManager) |
||||||
|
viewPager.adapter = viewPagerAdapter |
||||||
|
viewPager.offscreenPageLimit = 2 |
||||||
|
|
||||||
|
tabs.setupWithViewPager(viewPager) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Open change statistics |
||||||
|
*/ |
||||||
|
private fun openChangeStatistics() { |
||||||
|
//TODO |
||||||
|
toast("Open change statistics") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,93 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import kotlinx.android.synthetic.main.fragment_stats.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.activity.BankrollActivity |
||||||
|
import net.pokeranalytics.android.ui.activity.SettingsActivity |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate |
||||||
|
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.MoreTabRow |
||||||
|
|
||||||
|
class MoreFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create new instance |
||||||
|
*/ |
||||||
|
fun newInstance(): MoreFragment { |
||||||
|
val fragment = MoreFragment() |
||||||
|
val bundle = Bundle() |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
} |
||||||
|
|
||||||
|
val rowRepresentation: List<RowRepresentable> by lazy { |
||||||
|
val rows = ArrayList<RowRepresentable>() |
||||||
|
rows.addAll(MoreTabRow.values()) |
||||||
|
rows |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var moreAdapter: RowRepresentableAdapter |
||||||
|
|
||||||
|
|
||||||
|
// Life Cycle |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_more, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initData() |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
// Rows |
||||||
|
override fun adapterRows(): List<RowRepresentable>? { |
||||||
|
return rowRepresentation |
||||||
|
} |
||||||
|
|
||||||
|
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||||
|
super.onRowSelected(position, row, fromAction) |
||||||
|
when(row) { |
||||||
|
MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext()) |
||||||
|
MoreTabRow.SETTINGS -> SettingsActivity.newInstance(requireContext()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Business |
||||||
|
|
||||||
|
/** |
||||||
|
* Init data |
||||||
|
*/ |
||||||
|
private fun initData() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
moreAdapter = RowRepresentableAdapter(this, this) |
||||||
|
|
||||||
|
val viewManager = LinearLayoutManager(requireContext()) |
||||||
|
|
||||||
|
recyclerView.apply { |
||||||
|
setHasFixedSize(true) |
||||||
|
layoutManager = viewManager |
||||||
|
adapter = moreAdapter |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,92 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import com.google.android.material.tabs.TabLayout |
||||||
|
import kotlinx.android.synthetic.main.fragment_report_details.* |
||||||
|
import kotlinx.android.synthetic.main.fragment_statistic_details.toolbar |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.AggregationType |
||||||
|
import net.pokeranalytics.android.calculus.Report |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
import net.pokeranalytics.android.ui.adapter.ReportPagerAdapter |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
|
||||||
|
class ReportDetailsFragment : PokerAnalyticsFragment() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
fun newInstance(report: Report?, reportTitle: String): ReportDetailsFragment { |
||||||
|
val fragment = ReportDetailsFragment() |
||||||
|
fragment.reportTitle = reportTitle |
||||||
|
report?.let { |
||||||
|
fragment.selectedReport = it |
||||||
|
} |
||||||
|
val bundle = Bundle() |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var parentActivity: PokerAnalyticsActivity |
||||||
|
private lateinit var selectedReport: Report |
||||||
|
|
||||||
|
private var reports: MutableMap<AggregationType, Report> = hashMapOf() |
||||||
|
private var stat: Stat = Stat.NET_RESULT |
||||||
|
private var displayAggregationChoices: Boolean = true |
||||||
|
private var reportTitle: String = "" |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_report_details, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
parentActivity = activity as PokerAnalyticsActivity |
||||||
|
|
||||||
|
// Avoid a bug during setting the title |
||||||
|
toolbar.title = "" |
||||||
|
|
||||||
|
parentActivity.setSupportActionBar(toolbar) |
||||||
|
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) |
||||||
|
setHasOptionsMenu(true) |
||||||
|
|
||||||
|
toolbar.title = reportTitle |
||||||
|
|
||||||
|
val reportPagerAdapter = ReportPagerAdapter(requireContext(), parentActivity.supportFragmentManager, selectedReport) |
||||||
|
viewPager.adapter = reportPagerAdapter |
||||||
|
viewPager.offscreenPageLimit = 3 |
||||||
|
|
||||||
|
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { |
||||||
|
override fun onTabSelected(tab: TabLayout.Tab) { |
||||||
|
viewPager.setCurrentItem(tab.position, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onTabUnselected(tab: TabLayout.Tab) { |
||||||
|
} |
||||||
|
|
||||||
|
override fun onTabReselected(tab: TabLayout.Tab) { |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Set data |
||||||
|
*/ |
||||||
|
fun setData(report: Report) { |
||||||
|
this.selectedReport = report |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,140 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import android.widget.Toast |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import io.realm.Realm |
||||||
|
import kotlinx.android.synthetic.main.fragment_stats.* |
||||||
|
import kotlinx.coroutines.Dispatchers |
||||||
|
import kotlinx.coroutines.GlobalScope |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.Calculator |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.model.Criteria |
||||||
|
import net.pokeranalytics.android.model.combined |
||||||
|
import net.pokeranalytics.android.ui.activity.ReportDetailsActivity |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate |
||||||
|
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.ReportRow |
||||||
|
import timber.log.Timber |
||||||
|
import java.util.* |
||||||
|
import kotlin.collections.ArrayList |
||||||
|
|
||||||
|
class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create new instance |
||||||
|
*/ |
||||||
|
fun newInstance(): ReportsFragment { |
||||||
|
val fragment = ReportsFragment() |
||||||
|
val bundle = Bundle() |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
} |
||||||
|
|
||||||
|
val rowRepresentation: List<RowRepresentable> by lazy { |
||||||
|
val rows = ArrayList<RowRepresentable>() |
||||||
|
rows.addAll(ReportRow.getRows()) |
||||||
|
rows |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var reportsAdapter: RowRepresentableAdapter |
||||||
|
|
||||||
|
|
||||||
|
// Life Cycle |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_reports, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initData() |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
// Rows |
||||||
|
|
||||||
|
override fun adapterRows(): List<RowRepresentable>? { |
||||||
|
return rowRepresentation |
||||||
|
} |
||||||
|
|
||||||
|
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||||
|
super.onRowSelected(position, row, fromAction) |
||||||
|
if (row is ReportRow) { |
||||||
|
val reportName = row.localizedTitle(requireContext()) |
||||||
|
launchComputation(row.criteria, reportName) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Business |
||||||
|
|
||||||
|
/** |
||||||
|
* Init data |
||||||
|
*/ |
||||||
|
private fun initData() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
reportsAdapter = RowRepresentableAdapter(this, this) |
||||||
|
|
||||||
|
val viewManager = LinearLayoutManager(requireContext()) |
||||||
|
|
||||||
|
recyclerView.apply { |
||||||
|
setHasFixedSize(true) |
||||||
|
layoutManager = viewManager |
||||||
|
adapter = reportsAdapter |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Launch computation |
||||||
|
*/ |
||||||
|
private fun launchComputation(criteria: List<Criteria>, reportName: String) { |
||||||
|
|
||||||
|
if (criteria.combined().size < 2) { |
||||||
|
Toast.makeText(context, R.string.less_then_2_values_for_comparison, Toast.LENGTH_LONG).show() |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
showLoader() |
||||||
|
|
||||||
|
GlobalScope.launch { |
||||||
|
|
||||||
|
val startDate = Date() |
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
|
||||||
|
val requiredStats: List<Stat> = listOf(Stat.NET_RESULT) |
||||||
|
val options = Calculator.Options(evolutionValues = Calculator.Options.EvolutionValues.STANDARD, stats = requiredStats) |
||||||
|
|
||||||
|
val report = Calculator.computeStatsWithComparators(realm, criteria = criteria, options = options) |
||||||
|
|
||||||
|
Timber.d("launchComputation: ${System.currentTimeMillis() - startDate.time}ms") |
||||||
|
|
||||||
|
launch(Dispatchers.Main) { |
||||||
|
if (!isDetached) { |
||||||
|
hideLoader() |
||||||
|
ReportDetailsActivity.newInstance(requireContext(), report, reportName) |
||||||
|
} |
||||||
|
} |
||||||
|
realm.close() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,197 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.core.view.isVisible |
||||||
|
import com.github.mikephil.charting.data.BarDataSet |
||||||
|
import com.github.mikephil.charting.data.LineDataSet |
||||||
|
import com.google.android.material.chip.Chip |
||||||
|
import com.google.android.material.chip.ChipGroup |
||||||
|
import io.realm.Realm |
||||||
|
import kotlinx.android.synthetic.main.fragment_statistic_details.* |
||||||
|
import kotlinx.coroutines.Dispatchers |
||||||
|
import kotlinx.coroutines.GlobalScope |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.* |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
import net.pokeranalytics.android.ui.extensions.ChipGroupExtension |
||||||
|
import net.pokeranalytics.android.ui.extensions.hideWithAnimation |
||||||
|
import net.pokeranalytics.android.ui.extensions.px |
||||||
|
import net.pokeranalytics.android.ui.extensions.showWithAnimation |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
import timber.log.Timber |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
|
||||||
|
class StatisticDetailsFragment : PokerAnalyticsFragment() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create new instance |
||||||
|
*/ |
||||||
|
fun newInstance(): StatisticDetailsFragment { |
||||||
|
val fragment = StatisticDetailsFragment() |
||||||
|
val bundle = Bundle() |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private lateinit var parentActivity: PokerAnalyticsActivity |
||||||
|
private lateinit var computableGroup: ComputableGroup |
||||||
|
private lateinit var graphFragment: GraphFragment |
||||||
|
private lateinit var selectedReport: Report |
||||||
|
|
||||||
|
private var reports: MutableMap<AggregationType, Report> = hashMapOf() |
||||||
|
private var stat: Stat = Stat.NET_RESULT |
||||||
|
private var displayAggregationChoices: Boolean = true |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_statistic_details, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
parentActivity = activity as PokerAnalyticsActivity |
||||||
|
|
||||||
|
// Avoid a bug during setting the title |
||||||
|
toolbar.title = "" |
||||||
|
|
||||||
|
parentActivity.setSupportActionBar(toolbar) |
||||||
|
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) |
||||||
|
setHasOptionsMenu(true) |
||||||
|
|
||||||
|
|
||||||
|
val fragmentManager = parentActivity.supportFragmentManager |
||||||
|
val fragmentTransaction = fragmentManager.beginTransaction() |
||||||
|
graphFragment = GraphFragment() |
||||||
|
|
||||||
|
fragmentTransaction.add(R.id.graphContainer, graphFragment) |
||||||
|
fragmentTransaction.commit() |
||||||
|
|
||||||
|
stat.aggregationTypes.firstOrNull()?.let { aggregationType -> |
||||||
|
reports[aggregationType]?.let { report -> |
||||||
|
setGraphData(report, aggregationType) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
toolbar.title = stat.localizedTitle(requireContext()) |
||||||
|
val aggregationTypes = stat.aggregationTypes |
||||||
|
|
||||||
|
aggregationTypes.forEachIndexed { index, type -> |
||||||
|
val chip = Chip(requireContext()) |
||||||
|
chip.id = index |
||||||
|
chip.text = requireContext().getString(type.resId) |
||||||
|
chip.chipStartPadding = 8f.px |
||||||
|
chip.chipEndPadding = 8f.px |
||||||
|
this.chipGroup.addView(chip) |
||||||
|
} |
||||||
|
|
||||||
|
this.chipGroup.isVisible = displayAggregationChoices |
||||||
|
this.chipGroup.check(0) |
||||||
|
|
||||||
|
this.chipGroup.setOnCheckedChangeListener(object : ChipGroupExtension.SingleSelectionOnCheckedListener() { |
||||||
|
override fun onCheckedChanged(group: ChipGroup, checkedId: Int) { |
||||||
|
super.onCheckedChanged(group, checkedId) |
||||||
|
val aggregationType = aggregationTypes[checkedId] |
||||||
|
|
||||||
|
reports[aggregationType]?.let { report -> |
||||||
|
setGraphData(report, aggregationType) |
||||||
|
} ?: run { |
||||||
|
launchStatComputation(aggregationType) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Launch stat computation |
||||||
|
*/ |
||||||
|
private fun launchStatComputation(aggregationType: AggregationType) { |
||||||
|
|
||||||
|
graphContainer.hideWithAnimation() |
||||||
|
progressBar.showWithAnimation() |
||||||
|
|
||||||
|
GlobalScope.launch { |
||||||
|
|
||||||
|
val s = Date() |
||||||
|
Timber.d(">>> start...") |
||||||
|
|
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
|
||||||
|
val report = Calculator.computeStatsWithEvolutionByAggregationType(realm, stat, computableGroup, aggregationType) |
||||||
|
reports[aggregationType] = report |
||||||
|
|
||||||
|
realm.close() |
||||||
|
|
||||||
|
val e = Date() |
||||||
|
val duration = (e.time - s.time) / 1000.0 |
||||||
|
Timber.d(">>> ended in $duration seconds") |
||||||
|
|
||||||
|
launch(Dispatchers.Main) { |
||||||
|
setGraphData(report, aggregationType) |
||||||
|
progressBar.hideWithAnimation() |
||||||
|
graphContainer.showWithAnimation() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the graph data set |
||||||
|
*/ |
||||||
|
private fun setGraphData(report: Report, aggregationType: AggregationType) { |
||||||
|
|
||||||
|
val dataSet = when (aggregationType) { |
||||||
|
AggregationType.SESSION -> report.results.firstOrNull()?.defaultStatEntries(stat, requireContext()) |
||||||
|
AggregationType.DURATION -> { |
||||||
|
report.results.firstOrNull()?.durationEntries(stat, requireContext()) |
||||||
|
} |
||||||
|
AggregationType.MONTH, AggregationType.YEAR -> { |
||||||
|
when (this.stat) { |
||||||
|
Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> report.barEntries(this.stat, requireContext()) |
||||||
|
else -> report.lineEntries(this.stat, requireContext()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dataSet?.let { ds -> |
||||||
|
if (ds is LineDataSet) { |
||||||
|
graphFragment.setLineData(listOf(ds), stat, aggregationType.axisFormatting) |
||||||
|
} |
||||||
|
if (ds is BarDataSet) { |
||||||
|
graphFragment.setBarData(listOf(ds), stat, aggregationType.axisFormatting) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Set data |
||||||
|
*/ |
||||||
|
fun setData(stat: Stat, computableGroup: ComputableGroup, report: Report, displayAggregationChoices: Boolean) { |
||||||
|
this.stat = stat |
||||||
|
this.computableGroup = computableGroup |
||||||
|
this.selectedReport = report |
||||||
|
this.displayAggregationChoices = displayAggregationChoices |
||||||
|
|
||||||
|
stat.aggregationTypes.firstOrNull()?.let { |
||||||
|
reports[it] = report |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,145 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.View |
||||||
|
import io.realm.Realm |
||||||
|
import kotlinx.coroutines.Dispatchers |
||||||
|
import kotlinx.coroutines.GlobalScope |
||||||
|
import kotlinx.coroutines.async |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.Calculator |
||||||
|
import net.pokeranalytics.android.calculus.ComputableGroup |
||||||
|
import net.pokeranalytics.android.calculus.Report |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.StatRow |
||||||
|
import timber.log.Timber |
||||||
|
import java.util.* |
||||||
|
import kotlin.coroutines.CoroutineContext |
||||||
|
|
||||||
|
class StatisticsFragment : TableReportFragment() { |
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext |
||||||
|
get() = Dispatchers.Main |
||||||
|
|
||||||
|
private var stringAll = "" |
||||||
|
private var stringCashGame = "" |
||||||
|
private var stringTournament = "" |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create new instance |
||||||
|
*/ |
||||||
|
fun newInstance(report: Report? = null): StatisticsFragment { |
||||||
|
val fragment = StatisticsFragment() |
||||||
|
report?.let { |
||||||
|
fragment.report = it |
||||||
|
} |
||||||
|
val bundle = Bundle() |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Life Cycle |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
launchStatComputation() |
||||||
|
} |
||||||
|
|
||||||
|
override fun sessionsChanged() { |
||||||
|
this.launchStatComputation() |
||||||
|
this.statsAdapter?.notifyDataSetChanged() |
||||||
|
} |
||||||
|
|
||||||
|
override fun initData() { |
||||||
|
super.initData() |
||||||
|
|
||||||
|
this.stringAll = getString(R.string.all) |
||||||
|
this.stringCashGame = getString(R.string.cash_game) |
||||||
|
this.stringTournament = getString(R.string.tournament) |
||||||
|
} |
||||||
|
|
||||||
|
override fun convertReportIntoRepresentables(report: Report): ArrayList<RowRepresentable> { |
||||||
|
val rows: ArrayList<RowRepresentable> = ArrayList() |
||||||
|
report.results.forEach { result -> |
||||||
|
rows.add(CustomizableRowRepresentable(title = result.group.name)) |
||||||
|
result.group.stats?.forEach { stat -> |
||||||
|
rows.add(StatRow(stat, result.computedStat(stat), result.group.name)) |
||||||
|
} |
||||||
|
} |
||||||
|
return rows |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Business |
||||||
|
|
||||||
|
/** |
||||||
|
* Launch stat computation |
||||||
|
*/ |
||||||
|
private fun launchStatComputation() { |
||||||
|
|
||||||
|
GlobalScope.launch(coroutineContext) { |
||||||
|
|
||||||
|
val test = GlobalScope.async { |
||||||
|
val s = Date() |
||||||
|
Timber.d(">>> start...") |
||||||
|
|
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
report = createSessionGroupsAndStartCompute(realm) |
||||||
|
realm.close() |
||||||
|
|
||||||
|
val e = Date() |
||||||
|
val duration = (e.time - s.time) / 1000.0 |
||||||
|
Timber.d(">>> ended in $duration seconds") |
||||||
|
|
||||||
|
} |
||||||
|
test.await() |
||||||
|
|
||||||
|
if (!isDetached) { |
||||||
|
showResults() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create session groups and start computations |
||||||
|
*/ |
||||||
|
private fun createSessionGroupsAndStartCompute(realm: Realm): Report { |
||||||
|
|
||||||
|
val allStats: List<Stat> = listOf( |
||||||
|
Stat.NET_RESULT, |
||||||
|
Stat.HOURLY_RATE, |
||||||
|
Stat.AVERAGE, |
||||||
|
Stat.NUMBER_OF_SETS, |
||||||
|
Stat.AVERAGE_HOURLY_DURATION, |
||||||
|
Stat.HOURLY_DURATION |
||||||
|
) |
||||||
|
val allSessionGroup = ComputableGroup(stringAll, listOf(), allStats) |
||||||
|
val cgStats: List<Stat> = listOf( |
||||||
|
Stat.NET_RESULT, |
||||||
|
Stat.HOURLY_RATE, |
||||||
|
Stat.NET_BB_PER_100_HANDS, |
||||||
|
Stat.HOURLY_RATE_BB, |
||||||
|
Stat.AVERAGE, |
||||||
|
Stat.STANDARD_DEVIATION_HOURLY, |
||||||
|
Stat.WIN_RATIO, |
||||||
|
Stat.NUMBER_OF_GAMES, |
||||||
|
Stat.AVERAGE_BUYIN |
||||||
|
) |
||||||
|
val cgSessionGroup = ComputableGroup(stringCashGame, listOf(QueryCondition.IsCash), cgStats) |
||||||
|
val tStats: List<Stat> = |
||||||
|
listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) |
||||||
|
val tSessionGroup = ComputableGroup(stringTournament, listOf(QueryCondition.IsTournament), tStats) |
||||||
|
|
||||||
|
Timber.d(">>>>> Start computations...") |
||||||
|
|
||||||
|
return Calculator.computeGroups(realm, listOf(allSessionGroup, cgSessionGroup, tSessionGroup), Calculator.Options()) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,247 +0,0 @@ |
|||||||
package net.pokeranalytics.android.ui.fragment |
|
||||||
|
|
||||||
import android.os.Bundle |
|
||||||
import android.view.LayoutInflater |
|
||||||
import android.view.View |
|
||||||
import android.view.ViewGroup |
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager |
|
||||||
import io.realm.Realm |
|
||||||
import kotlinx.android.synthetic.main.fragment_stats.* |
|
||||||
import kotlinx.coroutines.* |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.calculus.* |
|
||||||
import net.pokeranalytics.android.model.StatRepresentable |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.ui.activity.GraphActivity |
|
||||||
import net.pokeranalytics.android.ui.adapter.DisplayDescriptor |
|
||||||
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter |
|
||||||
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate |
|
||||||
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource |
|
||||||
import net.pokeranalytics.android.ui.fragment.components.SessionObserverFragment |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import timber.log.Timber |
|
||||||
import java.util.* |
|
||||||
import kotlin.coroutines.CoroutineContext |
|
||||||
|
|
||||||
class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope, |
|
||||||
RowRepresentableDelegate { |
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext |
|
||||||
get() = Dispatchers.Main |
|
||||||
|
|
||||||
private var rowRepresentables: ArrayList<RowRepresentable> = ArrayList() |
|
||||||
private var stringAll = "" |
|
||||||
private var stringCashGame = "" |
|
||||||
private var stringTournament = "" |
|
||||||
|
|
||||||
private lateinit var statsAdapter: RowRepresentableAdapter |
|
||||||
private var computedResults : List<ComputedResults>? = null |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
/** |
|
||||||
* Create new instance |
|
||||||
*/ |
|
||||||
fun newInstance(): StatsFragment { |
|
||||||
val fragment = StatsFragment() |
|
||||||
val bundle = Bundle() |
|
||||||
fragment.arguments = bundle |
|
||||||
return fragment |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Life Cycle |
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
|
||||||
return inflater.inflate(R.layout.fragment_stats, container, false) |
|
||||||
} |
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
|
||||||
super.onViewCreated(view, savedInstanceState) |
|
||||||
initData() |
|
||||||
initUI() |
|
||||||
launchStatComputation() |
|
||||||
} |
|
||||||
|
|
||||||
// Row Representable DS |
|
||||||
|
|
||||||
override fun adapterRows(): List<RowRepresentable>? { |
|
||||||
return this.rowRepresentables |
|
||||||
} |
|
||||||
|
|
||||||
override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? { |
|
||||||
val dc = DisplayDescriptor() |
|
||||||
dc.textFormat = TextFormat(NULL_TEXT) |
|
||||||
if (row is StatRepresentable) { |
|
||||||
context?.let { context -> |
|
||||||
row.computedStat?.let { |
|
||||||
dc.textFormat = it.format(context) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return dc |
|
||||||
} |
|
||||||
|
|
||||||
override fun statFormatForRow(row: RowRepresentable): TextFormat { |
|
||||||
if (row is StatRepresentable) { |
|
||||||
context?.let { context -> |
|
||||||
row.computedStat?.let { return it.format(context) } |
|
||||||
} |
|
||||||
} |
|
||||||
return TextFormat(NULL_TEXT) |
|
||||||
} |
|
||||||
|
|
||||||
override fun onResume() { |
|
||||||
super.onResume() |
|
||||||
statsAdapter.notifyDataSetChanged() |
|
||||||
} |
|
||||||
|
|
||||||
// Override |
|
||||||
|
|
||||||
override fun sessionsChanged() { |
|
||||||
this.launchStatComputation() |
|
||||||
this.statsAdapter.notifyDataSetChanged() |
|
||||||
} |
|
||||||
|
|
||||||
// Business |
|
||||||
|
|
||||||
/** |
|
||||||
* Init data |
|
||||||
*/ |
|
||||||
private fun initData() { |
|
||||||
|
|
||||||
this.stringAll = getString(R.string.all) |
|
||||||
this.stringCashGame = getString(R.string.cash_game) |
|
||||||
this.stringTournament = getString(R.string.tournament) |
|
||||||
|
|
||||||
this.statsAdapter = RowRepresentableAdapter(this, this) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Init UI |
|
||||||
*/ |
|
||||||
private fun initUI() { |
|
||||||
|
|
||||||
val viewManager = LinearLayoutManager(requireContext()) |
|
||||||
|
|
||||||
recyclerView.apply { |
|
||||||
setHasFixedSize(true) |
|
||||||
layoutManager = viewManager |
|
||||||
adapter = statsAdapter |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun launchStatComputation() { |
|
||||||
|
|
||||||
GlobalScope.launch(coroutineContext) { |
|
||||||
|
|
||||||
var results = listOf<ComputedResults>() |
|
||||||
val test = GlobalScope.async { |
|
||||||
val s = Date() |
|
||||||
Timber.d(">>> start...") |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
results = createSessionGroupsAndStartCompute(realm) |
|
||||||
computedResults = results |
|
||||||
realm.close() |
|
||||||
|
|
||||||
val e = Date() |
|
||||||
val duration = (e.time - s.time) / 1000.0 |
|
||||||
Timber.d(">>> ended in ${duration} seconds") |
|
||||||
|
|
||||||
} |
|
||||||
test.await() |
|
||||||
|
|
||||||
if (!isDetached) { |
|
||||||
showResults(results) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun createSessionGroupsAndStartCompute(realm: Realm) : List<ComputedResults> { |
|
||||||
|
|
||||||
val allStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE, Stat.NUMBER_OF_SETS, Stat.AVERAGE_DURATION, Stat.DURATION) |
|
||||||
val allSessionGroup = ComputableGroup(stringAll, listOf(), allStats) |
|
||||||
val cgStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.NET_BB_PER_100_HANDS, Stat.HOURLY_RATE_BB, Stat.AVERAGE, Stat.STANDARD_DEVIATION_HOURLY, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) |
|
||||||
val cgSessionGroup = ComputableGroup(stringCashGame, listOf(QueryCondition.CASH), cgStats) |
|
||||||
val tStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) |
|
||||||
val tSessionGroup = ComputableGroup(stringTournament, listOf(QueryCondition.TOURNAMENT), tStats) |
|
||||||
|
|
||||||
Timber.d(">>>>> Start computations...") |
|
||||||
|
|
||||||
return Calculator.computeGroups(realm, listOf(allSessionGroup, cgSessionGroup, tSessionGroup), Calculator.Options()) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun showResults(results: List<ComputedResults>) { |
|
||||||
this.rowRepresentables = this.convertResultsIntoRepresentables(results) |
|
||||||
statsAdapter.notifyDataSetChanged() |
|
||||||
} |
|
||||||
|
|
||||||
private fun convertResultsIntoRepresentables(results: List<ComputedResults>) : ArrayList<RowRepresentable> { |
|
||||||
|
|
||||||
val rows: ArrayList<RowRepresentable> = ArrayList() |
|
||||||
|
|
||||||
results.forEach { result -> |
|
||||||
rows.add(CustomizableRowRepresentable(title = result.group.name)) |
|
||||||
result.group.stats?.forEach { stat -> |
|
||||||
rows.add(StatRepresentable(stat, result.computedStat(stat), result.group.name)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return rows |
|
||||||
} |
|
||||||
|
|
||||||
// RowRepresentableDelegate |
|
||||||
|
|
||||||
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
|
||||||
|
|
||||||
// if (row is StatRepresentable) { |
|
||||||
// |
|
||||||
// // filter groups |
|
||||||
// val groupResults = this.computedResults?.filter { |
|
||||||
// it.group.name == row.groupName |
|
||||||
// } |
|
||||||
// |
|
||||||
// groupResults?.firstOrNull()?.let { |
|
||||||
// this.launchStatComputationWithEvolution(row.stat, it.group) |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun launchStatComputationWithEvolution(stat: Stat, computableGroup: ComputableGroup) { |
|
||||||
|
|
||||||
GlobalScope.launch(coroutineContext) { |
|
||||||
|
|
||||||
var results = listOf<ComputedResults>() |
|
||||||
val test = GlobalScope.async { |
|
||||||
val s = Date() |
|
||||||
Timber.d(">>> start...") |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
val options = Calculator.Options() |
|
||||||
options.evolutionValues = Calculator.Options.EvolutionValues.STANDARD |
|
||||||
results = Calculator.computeGroups(realm, listOf(computableGroup), options) |
|
||||||
realm.close() |
|
||||||
|
|
||||||
val e = Date() |
|
||||||
val duration = (e.time - s.time) / 1000.0 |
|
||||||
Timber.d(">>> ended in ${duration} seconds") |
|
||||||
|
|
||||||
} |
|
||||||
test.await() |
|
||||||
|
|
||||||
if (!isDetached) { |
|
||||||
results.firstOrNull()?.defaultStatEntries(stat)?.let { entries -> |
|
||||||
GraphActivity.newInstance(requireContext(), stat, entries) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,199 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import io.realm.Realm |
||||||
|
import kotlinx.android.synthetic.main.fragment_stats.* |
||||||
|
import kotlinx.coroutines.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.* |
||||||
|
import net.pokeranalytics.android.ui.activity.StatisticDetailsActivity |
||||||
|
import net.pokeranalytics.android.ui.adapter.DisplayDescriptor |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter |
||||||
|
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate |
||||||
|
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.SessionObserverFragment |
||||||
|
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.StatRow |
||||||
|
import net.pokeranalytics.android.util.NULL_TEXT |
||||||
|
import timber.log.Timber |
||||||
|
import java.util.* |
||||||
|
import kotlin.coroutines.CoroutineContext |
||||||
|
|
||||||
|
open class TableReportFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope, |
||||||
|
RowRepresentableDelegate { |
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext |
||||||
|
get() = Dispatchers.Main |
||||||
|
|
||||||
|
private var rowRepresentables: ArrayList<RowRepresentable> = ArrayList() |
||||||
|
|
||||||
|
var statsAdapter: RowRepresentableAdapter? = null |
||||||
|
var report : Report? = null |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
/** |
||||||
|
* Create new instance |
||||||
|
*/ |
||||||
|
fun newInstance(report: Report? = null): TableReportFragment { |
||||||
|
val fragment = TableReportFragment() |
||||||
|
report?.let { |
||||||
|
fragment.report = it |
||||||
|
} |
||||||
|
val bundle = Bundle() |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Life Cycle |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_stats, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initData() |
||||||
|
initUI() |
||||||
|
|
||||||
|
report?.let { |
||||||
|
showResults() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Row Representable DS |
||||||
|
|
||||||
|
override fun adapterRows(): List<RowRepresentable>? { |
||||||
|
return this.rowRepresentables |
||||||
|
} |
||||||
|
|
||||||
|
override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? { |
||||||
|
val dc = DisplayDescriptor() |
||||||
|
dc.textFormat = TextFormat(NULL_TEXT) |
||||||
|
if (row is StatRow) { |
||||||
|
context?.let { _ -> |
||||||
|
row.computedStat?.let { |
||||||
|
dc.textFormat = it.format() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return dc |
||||||
|
} |
||||||
|
|
||||||
|
override fun statFormatForRow(row: RowRepresentable): TextFormat { |
||||||
|
if (row is StatRow) { |
||||||
|
context?.let { _ -> |
||||||
|
row.computedStat?.let { return it.format() } |
||||||
|
} |
||||||
|
} |
||||||
|
return TextFormat(NULL_TEXT) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onResume() { |
||||||
|
super.onResume() |
||||||
|
statsAdapter?.notifyDataSetChanged() |
||||||
|
} |
||||||
|
|
||||||
|
// Business |
||||||
|
|
||||||
|
/** |
||||||
|
* Init data |
||||||
|
*/ |
||||||
|
open fun initData() { |
||||||
|
this.statsAdapter = RowRepresentableAdapter(this, this) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
open fun initUI() { |
||||||
|
val viewManager = LinearLayoutManager(requireContext()) |
||||||
|
recyclerView.apply { |
||||||
|
setHasFixedSize(true) |
||||||
|
layoutManager = viewManager |
||||||
|
adapter = statsAdapter |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Show results |
||||||
|
*/ |
||||||
|
fun showResults() { |
||||||
|
report?.let { |
||||||
|
this.rowRepresentables = this.convertReportIntoRepresentables(it) |
||||||
|
statsAdapter?.notifyDataSetChanged() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
open fun convertReportIntoRepresentables(report: Report): ArrayList<RowRepresentable> { |
||||||
|
val rows: ArrayList<RowRepresentable> = ArrayList() |
||||||
|
report.options.displayedStats.forEach {stat -> |
||||||
|
rows.add(CustomizableRowRepresentable(title = stat.localizedTitle(requireContext()))) |
||||||
|
report.results.forEach { |
||||||
|
val title = it.group.name |
||||||
|
rows.add(StatRow(stat, it.computedStat(stat), it.group.name, title)) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
return rows |
||||||
|
} |
||||||
|
|
||||||
|
// RowRepresentableDelegate |
||||||
|
|
||||||
|
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { |
||||||
|
|
||||||
|
if (row is StatRow && row.stat.hasEvolutionGraph) { |
||||||
|
|
||||||
|
// queryWith groups |
||||||
|
val groupResults = this.report?.results?.filter { |
||||||
|
it.group.name == row.groupName |
||||||
|
} |
||||||
|
|
||||||
|
groupResults?.firstOrNull()?.let { |
||||||
|
this.launchStatComputationWithEvolution(row.stat, it.group) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private fun launchStatComputationWithEvolution(stat: Stat, computableGroup: ComputableGroup) { |
||||||
|
|
||||||
|
showLoader() |
||||||
|
|
||||||
|
GlobalScope.launch(coroutineContext) { |
||||||
|
|
||||||
|
var report: Report? = null |
||||||
|
val test = GlobalScope.async { |
||||||
|
val s = Date() |
||||||
|
Timber.d(">>> start...") |
||||||
|
|
||||||
|
val realm = Realm.getDefaultInstance() |
||||||
|
|
||||||
|
val aggregationType = stat.aggregationTypes.first() |
||||||
|
report = Calculator.computeStatsWithEvolutionByAggregationType(realm, stat, computableGroup, aggregationType) |
||||||
|
|
||||||
|
realm.close() |
||||||
|
|
||||||
|
val e = Date() |
||||||
|
val duration = (e.time - s.time) / 1000.0 |
||||||
|
Timber.d(">>> ended in $duration seconds") |
||||||
|
|
||||||
|
} |
||||||
|
test.await() |
||||||
|
|
||||||
|
if (!isDetached) { |
||||||
|
hideLoader() |
||||||
|
report?.let { |
||||||
|
StatisticDetailsActivity.newInstance(requireContext(), stat, computableGroup, it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,53 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment.components |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.fragment.app.DialogFragment |
||||||
|
import kotlinx.android.synthetic.main.fragment_loader.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
|
||||||
|
|
||||||
|
class LoaderDialogFragment: DialogFragment() { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
const val ARGUMENT_MESSAGE_RES_ID = "ARGUMENT_MESSAGE_RES_ID" |
||||||
|
|
||||||
|
/** |
||||||
|
* Create new instance |
||||||
|
*/ |
||||||
|
fun newInstance(resId: Int? = null, isCancelable: Boolean = false): LoaderDialogFragment { |
||||||
|
val fragment = LoaderDialogFragment() |
||||||
|
fragment.isCancelable = isCancelable |
||||||
|
val bundle = Bundle() |
||||||
|
resId?.let { |
||||||
|
bundle.putInt(ARGUMENT_MESSAGE_RES_ID, resId) |
||||||
|
} |
||||||
|
fragment.arguments = bundle |
||||||
|
return fragment |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_loader, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
arguments?.let {bundle -> |
||||||
|
if (bundle.containsKey(ARGUMENT_MESSAGE_RES_ID)) { |
||||||
|
loadingMessage.text = getString(bundle.getInt(ARGUMENT_MESSAGE_RES_ID)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onStart() { |
||||||
|
super.onStart() |
||||||
|
val window = dialog.window |
||||||
|
window?.setBackgroundDrawableResource(android.R.color.transparent) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
package net.pokeranalytics.android.ui.graph |
||||||
|
|
||||||
|
import com.github.mikephil.charting.components.AxisBase |
||||||
|
import com.github.mikephil.charting.formatter.ValueFormatter |
||||||
|
import net.pokeranalytics.android.util.extensions.kmbFormatted |
||||||
|
import kotlin.math.roundToInt |
||||||
|
|
||||||
|
class LargeNumberFormatter : ValueFormatter() { |
||||||
|
|
||||||
|
override fun getFormattedValue(value: Float): String { |
||||||
|
return value.kmbFormatted |
||||||
|
} |
||||||
|
|
||||||
|
override fun getAxisLabel(value: Float, axis: AxisBase?): String { |
||||||
|
return value.roundToInt().kmbFormatted |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class HourFormatter : ValueFormatter() { |
||||||
|
|
||||||
|
override fun getFormattedValue(value: Float): String { |
||||||
|
return value.kmbFormatted + "H" |
||||||
|
} |
||||||
|
|
||||||
|
override fun getAxisLabel(value: Float, axis: AxisBase?): String { |
||||||
|
val test = value.kmbFormatted + "H" |
||||||
|
return test |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,30 @@ |
|||||||
|
package net.pokeranalytics.android.ui.graph |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import com.github.mikephil.charting.data.Entry |
||||||
|
import com.github.mikephil.charting.data.LineDataSet |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
|
||||||
|
class PALineDataSet(yVals: List<Entry>, label: String, context: Context) : LineDataSet(yVals, label) { |
||||||
|
|
||||||
|
init { |
||||||
|
this.highLightColor = context.getColor(R.color.chart_highlight_indicator) |
||||||
|
this.setDrawValues(false) |
||||||
|
this.setDrawCircles(false) |
||||||
|
|
||||||
|
val colors = arrayOf(R.color.green_light).toIntArray() |
||||||
|
this.setColors(colors, context) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//class PABarDataSet(yVals: List<BarEntry>, label: String, context: Context) : BarDataSet(yVals, label) { |
||||||
|
// |
||||||
|
// init { |
||||||
|
// this.highLightColor = context.getColor(R.color.chart_highlight_indicator) |
||||||
|
// } |
||||||
|
// |
||||||
|
//} |
||||||
@ -1,66 +1,83 @@ |
|||||||
package net.pokeranalytics.android.ui.graph |
package net.pokeranalytics.android.ui.graph |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import androidx.core.content.ContextCompat |
||||||
|
import androidx.core.content.res.ResourcesCompat |
||||||
import com.github.mikephil.charting.charts.BarChart |
import com.github.mikephil.charting.charts.BarChart |
||||||
import com.github.mikephil.charting.charts.BarLineChartBase |
import com.github.mikephil.charting.charts.BarLineChartBase |
||||||
import com.github.mikephil.charting.charts.LineChart |
import com.github.mikephil.charting.components.XAxis |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.ui.extensions.px |
||||||
|
|
||||||
fun BarChart.setStyle() { |
enum class AxisFormatting { |
||||||
GraphHelper.setStyle(this) |
DEFAULT, |
||||||
|
X_DURATION, |
||||||
|
Y_DURATION, |
||||||
} |
} |
||||||
|
|
||||||
fun LineChart.setStyle() { |
fun BarLineChartBase<*>.setStyle( |
||||||
GraphHelper.setStyle(this) |
small: Boolean, |
||||||
} |
axisFormatting: AxisFormatting = AxisFormatting.DEFAULT, |
||||||
|
context: Context |
||||||
|
) { |
||||||
|
|
||||||
|
this.legend.isEnabled = false |
||||||
|
this.description.isEnabled = false |
||||||
|
|
||||||
class GraphHelper { |
// X Axis |
||||||
|
this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.chart_default) |
||||||
|
this.xAxis.enableGridDashedLine(3.0f.px, 5.0f.px, 1.0f.px) |
||||||
|
this.xAxis.position = XAxis.XAxisPosition.BOTTOM |
||||||
|
this.xAxis.setDrawGridLines(true) |
||||||
|
this.xAxis.isGranularityEnabled = true |
||||||
|
this.xAxis.granularity = 1.0f |
||||||
|
this.xAxis.textColor = ContextCompat.getColor(context, R.color.chart_default) |
||||||
|
this.xAxis.typeface = ResourcesCompat.getFont(context, R.font.roboto_medium) |
||||||
|
this.xAxis.labelCount = 4 |
||||||
|
this.xAxis.textSize = 12f |
||||||
|
this.xAxis.isEnabled = true |
||||||
|
|
||||||
companion object { |
when (this) { |
||||||
|
is BarChart -> { |
||||||
|
this.xAxis.setDrawLabels(false) |
||||||
|
} |
||||||
|
else -> { |
||||||
|
this.xAxis.setDrawLabels(true) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
fun setStyle(chart: BarLineChartBase<*>) { |
// Y Axis |
||||||
|
this.axisLeft.setDrawAxisLine(false) |
||||||
|
this.axisLeft.setDrawGridLines(true) |
||||||
|
this.axisLeft.enableGridDashedLine(3.0f.px, 5.0f.px, 1.0f.px) |
||||||
|
|
||||||
// this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.) ChartAppearance.defaultColor |
this.axisLeft.setDrawZeroLine(true) |
||||||
// this.xAxis.axisLineWidth = ChartAppearance.lineWidth |
this.axisLeft.zeroLineColor = ContextCompat.getColor(context, R.color.chart_default) |
||||||
// this.xAxis.enableGridDashedLine(3.0f, 5.0f, 1.0f) |
|
||||||
// |
|
||||||
// this.xAxis.labelTextColor = ChartAppearance.defaultColor |
|
||||||
// this.xAxis.labelFont = Fonts.graphAxis |
|
||||||
// this.xAxis.labelCount = 4 |
|
||||||
// this.xAxis.labelPosition = .bottom |
|
||||||
// |
|
||||||
// this.xAxis.drawLabelsEnabled = true |
|
||||||
// this.xAxis.drawGridLinesEnabled = true |
|
||||||
// this.xAxis.granularity = 1.0 |
|
||||||
// this.xAxis.granularityEnabled = true |
|
||||||
// this.xAxis.enabled = true |
|
||||||
// |
|
||||||
// // Y Axis |
|
||||||
// this.leftAxis.drawAxisLineEnabled = false |
|
||||||
// this.leftAxis.drawGridLinesEnabled = true |
|
||||||
// this.leftAxis.gridLineDashLengths = [3.0, 5.0] |
|
||||||
// |
|
||||||
// this.leftAxis.drawZeroLineEnabled = true |
|
||||||
// this.leftAxis.zeroLineWidth = ChartAppearance.lineWidth |
|
||||||
// this.leftAxis.zeroLineColor = ChartAppearance.defaultColor |
|
||||||
// |
|
||||||
// this.leftAxis.granularityEnabled = true |
|
||||||
// this.leftAxis.granularity = 1.0 |
|
||||||
// |
|
||||||
// this.leftAxis.labelTextColor = ChartAppearance.defaultColor |
|
||||||
// this.leftAxis.labelFont = Fonts.graphAxis |
|
||||||
// this.leftAxis.labelCount = small ? 1 : 7 // @todo not great if interval is [0..2] for number of records as we get decimals |
|
||||||
// |
|
||||||
// if timeYAxis { |
|
||||||
// this.leftAxis.valueFormatter = HourValueFormatter() |
|
||||||
// } else { |
|
||||||
// this.leftAxis.valueFormatter = LargeNumberFormatter() |
|
||||||
// } |
|
||||||
// |
|
||||||
// this.rightAxis.enabled = false |
|
||||||
// |
|
||||||
// this.legend.enabled = false |
|
||||||
|
|
||||||
} |
this.axisLeft.isGranularityEnabled = true |
||||||
|
this.axisLeft.granularity = 1.0f |
||||||
|
|
||||||
|
this.axisLeft.textColor = ContextCompat.getColor(context, R.color.chart_default) |
||||||
|
this.axisLeft.typeface = ResourcesCompat.getFont(context, R.font.roboto_medium) |
||||||
|
this.axisLeft.labelCount = |
||||||
|
if (small) 1 else 7 // @todo not great if interval is [0..2] for number of records as we get decimals |
||||||
|
this.axisLeft.textSize = 12f |
||||||
|
this.axisLeft.valueFormatter = LargeNumberFormatter() |
||||||
|
|
||||||
|
this.axisRight.isEnabled = false |
||||||
|
|
||||||
|
this.data?.isHighlightEnabled = !small |
||||||
|
|
||||||
|
when (axisFormatting) { |
||||||
|
AxisFormatting.DEFAULT -> { |
||||||
|
this.axisLeft.valueFormatter = LargeNumberFormatter() |
||||||
|
} |
||||||
|
AxisFormatting.X_DURATION -> { |
||||||
|
this.xAxis.valueFormatter = HourFormatter() |
||||||
|
} |
||||||
|
AxisFormatting.Y_DURATION -> { |
||||||
|
this.axisLeft.valueFormatter = HourFormatter() |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,38 @@ |
|||||||
|
package net.pokeranalytics.android.ui.graph |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import com.github.mikephil.charting.data.Entry |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.calculus.TextFormat |
||||||
|
import net.pokeranalytics.android.ui.fragment.GraphFragment |
||||||
|
import net.pokeranalytics.android.ui.view.DefaultLegendValues |
||||||
|
import net.pokeranalytics.android.ui.view.LegendContent |
||||||
|
|
||||||
|
interface GraphUnderlyingEntry { |
||||||
|
|
||||||
|
val entryTitle: String |
||||||
|
fun formattedValue(stat: Stat): TextFormat |
||||||
|
|
||||||
|
fun legendValues( |
||||||
|
stat: Stat, |
||||||
|
entry: Entry, |
||||||
|
style: GraphFragment.Style, |
||||||
|
groupName: String, |
||||||
|
context: Context |
||||||
|
): LegendContent { |
||||||
|
|
||||||
|
return when (stat) { |
||||||
|
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { |
||||||
|
val totalStatValue = stat.format(entry.y.toDouble(), currency = null) |
||||||
|
DefaultLegendValues(this.entryTitle, totalStatValue) |
||||||
|
} |
||||||
|
else -> { |
||||||
|
val entryValue = this.formattedValue(stat) |
||||||
|
val totalStatValue = stat.format(entry.y.toDouble(), currency = null) |
||||||
|
DefaultLegendValues(this.entryTitle, entryValue, totalStatValue) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
package net.pokeranalytics.android.ui.view |
||||||
|
|
||||||
|
import net.pokeranalytics.android.R |
||||||
|
|
||||||
|
|
||||||
|
enum class CalendarTabs : Displayable { |
||||||
|
NET_RESULTS, |
||||||
|
NET_HOURLY_RATE, |
||||||
|
NUMBER_OF_GAMES, |
||||||
|
WIN_RATIO, |
||||||
|
STANDARD_DEVIATION_PER_HOUR, |
||||||
|
AVERAGE_NET_RESULT, |
||||||
|
AVERAGE_DURATION, |
||||||
|
DURATION_OF_PLAY; |
||||||
|
|
||||||
|
override val resId: Int |
||||||
|
get() { |
||||||
|
return when (this) { |
||||||
|
NET_RESULTS -> R.string.net_result |
||||||
|
NET_HOURLY_RATE -> R.string.hour_rate_without_pauses |
||||||
|
NUMBER_OF_GAMES -> R.string.number_of_records |
||||||
|
WIN_RATIO -> R.string.win_ratio |
||||||
|
STANDARD_DEVIATION_PER_HOUR -> R.string.standard_deviation_per_hour |
||||||
|
AVERAGE_NET_RESULT -> R.string.average_net_result |
||||||
|
AVERAGE_DURATION -> R.string.average_hours_played |
||||||
|
DURATION_OF_PLAY -> R.string.total_hours_played |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,119 @@ |
|||||||
|
package net.pokeranalytics.android.ui.view |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.util.AttributeSet |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.widget.FrameLayout |
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout |
||||||
|
import androidx.core.view.isVisible |
||||||
|
import kotlinx.android.synthetic.main.layout_legend_default.view.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.calculus.TextFormat |
||||||
|
import net.pokeranalytics.android.ui.extensions.setTextFormat |
||||||
|
import net.pokeranalytics.android.ui.fragment.GraphFragment |
||||||
|
|
||||||
|
interface LegendContent |
||||||
|
|
||||||
|
data class DefaultLegendValues( |
||||||
|
var title: String, |
||||||
|
var leftFormat: TextFormat, |
||||||
|
var rightFormat: TextFormat? = null |
||||||
|
) : LegendContent |
||||||
|
|
||||||
|
/** |
||||||
|
* Display a row session |
||||||
|
*/ |
||||||
|
open class LegendView : FrameLayout { |
||||||
|
|
||||||
|
// open class Values(var title: String, var leftFormat: TextFormat, var rightFormat: TextFormat? = null) |
||||||
|
// class MultiLineValues( |
||||||
|
// var firstTitle: String, |
||||||
|
// var secondTitle: String, |
||||||
|
// leftFormat: TextFormat, |
||||||
|
// rightFormat: TextFormat? = null |
||||||
|
// ) : Values("", leftFormat, rightFormat) |
||||||
|
|
||||||
|
private lateinit var legendLayout: ConstraintLayout |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructors |
||||||
|
*/ |
||||||
|
constructor(context: Context) : super(context) { |
||||||
|
init() |
||||||
|
} |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { |
||||||
|
init() |
||||||
|
} |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { |
||||||
|
init() |
||||||
|
} |
||||||
|
|
||||||
|
open protected fun getResourceLayout(): Int { |
||||||
|
return R.layout.layout_legend_default |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init |
||||||
|
*/ |
||||||
|
private fun init() { |
||||||
|
val layoutInflater = LayoutInflater.from(context) |
||||||
|
legendLayout = layoutInflater.inflate(this.getResourceLayout(), this, false) as ConstraintLayout |
||||||
|
val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) |
||||||
|
addView(legendLayout, layoutParams) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the stat data to the view |
||||||
|
*/ |
||||||
|
open fun prepareWithStat(stat: Stat, counter: Int? = null, style: GraphFragment.Style) { |
||||||
|
|
||||||
|
when (style) { |
||||||
|
GraphFragment.Style.BAR -> { |
||||||
|
this.stat1Name.text = stat.localizedTitle(context) |
||||||
|
this.stat2Name.text = context.getString(R.string.sessions) |
||||||
|
this.counter.isVisible = false |
||||||
|
} |
||||||
|
GraphFragment.Style.LINE -> { |
||||||
|
if (stat.significantIndividualValue) { |
||||||
|
this.stat1Name.text = stat.localizedTitle(context) |
||||||
|
this.stat2Name.text = stat.cumulativeLabelResId(context) |
||||||
|
} else { |
||||||
|
this.stat1Name.text = stat.cumulativeLabelResId(context) |
||||||
|
this.stat2Name.isVisible = false |
||||||
|
} |
||||||
|
|
||||||
|
counter?.let { |
||||||
|
val counterText = "$it ${context.getString(R.string.sessions)}" |
||||||
|
this.counter.text = counterText |
||||||
|
this.counter.isVisible = stat.shouldShowNumberOfSessions |
||||||
|
} |
||||||
|
} |
||||||
|
else -> { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
*/ |
||||||
|
open fun setItemData(content: LegendContent) { |
||||||
|
|
||||||
|
if (content is DefaultLegendValues) { |
||||||
|
|
||||||
|
this.title.text = content.title |
||||||
|
|
||||||
|
this.stat1Value.setTextFormat(content.leftFormat, context) |
||||||
|
content.rightFormat?.let { |
||||||
|
this.stat2Value.setTextFormat(it, context) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
package net.pokeranalytics.android.ui.view |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import kotlinx.android.synthetic.main.layout_legend_default.view.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.calculus.TextFormat |
||||||
|
import net.pokeranalytics.android.ui.extensions.setTextFormat |
||||||
|
import net.pokeranalytics.android.ui.fragment.GraphFragment |
||||||
|
|
||||||
|
data class MultilineLegendValues( |
||||||
|
var firstTitle: String, |
||||||
|
var secondTitle: String, |
||||||
|
var leftFormat: TextFormat, |
||||||
|
var rightFormat: TextFormat |
||||||
|
) : LegendContent |
||||||
|
|
||||||
|
class MultiLineLegendView(context: Context) : LegendView(context = context) { |
||||||
|
|
||||||
|
override fun getResourceLayout(): Int { |
||||||
|
return R.layout.layout_legend_color |
||||||
|
} |
||||||
|
|
||||||
|
override fun prepareWithStat(stat: Stat, counter: Int?, style: GraphFragment.Style) { |
||||||
|
} |
||||||
|
|
||||||
|
override fun setItemData(content: LegendContent) { |
||||||
|
|
||||||
|
if (content is MultilineLegendValues) { |
||||||
|
|
||||||
|
this.stat1Name.text = content.firstTitle |
||||||
|
this.stat2Name.text = content.secondTitle |
||||||
|
|
||||||
|
this.stat1Value.setTextFormat(content.leftFormat, context) |
||||||
|
this.stat2Value.setTextFormat(content.rightFormat, context) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue