parent
baf755c4c8
commit
925ab12faf
@ -0,0 +1,217 @@ |
||||
package net.pokeranalytics.android.calculus |
||||
|
||||
import android.content.Context |
||||
import android.os.CountDownTimer |
||||
import io.realm.Realm |
||||
import io.realm.RealmResults |
||||
import kotlinx.coroutines.GlobalScope |
||||
import kotlinx.coroutines.launch |
||||
import net.pokeranalytics.android.model.realm.CustomField |
||||
import net.pokeranalytics.android.model.realm.Performance |
||||
import net.pokeranalytics.android.model.realm.Result |
||||
import net.pokeranalytics.android.model.realm.Session |
||||
import net.pokeranalytics.android.ui.view.rows.StaticReport |
||||
import timber.log.Timber |
||||
|
||||
interface NewPerformanceListener { |
||||
fun newBestPerformanceHandler() |
||||
} |
||||
|
||||
class ReportWhistleBlower(var context: Context) { |
||||
|
||||
var sessions: RealmResults<Session>? = null |
||||
var results: RealmResults<Result>? = null |
||||
|
||||
var timer: CountDownTimer? = null |
||||
|
||||
// private lateinit var realm: Realm |
||||
|
||||
private val currentNotifications: MutableList<String> = mutableListOf() |
||||
|
||||
private val listeners: MutableList<NewPerformanceListener> = mutableListOf() |
||||
|
||||
// companion object { |
||||
// |
||||
// @Volatile private var INSTANCE: ReportWhistleBlower? = null |
||||
// |
||||
// fun getInstance(context: Context, realm: Realm): ReportWhistleBlower = |
||||
// INSTANCE ?: synchronized(this) { |
||||
// INSTANCE ?: newInstance(context, realm).also { INSTANCE = it } |
||||
// } |
||||
// |
||||
// private fun newInstance(context: Context, realm: Realm): ReportWhistleBlower { |
||||
// return ReportWhistleBlower(context, realm) |
||||
// } |
||||
// |
||||
// } |
||||
|
||||
init { |
||||
|
||||
val realm = Realm.getDefaultInstance() |
||||
|
||||
this.sessions = realm.where(Session::class.java).findAll() |
||||
this.sessions?.addChangeListener { _ -> |
||||
launchReports() |
||||
} |
||||
|
||||
this.results = realm.where(Result::class.java).findAll() |
||||
this.results?.addChangeListener { _ -> |
||||
launchReports() |
||||
} |
||||
} |
||||
|
||||
fun addListener(newPerformanceListener: NewPerformanceListener) { |
||||
this.listeners.add(newPerformanceListener) |
||||
} |
||||
|
||||
private fun requestReportLaunch() { |
||||
|
||||
synchronized(this) { |
||||
this.timer?.cancel() |
||||
|
||||
this.timer = object : CountDownTimer(500L, 0L) { |
||||
override fun onTick(p0: Long) { } |
||||
override fun onFinish() { |
||||
launchReports() |
||||
timer = null |
||||
} |
||||
} |
||||
this.timer?.start() |
||||
} |
||||
|
||||
} |
||||
|
||||
private fun launchReports() { |
||||
|
||||
// Basic |
||||
for (basicReport in StaticReport.basicReports) { |
||||
launchReport(basicReport) |
||||
} |
||||
|
||||
val realm = Realm.getDefaultInstance() |
||||
|
||||
// CustomField |
||||
val customFields = realm.where(CustomField::class.java) |
||||
.equalTo("type", CustomField.Type.LIST.uniqueIdentifier).findAll() |
||||
for (customField in customFields) { |
||||
launchReport(StaticReport.CustomFieldList(customField)) |
||||
} |
||||
|
||||
} |
||||
|
||||
private fun launchReport(report: StaticReport) { |
||||
|
||||
Timber.d(">>> launch report = $report") |
||||
|
||||
val options = Calculator.Options( |
||||
stats = report.stats, |
||||
criterias = report.criteria |
||||
) |
||||
|
||||
this.launchReportWithOptions(report, options) |
||||
|
||||
} |
||||
|
||||
private fun launchReportWithOptions(staticReport: StaticReport, options: Calculator.Options) { |
||||
|
||||
GlobalScope.launch { |
||||
|
||||
val realm = Realm.getDefaultInstance() |
||||
// realm.refresh() |
||||
|
||||
val result = Calculator.computeStats(realm, options = options) |
||||
analyseReport(realm, staticReport, result) |
||||
|
||||
realm.close() |
||||
} |
||||
} |
||||
|
||||
private fun analyseReport(realm: Realm, staticReport: StaticReport, result: Report) { |
||||
|
||||
when (staticReport.uniqueIdentifier) { |
||||
StaticReport.OptimalDuration.uniqueIdentifier -> analyseOptimalDuration(staticReport, result) |
||||
else -> analyseDefaultReport(realm, staticReport, result) |
||||
} |
||||
} |
||||
|
||||
private fun analyseDefaultReport(realm: Realm, staticReport: StaticReport, result: Report) { |
||||
|
||||
for (stat in result.options.stats) { |
||||
|
||||
Timber.d("analyse stat: $stat for report: $staticReport") |
||||
|
||||
result.max(stat)?.let { computedResults -> |
||||
|
||||
val customField: CustomField? = |
||||
(staticReport as? StaticReport.CustomFieldList)?.customField |
||||
var query = realm.where(Performance::class.java) |
||||
.equalTo("statId", stat.uniqueIdentifier) |
||||
.equalTo("reportId", staticReport.uniqueIdentifier) |
||||
|
||||
customField?.let { |
||||
query = query.equalTo("customFieldId", it.id) |
||||
} |
||||
val currentPerf = query.findFirst() |
||||
|
||||
var storePerf = true |
||||
currentPerf?.let { |
||||
Timber.d("cr name = ${computedResults.group.query.getName(this.context)}") |
||||
currentPerf.name?.let { |
||||
if (computedResults.group.query.defaultName == it) { |
||||
storePerf = false |
||||
} |
||||
} |
||||
Timber.d("cr objectId = ${computedResults.group.query.objectId}") |
||||
currentPerf.objectId?.let { |
||||
if (computedResults.group.query.objectId == it) { |
||||
storePerf = false |
||||
} |
||||
} |
||||
|
||||
if (storePerf) { |
||||
realm.executeTransaction { |
||||
currentPerf.name = computedResults.group.query.getName(this.context) |
||||
currentPerf.objectId = computedResults.group.query.objectId |
||||
currentPerf.customFieldId = customField?.id |
||||
} |
||||
this.notify(currentPerf) |
||||
} |
||||
|
||||
} |
||||
|
||||
if (currentPerf == null && storePerf) { |
||||
val performance = Performance( |
||||
staticReport, |
||||
stat, |
||||
computedResults.group.query.getName(this.context), |
||||
computedResults.group.query.objectId, |
||||
customField?.id, |
||||
null |
||||
) |
||||
realm.executeTransaction { it.copyToRealm(performance) } |
||||
this.notify(performance) |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
private fun analyseOptimalDuration(staticReport: StaticReport, result: Report) { |
||||
|
||||
} |
||||
|
||||
private fun notify(performance: Performance) { |
||||
|
||||
this.currentNotifications.add(performance.id) |
||||
for (listener in this.listeners) { |
||||
listener.newBestPerformanceHandler() |
||||
} |
||||
} |
||||
|
||||
fun has(performanceId: String): Boolean { |
||||
return this.currentNotifications.contains(performanceId) |
||||
} |
||||
|
||||
} |
||||
@ -1,4 +1,4 @@ |
||||
package net.pokeranalytics.android.calcul |
||||
package net.pokeranalytics.android.calculus.calcul |
||||
|
||||
import net.pokeranalytics.android.calculus.AggregationType |
||||
import net.pokeranalytics.android.ui.graph.Graph |
||||
@ -1,4 +1,4 @@ |
||||
package net.pokeranalytics.android.calcul |
||||
package net.pokeranalytics.android.calculus.calcul |
||||
|
||||
import android.content.Context |
||||
import com.github.mikephil.charting.data.* |
||||
@ -1,4 +1,4 @@ |
||||
package net.pokeranalytics.android.calcul |
||||
package net.pokeranalytics.android.calculus.calcul |
||||
|
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.calculus.Calculator |
||||
@ -1,4 +1,4 @@ |
||||
package net.pokeranalytics.android.calcul |
||||
package net.pokeranalytics.android.calculus.calcul |
||||
|
||||
import android.content.Context |
||||
import com.github.mikephil.charting.data.BarDataSet |
||||
@ -1,4 +1,4 @@ |
||||
package net.pokeranalytics.android.calcul |
||||
package net.pokeranalytics.android.calculus.calcul |
||||
|
||||
import android.content.Context |
||||
import net.pokeranalytics.android.R |
||||
@ -0,0 +1,67 @@ |
||||
package net.pokeranalytics.android.model.realm |
||||
|
||||
import io.realm.Realm |
||||
import io.realm.RealmObject |
||||
import net.pokeranalytics.android.calculus.Stat |
||||
import net.pokeranalytics.android.ui.fragment.PerformanceKey |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.rows.StaticReport |
||||
import net.pokeranalytics.android.util.NULL_TEXT |
||||
import net.pokeranalytics.android.util.extensions.lookupForNameInAllTablesById |
||||
import java.util.* |
||||
|
||||
open class Performance() : RealmObject(), RowRepresentable { |
||||
|
||||
var id: String = UUID.randomUUID().toString() |
||||
|
||||
constructor( |
||||
report: StaticReport, |
||||
stat: Stat, |
||||
name: String? = null, |
||||
objectId: String? = null, |
||||
customFieldId: String? = null, |
||||
value: Double? = null |
||||
) : this() { |
||||
|
||||
this.reportId = report.uniqueIdentifier |
||||
this.statId = stat.uniqueIdentifier |
||||
this.name = name |
||||
this.objectId = objectId |
||||
this.customFieldId = customFieldId |
||||
this.value = value |
||||
|
||||
} |
||||
|
||||
var reportId: Int = 0 |
||||
var statId: Int = 0 |
||||
var name: String? = null |
||||
var objectId: String? = null |
||||
var customFieldId: String? = null |
||||
var value: Double? = null |
||||
|
||||
fun toStaticReport(realm: Realm): StaticReport { |
||||
return StaticReport.newInstance(realm, this.reportId, this.customFieldId) |
||||
} |
||||
|
||||
fun displayValue(realm: Realm): CharSequence { |
||||
this.name?.let { return it } |
||||
this.objectId?.let { realm.lookupForNameInAllTablesById(it) } |
||||
return NULL_TEXT |
||||
} |
||||
|
||||
val performanceKey: PerformanceKey |
||||
get() { |
||||
return Stat.valueByIdentifier(this.statId) |
||||
} |
||||
|
||||
val stat: Stat |
||||
get() { |
||||
return Stat.valueByIdentifier(this.statId) |
||||
} |
||||
|
||||
override val resId: Int? |
||||
get() { |
||||
return this.performanceKey.resId |
||||
} |
||||
|
||||
} |
||||
@ -1,63 +0,0 @@ |
||||
package net.pokeranalytics.android.ui.view.rows |
||||
|
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.model.Criteria |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.RowViewType |
||||
|
||||
/** |
||||
* An enum managing the report rows |
||||
*/ |
||||
enum class ReportRow : RowRepresentable { |
||||
BLINDS, |
||||
BUY_IN, |
||||
DAY_OF_WEEKS, |
||||
GENERAL, |
||||
LOCATIONS, |
||||
//NUMBER_OF_TABLES, |
||||
TOURNAMENT_TYPES, |
||||
GAME; |
||||
|
||||
companion object { |
||||
/** |
||||
* Return the report rows |
||||
*/ |
||||
fun getRows(): ArrayList<RowRepresentable> { |
||||
val rows = ArrayList<RowRepresentable>() |
||||
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.comparison)) |
||||
rows.addAll(values()) |
||||
return rows |
||||
} |
||||
} |
||||
|
||||
override val resId: Int? |
||||
get() { |
||||
return when (this) { |
||||
BLINDS -> R.string.blinds |
||||
BUY_IN -> R.string.buyin |
||||
DAY_OF_WEEKS -> R.string.day_of_the_week |
||||
GENERAL -> R.string.general |
||||
LOCATIONS -> R.string.locations |
||||
//NUMBER_OF_TABLES -> R.string.number_of_tables |
||||
TOURNAMENT_TYPES -> R.string.tournament_type_complete |
||||
GAME -> R.string.game |
||||
} |
||||
} |
||||
|
||||
override val viewType: Int = RowViewType.TITLE_ARROW.ordinal |
||||
|
||||
val criteria: List<Criteria> |
||||
get() { |
||||
return when (this) { |
||||
BLINDS -> listOf(Criteria.Stakes) |
||||
BUY_IN -> listOf(Criteria.TournamentFees) |
||||
DAY_OF_WEEKS -> listOf(Criteria.DaysOfWeek) |
||||
GENERAL -> listOf(Criteria.SessionTypes, Criteria.BankrollTypes) |
||||
LOCATIONS -> listOf(Criteria.Locations) |
||||
//NUMBER_OF_TABLES -> listOf() //TODO |
||||
TOURNAMENT_TYPES -> listOf(Criteria.TournamentTypes) |
||||
GAME -> listOf(Criteria.Games) |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,117 @@ |
||||
package net.pokeranalytics.android.ui.view.rows |
||||
|
||||
import android.content.Context |
||||
import io.realm.Realm |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.calculus.Stat |
||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
||||
import net.pokeranalytics.android.model.Criteria |
||||
import net.pokeranalytics.android.model.realm.CustomField |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.RowViewType |
||||
import net.pokeranalytics.android.util.NULL_TEXT |
||||
import net.pokeranalytics.android.util.enumerations.IntIdentifiable |
||||
|
||||
/** |
||||
* The list of possible static reports |
||||
*/ |
||||
sealed class StaticReport(override var uniqueIdentifier: Int) : RowRepresentable, IntIdentifiable { |
||||
object General : StaticReport(1) |
||||
object Blinds : StaticReport(2) |
||||
object TournamentBuyin : StaticReport(3) |
||||
object DayOfWeek : StaticReport(4) |
||||
object Location : StaticReport(5) |
||||
object TournamentType : StaticReport(6) |
||||
object Game : StaticReport(7) |
||||
object TableSize : StaticReport(8) |
||||
object Duration : StaticReport(9) |
||||
object OptimalDuration : StaticReport(10) |
||||
|
||||
data class CustomFieldList(var customField: CustomField) : StaticReport(11) |
||||
|
||||
companion object { |
||||
|
||||
fun newInstance(realm: Realm, reportIdentifier: Int, customFieldId: String?): StaticReport { |
||||
|
||||
return when (reportIdentifier) { |
||||
1 -> General |
||||
2 -> Blinds |
||||
3 -> TournamentBuyin |
||||
4 -> DayOfWeek |
||||
5 -> Location |
||||
6 -> TournamentType |
||||
7 -> Game |
||||
8 -> TableSize |
||||
9 -> Duration |
||||
10 -> OptimalDuration |
||||
11 -> { |
||||
customFieldId?.let { id -> |
||||
realm.where(CustomField::class.java).equalTo("id", customFieldId).findFirst()?.let { customField -> |
||||
CustomFieldList(customField) |
||||
} ?: run { throw PAIllegalStateException("Custom field not found: $id") } |
||||
} ?: run { throw PAIllegalStateException("Missing custom field id") } |
||||
} |
||||
else -> throw PAIllegalStateException("Can't create StaticReport. id = $reportIdentifier, cfid = $customFieldId") |
||||
} |
||||
|
||||
} |
||||
|
||||
val basicReports: Set<StaticReport> = setOf(Blinds) // setOf(General, Blinds, TournamentBuyin, |
||||
// DayOfWeek, Location, TournamentType, Game, TableSize, Duration, OptimalDuration) |
||||
|
||||
} |
||||
|
||||
override fun getDisplayName(context: Context): String { |
||||
return when (this) { |
||||
is CustomFieldList -> this.customField.getDisplayName(context) |
||||
else -> this.resId?.let { context.getString(it) } ?: NULL_TEXT |
||||
} |
||||
} |
||||
|
||||
override val resId: Int? |
||||
get() { |
||||
return when (this) { |
||||
Blinds -> R.string.blinds |
||||
TournamentBuyin -> R.string.buyin |
||||
DayOfWeek -> R.string.day_of_the_week |
||||
General -> R.string.general |
||||
Location -> R.string.locations |
||||
//NUMBER_OF_TABLES -> R.string.number_of_tables |
||||
TournamentType -> R.string.tournament_type_complete |
||||
Game -> R.string.game |
||||
TableSize -> R.string.table_size |
||||
Duration -> R.string.duration |
||||
OptimalDuration -> R.string.optimal_duration |
||||
is CustomFieldList -> null |
||||
} |
||||
} |
||||
|
||||
override val viewType: Int = RowViewType.TITLE_ARROW.ordinal |
||||
|
||||
val criteria: List<Criteria> |
||||
get() { |
||||
return when (this) { |
||||
Blinds -> listOf(Criteria.Stakes) |
||||
TournamentBuyin -> listOf(Criteria.TournamentFees) |
||||
DayOfWeek -> listOf(Criteria.DaysOfWeek) |
||||
General -> listOf(Criteria.SessionTypes, Criteria.BankrollTypes) |
||||
Location -> listOf(Criteria.Locations) |
||||
//NUMBER_OF_TABLES -> listOf() //TODO |
||||
TournamentType -> listOf(Criteria.TournamentTypes) |
||||
Game -> listOf(Criteria.Games) |
||||
TableSize -> listOf(Criteria.TableSizes) |
||||
Duration -> listOf(Criteria.Duration) |
||||
OptimalDuration -> listOf() |
||||
is CustomFieldList -> listOf(Criteria.ListCustomFields(this.customField.id)) |
||||
} |
||||
} |
||||
|
||||
val stats: List<Stat> |
||||
get() { |
||||
return when (this) { |
||||
OptimalDuration -> listOf(Stat.AVERAGE_NET_BB) |
||||
else -> listOf(Stat.NET_RESULT, Stat.HOURLY_RATE) |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,11 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:shape="oval"> |
||||
|
||||
<solid android:color="@color/red" /> |
||||
|
||||
<size |
||||
android:width="12dp" |
||||
android:height="12dp" /> |
||||
|
||||
</shape> |
||||
@ -0,0 +1,70 @@ |
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:id="@+id/container" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="48dp" |
||||
android:background="?selectableItemBackground"> |
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView |
||||
android:id="@+id/title" |
||||
style="@style/PokerAnalyticsTheme.TextView.RowTitle" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_marginTop="16dp" |
||||
android:layout_marginBottom="16dp" |
||||
app:layout_constraintBottom_toBottomOf="parent" |
||||
app:layout_constraintStart_toStartOf="@+id/guidelineStart" |
||||
app:layout_constraintTop_toTopOf="parent" |
||||
tools:text="Title" /> |
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView |
||||
android:id="@+id/badge" |
||||
android:visibility="invisible" |
||||
android:layout_width="8dp" |
||||
android:layout_height="8dp" |
||||
android:src="@drawable/circle_red" |
||||
android:layout_marginEnd="8dp" |
||||
app:layout_constraintBottom_toBottomOf="parent" |
||||
app:layout_constraintEnd_toStartOf="@+id/value" |
||||
app:layout_constraintTop_toTopOf="parent" /> |
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView |
||||
android:id="@+id/value" |
||||
style="@style/PokerAnalyticsTheme.TextView.RowValue" |
||||
android:layout_width="0dp" |
||||
android:layout_height="wrap_content" |
||||
android:layout_marginStart="16dp" |
||||
android:ellipsize="end" |
||||
android:gravity="end" |
||||
android:maxLines="1" |
||||
app:layout_constraintBottom_toBottomOf="parent" |
||||
app:layout_constraintEnd_toStartOf="@+id/nextArrow" |
||||
app:layout_constraintTop_toTopOf="parent" |
||||
tools:text="Value" /> |
||||
|
||||
<androidx.constraintlayout.widget.Guideline |
||||
android:id="@+id/guidelineStart" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:orientation="vertical" |
||||
app:layout_constraintGuide_begin="16dp" /> |
||||
|
||||
<androidx.constraintlayout.widget.Guideline |
||||
android:id="@+id/guidelineEnd" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:orientation="vertical" |
||||
app:layout_constraintGuide_end="8dp" /> |
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView |
||||
android:id="@+id/nextArrow" |
||||
android:layout_width="24dp" |
||||
android:layout_height="24dp" |
||||
android:src="@drawable/ic_arrow_right" |
||||
android:tint="@color/grey_light" |
||||
app:layout_constraintBottom_toBottomOf="parent" |
||||
app:layout_constraintEnd_toEndOf="@+id/guidelineEnd" |
||||
app:layout_constraintTop_toTopOf="parent" /> |
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout> |
||||
Loading…
Reference in new issue