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

feature/top10
Aurelien Hubert 7 years ago
commit fb36c2a7d3
  1. 2
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  2. 2
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  3. 41
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  4. 2
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  5. 43
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  6. 41
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  7. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt
  8. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  9. 12
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  10. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt
  11. 17
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt
  12. 13
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  13. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt
  14. 31
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt
  15. 32
      app/src/main/java/net/pokeranalytics/android/ui/graph/LargeNumberFormatter.kt
  16. 9
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  17. 2
      app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt
  18. 2
      app/src/main/java/net/pokeranalytics/android/ui/view/TransactionRowView.kt
  19. 14
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt
  20. 22
      app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt
  21. 34
      app/src/test/java/net/pokeranalytics/android/BasicUnitTest.kt
  22. 59
      app/src/test/java/net/pokeranalytics/android/ExampleUnitTest.kt

@ -52,7 +52,7 @@ class PokerAnalyticsApplication : Application() {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}") Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}")
//this.createFakeSessions() this.createFakeSessions()
} }
Patcher.patchBreaks() Patcher.patchBreaks()

@ -391,7 +391,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
override fun formattedValue(stat: Stat, context: Context): TextFormat { override fun formattedValue(stat: Stat, context: Context): TextFormat {
this.computedStat(stat)?.let { this.computedStat(stat)?.let {
return it.format(context) return it.format()
} ?: run { } ?: run {
throw IllegalStateException("Missing stat in results") throw IllegalStateException("Missing stat in results")
} }

@ -4,6 +4,7 @@ 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.model.interfaces.Timed
import net.pokeranalytics.android.ui.graph.AxisFormatting
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
@ -40,14 +41,23 @@ enum class AggregationType {
DURATION; DURATION;
val resId: Int val resId: Int
get() { get() {
return when (this) { return when (this) {
SESSION -> R.string.session SESSION -> R.string.session
MONTH -> R.string.month MONTH -> R.string.month
YEAR -> R.string.year YEAR -> R.string.year
DURATION -> R.string.duration DURATION -> R.string.duration
}
} }
}
val axisFormatting: AxisFormatting
get() {
return when (this) {
DURATION -> AxisFormatting.X_DURATION
else -> AxisFormatting.DEFAULT
}
}
} }
/** /**
@ -147,7 +157,7 @@ enum class Stat : RowRepresentable {
/** /**
* Formats the value of the stat to be suitable for display * Formats the value of the stat to be suitable for display
*/ */
fun format(value: Double, secondValue: Double? = null, currency: Currency? = null, context: Context): TextFormat { fun format(value: Double, secondValue: Double? = null, currency: Currency? = null): TextFormat {
if (value.isNaN()) { if (value.isNaN()) {
return TextFormat(NULL_TEXT, R.color.white) return TextFormat(NULL_TEXT, R.color.white)
@ -195,10 +205,10 @@ enum class Stat : RowRepresentable {
} }
fun cumulativeLabelResId(context: Context) : String { fun cumulativeLabelResId(context: Context): String {
val resId = when (this) { val resId = when (this) {
AVERAGE, AVERAGE_DURATION, NET_BB_PER_100_HANDS, AVERAGE, AVERAGE_DURATION, NET_BB_PER_100_HANDS,
HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, WIN_RATIO, HOURLY_RATE -> R.string.average HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, WIN_RATIO, HOURLY_RATE -> R.string.average
NUMBER_OF_SETS -> R.string.number_of_sessions NUMBER_OF_SETS -> R.string.number_of_sessions
NUMBER_OF_GAMES -> R.string.number_of_records NUMBER_OF_GAMES -> R.string.number_of_records
NET_RESULT, DURATION -> R.string.total NET_RESULT, DURATION -> R.string.total
@ -225,7 +235,12 @@ enum class Stat : RowRepresentable {
val aggregationTypes: List<AggregationType> val aggregationTypes: List<AggregationType>
get() { get() {
return when (this) { return when (this) {
NET_RESULT -> listOf(AggregationType.SESSION, AggregationType.MONTH, AggregationType.YEAR, AggregationType.DURATION) NET_RESULT -> listOf(
AggregationType.SESSION,
AggregationType.MONTH,
AggregationType.YEAR,
AggregationType.DURATION
)
NUMBER_OF_GAMES, NUMBER_OF_SETS -> listOf(AggregationType.MONTH, AggregationType.YEAR) NUMBER_OF_GAMES, NUMBER_OF_SETS -> listOf(AggregationType.MONTH, AggregationType.YEAR)
else -> listOf(AggregationType.SESSION, AggregationType.MONTH, AggregationType.YEAR) else -> listOf(AggregationType.SESSION, AggregationType.MONTH, AggregationType.YEAR)
} }
@ -282,8 +297,8 @@ class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? =
/** /**
* Formats the value of the stat to be suitable for display * Formats the value of the stat to be suitable for display
*/ */
fun format(context: Context): TextFormat { fun format(): TextFormat {
return this.stat.format(this.value, this.secondValue, this.currency, context) return this.stat.format(this.value, this.secondValue, this.currency)
} }
} }

@ -120,7 +120,7 @@ sealed class Criteria {
val objects = arrayListOf<S>() val objects = arrayListOf<S>()
values.forEach { values.forEach {
objects.add((S::class.java.newInstance()).apply { objects.add((S::class.java.newInstance()).apply {
intValues.add(it) intValues = arrayListOf(it)
}) })
} }
return objects return objects

@ -17,11 +17,12 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.util.extensions.endOfDay import net.pokeranalytics.android.util.extensions.endOfDay
import net.pokeranalytics.android.util.extensions.startOfDay import net.pokeranalytics.android.util.extensions.startOfDay
import java.text.DateFormatSymbols
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
fun List<QueryCondition>.name() : String { fun List<QueryCondition>.name() : String {
return this.map { it.id }.joinToString(" / ") return this.map { it.label() }.joinToString(" / ")
} }
//inline fun <reified T : Filterable> List<QueryCondition>.query(realm: Realm): RealmQuery<T> { //inline fun <reified T : Filterable> List<QueryCondition>.query(realm: Realm): RealmQuery<T> {
@ -43,14 +44,22 @@ inline fun <reified T : Filterable> List<QueryCondition>.queryWith(query: RealmQ
* A new type should also set the expected numericValues required in the [filterValuesExpectedKeys] * A new type should also set the expected numericValues required in the [filterValuesExpectedKeys]
*/ */
sealed class QueryCondition : FilterElementRow { interface Labelable {
fun label() : String
}
sealed class QueryCondition : FilterElementRow, Labelable {
interface Valuable <T : ArrayList<T>> {
var values: ArrayList<T>?
}
interface asListOfDouble { var doubleValues : ArrayList<Double> } interface asListOfDouble { var doubleValues : ArrayList<Double> }
interface asListOfBoolean { var booleanValues : ArrayList<Boolean> } interface asListOfBoolean { var booleanValues : ArrayList<Boolean> }
interface asListOfString { var stringValues : ArrayList<String> } interface asListOfString { var stringValues : ArrayList<String> }
interface asDateValue { interface asDateValue {
var dateValue: Date var dateValue: Date
var showTime: Boolean val showTime: Boolean
} }
interface asListOfInt : asListOfDouble { interface asListOfInt : asListOfDouble {
@ -61,13 +70,13 @@ sealed class QueryCondition : FilterElementRow {
interface asIntValue : asListOfInt { interface asIntValue : asListOfInt {
var intValue: Int var intValue: Int
get() { return intValues.first() } get() { return intValues.firstOrNull() ?: 0 }
set(value) { intValues = arrayListOf(value) } set(value) { intValues = arrayListOf(value) }
} }
interface asDoubleValue : asListOfDouble { interface asDoubleValue : asListOfDouble {
var doubleValue : Double var doubleValue : Double
get() { return doubleValues.first() } get() { return doubleValues.firstOrNull() ?: 0.0 }
set(value) { doubleValues = arrayListOf(value) } set(value) { doubleValues = arrayListOf(value) }
} }
@ -93,7 +102,23 @@ sealed class QueryCondition : FilterElementRow {
interface BetweenLeftExclusive : Between interface BetweenLeftExclusive : Between
interface BetweenRightExclusive : Between interface BetweenRightExclusive : Between
val id: String get() { return this::class.simpleName ?: throw PokerAnalyticsException.FilterElementUnknownName } val baseId = this::class.simpleName ?: throw PokerAnalyticsException.FilterElementUnknownName
val id: List<String> get() {
return when (this) {
is QueryDataCondition<*> -> this.stringValues.map { "$baseId+$it" }
is StaticDataQueryCondition -> this.intValues.map { "$baseId+$it" }
else -> listOf(baseId)
}
}
override fun label(): String {
return when (this) {
is YEAR -> "$intValue"
is MONTH -> DateFormatSymbols.getInstance(Locale.getDefault()).months[intValue]
else -> baseId
}
}
override var filterSectionRow: FilterSectionRow = FilterSectionRow.CASH_TOURNAMENT override var filterSectionRow: FilterSectionRow = FilterSectionRow.CASH_TOURNAMENT
@ -183,7 +208,7 @@ sealed class QueryCondition : FilterElementRow {
get() { get() {
if (intValues.size > 1) { if (intValues.size > 1) {
return "multiple" return "multiple"
} else { } else if (intValues.size > 0) {
return Limit.values()[intValues.first()].longName return Limit.values()[intValues.first()].longName
} }
return "todo" return "todo"
@ -255,11 +280,11 @@ sealed class QueryCondition : FilterElementRow {
// Dates // Dates
open class DateQuery: QueryCondition(), asDateValue { open class DateQuery: QueryCondition(), asDateValue {
override var dateValue: Date = Date() override var dateValue: Date = Date()
override var showTime: Boolean = false override val showTime: Boolean = false
} }
open class TimeQuery: DateQuery() { open class TimeQuery: DateQuery() {
override var showTime: Boolean = true override val showTime: Boolean = true
} }
class STARTED_FROM_DATE: DateQuery() class STARTED_FROM_DATE: DateQuery()

@ -66,22 +66,26 @@ open class Filter : RealmObject() {
private set private set
fun createOrUpdateFilterConditions(filterConditionRows: ArrayList<QueryCondition>) { fun createOrUpdateFilterConditions(filterConditionRows: ArrayList<QueryCondition>) {
val casted = arrayListOf<QueryCondition>() println("list of querys saving: ${filterConditionRows.map { it.id }}")
println("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}")
filterConditionRows filterConditionRows
.map { .map {
it.id it.filterSectionRow
} }
.distinct() .distinct()
.forEach { filterName-> .forEach { filterName->
filterConditionRows filterConditionRows
.filter { .filter {
it.id == filterName it.filterSectionRow == filterName
} }
.apply { .apply {
println("list of querys: ${this.map { it.id }}")
val casted = arrayListOf<QueryCondition>()
casted.addAll(this) casted.addAll(this)
val newFilterCondition = FilterCondition(casted) val newFilterCondition = FilterCondition(casted)
val previousCondition = filterConditions.filter { val previousCondition = filterConditions.filter {
it.sectionName == newFilterCondition.filterName it.filterName == newFilterCondition.filterName
} }
filterConditions.removeAll(previousCondition) filterConditions.removeAll(previousCondition)
filterConditions.add(newFilterCondition) filterConditions.add(newFilterCondition)
@ -89,21 +93,26 @@ open class Filter : RealmObject() {
} }
} }
fun remove(filterCategoryRow: FilterCategoryRow) {
val sections = filterCategoryRow.filterSectionRows.map { it.name }
val savedSections = filterConditions.filter { sections.contains(it.sectionName) }
this.filterConditions.removeAll(savedSections)
}
fun countBy(filterCategoryRow: FilterCategoryRow): Int { fun countBy(filterCategoryRow: FilterCategoryRow): Int {
val sections = filterCategoryRow.filterSectionRows val sections = filterCategoryRow.filterSectionRows.map { it.name }
return filterConditions.count { println("list of sections $sections")
sections.contains(FilterSectionRow.valueOf(it.sectionName ?: throw PokerAnalyticsException.FilterElementUnknownSectionName)) val savedSections = filterConditions.filter { sections.contains(it.sectionName) }.flatMap { it.queryCondition.id }
} println("list of savedSections $savedSections")
return savedSections.size
} }
fun contains(filterElementRow: QueryCondition): Boolean { fun contains(filterElementRow: QueryCondition): Boolean {
val filtered = filterConditions.filter { println("list of saved queries ${filterConditions.map { it.queryCondition.id }}")
it.filterName == filterElementRow.id println("list of contains ${filterElementRow.id}")
} val contained = filterConditions.flatMap{ it.queryCondition.id }.contains(filterElementRow.id.first())
if (filtered.isEmpty()) { println("list of : $contained")
return false return contained
}
return filterElementRow.contains(filtered)
} }
/** /**
@ -111,7 +120,7 @@ open class Filter : RealmObject() {
*/ */
fun loadValueForElement(filterElementRow: QueryCondition) { fun loadValueForElement(filterElementRow: QueryCondition) {
val filtered = filterConditions.filter { val filtered = filterConditions.filter {
it.filterName == filterElementRow.id it.queryCondition == filterElementRow.id
} }
if (filtered.isNotEmpty()) { if (filtered.isNotEmpty()) {
return filterElementRow.updateValueMap(filtered.first()) return filterElementRow.updateValueMap(filtered.first())

@ -13,7 +13,7 @@ open class FilterCondition() : RealmObject() {
this.sectionName = sectionName this.sectionName = sectionName
} }
constructor(filterElementRows: ArrayList<QueryCondition>) : this(filterElementRows.first().id, filterElementRows.first().filterSectionRow.name) { constructor(filterElementRows: ArrayList<QueryCondition>) : this(filterElementRows.first().baseId, filterElementRows.first().filterSectionRow.name) {
val row = filterElementRows.first() val row = filterElementRows.first()
this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName

@ -950,7 +950,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
value?.let { value?.let {
return stat.format(it, currency = currency, context = context) return stat.format(it, currency = currency)
} ?: run { } ?: run {
return TextFormat(NULL_TEXT) return TextFormat(NULL_TEXT)
} }

@ -120,15 +120,15 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
override fun formattedValue(stat: Stat, context: Context) : TextFormat { override fun formattedValue(stat: Stat, context: Context) : TextFormat {
return when (stat) { return when (stat) {
Stat.NET_RESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null, context = context) Stat.NET_RESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null)
Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), currency = null, context = context) Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), currency = null)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null, context = context) Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null, context = context) Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null)
Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, currency = null, context = context) Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, currency = null)
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands) val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands)
if (netBBPer100Hands != null) { if (netBBPer100Hands != null) {
return stat.format(this.estimatedHands, currency = null, context = context) return stat.format(this.estimatedHands, currency = null)
} else { } else {
return TextFormat(NULL_TEXT) return TextFormat(NULL_TEXT)
} }

@ -240,6 +240,8 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
// Compute data per YEAR and MONTH // Compute data per YEAR and MONTH
println(">>>> ${Comparator.MONTH_OF_YEAR.queryConditions.map { it.id }}")
val monthConditions = when (sessionTypeCondition) { val monthConditions = when (sessionTypeCondition) {
QueryCondition.CASH -> listOf(Comparator.YEAR, Comparator.MONTH_OF_YEAR, Comparator.CASH).combined() QueryCondition.CASH -> listOf(Comparator.YEAR, Comparator.MONTH_OF_YEAR, Comparator.CASH).combined()
QueryCondition.TOURNAMENT -> listOf(Comparator.YEAR, Comparator.MONTH_OF_YEAR, Comparator.TOURNAMENT).combined() QueryCondition.TOURNAMENT -> listOf(Comparator.YEAR, Comparator.MONTH_OF_YEAR, Comparator.TOURNAMENT).combined()
@ -255,7 +257,7 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
conditions.forEach { condition -> conditions.forEach { condition ->
when (condition) { when (condition) {
is QueryCondition.YEAR -> calendar.set(Calendar.YEAR, condition.intValue) is QueryCondition.YEAR -> calendar.set(Calendar.YEAR, condition.intValue)
is QueryCondition.MONTH -> calendar.set(Calendar.YEAR, condition.intValue) is QueryCondition.MONTH -> calendar.set(Calendar.MONTH, condition.intValue)
} }
} }

@ -38,14 +38,14 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
lateinit var parentActivity: PokerAnalyticsActivity lateinit var parentActivity: PokerAnalyticsActivity
lateinit var rowRepresentableAdapter: RowRepresentableAdapter lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private lateinit var primaryKey: String
private lateinit var filterCategoryRow: FilterCategoryRow
private var currentFilter: Filter? = null private var currentFilter: Filter? = null
private var rows: ArrayList<RowRepresentable> = ArrayList() private var rows: ArrayList<RowRepresentable> = ArrayList()
private var rowsForFilterSubcategoryRow: HashMap<FilterSectionRow, ArrayList<RowRepresentable>> = HashMap() private var rowsForFilterSubcategoryRow: HashMap<FilterSectionRow, ArrayList<RowRepresentable>> = HashMap()
private var primaryKey: String? = null
private var filterMenu: Menu? = null private var filterMenu: Menu? = null
private var filterCategoryRow: FilterCategoryRow? = null
private val selectedRows = ArrayList<QueryCondition>() private val selectedRows = ArrayList<QueryCondition>()
private var isUpdating = false private var isUpdating = false
private var shouldOpenKeyboard = true private var shouldOpenKeyboard = true
@ -174,17 +174,14 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
*/ */
private fun initData() { private fun initData() {
primaryKey?.let { currentFilter = Filter.getFilterBydId(getRealm(), primaryKey)
currentFilter = Filter.getFilterBydId(getRealm(), it)
}
filterCategoryRow?.let {
this.appBar.toolbar.title = it.localizedTitle(requireContext()) this.appBar.toolbar.title = filterCategoryRow.localizedTitle(requireContext())
this.rows.clear() this.rows.clear()
this.rowsForFilterSubcategoryRow.clear() this.rowsForFilterSubcategoryRow.clear()
this.rows.addAll(it.filterElements) this.rows.addAll(filterCategoryRow.filterElements)
this.rows.forEach { element -> this.rows.forEach { element ->
if (element is QueryCondition && currentFilter?.contains(element) == true) { if (element is QueryCondition && currentFilter?.contains(element) == true) {
@ -195,7 +192,6 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
this.rowRepresentableAdapter = RowRepresentableAdapter(this, this) this.rowRepresentableAdapter = RowRepresentableAdapter(this, this)
this.recyclerView.adapter = rowRepresentableAdapter this.recyclerView.adapter = rowRepresentableAdapter
}
} }
/** /**
@ -220,6 +216,8 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
} }
} }
println("list of selected rows : $selectedRows")
// Update UI // Update UI
rowRepresentableAdapter.refreshRow(row) rowRepresentableAdapter.refreshRow(row)
} }
@ -236,6 +234,7 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
val realm = getRealm() val realm = getRealm()
realm.beginTransaction() realm.beginTransaction()
currentFilter?.remove(filterCategoryRow)
currentFilter?.createOrUpdateFilterConditions(selectedRows) currentFilter?.createOrUpdateFilterConditions(selectedRows)
realm.commitTransaction() realm.commitTransaction()

@ -84,7 +84,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
val formattedDate = it.entryTitle val formattedDate = it.entryTitle
val entryValue = it.formattedValue(this.stat, requireContext()) val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatValue = this.stat.format(entry.y.toDouble(), currency = null, context = requireContext()) val totalStatValue = this.stat.format(entry.y.toDouble(), currency = null)
this.legendView.setItemData(this.stat, formattedDate, entryValue, totalStatValue) this.legendView.setItemData(this.stat, formattedDate, entryValue, totalStatValue)
} }
@ -108,7 +108,8 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
GraphType.BAR -> BarChart(context) GraphType.BAR -> BarChart(context)
} }
this.chartView.setStyle(false, requireContext()) val axisFormatting = aggregationType.axisFormatting
this.chartView.setStyle(false, axisFormatting, requireContext())
this.chartContainer.addView(this.chartView) this.chartContainer.addView(this.chartView)
} }
@ -118,7 +119,10 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
private fun loadGraph() { private fun loadGraph() {
val graphEntries = when (aggregationType) { val graphEntries = when (aggregationType) {
AggregationType.SESSION, AggregationType.DURATION -> selectedReport.results.firstOrNull()?.defaultStatEntries(stat) AggregationType.SESSION -> selectedReport.results.firstOrNull()?.defaultStatEntries(stat)
AggregationType.DURATION -> {
selectedReport.results.firstOrNull()?.durationEntries(stat)
}
AggregationType.MONTH, AggregationType.YEAR -> { AggregationType.MONTH, AggregationType.YEAR -> {
when (this.stat) { when (this.stat) {
Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> selectedReport.barEntries(this.stat) Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> selectedReport.barEntries(this.stat)
@ -153,7 +157,8 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
} }
} }
this.chartView.setStyle(false, requireContext()) val axisFormatting = aggregationType.axisFormatting
this.chartView.setStyle(false, axisFormatting, requireContext())
this.chartView.setOnChartValueSelectedListener(this) this.chartView.setOnChartValueSelectedListener(this)
this.chartView.highlightValue((entries.size - 1).toFloat(), 0) this.chartView.highlightValue((entries.size - 1).toFloat(), 0)

@ -77,7 +77,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
if (row is StatRow) { if (row is StatRow) {
context?.let { context -> context?.let { context ->
row.computedStat?.let { row.computedStat?.let {
dc.textFormat = it.format(context) dc.textFormat = it.format()
} }
} }
} }
@ -87,7 +87,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
override fun statFormatForRow(row: RowRepresentable): TextFormat { override fun statFormatForRow(row: RowRepresentable): TextFormat {
if (row is StatRow) { if (row is StatRow) {
context?.let { context -> context?.let { context ->
row.computedStat?.let { return it.format(context) } row.computedStat?.let { return it.format() }
} }
} }
return TextFormat(NULL_TEXT) return TextFormat(NULL_TEXT)

@ -8,9 +8,16 @@ import com.github.mikephil.charting.components.XAxis
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.extensions.px import net.pokeranalytics.android.ui.extensions.px
enum class AxisFormatting {
DEFAULT,
X_DURATION,
Y_DURATION,
}
fun BarLineChartBase<*>.setStyle(small: Boolean, axisFormatting: AxisFormatting = AxisFormatting.DEFAULT, context: Context) {
fun BarLineChartBase<*>.setStyle(small: Boolean, context: Context) { this.legend.isEnabled = false
this.description.isEnabled = false
// X Axis // X Axis
this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.chart_default) this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.chart_default)
@ -42,20 +49,22 @@ fun BarLineChartBase<*>.setStyle(small: Boolean, context: Context) {
this.axisLeft.typeface = ResourcesCompat.getFont(context, R.font.roboto_medium) 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.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.textSize = 12f
this.axisLeft.valueFormatter = LargeNumberFormatter()
this.axisRight.isEnabled = false this.axisRight.isEnabled = false
this.legend.isEnabled = false
this.description.isEnabled = false
this.data?.isHighlightEnabled = !small this.data?.isHighlightEnabled = !small
// @todo when (axisFormatting) {
// if timeYAxis { AxisFormatting.DEFAULT -> {
// this.axisLeft.valueFormatter = HourValueFormatter() this.axisLeft.valueFormatter = LargeNumberFormatter()
// } else { }
// this.axisLeft.valueFormatter = LargeNumberFormatter() AxisFormatting.X_DURATION -> {
// } this.xAxis.valueFormatter = HourFormatter()
// }
AxisFormatting.Y_DURATION -> {
this.axisLeft.valueFormatter = HourFormatter()
}
}
} }

@ -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
class LargeNumberFormatter : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return value.kmbFormatted
}
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
val test = value.kmbFormatted
return test
}
}
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
}
}

@ -22,6 +22,7 @@ import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.Transaction import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.extensions.setTextFormat import net.pokeranalytics.android.ui.extensions.setTextFormat
import net.pokeranalytics.android.ui.graph.AxisFormatting
import net.pokeranalytics.android.ui.graph.PALineDataSet import net.pokeranalytics.android.ui.graph.PALineDataSet
import net.pokeranalytics.android.ui.graph.setStyle import net.pokeranalytics.android.ui.graph.setStyle
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
@ -135,7 +136,7 @@ enum class RowViewType(private var layoutRes: Int) {
// Value // Value
itemView.findViewById<AppCompatTextView?>(R.id.value)?.let { itemView.findViewById<AppCompatTextView?>(R.id.value)?.let {
if (row.computedStat != null) { if (row.computedStat != null) {
val format = row.computedStat!!.format(itemView.context) val format = row.computedStat!!.format()
it.setTextFormat(format, itemView.context) it.setTextFormat(format, itemView.context)
} else if (row.value != null) { } else if (row.value != null) {
it.text = row.value it.text = row.value
@ -278,7 +279,7 @@ enum class RowViewType(private var layoutRes: Int) {
itemView.findViewById<AppCompatTextView?>(R.id.stat1Value)?.let { view -> itemView.findViewById<AppCompatTextView?>(R.id.stat1Value)?.let { view ->
view.text = "" view.text = ""
row.computedStat1?.format(view.context)?.let { row.computedStat1?.format()?.let {
view.setTextFormat(it, itemView.context) view.setTextFormat(it, itemView.context)
} }
} }
@ -293,7 +294,7 @@ enum class RowViewType(private var layoutRes: Int) {
itemView.findViewById<AppCompatTextView?>(R.id.stat2Value)?.let { view -> itemView.findViewById<AppCompatTextView?>(R.id.stat2Value)?.let { view ->
view.text = "" view.text = ""
row.computedStat2?.format(view.context)?.let { row.computedStat2?.format()?.let {
view.setTextFormat(it, itemView.context) view.setTextFormat(it, itemView.context)
} }
} }
@ -349,7 +350,7 @@ enum class RowViewType(private var layoutRes: Int) {
it.addView(chartView) it.addView(chartView)
} }
chartView.setStyle(true, context) chartView.setStyle(true, AxisFormatting.DEFAULT, context)
chartView.setTouchEnabled(false) chartView.setTouchEnabled(false)
chartView.highlightValue((entries.size - 1).toFloat(), 0) chartView.highlightValue((entries.size - 1).toFloat(), 0)
} }

@ -149,7 +149,7 @@ class SessionRowView : FrameLayout {
rowHistorySession.infoTitle.isVisible = false rowHistorySession.infoTitle.isVisible = false
val result = session.result?.net ?: 0.0 val result = session.result?.net ?: 0.0
val formattedStat = ComputedStat(Stat.NET_RESULT, result, currency = session.currency).format(context) val formattedStat = ComputedStat(Stat.NET_RESULT, result, currency = session.currency).format()
rowHistorySession.gameResult.setTextFormat(formattedStat, context) rowHistorySession.gameResult.setTextFormat(formattedStat, context)
} }

@ -63,7 +63,7 @@ class TransactionRowView : FrameLayout {
rowTransaction.transactionSubtitle.text = subtitle rowTransaction.transactionSubtitle.text = subtitle
// Amount // Amount
val formattedStat = ComputedStat(Stat.NET_RESULT, transaction.amount).format(context) val formattedStat = ComputedStat(Stat.NET_RESULT, transaction.amount).format()
rowTransaction.transactionAmount.setTextFormat(formattedStat, context) rowTransaction.transactionAmount.setTextFormat(formattedStat, context)
} }

@ -68,8 +68,8 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
when (this@FilterSectionRow) { when (this@FilterSectionRow) {
// General // General
CASH_TOURNAMENT -> arrayListOf(QueryCondition.CASH, QueryCondition.TOURNAMENT) CASH_TOURNAMENT -> Criteria.SessionType.queryConditions
LIVE_ONLINE -> arrayListOf(QueryCondition.LIVE, QueryCondition.ONLINE) LIVE_ONLINE -> Criteria.BankrollType.queryConditions
GAME -> Criteria.Games.queryConditions GAME -> Criteria.Games.queryConditions
LIMIT_TYPE -> Criteria.Limits.queryConditions LIMIT_TYPE -> Criteria.Limits.queryConditions
TABLE_SIZE -> Criteria.TableSizes.queryConditions TABLE_SIZE -> Criteria.TableSizes.queryConditions
@ -151,7 +151,7 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
) )
// Tournament // Tournament
TOURNAMENT_TYPE -> arrayListOf() TOURNAMENT_TYPE -> Criteria.TournamentTypes.queryConditions
COMPLETION_PERCENTAGE -> arrayListOf() COMPLETION_PERCENTAGE -> arrayListOf()
PLACE -> arrayListOf() PLACE -> arrayListOf()
PLAYERS_COUNT -> arrayListOf() PLAYERS_COUNT -> arrayListOf()
@ -159,10 +159,10 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
BUY_IN -> arrayListOf() BUY_IN -> arrayListOf()
TOURNAMENT_NAME -> arrayListOf() TOURNAMENT_NAME -> Criteria.TournamentNames.queryConditions
TOURNAMENT_FEATURE -> arrayListOf() TOURNAMENT_FEATURE -> Criteria.TournamentFeatures.queryConditions
LOCATION -> arrayListOf() LOCATION -> Criteria.Locations.queryConditions
BANKROLL -> arrayListOf() BANKROLL -> Criteria.Bankrolls.queryConditions
MULTI_TABLING -> arrayListOf() MULTI_TABLING -> arrayListOf()
NUMBER_OF_PLAYERS -> arrayListOf() NUMBER_OF_PLAYERS -> arrayListOf()
MULTI_PLAYER -> arrayListOf() MULTI_PLAYER -> arrayListOf()

@ -2,11 +2,31 @@ package net.pokeranalytics.android.util.extensions
import android.content.Context import android.content.Context
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.util.UserDefaults import java.lang.Math.abs
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.NumberFormat import java.text.NumberFormat
import java.util.* import java.util.*
val Number.kmbFormatted: String
get() {
var thousandsExponent = 0
var v = this.toDouble()
while (abs(v) >= 10000 && thousandsExponent < 3) {
v /= 1000
thousandsExponent++
}
val unit = when(thousandsExponent) {
0 -> ""
1 -> "K"
2 -> "M"
3 -> "B"
else -> "B+" // shouldn't happen
}
val formatter = NumberFormat.getInstance()
return formatter.format(v) + unit
}
// Double // Double

@ -0,0 +1,34 @@
package net.pokeranalytics.android
import net.pokeranalytics.android.util.extensions.kmbFormatted
import org.junit.Assert
import org.junit.Test
class BasicUnitTest : RealmUnitTest() {
@Test
fun testStats() {
Assert.assertEquals(0, 0)
}
@Test
fun testFormatting() {
val n1 = 100.0
val n2 = 1000.0
val n3 = n2 * n2 // 1M
val n4 = n3 * n2 // 1B
val s1 = n1.kmbFormatted
val s2 = n2.kmbFormatted
val s3 = n3.kmbFormatted
val s4 = n4.kmbFormatted
Assert.assertEquals("100", s1)
Assert.assertEquals("1K", s2)
Assert.assertEquals("1M", s3)
Assert.assertEquals("1B", s4)
}
}

@ -1,59 +0,0 @@
package net.pokeranalytics.android
import org.junit.Assert
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest : RealmUnitTest() {
@Test
fun testStats() {
Assert.assertEquals(0, 0)
}
// class Grade(someValue: Double) : SessionInterface {
//
// override var bbPer100Hands: Double = 0.0
// override var ratedNet: Double = 0.0
// override var value: Double = someValue
//
// override var sessionSet: SessionSet? = SessionSet()
// override var estimatedHands: Double = 0.0
// override var bbNet: Double = 0.0
// override var bigBlindSessionCount: Int = 0 // 0 or 1
// override var ratedBuyin: Double = 0.0
//
// }
//
// @Test
// fun testStats() {
//
// val grades: List<Grade> = listOf(Grade(10.0), Grade(20.0))
// val group = ComputableGroup(name = "test", computables = grades)
//
// val results: ComputedResults = Calculator.compute(group, Calculator.Options())
//
// val sum = results.computedStat(Stat.NET_RESULT)
// if (sum != null) {
// assert(sum.value == 0.0) { "sum is ${sum.value}" }
// } else {
// fail("No Net result stat")
// }
//
// val average = results.computedStat(Stat.AVERAGE)
// if (average != null) {
// assert(average.value == 0.0) { "average is ${average.value}" }
// } else {
// fail("No AVERAGE stat")
// }
//
// }
}
Loading…
Cancel
Save