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 |
||||
|
||||
import io.realm.RealmList |
||||
import io.realm.Realm |
||||
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.model.realm.FilterCondition |
||||
import net.pokeranalytics.android.model.realm.FilterElementBlind |
||||
import net.pokeranalytics.android.model.realm.Session |
||||
import net.pokeranalytics.android.model.Limit |
||||
import net.pokeranalytics.android.model.TableSize |
||||
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.startOfDay |
||||
import net.pokeranalytics.android.util.extensions.toCurrency |
||||
import java.text.DateFormatSymbols |
||||
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 |
||||
* 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, |
||||
CASH, |
||||
ONLINE, |
||||
TOURNAMENT, |
||||
BANKROLL, |
||||
GAME, |
||||
TOURNAMENT_NAME, |
||||
ANY_TOURNAMENT_FEATURES, |
||||
ALL_TOURNAMENT_FEATURES, |
||||
LOCATION, |
||||
LIMIT, |
||||
TABLE_SIZE, |
||||
TOURNAMENT_TYPE, |
||||
BLINDS, |
||||
LAST_GAMES, |
||||
LAST_SESSIONS, |
||||
MORE_NUMBER_OF_TABLE(Operator.MORE), |
||||
LESS_NUMBER_OF_TABLE(Operator.LESS), |
||||
BETWEEN_NUMBER_OF_TABLE(Operator.BETWEEN), |
||||
MORE_THAN_NET_RESULT(Operator.MORE), |
||||
LESS_THAN_NET_RESULT(Operator.LESS), |
||||
MORE_THAN_BUY_IN(Operator.MORE), |
||||
LESS_THAN_BUY_IN(Operator.LESS), |
||||
MORE_THAN_CASH_OUT(Operator.MORE), |
||||
LESS_THAN_CASH_OUT(Operator.LESS), |
||||
MORE_THAN_TIPS(Operator.MORE), |
||||
LESS_THAN_TIPS(Operator.LESS), |
||||
MORE_THAN_NUMBER_OF_PLAYER(Operator.MORE), |
||||
LESS_THAN_NUMBER_OF_PLAYER(Operator.LESS), |
||||
BETWEEN_NUMBER_OF_PLAYER(Operator.BETWEEN), |
||||
MORE_THAN_TOURNAMENT_FEE(Operator.MORE), |
||||
LESS_THAN_TOURNAMENT_FEE(Operator.LESS), |
||||
BETWEEN_TOURNAMENT_FEE(Operator.BETWEEN), |
||||
MIN_RE_BUY(Operator.MORE), |
||||
MAX_RE_BUY(Operator.LESS), |
||||
|
||||
// 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, |
||||
|
||||
; |
||||
|
||||
sealed class QueryCondition : FilterElementRow { |
||||
companion object { |
||||
inline fun < reified T:QueryCondition> more():T { return T::class.java.newInstance().apply { this.operator = Operator.MORE } } |
||||
inline fun < reified T:QueryCondition> less():T { return T::class.java.newInstance().apply { this.operator = Operator.LESS } } |
||||
inline fun < reified T:QueryCondition> moreOrLess():ArrayList<T> { return arrayListOf(more(), less()) } |
||||
|
||||
fun <T:QueryCondition> valueOf(name:String) : T { |
||||
val kClass = Class.forName("${QueryCondition::class.qualifiedName}$$name").kotlin |
||||
val instance = kClass.objectInstance ?: kClass.java.newInstance() |
||||
return instance as T |
||||
} |
||||
|
||||
inline fun <reified T:Identifiable>getInstance(): QueryCondition { |
||||
return when (T::class.java) { |
||||
Bankroll::class.java -> AnyBankroll() |
||||
Game::class.java -> AnyGame() |
||||
Location::class.java -> AnyLocation() |
||||
TournamentName::class.java -> AnyTournamentName() |
||||
TournamentFeature::class.java -> AnyTournamentFeature() |
||||
else -> throw PokerAnalyticsException.QueryTypeUnhandled |
||||
} |
||||
} |
||||
|
||||
inline fun < reified T: Filterable, reified S: QueryCondition, reified U:Comparable<U>>distinct(): RealmResults<T>? { |
||||
FilterHelper.fieldNameForQueryType<T>(S::class.java)?.let { |
||||
val realm = Realm.getDefaultInstance() |
||||
|
||||
val distincts = when (T::class) { |
||||
String::class, Int::class -> realm.where<T>().distinct(it).findAll().sort(it, Sort.ASCENDING) |
||||
else -> realm.where<T>().isNotNull(it).findAll().sort(it, Sort.ASCENDING) |
||||
} |
||||
|
||||
realm.close() |
||||
return distincts |
||||
} |
||||
return null |
||||
} |
||||
} |
||||
|
||||
enum class Operator { |
||||
BETWEEN, |
||||
ANY, |
||||
ALL, |
||||
MORE, |
||||
LESS; |
||||
LESS, |
||||
EQUALS, |
||||
BETWEEN, |
||||
BETWEEN_RIGHT_EXCLUSIVE, |
||||
BETWEEN_LEFT_EXCLUSIVE, |
||||
; |
||||
} |
||||
|
||||
var valueMap : Map<String, Any?>? = null |
||||
get() { |
||||
this.filterValuesExpectedKeys?.let { valueMapExceptedKeys -> |
||||
field?.let { map -> |
||||
val missingKeys = map.keys.filter { !valueMapExceptedKeys.contains(it) } |
||||
if (map.keys.size == valueMapExceptedKeys.size && missingKeys.isNotEmpty()) { |
||||
throw PokerAnalyticsException.QueryValueMapMissingKeys(missingKeys) |
||||
} |
||||
} ?: run { |
||||
throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
||||
} |
||||
} |
||||
return field |
||||
val baseId = this::class.simpleName ?: throw PokerAnalyticsException.FilterElementUnknownName |
||||
|
||||
val id: List<String> get() { |
||||
when (this.operator) { |
||||
Operator.MORE, Operator.LESS -> return listOf("$baseId+${this.operator.name}") |
||||
} |
||||
private set |
||||
|
||||
private val filterValuesExpectedKeys : Array<String>? |
||||
get() { |
||||
this.operator?.let { |
||||
return when (it) { |
||||
Operator.BETWEEN -> arrayOf("leftValue", "rightValue") |
||||
else -> arrayOf("value") |
||||
} |
||||
return when (this) { |
||||
is SingleValue<*> -> listOf(baseId) |
||||
is ListOfValues<*> -> { |
||||
if (listOfValues.isEmpty()) { return listOf(baseId) } |
||||
this.listOfValues.map{ "$baseId+$it" } |
||||
} |
||||
return when (this) { |
||||
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") |
||||
DAY_OF_WEEK -> arrayOf("dayOfWeek") |
||||
MONTH -> arrayOf("month") |
||||
YEAR -> arrayOf("year") |
||||
else -> null |
||||
else -> listOf(baseId) |
||||
} |
||||
} |
||||
|
||||
open var operator: Operator = Operator.ANY |
||||
|
||||
abstract class ListOfValues<T>: QueryCondition(), Comparable<ListOfValues<T>> where T:Comparable<T> { |
||||
abstract var listOfValues: ArrayList<T> |
||||
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" |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* main method of the enum |
||||
* providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery] |
||||
*/ |
||||
inline fun <reified T : Filterable> filter(realmQuery: RealmQuery<T>): RealmQuery<T> { |
||||
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() |
||||
} |
||||
override fun compareTo(other: ListOfValues<T>): Int { |
||||
return listOfValues.sorted().first().compareTo(other.listOfValues.sorted().first()) |
||||
} |
||||
} |
||||
|
||||
realmQuery |
||||
.equalTo(bigBlindFieldName, blind.bb) |
||||
.and() |
||||
abstract class SingleValue<T>: ListOfValues<T>() where T:Comparable<T> { |
||||
override var listOfValues = ArrayList<T>() |
||||
abstract var singleValue : T |
||||
} |
||||
|
||||
blind.currencyCode?.let { |
||||
realmQuery.equalTo(currencyCodeFieldName, it) |
||||
} ?: run { |
||||
realmQuery.isNull(currencyCodeFieldName) |
||||
} |
||||
abstract class ListOfDouble: ListOfValues<Double>() { |
||||
open var sign: Int = 1 |
||||
|
||||
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) { |
||||
realmQuery.or() |
||||
} |
||||
} |
||||
return realmQuery |
||||
} |
||||
else -> { |
||||
abstract class ListOfInt: ListOfValues<Int>() { |
||||
override var listOfValues = arrayListOf(0) |
||||
override fun updateValueMap(filterCondition: FilterCondition) { |
||||
super.updateValueMap(filterCondition) |
||||
println("<<<< updateValueMap ${filterCondition.intValues}") |
||||
listOfValues = filterCondition.getValues() |
||||
} |
||||
override fun labelForValue(value: Int): String { |
||||
return value.toString() |
||||
} |
||||
} |
||||
|
||||
val fieldName = FilterHelper.fieldNameForQueryType<T>(this) |
||||
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown |
||||
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() |
||||
} |
||||
} |
||||
|
||||
when (operator) { |
||||
Operator.LESS -> { |
||||
val value: Double by valueMap |
||||
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) |
||||
} |
||||
} |
||||
abstract class SingleDate: SingleValue<Date>() { |
||||
override fun labelForValue(value: Date): String { |
||||
return value.toString() |
||||
} |
||||
|
||||
return when (this) { |
||||
LIVE, ONLINE -> realmQuery.equalTo(fieldName, this == LIVE) |
||||
CASH -> realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal) |
||||
TOURNAMENT -> realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal) |
||||
ALL_TOURNAMENT_FEATURES -> { |
||||
val ids: Array<String> by valueMap |
||||
ids.forEach { |
||||
realmQuery.equalTo(fieldName, it) |
||||
} |
||||
realmQuery |
||||
} |
||||
ANY_TOURNAMENT_FEATURES -> { |
||||
val ids: Array<String> by valueMap |
||||
realmQuery.`in`(fieldName, ids) |
||||
} |
||||
BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> { |
||||
val ids: Array<String> by valueMap |
||||
realmQuery.`in`(fieldName, ids) |
||||
} |
||||
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> { |
||||
val values: Array<Int?>? by valueMap |
||||
realmQuery.`in`(fieldName, values) |
||||
} |
||||
STARTED_FROM_DATE -> { |
||||
val date: Date by valueMap |
||||
realmQuery.greaterThanOrEqualTo(fieldName, date) |
||||
} |
||||
STARTED_TO_DATE -> { |
||||
val date: Date by valueMap |
||||
realmQuery.lessThanOrEqualTo(fieldName, date) |
||||
} |
||||
ENDED_FROM_DATE -> { |
||||
val date: Date by valueMap |
||||
realmQuery.greaterThanOrEqualTo(fieldName, date) |
||||
} |
||||
ENDED_TO_DATE -> { |
||||
val date: Date by valueMap |
||||
realmQuery.lessThanOrEqualTo(fieldName, date) |
||||
} |
||||
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 |
||||
} |
||||
override var singleValue: Date |
||||
get() { return listOfValues.firstOrNull() ?: Date() } |
||||
set(value) { listOfValues.add(value) } |
||||
|
||||
override fun updateValueMap(filterCondition: FilterCondition) { |
||||
super.updateValueMap(filterCondition) |
||||
singleValue = filterCondition.getValue() |
||||
} |
||||
} |
||||
|
||||
abstract class SingleInt: SingleValue<Int>() { |
||||
override fun labelForValue(value: Int): String { |
||||
return value.toString() |
||||
} |
||||
override var singleValue: Int |
||||
get() { return listOfValues.firstOrNull() ?: 0 } |
||||
set(value) { listOfValues.add(value) } |
||||
|
||||
override fun updateValueMap(filterCondition: FilterCondition) { |
||||
super.updateValueMap(filterCondition) |
||||
singleValue = filterCondition.getValue() |
||||
} |
||||
} |
||||
|
||||
override fun getDisplayName(): String { return baseId } |
||||
|
||||
override var filterSectionRow: FilterSectionRow = FilterSectionRow.CASH_TOURNAMENT |
||||
|
||||
abstract class QueryDataCondition < T: NameManageable > : ListOfString() { |
||||
fun setObject(dataObject: T) { |
||||
this.listOfValues.removeAll(this.listOfValues) |
||||
this.listOfValues.add(dataObject.id) |
||||
} |
||||
|
||||
abstract val entity : Class<T> |
||||
|
||||
override fun getDisplayName(): String { |
||||
val realm = Realm.getDefaultInstance() |
||||
val completeLabel = when (listOfValues.size) { |
||||
0 -> return NULL_TEXT |
||||
1,2 -> { |
||||
return listOfValues.map { labelForValue(realm, it) }.joinToString(", ") |
||||
} |
||||
else -> "${listOfValues.size} $baseId" |
||||
} |
||||
realm.close() |
||||
return completeLabel |
||||
} |
||||
|
||||
private fun labelForValue(realm:Realm, value:String): String { |
||||
val query = realm.where(entity) |
||||
return query.equalTo("id", value).findFirst()?.name ?: NULL_TEXT |
||||
} |
||||
} |
||||
|
||||
|
||||
interface DateTime { |
||||
val showTime: Boolean |
||||
} |
||||
|
||||
abstract class DateQuery: SingleDate(), DateTime { |
||||
override val showTime: Boolean = false |
||||
|
||||
} |
||||
|
||||
abstract class TimeQuery: DateQuery() { |
||||
override val showTime: Boolean = true |
||||
} |
||||
|
||||
object IsLive : QueryCondition() { |
||||
override fun getDisplayName(): String { return "Live" } |
||||
} |
||||
|
||||
object IsCash : QueryCondition() { |
||||
override fun getDisplayName(): String { return "Cash" } |
||||
} |
||||
|
||||
object IsOnline : QueryCondition() { |
||||
override fun getDisplayName(): String { return "Online" } |
||||
} |
||||
|
||||
object IsTournament : QueryCondition() { |
||||
override fun getDisplayName(): String { return "Tournament" } |
||||
} |
||||
|
||||
class AnyBankroll(): QueryDataCondition<Bankroll>() { |
||||
override var entity: Class<Bankroll> = Bankroll::class.java |
||||
constructor(bankroll: Bankroll): this() { |
||||
this.setObject(bankroll) |
||||
} |
||||
} |
||||
class AnyGame(): QueryDataCondition<Game>() { |
||||
override val entity: Class<Game> = Game::class.java |
||||
constructor(game: Game): this() { |
||||
this.setObject(game) |
||||
} |
||||
} |
||||
|
||||
class AnyTournamentName(): QueryDataCondition<TournamentName>() { |
||||
override val entity: Class<TournamentName> = TournamentName::class.java |
||||
constructor(tournamentName: TournamentName): this() { |
||||
this.setObject(tournamentName) |
||||
} |
||||
} |
||||
|
||||
fun updateValueMap(filterCondition: FilterCondition) { |
||||
if (filterValuesExpectedKeys == null) { |
||||
return |
||||
class AnyTournamentFeature(): QueryDataCondition<TournamentFeature>() { |
||||
override val entity: Class<TournamentFeature> = TournamentFeature::class.java |
||||
constructor(tournamentFeature: TournamentFeature): this() { |
||||
this.setObject(tournamentFeature) |
||||
} |
||||
} |
||||
|
||||
this.operator?.let { |
||||
valueMap = mapOf("value" to filterCondition.value) |
||||
return |
||||
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 |
||||
} |
||||
|
||||
class StartedFromTime: TimeQuery() { |
||||
override var operator = Operator.MORE |
||||
init { |
||||
this.singleValue = Date().startOfDay() |
||||
} |
||||
} |
||||
|
||||
class EndedToTime: TimeQuery() { |
||||
override var operator = Operator.LESS |
||||
init { |
||||
this.singleValue = Date().endOfDay() |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* main method of the enum |
||||
* providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery] |
||||
*/ |
||||
inline fun <reified T : Filterable> queryWith(realmQuery: RealmQuery<T>): RealmQuery<T> { |
||||
val fieldName = FilterHelper.fieldNameForQueryType<T>(this::class.java) |
||||
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown |
||||
|
||||
when (this) { |
||||
ALL_TOURNAMENT_FEATURES, ANY_TOURNAMENT_FEATURES, BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> { |
||||
valueMap = mapOf("ids" to filterCondition.ids) |
||||
//is Between -> realmQuery.between(fieldName, leftValue, rightValue) |
||||
//is BetweenLeftExclusive -> realmQuery.greaterThan(fieldName, leftValue).and().lessThanOrEqualTo(fieldName, rightValue) |
||||
//is BetweenRightExclusive -> realmQuery.greaterThanOrEqualTo(fieldName, leftValue).and().lessThan(fieldName, rightValue) |
||||
IsLive, IsOnline -> return realmQuery.equalTo(fieldName, this == IsLive) |
||||
IsCash -> return realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal) |
||||
IsTournament -> return realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal) |
||||
IsWeekEnd, IsWeekDay -> { |
||||
var query = realmQuery |
||||
if (this == IsWeekDay) { |
||||
query = realmQuery.not() |
||||
} |
||||
return query.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY)) |
||||
} |
||||
IsToday -> { |
||||
val startDate = Date() |
||||
return realmQuery.between(fieldName, startDate.startOfDay(), startDate.endOfDay()) |
||||
} |
||||
WasTodayAndYesterday-> { |
||||
val startDate = Date() |
||||
val calendar = Calendar.getInstance() |
||||
calendar.time = startDate |
||||
calendar.add(Calendar.HOUR_OF_DAY, -24) |
||||
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
||||
} |
||||
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> { |
||||
valueMap = mapOf("values" to filterCondition.values) |
||||
WasYesterday -> { |
||||
val calendar = Calendar.getInstance() |
||||
calendar.time = Date() |
||||
calendar.add(Calendar.HOUR_OF_DAY, -24) |
||||
return realmQuery.between(fieldName, calendar.time.startOfDay(), calendar.time.endOfDay()) |
||||
} |
||||
BLINDS -> { |
||||
valueMap = mapOf("blinds" to filterCondition.blinds) |
||||
DuringThisWeek -> { |
||||
val startDate = Date() |
||||
val calendar = Calendar.getInstance() |
||||
calendar.time = startDate |
||||
calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY) |
||||
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
||||
} |
||||
STARTED_FROM_DATE, STARTED_TO_DATE, ENDED_FROM_DATE, ENDED_TO_DATE -> { |
||||
valueMap = mapOf("date" to filterCondition.date) |
||||
DuringThisMonth -> { |
||||
val startDate = Date() |
||||
val calendar = Calendar.getInstance() |
||||
calendar.time = startDate |
||||
calendar.set(Calendar.DAY_OF_MONTH, 1) |
||||
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
||||
} |
||||
DuringThisYear -> { |
||||
val startDate = Date() |
||||
val calendar = Calendar.getInstance() |
||||
calendar.time = startDate |
||||
calendar.set(Calendar.DAY_OF_YEAR, 1) |
||||
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay()) |
||||
} |
||||
} |
||||
|
||||
return when (operator) { |
||||
Operator.EQUALS -> { |
||||
when (this) { |
||||
is SingleDate -> realmQuery.equalTo(fieldName, singleValue) |
||||
is SingleInt -> realmQuery.equalTo(fieldName, singleValue) |
||||
is ListOfInt -> realmQuery.equalTo(fieldName, listOfValues.first()) |
||||
is ListOfDouble -> realmQuery.equalTo(fieldName, listOfValues.first() * sign) |
||||
is ListOfString -> realmQuery.equalTo(fieldName, listOfValues.first()) |
||||
else -> realmQuery |
||||
} |
||||
} |
||||
DAY_OF_WEEK -> { |
||||
valueMap = mapOf("dayOfWeek" to filterCondition.dayOfWeek) |
||||
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 |
||||
} |
||||
} |
||||
MONTH -> { |
||||
valueMap = mapOf("month" to filterCondition.month) |
||||
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 |
||||
} |
||||
} |
||||
YEAR -> { |
||||
valueMap = mapOf("year" to filterCondition.year) |
||||
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 |
||||
} |
||||
} |
||||
else -> { |
||||
throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
||||
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 |
||||
|
||||
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.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() { |
||||
GraphHelper.setStyle(this) |
||||
enum class AxisFormatting { |
||||
DEFAULT, |
||||
X_DURATION, |
||||
Y_DURATION, |
||||
} |
||||
|
||||
fun LineChart.setStyle() { |
||||
GraphHelper.setStyle(this) |
||||
} |
||||
fun BarLineChartBase<*>.setStyle( |
||||
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.xAxis.axisLineWidth = ChartAppearance.lineWidth |
||||
// this.xAxis.enableGridDashedLine(3.0f, 5.0f, 1.0f) |
||||
// |
||||
// this.xAxis.labelTextColor = ChartAppearance.defaultColor |
||||
// this.xAxis.labelFont = Fonts.graphAxis |
||||
// this.xAxis.labelCount = 4 |
||||
// this.xAxis.labelPosition = .bottom |
||||
// |
||||
// this.xAxis.drawLabelsEnabled = true |
||||
// this.xAxis.drawGridLinesEnabled = true |
||||
// this.xAxis.granularity = 1.0 |
||||
// this.xAxis.granularityEnabled = true |
||||
// this.xAxis.enabled = true |
||||
// |
||||
// // Y Axis |
||||
// this.leftAxis.drawAxisLineEnabled = false |
||||
// this.leftAxis.drawGridLinesEnabled = true |
||||
// this.leftAxis.gridLineDashLengths = [3.0, 5.0] |
||||
// |
||||
// this.leftAxis.drawZeroLineEnabled = true |
||||
// this.leftAxis.zeroLineWidth = ChartAppearance.lineWidth |
||||
// this.leftAxis.zeroLineColor = ChartAppearance.defaultColor |
||||
// |
||||
// this.leftAxis.granularityEnabled = true |
||||
// this.leftAxis.granularity = 1.0 |
||||
// |
||||
// this.leftAxis.labelTextColor = ChartAppearance.defaultColor |
||||
// this.leftAxis.labelFont = Fonts.graphAxis |
||||
// this.leftAxis.labelCount = small ? 1 : 7 // @todo not great if interval is [0..2] for number of records as we get decimals |
||||
// |
||||
// if timeYAxis { |
||||
// this.leftAxis.valueFormatter = HourValueFormatter() |
||||
// } else { |
||||
// this.leftAxis.valueFormatter = LargeNumberFormatter() |
||||
// } |
||||
// |
||||
// this.rightAxis.enabled = false |
||||
// |
||||
// this.legend.enabled = false |
||||
this.axisLeft.setDrawZeroLine(true) |
||||
this.axisLeft.zeroLineColor = ContextCompat.getColor(context, R.color.chart_default) |
||||
|
||||
} |
||||
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