Add custom report launching + Savable enums + Tests

dev
Laurent 7 years ago
parent 3c26986a41
commit c78ede97fa
  1. 18
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  2. 16
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  3. 104
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  4. 5
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  5. 61
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  6. 4
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt
  7. 2
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  8. 8
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  9. 56
      app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt
  10. 5
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  11. 9
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  12. 2
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  13. 2
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt
  14. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  15. 8
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportCreationFragment.kt
  16. 77
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt
  17. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/TableReportFragment.kt
  18. 5
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt
  19. 4
      app/src/main/java/net/pokeranalytics/android/ui/view/LegendView.kt
  20. 2
      app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt
  21. 2
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  22. 23
      app/src/main/java/net/pokeranalytics/android/util/enumerations/SavableEnumeration.kt
  23. 2
      app/src/main/res/menu/navigation_home.xml
  24. 21
      app/src/test/java/net/pokeranalytics/android/SavableEnumTest.kt

@ -20,7 +20,7 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/** /**
* The class performing stats computation * The class performing statIds computation
*/ */
class Calculator { class Calculator {
@ -64,7 +64,7 @@ class Calculator {
} }
/** /**
* The way the stats are going to be displayed * The way the statIds are going to be displayed
*/ */
enum class Display : RowRepresentable { enum class Display : RowRepresentable {
TABLE, TABLE,
@ -139,7 +139,13 @@ class Calculator {
val rs = ReportSetup() val rs = ReportSetup()
rs.name = name rs.name = name
rs.display = this.display.ordinal rs.display = this.display.ordinal
this.stats.forEach {
rs.statIds.add(it.uniqueIdentifier)
}
this.criterias.forEach {
rs.criteriaIds.add(it.uniqueIdentifier)
}
rs.filter = this._filter
return rs return rs
} }
@ -207,7 +213,7 @@ class Calculator {
} }
/** /**
* Computes all stats for list of Session sessionGroup * Computes all statIds for list of Session sessionGroup
*/ */
fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options = Options()): Report { fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options = Options()): Report {
@ -218,7 +224,7 @@ class Calculator {
// Clean existing computables / sessionSets if group is reused // Clean existing computables / sessionSets if group is reused
group.cleanup() group.cleanup()
// Computes actual sessionGroup stats // Computes actual sessionGroup statIds
val results: ComputedResults = this.compute(realm, group, options) val results: ComputedResults = this.compute(realm, group, options)
// Computes the compared sessionGroup if existing // Computes the compared sessionGroup if existing
@ -246,7 +252,7 @@ class Calculator {
} }
/** /**
* Computes stats for a SessionSet * Computes statIds for a SessionSet
*/ */
fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options = Options()): ComputedResults { fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options = Options()): ComputedResults {

@ -97,9 +97,9 @@ class Report(var options: Calculator.Options) {
*/ */
class ComputableGroup(query: Query, stats: List<Stat>? = null) { class ComputableGroup(query: Query, stats: List<Stat>? = null) {
// constructor(query: Query, stats: List<Stat>? = null) : this(query.name, query.conditions) // constructor(query: Query, statIds: List<Stat>? = null) : this(query.name, query.conditions)
// //
// private constructor(name: String = "", conditions: List<QueryCondition> = listOf(), stats: List<Stat>? = null) // private constructor(name: String = "", conditions: List<QueryCondition> = listOf(), statIds: List<Stat>? = null)
var query: Query = query var query: Query = query
@ -165,7 +165,7 @@ class ComputableGroup(query: Query, stats: List<Stat>? = null) {
} }
/** /**
* The list of stats to display * The list of statIds to display
*/ */
var stats: List<Stat>? = stats var stats: List<Stat>? = stats
@ -175,7 +175,7 @@ class ComputableGroup(query: Query, stats: List<Stat>? = null) {
var comparedGroup: ComputableGroup? = null var comparedGroup: ComputableGroup? = null
/** /**
* The computed stats of the comparable sessionGroup * The computed statIds of the comparable sessionGroup
*/ */
var comparedComputedResults: ComputedResults? = null var comparedComputedResults: ComputedResults? = null
@ -194,14 +194,14 @@ class ComputableGroup(query: Query, stats: List<Stat>? = null) {
class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) : GraphUnderlyingEntry { class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) : GraphUnderlyingEntry {
/** /**
* The session group used to computed the stats * The session group used to computed the statIds
*/ */
var group: ComputableGroup = group var group: ComputableGroup = group
// The computed stats of the sessionGroup // The computed statIds of the sessionGroup
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf()
// The map containing all evolution numericValues for all stats // The map containing all evolution numericValues for all statIds
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf() private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf()
private var shouldManageMultiGroupProgressValues = shouldManageMultiGroupProgressValues private var shouldManageMultiGroupProgressValues = shouldManageMultiGroupProgressValues
@ -245,7 +245,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
} }
/** /**
* Adds a [computedStat] to the list of stats * Adds a [computedStat] to the list of statIds
* Also computes evolution values using the previously computed values * Also computes evolution values using the previously computed values
*/ */
private fun addComputedStat(computedStat: ComputedStat) { private fun addComputedStat(computedStat: ComputedStat) {

@ -3,10 +3,11 @@ package net.pokeranalytics.android.calculus
import android.content.Context import android.content.Context
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FormattingException import net.pokeranalytics.android.exceptions.FormattingException
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import net.pokeranalytics.android.util.enumerations.IntSearchable
import net.pokeranalytics.android.util.extensions.formatted import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import net.pokeranalytics.android.util.extensions.toCurrency import net.pokeranalytics.android.util.extensions.toCurrency
@ -17,46 +18,46 @@ class StatFormattingException(message: String) : Exception(message) {
} }
class ObjectIdentifier(var id: String, var clazz: Class<out Timed>) {
}
/** /**
* An enum representing all the types of Session statistics * An enum representing all the types of Session statistics
*/ */
enum class Stat : RowRepresentable { enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable {
NET_RESULT, NET_RESULT(1),
BB_NET_RESULT, BB_NET_RESULT(2),
HOURLY_RATE, HOURLY_RATE(3),
AVERAGE, AVERAGE(4),
NUMBER_OF_SETS, NUMBER_OF_SETS(5),
NUMBER_OF_GAMES, NUMBER_OF_GAMES(6),
HOURLY_DURATION, HOURLY_DURATION(7),
AVERAGE_HOURLY_DURATION, AVERAGE_HOURLY_DURATION(8),
NET_BB_PER_100_HANDS, NET_BB_PER_100_HANDS(9),
HOURLY_RATE_BB, HOURLY_RATE_BB(10),
AVERAGE_NET_BB, AVERAGE_NET_BB(11),
WIN_RATIO, WIN_RATIO(12),
AVERAGE_BUYIN, AVERAGE_BUYIN(13),
ROI, ROI(14),
STANDARD_DEVIATION, STANDARD_DEVIATION(15),
STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_HOURLY(16),
STANDARD_DEVIATION_BB_PER_100_HANDS, STANDARD_DEVIATION_BB_PER_100_HANDS(17),
HANDS_PLAYED, HANDS_PLAYED(18),
LOCATIONS_PLAYED, LOCATIONS_PLAYED(19),
LONGEST_STREAKS, LONGEST_STREAKS(20),
MAXIMUM_NETRESULT, MAXIMUM_NETRESULT(21),
MINIMUM_NETRESULT, MINIMUM_NETRESULT(22),
MAXIMUM_DURATION, MAXIMUM_DURATION(23),
DAYS_PLAYED, DAYS_PLAYED(24),
WINNING_SESSION_COUNT, WINNING_SESSION_COUNT(25),
BB_SESSION_COUNT, BB_SESSION_COUNT(26),
TOTAL_BUYIN, TOTAL_BUYIN(27),
RISK_OF_RUIN, RISK_OF_RUIN(28),
; ;
companion object { companion object : IntSearchable<Stat> {
override fun valuesInternal(): Array<Stat> {
return values()
}
val userSelectableList: List<Stat> val userSelectableList: List<Stat>
get() { get() {
@ -65,7 +66,7 @@ enum class Stat : RowRepresentable {
val evolutionValuesList: List<Stat> val evolutionValuesList: List<Stat>
get() { get() {
return values().filter { it.hasEvolutionValues } return values().filter { it.hasProgressValues }
} }
fun returnOnInvestment(netResult: Double, buyin: Double): Double? { fun returnOnInvestment(netResult: Double, buyin: Double): Double? {
@ -172,7 +173,7 @@ enum class Stat : RowRepresentable {
} }
} }
val threshold: Double private val threshold: Double
get() { get() {
return when (this) { return when (this) {
WIN_RATIO -> 50.0 WIN_RATIO -> 50.0
@ -181,6 +182,9 @@ enum class Stat : RowRepresentable {
} }
/**
* Returns a label used to display the legend right value, typically a total or an average
*/
fun cumulativeLabelResId(context: Context): String { fun cumulativeLabelResId(context: Context): String {
val resId = when (this) { val resId = when (this) {
AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS, AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS,
@ -201,6 +205,9 @@ enum class Stat : RowRepresentable {
} }
} }
/**
* Returns the different available aggregation type for each statistic
*/
val aggregationTypes: List<AggregationType> val aggregationTypes: List<AggregationType>
get() { get() {
return when (this) { return when (this) {
@ -215,7 +222,10 @@ enum class Stat : RowRepresentable {
} }
} }
val hasEvolutionGraph: Boolean /**
* Returns if the stat has an evolution graph
*/
val hasProgressGraph: Boolean
get() { get() {
return when (this) { return when (this) {
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, HOURLY_DURATION, AVERAGE_HOURLY_DURATION,
@ -224,7 +234,10 @@ enum class Stat : RowRepresentable {
} }
} }
val significantIndividualValue: Boolean /**
* Returns if the stat has a significant value to display in a progress graph
*/
val graphSignificantIndividualValue: Boolean
get() { get() {
return when (this) { return when (this) {
WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false
@ -232,7 +245,10 @@ enum class Stat : RowRepresentable {
} }
} }
val shouldShowNumberOfSessions: Boolean /**
* Returns if the stat graph should show the number of sessions
*/
val graphShouldShowNumberOfSessions: Boolean
get() { get() {
return when (this) { return when (this) {
NUMBER_OF_GAMES, NUMBER_OF_SETS -> false NUMBER_OF_GAMES, NUMBER_OF_SETS -> false
@ -240,7 +256,7 @@ enum class Stat : RowRepresentable {
} }
} }
val showXAxisZero: Boolean val graphShowsXAxisZero: Boolean
get() { get() {
return when (this) { return when (this) {
HOURLY_DURATION -> true HOURLY_DURATION -> true
@ -248,7 +264,7 @@ enum class Stat : RowRepresentable {
} }
} }
val showYAxisZero: Boolean val graphShowsYAxisZero: Boolean
get() { get() {
return when (this) { return when (this) {
HOURLY_DURATION -> true HOURLY_DURATION -> true
@ -264,7 +280,7 @@ enum class Stat : RowRepresentable {
} }
} }
private val hasEvolutionValues: Boolean private val hasProgressValues: Boolean
get() { get() {
return when (this) { return when (this) {
NET_RESULT, NET_BB_PER_100_HANDS, HOURLY_RATE_BB, NET_RESULT, NET_BB_PER_100_HANDS, HOURLY_RATE_BB,

@ -8,6 +8,9 @@ class RowRepresentableEditDescriptorException(message: String) : Exception(messa
class ConfigurationException(message: String) : Exception(message) class ConfigurationException(message: String) : Exception(message)
class EnumIdentifierNotFoundException(message: String) : Exception(message)
class MisconfiguredSavableEnumException(message: String) : Exception(message)
sealed class PokerAnalyticsException(message: String) : Exception(message) { sealed class PokerAnalyticsException(message: String) : Exception(message) {
object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition") object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition")
object FilterElementUnknownSectionName: PokerAnalyticsException(message = "No filterElement section name was found to identify the queryCondition") object FilterElementUnknownSectionName: PokerAnalyticsException(message = "No filterElement section name was found to identify the queryCondition")
@ -21,4 +24,4 @@ sealed class PokerAnalyticsException(message: String) : Exception(message) {
data class QueryValueMapMissingKeys(val missingKeys: List<String>) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys") data class QueryValueMapMissingKeys(val missingKeys: List<String>) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys")
data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow") data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow")
data class MissingFieldNameForQueryCondition(val name: String) : PokerAnalyticsException(message = "Missing fieldname for QueryCondition ${name}") data class MissingFieldNameForQueryCondition(val name: String) : PokerAnalyticsException(message = "Missing fieldname for QueryCondition ${name}")
} }

@ -21,6 +21,8 @@ import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import net.pokeranalytics.android.util.enumerations.IntSearchable
fun List<Criteria>.combined(): List<Query> { fun List<Criteria>.combined(): List<Query> {
val comparatorList = ArrayList<List<Query>>() val comparatorList = ArrayList<List<Query>>()
@ -54,21 +56,21 @@ fun getCombinations(queries: List<List<Query>>): List<Query> {
return combinations return combinations
} }
sealed class Criteria : RowRepresentable { sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable {
abstract class RealmCriteria : Criteria() { abstract class RealmCriteria(uniqueIdentifier: Int) : Criteria(uniqueIdentifier) {
inline fun <reified T : NameManageable> comparison(): List<Query> { inline fun <reified T : NameManageable> comparison(): List<Query> {
return compare<QueryCondition.QueryDataCondition<NameManageable>, T>() return compare<QueryCondition.QueryDataCondition<NameManageable>, T>()
} }
} }
abstract class SimpleCriteria(private val conditions: List<QueryCondition>) : Criteria() { abstract class SimpleCriteria(private val conditions: List<QueryCondition>, uniqueIdentifier: Int) : Criteria(uniqueIdentifier) {
fun comparison(): List<Query> { fun comparison(): List<Query> {
return conditions.map { Query(it) } return conditions.map { Query(it) }
} }
} }
abstract class ListCriteria : Criteria() { abstract class ListCriteria(uniqueIdentifier: Int) : Criteria(uniqueIdentifier) {
inline fun <reified T : QueryCondition.ListOfValues<S>, reified S : Comparable<S>> comparison(): List<Query> { inline fun <reified T : QueryCondition.ListOfValues<S>, reified S : Comparable<S>> comparison(): List<Query> {
QueryCondition.distinct<Session, T, S>()?.let { QueryCondition.distinct<Session, T, S>()?.let {
val values = it.mapNotNull { session -> val values = it.mapNotNull { session ->
@ -98,32 +100,32 @@ sealed class Criteria : RowRepresentable {
} }
object Bankrolls : RealmCriteria() object Bankrolls : RealmCriteria(1)
object Games : RealmCriteria() object Games : RealmCriteria(2)
object TournamentNames : RealmCriteria() object TournamentNames : RealmCriteria(3)
object Locations : RealmCriteria() object Locations : RealmCriteria(4)
object TournamentFeatures : RealmCriteria() object TournamentFeatures : RealmCriteria(5)
object TransactionTypes : RealmCriteria() object TransactionTypes : RealmCriteria(6)
object Limits : ListCriteria() object Limits : ListCriteria(7)
object TableSizes : ListCriteria() object TableSizes : ListCriteria(8)
object TournamentTypes : ListCriteria() object TournamentTypes : ListCriteria(9)
object MonthsOfYear : SimpleCriteria(List(12) { index -> object MonthsOfYear : SimpleCriteria(List(12) { index ->
QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index) } QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index) }
}) }, 10)
object DaysOfWeek : SimpleCriteria(List(7) { index -> object DaysOfWeek : SimpleCriteria(List(7) { index ->
QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) } QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) }
}) }, 11)
object SessionTypes : SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament)) object SessionTypes : SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament), 12)
object BankrollTypes : SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline)) object BankrollTypes : SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline), 13)
object DayPeriods : SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd)) object DayPeriods : SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd), 14)
object Years : ListCriteria() object Years : ListCriteria(15)
object AllMonthsUpToNow : ListCriteria() object AllMonthsUpToNow : ListCriteria(16)
object Blinds : ListCriteria() object Blinds : ListCriteria(17)
object TournamentFees : ListCriteria() object TournamentFees : ListCriteria(18)
object Cash : SimpleCriteria(listOf(QueryCondition.IsCash)) object Cash : SimpleCriteria(listOf(QueryCondition.IsCash), 19)
object Tournament : SimpleCriteria(listOf(QueryCondition.IsTournament)) object Tournament : SimpleCriteria(listOf(QueryCondition.IsTournament), 20)
val queries: List<Query> val queries: List<Query>
get() { get() {
@ -225,7 +227,8 @@ sealed class Criteria : RowRepresentable {
} }
} }
companion object { companion object : IntSearchable<Criteria> {
inline fun <reified S : QueryCondition.QueryDataCondition<NameManageable>, reified T : NameManageable> compare(): List<Query> { inline fun <reified S : QueryCondition.QueryDataCondition<NameManageable>, reified T : NameManageable> compare(): List<Query> {
val objects = mutableListOf<S>() val objects = mutableListOf<S>()
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
@ -263,7 +266,11 @@ sealed class Criteria : RowRepresentable {
) )
} }
// SavableEnum
override fun valuesInternal(): Array<Criteria> {
return all.toTypedArray()
}
} }
} }

@ -46,12 +46,12 @@ interface NameManageable : Manageable {
/** /**
* An interface associate a unique identifier to an object * An interface associate a unique uniqueIdentifier to an object
*/ */
interface Identifiable : RealmModel { interface Identifiable : RealmModel {
/** /**
* A unique identifier getter * A unique uniqueIdentifier getter
*/ */
var id: String var id: String
} }

@ -1,7 +1,7 @@
package net.pokeranalytics.android.model.interfaces package net.pokeranalytics.android.model.interfaces
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import java.util.* import java.util.*
interface Timed : GraphUnderlyingEntry, Identifiable { interface Timed : GraphUnderlyingEntry, Identifiable {

@ -112,6 +112,14 @@ class PokerAnalyticsMigration : RealmMigration {
it.addField("duplicateValue", Boolean::class.java) it.addField("duplicateValue", Boolean::class.java)
it.addRealmListField("entries", CustomFieldEntry::class.java) it.addRealmListField("entries", CustomFieldEntry::class.java)
} }
schema.get("ReportSetup")?.let {
it.addRealmListField("statIds", Int::class.java)
it.addRealmListField("criteriaIds", Int::class.java)
it.removeField("filters")
schema.get("Filter")?.let { filterSchema ->
it.addRealmObjectField("filter", filterSchema)
}
}
currentVersion++ currentVersion++
} }
} }

@ -2,17 +2,17 @@ package net.pokeranalytics.android.model.realm
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import java.util.* import java.util.*
enum class ReportDisplay : RowRepresentable {
FIGURES,
EVO_GRAPH,
COMPARISON_GRAPH
}
open class ReportSetup : RealmObject() { open class ReportSetup : RealmObject(), RowRepresentable {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() var id = UUID.randomUUID().toString()
@ -21,14 +21,46 @@ open class ReportSetup : RealmObject() {
var name: String = "" var name: String = ""
// The type of display of the report // The type of display of the report
var display: Int = ReportDisplay.FIGURES.ordinal var display: Int = Calculator.Options.Display.TABLE.ordinal
/**
* A list of statIds to compute
* Must contain at least 1
*/
var statIds: RealmList<Int> = RealmList()
/**
* An optional list of criteriaIds to compare statIds
*/
var criteriaIds: RealmList<Int> = RealmList()
/**
* An optional filter to narrow the results
*/
var filter: Filter? = null
// @todo define the configuration options // RowRepresentable
override fun getDisplayName(): String {
return this.name
}
// var criteria: List<Int> = listOf() @Ignore
// var stats: List<Int> = listOf() override val viewType: Int = RowViewType.TITLE_ARROW.ordinal
// The filters associated with the report /**
var filters: RealmList<Filter> = RealmList() * Returns the Options based on the ReportSetup parameters
*/
val options: Calculator.Options
get() {
val stats = this.statIds.map { Stat.valueByIdentifier(it) }
val criteria = this.criteriaIds.map { Criteria.valueByIdentifier(it) }
return Calculator.Options(
display = Calculator.Options.Display.values()[this.display],
stats = stats,
criterias = criteria,
filter = this.filter,
userGenerated = true
)
}
} }

@ -28,6 +28,7 @@ import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.fragment.GraphFragment import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import net.pokeranalytics.android.ui.view.* import net.pokeranalytics.android.ui.view.*
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
@ -344,7 +345,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
/** /**
* Pre-compute various stats * Pre-compute various statIds
*/ */
fun computeStats() { fun computeStats() {
@ -961,7 +962,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
} ?: run { } ?: run {
throw java.lang.IllegalStateException("Asking for stats on Session without Result") throw java.lang.IllegalStateException("Asking for statIds on Session without Result")
} }
} }

@ -3,16 +3,15 @@ package net.pokeranalytics.android.model.realm
import io.realm.Realm import io.realm.Realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.calculus.TextFormat import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import java.text.DateFormat import java.text.DateFormat
import java.util.* import java.util.*
@ -74,8 +73,10 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
var ratedNet: Double = 0.0 var ratedNet: Double = 0.0
@Ignore val hourlyRate: Double
val hourlyRate: Double = this.ratedNet / this.hourlyDuration get() {
return this.ratedNet / this.hourlyDuration
}
var estimatedHands: Double = 0.0 var estimatedHands: Double = 0.0

@ -175,7 +175,7 @@ class HomeActivity : PokerAnalyticsActivity() {
homeMenu?.findItem(R.id.queryWith)?.isVisible = false homeMenu?.findItem(R.id.queryWith)?.isVisible = false
} }
1 -> { 1 -> {
toolbar.titleResId = getString(R.string.stats) toolbar.titleResId = getString(R.string.statIds)
homeMenu?.findItem(R.id.queryWith)?.isVisible = false homeMenu?.findItem(R.id.queryWith)?.isVisible = false
} }
2 -> { 2 -> {

@ -147,7 +147,7 @@ interface DisplayableDataSource {
} }
/** /**
* Returns an action icon identifier for a specific row * Returns an action icon uniqueIdentifier for a specific row
*/ */
fun actionIconForRow(row: RowRepresentable): Int? { fun actionIconForRow(row: RowRepresentable): Int? {
return 0 return 0

@ -13,12 +13,12 @@ import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBub
import com.github.mikephil.charting.listener.OnChartValueSelectedListener import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import kotlinx.android.synthetic.main.fragment_graph.* import kotlinx.android.synthetic.main.fragment_graph.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.graph.AxisFormatting import net.pokeranalytics.android.ui.graph.AxisFormatting
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import net.pokeranalytics.android.ui.graph.setStyle import net.pokeranalytics.android.ui.graph.setStyle
import net.pokeranalytics.android.ui.view.LegendView import net.pokeranalytics.android.ui.view.LegendView
import net.pokeranalytics.android.ui.view.MultiLineLegendView import net.pokeranalytics.android.ui.view.MultiLineLegendView
@ -143,10 +143,10 @@ class GraphFragment : RealmFragment(), OnChartValueSelectedListener {
val barChart = BarChart(context) val barChart = BarChart(context)
barChart.setOnChartValueSelectedListener(this) barChart.setOnChartValueSelectedListener(this)
if (stat.showXAxisZero) { if (stat.graphShowsXAxisZero) {
barChart.xAxis.axisMinimum = 0.0f barChart.xAxis.axisMinimum = 0.0f
} }
if (stat.showYAxisZero) { if (stat.graphShowsYAxisZero) {
barChart.axisLeft.axisMinimum = 0.0f barChart.axisLeft.axisMinimum = 0.0f
} }
this.chartView = barChart this.chartView = barChart

@ -54,14 +54,18 @@ class ReportCreationFragment : RealmFragment(), RowRepresentableDataSource, RowR
this.assistant.nextStep() this.assistant.nextStep()
if (this.assistant.step == Assistant.Step.FINALIZE) { if (this.assistant.step == Assistant.Step.FINALIZE) {
// launch report
// getRealm().executeTransaction {
// val rs = this.assistant.options.reportSetup("test")
// it.insert(rs)
// }
// launch report
this.finishActivityWithOptions(this.assistant.options) this.finishActivityWithOptions(this.assistant.options)
} else { } else {
this.updateUIWithCurrentStep() this.updateUIWithCurrentStep()
} }
} }
} }

@ -9,6 +9,7 @@ import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm import io.realm.Realm
import io.realm.RealmResults
import kotlinx.android.synthetic.main.fragment_data_list.* import kotlinx.android.synthetic.main.fragment_data_list.*
import kotlinx.android.synthetic.main.fragment_stats.recyclerView import kotlinx.android.synthetic.main.fragment_stats.recyclerView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -19,21 +20,27 @@ import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.realm.ReportSetup
import net.pokeranalytics.android.ui.activity.ComparisonReportActivity import net.pokeranalytics.android.ui.activity.ComparisonReportActivity
import net.pokeranalytics.android.ui.activity.ReportCreationActivity
import net.pokeranalytics.android.ui.activity.ProgressReportActivity import net.pokeranalytics.android.ui.activity.ProgressReportActivity
import net.pokeranalytics.android.ui.activity.ReportCreationActivity
import net.pokeranalytics.android.ui.activity.TableReportActivity import net.pokeranalytics.android.ui.activity.TableReportActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.ReportRow import net.pokeranalytics.android.ui.view.rowrepresentable.ReportRow
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { class ReportsFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
private lateinit var reportsAdapter: RowRepresentableAdapter
private lateinit var reportSetups: RealmResults<ReportSetup>
private var adapterRows = mutableListOf<RowRepresentable>()
companion object { companion object {
@ -47,19 +54,12 @@ class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSour
return fragment return fragment
} }
val rowRepresentation: List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.addAll(ReportRow.getRows())
rows
}
} }
private lateinit var reportsAdapter: RowRepresentableAdapter
// Life Cycle // Life Cycle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_reports, container, false) return inflater.inflate(R.layout.fragment_reports, container, false)
} }
@ -67,6 +67,7 @@ class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSour
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initData() initData()
initUI() initUI()
this.updateRows()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -80,19 +81,6 @@ class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSour
} }
} }
// 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 // Business
@ -100,6 +88,10 @@ class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSour
* Init data * Init data
*/ */
private fun initData() { private fun initData() {
this.reportSetups = getRealm().where(ReportSetup::class.java).findAll().sort("name")
this.reportSetups.addChangeListener { _, _ ->
this.updateRows()
}
} }
/** /**
@ -123,6 +115,36 @@ class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSour
} }
// Rows
fun updateRows() {
this.adapterRows.clear()
if (this.reportSetups.size > 0) {
adapterRows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.custom))
adapterRows.addAll(this.reportSetups)
}
adapterRows.addAll(ReportRow.getRows())
this.reportsAdapter.notifyDataSetChanged()
}
override fun adapterRows(): List<RowRepresentable>? {
return this.adapterRows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
super.onRowSelected(position, row, fromAction)
when (row) {
is ReportRow -> {
val reportName = row.localizedTitle(requireContext())
launchComputation(row.criteria, reportName)
}
is ReportSetup -> {
launchReportWithOptions(row.options, row.name)
}
}
}
/** /**
* Launch computation * Launch computation
*/ */
@ -145,6 +167,9 @@ class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSour
} }
/**
* Launch and display a report with some [options] and a [reportName]
*/
private fun launchReportWithOptions(options: Calculator.Options, reportName: String) { private fun launchReportWithOptions(options: Calculator.Options, reportName: String) {
showLoader() showLoader()
@ -176,8 +201,6 @@ class ReportsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSour
Timber.d("Report type not handled at the moment") Timber.d("Report type not handled at the moment")
} }
} }
} }
} }
realm.close() realm.close()

@ -157,7 +157,7 @@ open class TableReportFragment : ResultsObserverFragment(), StaticRowRepresentab
return return
} }
if (row is StatRow && row.stat.hasEvolutionGraph) { if (row is StatRow && row.stat.hasProgressGraph) {
// queryWith groups // queryWith groups
val groupResults = this.report?.results?.filter { val groupResults = this.report?.results?.filter {

@ -4,10 +4,15 @@ import android.content.Context
import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.Entry
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.TextFormat import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.fragment.GraphFragment import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.view.DefaultLegendValues import net.pokeranalytics.android.ui.view.DefaultLegendValues
import net.pokeranalytics.android.ui.view.LegendContent import net.pokeranalytics.android.ui.view.LegendContent
class ObjectIdentifier(var id: String, var clazz: Class<out Timed>) {
}
interface GraphUnderlyingEntry { interface GraphUnderlyingEntry {
val entryTitle: String val entryTitle: String

@ -77,7 +77,7 @@ open class LegendView : FrameLayout {
this.counter.isVisible = false this.counter.isVisible = false
} }
GraphFragment.Style.LINE -> { GraphFragment.Style.LINE -> {
if (stat.significantIndividualValue) { if (stat.graphSignificantIndividualValue) {
this.stat1Name.text = stat.localizedTitle(context) this.stat1Name.text = stat.localizedTitle(context)
this.stat2Name.text = stat.cumulativeLabelResId(context) this.stat2Name.text = stat.cumulativeLabelResId(context)
} else { } else {
@ -88,7 +88,7 @@ open class LegendView : FrameLayout {
counter?.let { counter?.let {
val counterText = "$it ${context.getString(R.string.sessions)}" val counterText = "$it ${context.getString(R.string.sessions)}"
this.counter.text = counterText this.counter.text = counterText
this.counter.isVisible = stat.shouldShowNumberOfSessions this.counter.isVisible = stat.graphShouldShowNumberOfSessions
} }
} }
else -> { else -> {

@ -80,7 +80,7 @@ interface Displayable : Localizable {
interface Localizable { interface Localizable {
/** /**
* The resource identifier of the localized titleResId * The resource uniqueIdentifier of the localized titleResId
*/ */
val resId: Int? val resId: Int?
get() { get() {

@ -258,7 +258,7 @@ enum class RowViewType(private var layoutRes: Int) {
} }
if (row is StatRow) { if (row is StatRow) {
itemView.findViewById<AppCompatImageView?>(R.id.nextArrow)?.isVisible = row.stat.hasEvolutionGraph itemView.findViewById<AppCompatImageView?>(R.id.nextArrow)?.isVisible = row.stat.hasProgressGraph
} }
// Listener // Listener

@ -0,0 +1,23 @@
package net.pokeranalytics.android.util.enumerations
import net.pokeranalytics.android.exceptions.EnumIdentifierNotFoundException
import net.pokeranalytics.android.exceptions.MisconfiguredSavableEnumException
interface IntSearchable<T : IntIdentifiable> {
fun valuesInternal(): Array<T>
fun valueByIdentifier(identifier: Int) : T {
val values = this.valuesInternal().filter { it.uniqueIdentifier == identifier }
return when (values.size) {
0 -> throw EnumIdentifierNotFoundException("Savable enumeration uniqueIdentifier $identifier not found")
1 -> values.first()
else -> throw MisconfiguredSavableEnumException("Savable enumeration has multiple elements with uniqueIdentifier $identifier")
}
}
}
interface IntIdentifiable {
var uniqueIdentifier: Int
}

@ -10,7 +10,7 @@
<item <item
android:id="@+id/navigation_stats" android:id="@+id/navigation_stats"
android:icon="@drawable/ic_outline_chart" android:icon="@drawable/ic_outline_chart"
android:titleResId="@string/stats" /> android:titleResId="@string/statIds" />
<item <item
android:id="@+id/navigation_settings" android:id="@+id/navigation_settings"

@ -0,0 +1,21 @@
package net.pokeranalytics.android
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria
import org.junit.Assert
import org.junit.Test
class SavableEnumTest {
@Test
fun testSavableEnumConfiguration() {
val statIds = Stat.valuesInternal().map { it.uniqueIdentifier }
Assert.assertEquals(statIds.toSet().size, statIds.size)
val criteriaIds = Criteria.valuesInternal().map { it.uniqueIdentifier }
Assert.assertEquals(criteriaIds.toSet().size, criteriaIds.size)
}
}
Loading…
Cancel
Save