Compare commits

...

6 Commits

  1. 4
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt
  2. 47
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  3. 318
      app/src/main/java/net/pokeranalytics/android/calculus/DataManager.kt
  4. 189
      app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt
  5. 2
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt
  6. 10
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt
  7. 11
      app/src/main/java/net/pokeranalytics/android/calculus/optimalduration/CashGameOptimalDurationCalculator.kt
  8. 2
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  9. 19
      app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
  10. 7
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  11. 1
      app/src/main/java/net/pokeranalytics/android/model/filter/Query.kt
  12. 4
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  13. 66
      app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt
  14. 42
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  15. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  16. 18
      app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt
  17. 38
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt
  18. 15
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt
  19. 17
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  20. 9
      app/src/main/java/net/pokeranalytics/android/model/realm/Performance.kt
  21. 4
      app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt
  22. 26
      app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt
  23. 481
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  24. 18
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  25. 49
      app/src/main/java/net/pokeranalytics/android/model/realm/UserConfig.kt
  26. 20
      app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/HandHistory.kt
  27. 2
      app/src/main/java/net/pokeranalytics/android/model/utils/DataUtils.kt
  28. 19
      app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt
  29. 209
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt
  30. 28
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  31. 39
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt
  32. 10
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt
  33. 11
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt
  34. 11
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  35. 16
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt
  36. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/Top10Fragment.kt
  37. 15
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt
  38. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/FilterableFragment.kt
  39. 3
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt
  40. 10
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt
  41. 49
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt
  42. 11
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt
  43. 9
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt
  44. 6
      app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollFragment.kt
  45. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt
  46. 16
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt
  47. 5
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/GridCalendarViewModel.kt
  48. 27
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/DataManagerFragment.kt
  49. 20
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataFragment.kt
  50. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataFragment.kt
  51. 9
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataViewModel.kt
  52. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/TransactionDataFragment.kt
  53. 12
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedFragment.kt
  54. 6
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedSessionRowRepresentableAdapter.kt
  55. 5
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt
  56. 10
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsFragment.kt
  57. 13
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterHandler.kt
  58. 23
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersFragment.kt
  59. 22
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersListFragment.kt
  60. 11
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/editor/EditorFragment.kt
  61. 96
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt
  62. 22
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt
  63. 111
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt
  64. 16
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionViewModel.kt
  65. 9
      app/src/main/java/net/pokeranalytics/android/ui/modules/settings/DealtHandsPerHourFragment.kt
  66. 12
      app/src/main/java/net/pokeranalytics/android/ui/modules/settings/TransactionFilterFragment.kt
  67. 3
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  68. 6
      app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt
  69. 10
      app/src/main/java/net/pokeranalytics/android/ui/view/rows/SessionPropertiesRow.kt
  70. 34
      app/src/main/java/net/pokeranalytics/android/ui/viewmodel/BottomSheetViewModel.kt
  71. 2
      app/src/main/java/net/pokeranalytics/android/util/BackupOperator.kt
  72. 24
      app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt
  73. 5
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt
  74. 23
      app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt
  75. 10
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt
  76. 47
      app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt
  77. 2
      app/src/main/res/layout/activity_new_data.xml

@ -186,13 +186,13 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
Assert.fail("No std100 stat") Assert.fail("No std100 stat")
} }
results.computedStat(Stat.MAXIMUM_NETRESULT)?.let { results.computedStat(Stat.MAXIMUM_NET_RESULT)?.let {
assertEquals(300.0, it.value, delta) assertEquals(300.0, it.value, delta)
} ?: run { } ?: run {
Assert.fail("No MAXIMUM_NETRESULT") Assert.fail("No MAXIMUM_NETRESULT")
} }
results.computedStat(Stat.MINIMUM_NETRESULT)?.let { results.computedStat(Stat.MINIMUM_NET_RESULT)?.let {
assertEquals(-100.0, it.value, delta) assertEquals(-100.0, it.value, delta)
} ?: run { } ?: run {
Assert.fail("No MINIMUM_NETRESULT") Assert.fail("No MINIMUM_NETRESULT")

@ -6,14 +6,13 @@ import android.os.Build
import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseApp
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.kotlin.where
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.calculus.ReportWhistleBlower import net.pokeranalytics.android.calculus.DataManager
import net.pokeranalytics.android.model.migrations.Patcher import net.pokeranalytics.android.model.migrations.Patcher
import net.pokeranalytics.android.model.migrations.PokerAnalyticsMigration import net.pokeranalytics.android.model.migrations.PokerAnalyticsMigration
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.UserConfigObserver
import net.pokeranalytics.android.model.utils.Seed import net.pokeranalytics.android.model.utils.Seed
import net.pokeranalytics.android.util.* import net.pokeranalytics.android.util.*
import net.pokeranalytics.android.util.billing.AppGuard import net.pokeranalytics.android.util.billing.AppGuard
@ -23,7 +22,6 @@ import java.util.*
class PokerAnalyticsApplication : Application() { class PokerAnalyticsApplication : Application() {
var reportWhistleBlower: ReportWhistleBlower? = null
var backupOperator: BackupOperator? = null var backupOperator: BackupOperator? = null
companion object { companion object {
@ -44,6 +42,11 @@ class PokerAnalyticsApplication : Application() {
UserDefaults.init(this) UserDefaults.init(this)
if (BuildConfig.DEBUG) {
// Logs
Timber.plant(PokerAnalyticsLogs())
}
// AppGuard / Billing services // AppGuard / Billing services
AppGuard.load(this.applicationContext) AppGuard.load(this.applicationContext)
@ -51,8 +54,8 @@ class PokerAnalyticsApplication : Application() {
Realm.init(this) Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder() val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME) .name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(14) .schemaVersion(15)
.allowWritesOnUiThread(true) // .allowWritesOnUiThread(true)
.migration(PokerAnalyticsMigration()) .migration(PokerAnalyticsMigration())
.initialData(Seed(this)) .initialData(Seed(this))
.build() .build()
@ -63,24 +66,20 @@ class PokerAnalyticsApplication : Application() {
CrashLogging.log("App onCreate. Locales = $locales") CrashLogging.log("App onCreate. Locales = $locales")
} }
if (BuildConfig.DEBUG) {
// Logs
Timber.plant(PokerAnalyticsLogs())
}
Timber.d("SDK version = ${Build.VERSION.SDK_INT}") Timber.d("SDK version = ${Build.VERSION.SDK_INT}")
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}") Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}")
Timber.d("Realm path = ${Realm.getDefaultInstance().path}") Timber.d("Realm path = ${Realm.getDefaultInstance().path}")
// this.createFakeSessions()
} }
// Patch // Patch
Patcher.patchAll(this) Patcher.patchAll(this)
// Report // Processors
this.reportWhistleBlower = ReportWhistleBlower(this.applicationContext) DataManager.configure(this.applicationContext)
UserConfigObserver.create()
// this.reportWhistleBlower = ReportWhistleBlower(this.applicationContext)
// Backups // Backups
this.backupOperator = BackupOperator(this.applicationContext) this.backupOperator = BackupOperator(this.applicationContext)
@ -89,11 +88,17 @@ class PokerAnalyticsApplication : Application() {
val locale = Locale.getDefault() val locale = Locale.getDefault()
CrashLogging.log("Country: ${locale.country}, language: ${locale.language}") CrashLogging.log("Country: ${locale.country}, language: ${locale.language}")
// val realm = Realm.getDefaultInstance()
// val v:Int = 0
// val set = realm.where<SessionSet>().equalTo("sessions.type", v).findAll()
// Timber.d("SESSION SET COUNT = ${set.size}")
// Realm.getDefaultInstance().executeTransaction { // Realm.getDefaultInstance().executeTransaction {
// it.delete(Performance::class.java) // it.delete(Performance::class.java)
// } // }
// createFakeSessions()
} }
/** /**
@ -101,15 +106,15 @@ class PokerAnalyticsApplication : Application() {
*/ */
private fun createFakeSessions() { private fun createFakeSessions() {
val realm = Realm.getDefaultInstance() // val realm = Realm.getDefaultInstance()
val sessionsCount = realm.where<Session>().count() // val sessionsCount = realm.where<Session>().count()
realm.close() // realm.close()
if (sessionsCount < 10) { // if (sessionsCount < 10) {
CoroutineScope(context = Dispatchers.IO).launch { CoroutineScope(context = Dispatchers.Default).launch {
FakeDataManager.createFakeSessions(500) FakeDataManager.createFakeSessions(8000)
} }
} // }
} }

@ -0,0 +1,318 @@
package net.pokeranalytics.android.calculus
import android.content.Context
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.writeAsync
import timber.log.Timber
import kotlin.math.max
class CorruptSessionSetException(message: String) : Exception(message)
/**
* The manager is in charge of updating the abstract concept of timeline,
* representing the sequenced time frames where the user plays.
*/
object DataManager {
private var sessions: RealmResults<Session>? = null
private var currencies: RealmResults<Currency>? = null
private var dateModifiedSessionIds: MutableSet<String> = mutableSetOf()
private var statsToComputeSessionIds: MutableSet<String> = mutableSetOf()
private var changedCurrencies: MutableSet<String> = mutableSetOf()
var reportWhistleBlower: ReportWhistleBlower? = null
init {
val realm = Realm.getDefaultInstance()
sessions = realm.where(Session::class.java).findAllAsync()
sessions?.addChangeListener { sessions ->
// if (this.dateModifiedSessionIds.isNotEmpty() || this.statsToComputeSessionIds.isNotEmpty()) {
sessions.realm.writeAsync { asyncRealm ->
computeStatsIfNecessary(asyncRealm)
computeDatesIfNecessary(asyncRealm)
reportWhistleBlower?.requestReportLaunch()
}
// }
}
this.currencies = realm.where(Currency::class.java).findAll()
this.currencies?.addChangeListener { currencies, _ ->
if (changedCurrencies.isNotEmpty()) {
currencies.realm.writeAsync { asyncRealm ->
for (currencyId in this.changedCurrencies) {
asyncRealm.findById<Currency>(currencyId)?.let { currency ->
Timber.d("Compute currency ${currency.code} ")
currency.refreshRelatedRatedValues()
}
}
changedCurrencies.clear()
}
}
}
realm.close()
}
fun configure(context: Context) {
reportWhistleBlower = ReportWhistleBlower(context)
}
fun sessionToCompute(session: Session) {
// Timber.d("sessionToCompute, date = ${session.startDate} / ${session.endDate}")
statsToComputeSessionIds.add(session.id)
}
fun sessionDateChanged(session: Session) {
// Timber.d("sessionDateChanged")
dateModifiedSessionIds.add(session.id)
}
fun currencyToCompute(currency: Currency) {
// Timber.d("currencyToCompute")
changedCurrencies.add(currency.id)
}
private fun computeStatsIfNecessary(realm: Realm) {
if (statsToComputeSessionIds.isNotEmpty()) {
for (sessionId in statsToComputeSessionIds) {
realm.findById<Session>(sessionId)?.let { session ->
// Timber.d("Session Manager > compute stats, set = ${session.sessionSet}")
session.computeStats()
session.sessionSet?.computeStats()
}
}
statsToComputeSessionIds.clear()
}
}
private fun computeDatesIfNecessary(realm: Realm) {
if (dateModifiedSessionIds.isNotEmpty()) {
for (sessionId in dateModifiedSessionIds) {
realm.findById<Session>(sessionId)?.let { session ->
// Timber.d("Session Manager > manage dates, set = ${session.sessionSet}")
if (session.endDate != null) {
updateTimeline(session)
} else if (session.sessionSet != null) {
removeFromTimeline(session)
}
}
}
dateModifiedSessionIds.clear()
}
}
/**
* Updates the global timeline using the updated [session]
*/
fun updateTimeline(session: Session) {
// Timber.d("updateTimeline...")
if (!session.realm.isInTransaction) {
throw PAIllegalStateException("realm should be in transaction at this point")
}
if (session.startDate == null) {
throw ModelException("Start date should never be null here")
}
if (session.endDate == null) {
throw ModelException("End date should never be null here")
}
val sessionSets = matchingSets(session)
cleanupSessionSets(session, sessionSets)
}
private fun matchingSets(session: Session): RealmResults<SessionSet> {
val realm = session.realm
val endDate = session.endDate!! // tested above
val startDate = session.startDate!!
val query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java)
query
.lessThanOrEqualTo("startDate", startDate)
.greaterThanOrEqualTo("endDate", startDate)
.or()
.lessThanOrEqualTo("startDate", endDate)
.greaterThanOrEqualTo("endDate", endDate)
.or()
.greaterThanOrEqualTo("startDate", startDate)
.lessThanOrEqualTo("endDate", endDate)
return query.findAll()
}
/**
* Multiple session sets update:
* Merges or splits session sets
* Does that by deleting then recreating
*/
private fun cleanupSessionSets(session: Session, sessionSets: RealmResults<SessionSet>) {
// Timber.d("cleanupSessionSets...")
// get all endedSessions from sets
val allImpactedSessions = mutableSetOf<Session>()
sessionSets.forEach { set ->
set.sessions?.asIterable()?.let { allImpactedSessions.addAll(it) }
}
allImpactedSessions.add(session)
// delete all sets
sessionSets.deleteAllFromRealm()
allImpactedSessions.forEach { impactedSession ->
val sets = matchingSets(impactedSession)
updateTimeFrames(sets, impactedSession)
}
}
/**
* Update the global timeline using the impacted [sessionSets] and the updated [session]
*/
private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, session: Session) {
// Timber.d("updateTimeFrames...")
when (sessionSets.size) {
0 -> createOrUpdateSessionSet(session)
else -> mergeSessionGroups(session, sessionSets)
}
}
/**
* Creates or update the session set for the [session]
*/
private fun createOrUpdateSessionSet(session: Session) {
// Timber.d("createOrUpdateSessionSet...")
val set = session.sessionSet
if (set != null) {
set.startDate = session.startDate!! // tested above
set.endDate = session.endDate!!
} else {
createSessionSet(session)
}
}
/**
* Create a set and affect it to the [session]
*/
private fun createSessionSet(session: Session) {
// Timber.d("createSessionSet...")
val realm = session.realm
val set = SessionSet.newInstance(realm)
set.startDate = session.startDate!!
set.endDate = session.endDate!!
set.breakDuration = session.breakDuration
session.sessionSet = set
set.computeStats()
// Timber.d("SET SESSION count = ${set.sessions?.size}")
//
// val t = 0
// val f = realm.where<SessionSet>().equalTo("sessions.type", t).findAll()
// Timber.d("CASH SET COUNT = ${f.size}")
}
/**
* Multiple session sets update:
* Merges all sets into one (delete all then create a new one)
*/
private fun mergeSessionGroups(session: Session, sessionSets: RealmResults<SessionSet>) {
// Timber.d("mergeSessionGroups")
var startDate = session.startDate!!
var endDate = session.endDate!!
// get all endedSessions from sets
val sessions = mutableSetOf<Session>()
sessionSets.forEach { set ->
set.sessions?.asIterable()?.let { sessions.addAll(it) }
}
// find earlier and later dates from all sets
sessions.forEach { s ->
if (s.startDate != null && s.endDate != null) {
val start = s.startDate!!
val end = s.endDate!!
if (start.before(startDate)) {
startDate = start
}
if (end.after(endDate)) {
endDate = end
}
} else {
throw CorruptSessionSetException("Set contains unfinished sessions!")
}
}
// delete all sets
sessionSets.deleteAllFromRealm()
// Create a new set
val set: SessionSet = SessionSet.newInstance(session.realm)
set.startDate = startDate
set.endDate = endDate
// Add the session linked to this timeframe to the new sessionGroup
session.sessionSet = set
// Add all orphan endedSessions
sessions.forEach { s ->
s.sessionSet = set
set.breakDuration = max(set.breakDuration, s.breakDuration)
}
set.computeStats()
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}")
}
/**
* Removes the [session] from the timeline
*/
fun removeFromTimeline(session: Session) {
// Timber.d("removeFromTimeline")
if (!session.realm.isInTransaction) {
throw PAIllegalStateException("realm should be in transaction at this point")
}
val sessionSet = session.sessionSet
if (sessionSet != null) {
val sessions = mutableSetOf<Session>()
sessionSet.sessions?.asIterable()?.let { sessions.addAll(it) }
sessions.remove(session)
// Timber.d(">>> sessionSet.deleteFromRealm")
sessionSet.deleteFromRealm()
sessions.forEach {
updateTimeline(it)
}
}
}
}

@ -1,20 +1,17 @@
package net.pokeranalytics.android.calculus package net.pokeranalytics.android.calculus
import android.content.Context import android.content.Context
import android.os.CountDownTimer
import io.realm.Realm import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.RealmResults
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.pokeranalytics.android.calculus.optimalduration.CashGameOptimalDurationCalculator import net.pokeranalytics.android.calculus.optimalduration.CashGameOptimalDurationCalculator
import net.pokeranalytics.android.model.LiveOnline import net.pokeranalytics.android.model.LiveOnline
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.CustomField
import net.pokeranalytics.android.model.realm.Performance
import net.pokeranalytics.android.model.realm.PerformanceKey
import net.pokeranalytics.android.ui.view.rows.StaticReport import net.pokeranalytics.android.ui.view.rows.StaticReport
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import timber.log.Timber import net.pokeranalytics.android.util.extensions.writeAsync
import kotlin.coroutines.CoroutineContext
interface NewPerformanceListener { interface NewPerformanceListener {
@ -23,8 +20,8 @@ interface NewPerformanceListener {
class ReportWhistleBlower(var context: Context) { class ReportWhistleBlower(var context: Context) {
private var sessions: RealmResults<Session>? = null // private var sessions: RealmResults<Session>? = null
private var results: RealmResults<Result>? = null // private var results: RealmResults<Result>? = null
private var currentTask: ReportTask? = null private var currentTask: ReportTask? = null
@ -34,23 +31,23 @@ class ReportWhistleBlower(var context: Context) {
private var paused: Boolean = false private var paused: Boolean = false
private var timer: CountDownTimer? = null // private var timer: CountDownTimer? = null
init { init {
val realm = Realm.getDefaultInstance() // val realm = Realm.getDefaultInstance()
//
this.sessions = realm.where(Session::class.java).findAll() // this.sessions = realm.where(Session::class.java).findAll()
this.sessions?.addChangeListener { _ -> // this.sessions?.addChangeListener { _ ->
requestReportLaunch() // requestReportLaunch()
} // }
//
this.results = realm.where(Result::class.java).findAll() //// this.results = realm.where(Result::class.java).findAll()
this.results?.addChangeListener { _ -> //// this.results?.addChangeListener { _ ->
requestReportLaunch() //// requestReportLaunch()
} //// }
//
realm.close() // realm.close()
} }
fun addListener(newPerformanceListener: NewPerformanceListener) { fun addListener(newPerformanceListener: NewPerformanceListener) {
@ -62,24 +59,26 @@ class ReportWhistleBlower(var context: Context) {
} }
fun requestReportLaunch() { fun requestReportLaunch() {
Timber.d(">>> Launch report") // Timber.d(">>> Launch report")
if (paused) { if (paused) {
return return
} }
this.timer?.cancel() launchReportTask()
val launchStart = 100L // this.timer?.cancel()
val timer = object: CountDownTimer(launchStart, launchStart) { //
override fun onTick(p0: Long) { } // val launchStart = 100L
// val timer = object : CountDownTimer(launchStart, launchStart) {
override fun onFinish() { // override fun onTick(p0: Long) {}
launchReportTask() //
} // override fun onFinish() {
} // launchReportTask()
this.timer = timer // }
timer.start() // }
// this.timer = timer
// timer.start()
} }
@ -106,7 +105,11 @@ class ReportWhistleBlower(var context: Context) {
fun resume() { fun resume() {
this.paused = false this.paused = false
this.requestReportLaunch() val realm = Realm.getDefaultInstance()
realm.writeAsync {
this.requestReportLaunch()
}
realm.close()
} }
fun has(performanceId: String): Boolean { fun has(performanceId: String): Boolean {
@ -131,8 +134,8 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
private var cancelled = false private var cancelled = false
private val coroutineContext: CoroutineContext // private val coroutineContext: CoroutineContext
get() = Dispatchers.Default // get() = Dispatchers.Default
fun start() { fun start() {
launchReports() launchReports()
@ -143,35 +146,35 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
} }
private fun launchReports() { private fun launchReports() {
CoroutineScope(coroutineContext).launch { // CoroutineScope(coroutineContext).launch {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
// Basic // Basic
for (basicReport in StaticReport.basicReports) { for (basicReport in StaticReport.basicReports) {
if (cancelled) { if (cancelled) {
break break
}
launchReport(realm, basicReport)
} }
launchReport(realm, basicReport)
}
// CustomField // CustomField
val customFields = realm.where(CustomField::class.java) val customFields = realm.where(CustomField::class.java)
.equalTo("type", CustomField.Type.LIST.uniqueIdentifier).findAll() .equalTo("type", CustomField.Type.LIST.uniqueIdentifier).findAll()
for (customField in customFields) { for (customField in customFields) {
if (cancelled) { if (cancelled) {
break break
}
launchReport(realm, StaticReport.CustomFieldList(customField))
} }
realm.close() launchReport(realm, StaticReport.CustomFieldList(customField))
} }
realm.close()
// }
} }
private fun launchReport(realm: Realm, report: StaticReport) { private fun launchReport(realm: Realm, report: StaticReport) {
Timber.d(">>> launch report = $report") // Timber.d(">>> launch report = $report")
when (report) { when (report) {
StaticReport.OptimalDuration -> launchOptimalDuration(realm, report) StaticReport.OptimalDuration -> launchOptimalDuration(realm, report)
@ -202,7 +205,7 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
for (stat in result.options.stats) { for (stat in result.options.stats) {
Timber.d("analyse stat: $stat for report: $staticReport") // Timber.d("analyse stat: $stat for report: $staticReport")
// Get current performance // Get current performance
var query = performancesQuery(realm, staticReport, stat) var query = performancesQuery(realm, staticReport, stat)
@ -212,7 +215,10 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
customField?.let { customField?.let {
query = query.equalTo("customFieldId", it.id) query = query.equalTo("customFieldId", it.id)
} }
val currentPerf = query.findFirst() var currentPerf = query.findFirst()
if (currentPerf != null) {
currentPerf = realm.copyFromRealm(currentPerf)
}
// Store if necessary, delete if necessary // Store if necessary, delete if necessary
val bestComputedResults = result.max(stat) val bestComputedResults = result.max(stat)
@ -221,12 +227,16 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
val performanceQuery = computedResults.group.query val performanceQuery = computedResults.group.query
val performanceName = performanceQuery.getName(this.context, nameSeparator) val performanceName = performanceQuery.getName(this.context, nameSeparator)
Timber.d("Best computed = $performanceName, ${computedResults.computedStat(Stat.NET_RESULT)?.value}") // Timber.d("Best computed = $performanceName, ${computedResults.computedStat(Stat.NET_RESULT)?.value}")
var storePerf = true var storePerf = true
currentPerf?.let { currentPerf?.let {
currentPerf.name?.let { name -> currentPerf.name?.let { name ->
if (computedResults.group.query.getName(this.context, nameSeparator) == name) { if (computedResults.group.query.getName(
this.context,
nameSeparator
) == name
) {
storePerf = false storePerf = false
} }
} }
@ -237,11 +247,10 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
} }
if (storePerf) { if (storePerf) {
realm.executeTransaction { currentPerf.name = performanceName
currentPerf.name = performanceName currentPerf.objectId = performanceQuery.objectId
currentPerf.objectId = performanceQuery.objectId currentPerf.customFieldId = customField?.id
currentPerf.customFieldId = customField?.id // realm.copyToRealm(currentPerf)
}
this.whistleBlower.notify(currentPerf) this.whistleBlower.notify(currentPerf)
} }
@ -256,17 +265,17 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
customField?.id, customField?.id,
null null
) )
realm.executeTransaction { it.copyToRealm(performance) } realm.copyToRealm(performance)
this.whistleBlower.notify(performance) this.whistleBlower.notify(performance)
} }
} ?: run { // if there is no max but a now irrelevant Performance, we delete it } ?: run { // if there is no max but a now irrelevant Performance, we delete it
Timber.d("NO best computed value, current perf = $currentPerf ") // Timber.d("NO best computed value, current perf = $currentPerf ")
currentPerf?.let { perf -> currentPerf?.let { perf ->
realm.executeTransaction { // Timber.d("Delete perf: stat = ${perf.stat}, report = ${perf.reportId}")
Timber.d("Delete perf: stat = ${perf.stat}, report = ${perf.reportId}") realm.findById<Performance>(perf.id)?.deleteFromRealm()
perf.deleteFromRealm() // perf.deleteFromRealm()
}
} }
} }
@ -274,9 +283,17 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
} }
private fun analyseOptimalDuration(realm: Realm, staticReport: StaticReport, key: PerformanceKey, duration: Double?) { private fun analyseOptimalDuration(
realm: Realm,
staticReport: StaticReport,
key: PerformanceKey,
duration: Double?
) {
val performance = performancesQuery(realm, staticReport, key).findFirst() var performance = performancesQuery(realm, staticReport, key).findFirst()
if (performance != null) {
performance = realm.copyFromRealm(performance)
}
duration?.let { duration?.let {
var storePerf = true var storePerf = true
@ -288,30 +305,32 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
} }
if (storePerf) { if (storePerf) {
realm.executeTransaction { perf.name = formattedDuration
perf.name = formattedDuration perf.value = duration
perf.value = duration // realm.copyToRealm(perf)
}
} }
} }
if (storePerf) { if (storePerf) {
val perf = Performance(staticReport, key, name = formattedDuration, value = duration) val perf =
realm.executeTransaction { it.copyToRealm(perf) } Performance(staticReport, key, name = formattedDuration, value = duration)
realm.copyToRealm(perf)
this.whistleBlower.notify(perf) this.whistleBlower.notify(perf)
} }
} ?: run { // no duration } ?: run { // no duration
performance?.let { perf -> performance?.let { perf ->
realm.executeTransaction { realm.findById<Performance>(perf.id)?.deleteFromRealm()
perf.deleteFromRealm() // delete if the perf exists
}
} }
} }
} }
private fun performancesQuery(realm: Realm, staticReport: StaticReport, key: PerformanceKey): RealmQuery<Performance> { private fun performancesQuery(
realm: Realm,
staticReport: StaticReport,
key: PerformanceKey
): RealmQuery<Performance> {
return realm.where(Performance::class.java) return realm.where(Performance::class.java)
.equalTo("reportId", staticReport.uniqueIdentifier) .equalTo("reportId", staticReport.uniqueIdentifier)
.equalTo("key", key.value) .equalTo("key", key.value)

@ -89,7 +89,7 @@ class BankrollCalculator {
this.computeRiskOfRuin(report, result) this.computeRiskOfRuin(report, result)
} else { } else {
val results = Filter.queryOn<Result>(realm, baseQuery) val results = Filter.queryOn<Session>(realm, baseQuery)
report.netResult = results.sum("net").toDouble() report.netResult = results.sum("net").toDouble()
} }

@ -2,8 +2,8 @@ package net.pokeranalytics.android.calculus.bankroll
import io.realm.Realm import io.realm.Realm
import io.realm.RealmResults import io.realm.RealmResults
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Bankroll
@ -68,12 +68,12 @@ object BankrollReportManager {
} }
// otherwise compute it // otherwise compute it
GlobalScope.launch(coroutineContext) { CoroutineScope(context = coroutineContext).launch {
var report: BankrollReport? = null var report: BankrollReport? = null
val coroutine = GlobalScope.async { val coroutine = CoroutineScope(context = Dispatchers.IO).async {
val s = Date() val s = Date()
Timber.d(">>>>> start computing bankroll...") // Timber.d(">>>>> start computing bankroll...")
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
@ -84,7 +84,7 @@ object BankrollReportManager {
val e = Date() val e = Date()
val duration = (e.time - s.time) / 1000.0 val duration = (e.time - s.time) / 1000.0
Timber.d(">>>>> ended in $duration seconds") // Timber.d(">>>>> ended in $duration seconds")
} }
coroutine.await() coroutine.await()

@ -8,7 +8,6 @@ import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import org.apache.commons.math3.fitting.PolynomialCurveFitter import org.apache.commons.math3.fitting.PolynomialCurveFitter
import org.apache.commons.math3.fitting.WeightedObservedPoints import org.apache.commons.math3.fitting.WeightedObservedPoints
import timber.log.Timber
import java.util.* import java.util.*
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.round import kotlin.math.round
@ -65,7 +64,7 @@ class CashGameOptimalDurationCalculator {
var validBuckets = 0 var validBuckets = 0
val hkeys = sessionsByDuration.keys.map { it / 3600 / 1000.0 }.sorted() val hkeys = sessionsByDuration.keys.map { it / 3600 / 1000.0 }.sorted()
Timber.d("Stop notif > keys: $hkeys ") // Timber.d("Stop notif > keys: $hkeys ")
for (key in sessionsByDuration.keys.sorted()) { for (key in sessionsByDuration.keys.sorted()) {
val sessionCount = sessionsByDuration[key]?.size ?: 0 val sessionCount = sessionsByDuration[key]?.size ?: 0
if (start == null && sessionCount >= minimumValidityCount) { if (start == null && sessionCount >= minimumValidityCount) {
@ -76,15 +75,15 @@ class CashGameOptimalDurationCalculator {
validBuckets++ validBuckets++
} }
} }
Timber.d("Stop notif > validBuckets: $validBuckets ") // Timber.d("Stop notif > validBuckets: $validBuckets ")
if (!(start != null && end != null && (end - start) >= intervalValidity)) { if (!(start != null && end != null && (end - start) >= intervalValidity)) {
Timber.d("Stop notif > invalid setup: $start / $end ") // Timber.d("Stop notif > invalid setup: $start / $end ")
return null return null
} }
// define if we have enough sessions // define if we have enough sessions
if (sessions.size < 50) { if (sessions.size < 50) {
Timber.d("Stop notif > not enough sessions: ${sessions.size} ") // Timber.d("Stop notif > not enough sessions: ${sessions.size} ")
return null return null
} }
@ -134,7 +133,7 @@ class CashGameOptimalDurationCalculator {
return bestDuration return bestDuration
} }
Timber.d("Stop notif > not found, best duration: $bestDuration") // Timber.d("Stop notif > not found, best duration: $bestDuration")
realm.close() realm.close()
return null return null
} }

@ -87,7 +87,7 @@ sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, Row
if (this is ValueCustomFields) { if (this is ValueCustomFields) {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val distincts = realm.where<CustomFieldEntry>().equalTo("customFields.id", this.customFieldId).findAll().sort("numericValue", Sort.ASCENDING) val distincts = realm.where<CustomFieldEntry>().equalTo("customField.id", this.customFieldId).findAll().sort("numericValue", Sort.ASCENDING)
realm.close() realm.close()
val objects = mutableListOf<QueryCondition.CustomFieldNumberQuery>() val objects = mutableListOf<QueryCondition.CustomFieldNumberQuery>()

@ -51,12 +51,17 @@ enum class LiveData : Localizable {
} }
fun updateOrCreate(realm: Realm, primaryKey: String?): Deletable { fun updateOrCreate(realm: Realm, primaryKey: String?): Deletable {
val proxyItem: Deletable? = this.getData(realm, primaryKey)
proxyItem?.let { val data = this.getData(realm, primaryKey)
return realm.copyFromRealm(it) data?.let { return data }
} ?: run { return this.newEntity()
return this.newEntity()
} // val proxyItem: Deletable? = this.getData(realm, primaryKey)
// proxyItem?.let {
// return realm.copyFromRealm(it)
// } ?: run {
// return this.newEntity()
// }
} }
private fun newEntity(): Deletable { private fun newEntity(): Deletable {
@ -68,7 +73,7 @@ enum class LiveData : Localizable {
primaryKey?.let { primaryKey?.let {
val t = realm.findById(this.relatedEntity, it) val t = realm.findById(this.relatedEntity, it)
t?.let { t?.let {
proxyItem = t proxyItem = realm.copyFromRealm(t) // make an unmanaged object
} }
} }
return proxyItem return proxyItem

@ -3,7 +3,10 @@ package net.pokeranalytics.android.model.filter
import io.realm.RealmModel import io.realm.RealmModel
import io.realm.RealmResults import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.util.CrashLogging import net.pokeranalytics.android.util.CrashLogging
/** /**
@ -63,7 +66,7 @@ class FilterHelper {
ComputableResult::class.java -> ComputableResult.fieldNameForQueryType(queryCondition) ComputableResult::class.java -> ComputableResult.fieldNameForQueryType(queryCondition)
SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition) SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition)
Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition) Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition)
Result::class.java -> Result.fieldNameForQueryType(queryCondition) // Result::class.java -> Result.fieldNameForQueryType(queryCondition)
else -> { else -> {
CrashLogging.logException(PAIllegalStateException("Filterable type fields are not defined for condition ${queryCondition::class}, class ${T::class}")) CrashLogging.logException(PAIllegalStateException("Filterable type fields are not defined for condition ${queryCondition::class}, class ${T::class}"))
null null

@ -108,7 +108,6 @@ class Query {
return Query(this) return Query(this)
} }
/* /*
Returns the first object Id of any QueryCondition Returns the first object Id of any QueryCondition
*/ */

@ -96,7 +96,9 @@ sealed class QueryCondition : RowRepresentable {
inline fun <reified T : Filterable, reified S : QueryCondition, reified U : Comparable<U>> distinct(): RealmResults<T>? { inline fun <reified T : Filterable, reified S : QueryCondition, reified U : Comparable<U>> distinct(): RealmResults<T>? {
FilterHelper.fieldNameForQueryType<T>(S::class.java)?.let { FilterHelper.fieldNameForQueryType<T>(S::class.java)?.let {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.refresh() if (!realm.isInTransaction) {
realm.refresh()
}
val distincts = when (T::class) { val distincts = when (T::class) {
String::class, Int::class -> realm.where<T>().distinct(it).findAll().sort(it, Sort.ASCENDING) String::class, Int::class -> realm.where<T>().distinct(it).findAll().sort(it, Sort.ASCENDING)

@ -4,14 +4,15 @@ import android.content.Context
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.PokerAnalyticsApplication import net.pokeranalytics.android.PokerAnalyticsApplication
import net.pokeranalytics.android.calculus.DataManager
import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.model.utils.Seed import net.pokeranalytics.android.model.utils.Seed
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.util.BLIND_SEPARATOR import net.pokeranalytics.android.util.BLIND_SEPARATOR
import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.extensions.writeAsync
import java.text.NumberFormat import java.text.NumberFormat
class Patcher { class Patcher {
@ -61,7 +62,7 @@ class Patcher {
patchRatedAmounts() patchRatedAmounts()
} }
patchPerformances(application) patchPerformances()
} }
@ -70,8 +71,8 @@ class Patcher {
val transactionTypes = TransactionType.Value.values() val transactionTypes = TransactionType.Value.values()
realm.executeTransaction { realm.writeAsync { asyncRealm ->
Seed.createDefaultTransactionTypes(transactionTypes, context, realm) Seed.createDefaultTransactionTypes(transactionTypes, context, asyncRealm)
} }
realm.close() realm.close()
@ -80,19 +81,17 @@ class Patcher {
private fun patchBreaks() { private fun patchBreaks() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val sets = realm.where(SessionSet::class.java).findAll()
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsCash))
val results = realm.where(Result::class.java).findAll()
realm.executeTransaction { realm.writeAsync { asyncRealm ->
val sets = asyncRealm.where(SessionSet::class.java).findAll()
val sessions = Filter.queryOn<Session>(asyncRealm, Query(QueryCondition.IsCash))
sets.forEach { sets.forEach {
it.computeStats() it.computeStats()
} }
sessions.forEach { sessions.forEach {
it.generateStakes() it.generateStakes()
it.defineHighestBet() it.defineHighestBet()
}
results.forEach {
it.computeNumberOfRebuy() it.computeNumberOfRebuy()
} }
} }
@ -103,8 +102,8 @@ class Patcher {
private fun patchDefaultTransactionTypes(context: Context) { private fun patchDefaultTransactionTypes(context: Context) {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction { realm.writeAsync { asyncRealm ->
val tts = realm.where(TransactionType::class.java).findAll() val tts = asyncRealm.where(TransactionType::class.java).findAll()
tts.forEach { tt -> tts.forEach { tt ->
tt.kind?.let { kind -> tt.kind?.let { kind ->
val value = TransactionType.Value.values()[kind] val value = TransactionType.Value.values()[kind]
@ -117,8 +116,8 @@ class Patcher {
private fun patchStakes() { private fun patchStakes() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction { realm.writeAsync { asyncRealm ->
val sessions = realm.where(Session::class.java).findAll() val sessions = asyncRealm.where(Session::class.java).findAll()
sessions.forEach { session -> sessions.forEach { session ->
val blinds = arrayListOf(session.cgOldSmallBlind, session.cgOldBigBlind).filterNotNull() val blinds = arrayListOf(session.cgOldSmallBlind, session.cgOldBigBlind).filterNotNull()
val blindsFormatted = blinds.map { NumberFormat.getInstance().format(it) } val blindsFormatted = blinds.map { NumberFormat.getInstance().format(it) }
@ -128,7 +127,7 @@ class Patcher {
} }
} }
val handHistories = realm.where(HandHistory::class.java).findAll() val handHistories = asyncRealm.where(HandHistory::class.java).findAll()
handHistories.forEach { hh -> handHistories.forEach { hh ->
val blinds = arrayListOf(hh.oldSmallBlind, hh.oldBigBlind).filterNotNull() val blinds = arrayListOf(hh.oldSmallBlind, hh.oldBigBlind).filterNotNull()
val blindsFormatted = blinds.map { NumberFormat.getInstance().format(it) } val blindsFormatted = blinds.map { NumberFormat.getInstance().format(it) }
@ -143,8 +142,8 @@ class Patcher {
private fun patchNegativeLimits() { private fun patchNegativeLimits() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction { realm.writeAsync { asyncRealm ->
val sessions = realm.where(Session::class.java).lessThan("limit", 0).findAll() val sessions = asyncRealm.where(Session::class.java).lessThan("limit", 0).findAll()
sessions.forEach { session -> sessions.forEach { session ->
session.limit = null session.limit = null
} }
@ -154,10 +153,10 @@ class Patcher {
private fun cleanBlindsFilters() { private fun cleanBlindsFilters() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction { realm.writeAsync { asyncRealm ->
val blindFilterConditions = realm.where(FilterCondition::class.java).equalTo("filterName", "AnyBlind").findAll() val blindFilterConditions = asyncRealm.where(FilterCondition::class.java).equalTo("filterName", "AnyBlind").findAll()
val filterIds = blindFilterConditions.mapNotNull { it.filters?.firstOrNull() }.map { it.id } val filterIds = blindFilterConditions.mapNotNull { it.filters?.firstOrNull() }.map { it.id }
val filters = realm.where(Filter::class.java).`in`("id", filterIds.toTypedArray()).findAll() val filters = asyncRealm.where(Filter::class.java).`in`("id", filterIds.toTypedArray()).findAll()
filters.deleteAllFromRealm() filters.deleteAllFromRealm()
} }
realm.close() realm.close()
@ -170,11 +169,12 @@ class Patcher {
private fun patchSessionSet() { private fun patchSessionSet() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction { realm.writeAsync { asyncRealm ->
realm.where(SessionSet::class.java).findAll().deleteAllFromRealm() asyncRealm.where(SessionSet::class.java).findAll().deleteAllFromRealm()
val sessions = realm.where(Session::class.java).isNotNull("startDate").isNotNull("endDate").findAll() val sessions = asyncRealm.where(Session::class.java)
.isNotNull("startDate").isNotNull("endDate").findAll()
sessions.forEach { session -> sessions.forEach { session ->
SessionSetManager.updateTimeline(session) DataManager.updateTimeline(session)
} }
} }
realm.close() realm.close()
@ -187,8 +187,8 @@ class Patcher {
*/ */
private fun patchComputableResults() { private fun patchComputableResults() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction { realm.writeAsync { asyncRealm ->
val crs = realm.where(ComputableResult::class.java).findAll() val crs = asyncRealm.where(ComputableResult::class.java).findAll()
crs.forEach { cr -> crs.forEach { cr ->
cr.session?.let { cr.updateWith(it) } cr.session?.let { cr.updateWith(it) }
} }
@ -196,13 +196,15 @@ class Patcher {
realm.close() realm.close()
} }
private fun patchPerformances(application: PokerAnalyticsApplication) { private fun patchPerformances() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val sessionCount = realm.where<Session>().findAll().size val sessionCount = realm.where<Session>().findAll().size
val performanceCount = realm.where<Performance>().findAll().size val performanceCount = realm.where<Performance>().findAll().size
if (sessionCount > 1 && performanceCount == 0) { if (sessionCount > 1 && performanceCount == 0) {
application.reportWhistleBlower?.requestReportLaunch() realm.writeAsync {
DataManager.reportWhistleBlower?.requestReportLaunch()
}
} }
realm.close() realm.close()
} }
@ -210,8 +212,8 @@ class Patcher {
private fun patchZeroTable() { private fun patchZeroTable() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val zero = 0 val zero = 0
val sessions = realm.where<Session>().equalTo("numberOfTables", zero).findAll() realm.writeAsync { asyncRealm ->
realm.executeTransaction { val sessions = asyncRealm.where<Session>().equalTo("numberOfTables", zero).findAll()
sessions.forEach { s -> sessions.forEach { s ->
s.numberOfTables = 1 s.numberOfTables = 1
} }
@ -221,8 +223,8 @@ class Patcher {
private fun patchRatedAmounts() { private fun patchRatedAmounts() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val transactions = realm.where<Transaction>().findAll() realm.writeAsync { asyncRealm ->
realm.executeTransaction { val transactions = asyncRealm.where<Transaction>().findAll()
transactions.forEach { t -> transactions.forEach { t ->
t.computeRatedAmount() t.computeRatedAmount()
} }

@ -1,6 +1,7 @@
package net.pokeranalytics.android.model.migrations package net.pokeranalytics.android.model.migrations
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.DynamicRealmObject
import io.realm.RealmMigration import io.realm.RealmMigration
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import timber.log.Timber import timber.log.Timber
@ -335,6 +336,47 @@ class PokerAnalyticsMigration : RealmMigration {
currentVersion++ currentVersion++
} }
// Migrate to version 15
if (currentVersion == 14) {
schema.get("Transaction")?.let { ts ->
schema.get("Session")?.let { ss ->
ss.addField("buyin", Double::class.java).setNullable("buyin", true)
.addField("cashout", Double::class.java).setNullable("cashout", true)
.addField("netResult", Double::class.java).setNullable("netResult", true)
.addField("net", Double::class.java)
.addField("tips", Double::class.java).setNullable("tips", true)
.addRealmListField("transactions", ts)
.addField("tournamentFinalPosition", Int::class.java).setNullable("tournamentFinalPosition", true)
.addField("numberOfRebuy", Double::class.java).setNullable("numberOfRebuy", true)
.transform { obj ->
val result = obj.get<DynamicRealmObject>("result")
obj.set("buyin", result.get("buyin"))
obj.set("cashout", result.get("cashout"))
obj.set("netResult", result.get("netResult"))
obj.set("net", result.get("net"))
obj.set("tips", result.get("tips"))
obj.set("tournamentFinalPosition", result.get("tournamentFinalPosition"))
obj.set("numberOfRebuy", result.get("numberOfRebuy"))
obj.set("transactions", result.get("transactions"))
}
}
}
schema.get("CustomFieldEntry")?.let { cfes ->
schema.get("CustomField")?.let { cfs ->
cfes.addRealmObjectField("customField", cfs)
cfs.transform { customField ->
val entries = customField.getList("entries")
for (entry in entries) {
entry.setObject("customField", customField)
}
}
}
}
currentVersion++
}
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

@ -28,12 +28,10 @@ open class ComputableResult : RealmObject(), Filterable {
val rate = session.bankroll?.currency?.rate ?: 1.0 val rate = session.bankroll?.currency?.rate ?: 1.0
session.result?.let { result -> this.ratedNet = session.net * rate
this.ratedNet = result.net * rate this.isPositive = session.isPositive
this.isPositive = result.isPositive this.ratedBuyin = (session.buyin ?: 0.0) * rate
this.ratedBuyin = (result.buyin ?: 0.0) * rate this.ratedTips = (session.tips ?: 0.0) * rate
this.ratedTips = (result.tips ?: 0.0) * rate
}
this.bbNet = session.bbNet this.bbNet = session.bbNet
this.hasBigBlind = if (session.cgBiggestBet != null) 1 else 0 this.hasBigBlind = if (session.cgBiggestBet != null) 1 else 0

@ -3,11 +3,13 @@ package net.pokeranalytics.android.model.realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.DataManager
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.util.UserDefaults import net.pokeranalytics.android.util.UserDefaults
import java.util.* import java.util.*
open class Currency : RealmObject() { open class Currency : RealmObject(), Identifiable {
companion object { companion object {
@ -17,7 +19,10 @@ open class Currency : RealmObject() {
} }
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
@Ignore
override val realmObjectClass: Class<out Identifiable> = Currency::class.java
/** /**
* The currency code of the currency, i.e. USD, EUR... * The currency code of the currency, i.e. USD, EUR...
@ -39,18 +44,23 @@ open class Currency : RealmObject() {
* The rate of the currency with the main currency * The rate of the currency with the main currency
*/ */
var rate: Double? = DEFAULT_RATE var rate: Double? = DEFAULT_RATE
set(value) {
field = value
DataManager.currencyToCompute(this)
}
fun refreshRelatedRatedValues() { fun refreshRelatedRatedValues() {
// Timber.d("refreshRelatedRatedValues of $code")
val rate = this.rate ?: DEFAULT_RATE val rate = this.rate ?: DEFAULT_RATE
val query = this.realm.where(ComputableResult::class.java) val query = this.realm.where(ComputableResult::class.java)
query.`in`("session.bankroll.currency.id", arrayOf(this.id)) query.`in`("session.bankroll.currency.id", arrayOf(this.id))
val cResults = query.findAll() val cResults = query.findAll()
cResults.forEach { computable -> cResults.forEach { computable ->
computable.session?.result?.net?.let { computable.session?.net?.let {
computable.ratedNet = it * rate computable.ratedNet = it * rate
} }
computable.session?.result?.buyin?.let { computable.session?.buyin?.let {
computable.ratedBuyin = it * rate computable.ratedBuyin = it * rate
} }

@ -24,9 +24,8 @@ import net.pokeranalytics.android.ui.view.rows.CustomFieldPropertiesRow
import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rows.SimpleRow import net.pokeranalytics.android.ui.view.rows.SimpleRow
import net.pokeranalytics.android.util.enumerations.IntIdentifiable import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import timber.log.Timber import net.pokeranalytics.android.util.extensions.writeAsync
import java.util.* import java.util.*
import kotlin.collections.ArrayList
open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameManageable, StaticRowRepresentableDataSource { open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameManageable, StaticRowRepresentableDataSource {
@ -167,10 +166,9 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana
return R.string.cf_entry_delete_popup_message return R.string.cf_entry_delete_popup_message
} }
override fun deleteDependencies(realm: Realm) { override fun deleteDependencies(realm: Realm) {
if (isValid) { if (isValid) {
val entries = realm.where<CustomFieldEntry>().equalTo("customFields.id", id).findAll() val entries = realm.where<CustomFieldEntry>().equalTo("customField.id", id).findAll()
entries.deleteAllFromRealm() entries.deleteAllFromRealm()
} }
} }
@ -230,6 +228,7 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana
*/ */
fun addEntry(): CustomFieldEntry { fun addEntry(): CustomFieldEntry {
val entry = CustomFieldEntry() val entry = CustomFieldEntry()
entry.customField = this
this.entries.add(entry) this.entries.add(entry)
sortEntries() sortEntries()
updateRowRepresentation() updateRowRepresentation()
@ -250,9 +249,11 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana
fun cleanupEntries() { // called when saving the custom field fun cleanupEntries() { // called when saving the custom field
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction {
this.entriesToDelete.forEach { // entries are out of realm val ids = this.entriesToDelete.map { it.id }
realm.where<CustomFieldEntry>().equalTo("id", it.id).findFirst()?.deleteFromRealm() realm.writeAsync { asyncRealm ->
ids.forEach { id -> // entries are out of realm
asyncRealm.where<CustomFieldEntry>().equalTo("id", id).findFirst()?.deleteFromRealm()
} }
} }
realm.close() realm.close()
@ -261,37 +262,16 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana
fun getOrCreateEntry(realm: Realm, value: String): CustomFieldEntry { fun getOrCreateEntry(realm: Realm, value: String): CustomFieldEntry {
this.entries.find { it.value == value }?.let { this.entries.find { it.value == value }?.let {
Timber.d("L>> get")
return it return it
} ?: run { } ?: run {
Timber.d("L>> create")
val entry = realm.copyToRealm(CustomFieldEntry()) val entry = realm.copyToRealm(CustomFieldEntry())
entry.customField = this
entry.value = value entry.value = value
this.entries.add(entry) this.entries.add(entry)
return entry return entry
} }
} }
/**
* Clean the entries if the type is not a list & remove the deleted entries from realm
*/
// fun cleanEntries(realm: Realm) {
// realm.executeTransaction {
//
// if (!isListType) {
// entriesToDelete.addAll(entries)
// entries.clear()
// }
//
// // @TODO
// entriesToDelete.forEach {
// Timber.d("Delete entry: V=${it.value} N=${it.numericValue} / ID=${it.id}")
// realm.where<CustomFieldEntry>().equalTo("id", it.id).findFirst()?.deleteFromRealm()
// }
// entriesToDelete.clear()
// }
// }
/** /**
* Returns a comparison criteria based on this custom field * Returns a comparison criteria based on this custom field
*/ */

@ -4,9 +4,7 @@ import android.content.Context
import android.text.InputType import android.text.InputType
import io.realm.Realm import io.realm.Realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -43,13 +41,14 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable, R
/** /**
* The inverse relationship with CustomField * The inverse relationship with CustomField
*/ */
@LinkingObjects("entries") // @LinkingObjects("entries")
val customFields: RealmResults<CustomField>? = null // val customFields: RealmResults<CustomField>? = null
val customField: CustomField? var customField: CustomField? = null
get() {
return this.customFields?.first() // get() {
} // return this.customFields?.first()
// }
/** /**
* The string value of the entry * The string value of the entry

@ -9,7 +9,10 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.* import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.UsageCountable
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.modules.filter.FilterableType import net.pokeranalytics.android.ui.modules.filter.FilterableType
import net.pokeranalytics.android.ui.view.* import net.pokeranalytics.android.ui.view.*
@ -40,6 +43,7 @@ open class Filter : RealmObject(), RowRepresentable, RowUpdatable, Deletable, Us
inline fun <reified T : Filterable> queryOn(realm: Realm, query: Query, sortField: String? = null): RealmResults<T> { inline fun <reified T : Filterable> queryOn(realm: Realm, query: Query, sortField: String? = null): RealmResults<T> {
val rootQuery = realm.where<T>() val rootQuery = realm.where<T>()
var realmQuery = query.queryWith(rootQuery) var realmQuery = query.queryWith(rootQuery)
// Timber.d("entity = ${realmQuery.typeQueried} > desc = ${realmQuery.description}")
sortField?.let { sortField?.let {
realmQuery = realmQuery.sort(it) realmQuery = realmQuery.sort(it)
} }
@ -56,18 +60,11 @@ open class Filter : RealmObject(), RowRepresentable, RowUpdatable, Deletable, Us
override val imageClickable: Boolean? override val imageClickable: Boolean?
get() = true get() = true
@PrimaryKey @PrimaryKey
override var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
// the queryWith name // the queryWith name
var name: String = "" var name: String = ""
get() {
if (field.isEmpty()) {
return this.query.defaultName
}
return field
}
override var useCount: Int = 0 override var useCount: Int = 0
@ -109,7 +106,7 @@ open class Filter : RealmObject(), RowRepresentable, RowUpdatable, Deletable, Us
val previousCondition = filterConditions.filter { val previousCondition = filterConditions.filter {
it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator
} }
filterConditions.removeAll(previousCondition) filterConditions.removeAll(previousCondition.toSet())
filterConditions.add(newFilterCondition) filterConditions.add(newFilterCondition)
} }
} }
@ -118,7 +115,7 @@ open class Filter : RealmObject(), RowRepresentable, RowUpdatable, Deletable, Us
fun remove(filterCategoryRow: FilterCategoryRow) { fun remove(filterCategoryRow: FilterCategoryRow) {
val sections = filterCategoryRow.filterSectionRows.map { it.name } val sections = filterCategoryRow.filterSectionRows.map { it.name }
val savedSections = filterConditions.filter { sections.contains(it.sectionName) } val savedSections = filterConditions.filter { sections.contains(it.sectionName) }
this.filterConditions.removeAll(savedSections) this.filterConditions.removeAll(savedSections.toSet())
} }
fun countBy(filterCategoryRow: FilterCategoryRow): Int { fun countBy(filterCategoryRow: FilterCategoryRow): Int {

@ -2,9 +2,11 @@ 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.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.LiveOnline import net.pokeranalytics.android.model.LiveOnline
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.ui.view.rows.StaticReport import net.pokeranalytics.android.ui.view.rows.StaticReport
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.lookupForNameInAllTablesById import net.pokeranalytics.android.util.extensions.lookupForNameInAllTablesById
@ -16,10 +18,13 @@ interface PerformanceKey {
val value: Int val value: Int
} }
open class Performance() : RealmObject() { open class Performance() : RealmObject(), Identifiable {
@PrimaryKey @PrimaryKey
var id: String = UUID.randomUUID().toString() override var id: String = UUID.randomUUID().toString()
@Ignore
override val realmObjectClass: Class<out Identifiable> = Performance::class.java
constructor( constructor(
report: StaticReport, report: StaticReport,

@ -6,9 +6,9 @@ import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.calcul.ReportDisplay
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.calcul.ReportDisplay
import net.pokeranalytics.android.model.Criteria import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
@ -16,6 +16,7 @@ import net.pokeranalytics.android.model.interfaces.Identifiable
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.extensions.findById import net.pokeranalytics.android.util.extensions.findById
import timber.log.Timber
import java.util.* import java.util.*
@ -67,6 +68,7 @@ open class ReportSetup : RealmObject(), RowRepresentable, Deletable {
*/ */
fun options(realm: Realm, reportDisplay: ReportDisplay): Calculator.Options { fun options(realm: Realm, reportDisplay: ReportDisplay): Calculator.Options {
Timber.d("stats = ${this.statIds.size}")
val stats = this.statIds.map { Stat.valueByIdentifier(it) } val stats = this.statIds.map { Stat.valueByIdentifier(it) }
// Comparison criteria // Comparison criteria

@ -9,12 +9,13 @@ import io.realm.annotations.RealmClass
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
@RealmClass @RealmClass
open class Result : RealmObject(), Filterable { open class Result : RealmObject(), Filterable {
companion object { companion object {
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition>): String? { fun fieldNameForQueryType(queryCondition: Class <out QueryCondition>): String? {
Session.fieldNameForQueryType(queryCondition)?.let { Session.fieldNameForQueryType(queryCondition)?.let {
return "sessions.$it" return "sessions.$it"
} }
@ -22,6 +23,7 @@ open class Result : RealmObject(), Filterable {
} }
} }
/** /**
* The buyin amount * The buyin amount
*/ */
@ -29,6 +31,7 @@ open class Result : RealmObject(), Filterable {
set(value) { set(value) {
field = value field = value
this.computeNumberOfRebuy() this.computeNumberOfRebuy()
// SessionManager.sessionNetChanged(this.session)
this.computeNet(true) this.computeNet(true)
} }
@ -39,9 +42,6 @@ open class Result : RealmObject(), Filterable {
set(value) { set(value) {
field = value field = value
this.computeNet(true) this.computeNet(true)
if (value != null) {
this.session?.end()
}
} }
/** /**
@ -49,20 +49,8 @@ open class Result : RealmObject(), Filterable {
*/ */
var netResult: Double? = null var netResult: Double? = null
set(value) { set(value) {
// this.session?.bankroll?.let { bankroll ->
// if (bankroll.live) {
// throw PAIllegalStateException("Can't set net result on a live bankroll")
// }
// } ?: run {
// throw PAIllegalStateException("Session doesn't have any bankroll")
// }
field = value field = value
this.computeNet(false) this.computeNet(false)
if (value != null) {
this.session?.end()
}
} }
/** /**
@ -105,14 +93,14 @@ open class Result : RealmObject(), Filterable {
val isPositive: Int val isPositive: Int
get() { get() {
return if (session?.isTournament() == true) { return if (session?.isTournament() == true) {
if (this.cashout ?: -1.0 >= 0.0) 1 else 0 // if cashout is null we want to count a negative session if ((this.cashout ?: -1.0) >= 0.0) 1 else 0 // if cashout is null we want to count a negative session
} else { } else {
if (this.net >= 0.0) 1 else 0 if (this.net >= 0.0) 1 else 0
} }
} }
// Computes the Net // Computes the Net
private fun computeNet(withBuyin: Boolean? = null) { fun computeNet(withBuyin: Boolean? = null) {
val transactionsSum = transactions.sumOf { it.amount } val transactionsSum = transactions.sumOf { it.amount }
@ -147,7 +135,7 @@ open class Result : RealmObject(), Filterable {
} }
// Computes the number of rebuy // Computes the number of rebuy
fun computeNumberOfRebuy() { private fun computeNumberOfRebuy() {
this.session?.let { this.session?.let {
if (it.isCashGame()) { if (it.isCashGame()) {
it.cgBiggestBet?.let { bb -> it.cgBiggestBet?.let { bb ->

@ -11,9 +11,9 @@ import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.DataManager
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.exceptions.ModelException
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.Stakes import net.pokeranalytics.android.model.Stakes
@ -27,7 +27,6 @@ import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.filter.QueryCondition.* import net.pokeranalytics.android.model.filter.QueryCondition.*
import net.pokeranalytics.android.model.interfaces.* import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.graph.Graph import net.pokeranalytics.android.ui.graph.Graph
import net.pokeranalytics.android.ui.view.* import net.pokeranalytics.android.ui.view.*
@ -45,7 +44,7 @@ import java.util.Currency
typealias BB = Double typealias BB = Double
open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Timed, open class Session : RealmObject(), Savable, RowRepresentable, Timed,
TimeFilterable, Filterable, DatedBankrollGraphEntry, StakesHolder { TimeFilterable, Filterable, DatedBankrollGraphEntry, StakesHolder {
enum class Type(val value: String) { enum class Type(val value: String) {
@ -67,24 +66,23 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
companion object { companion object {
fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null, managed: Boolean = true): Session { fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session {
val session = Session() val session = Session()
session.result = Result()
if (bankroll != null) { val br: Bankroll? = bankroll ?: realm.where<Bankroll>().findFirst()
session.bankroll = bankroll br?.let {
} else { session.bankroll = realm.copyFromRealm(br)
session.bankroll = realm.where<Bankroll>().findFirst()
} }
session.type = if (isTournament) Type.TOURNAMENT.ordinal else Type.CASH_GAME.ordinal
session.type = if (isTournament) Type.TOURNAMENT.ordinal else Type.CASH_GAME.ordinal
session.limit = Limit.NO.ordinal session.limit = Limit.NO.ordinal
session.game = realm.where(Game::class.java).equalTo("shortName", "HE").findFirst()
return if (managed) { val game = realm.where(Game::class.java).equalTo("shortName", "HE").findFirst()
realm.copyToRealm(session) game?.let {
} else { session.game = realm.copyFromRealm(game)
session
} }
return session
} }
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? {
@ -102,9 +100,9 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
AnyStake::class.java -> "cgStakes" AnyStake::class.java -> "cgStakes"
NumberOfTable::class.java -> "numberOfTables" NumberOfTable::class.java -> "numberOfTables"
NetAmountWon::class.java, NetAmountLost::class.java -> "computableResults.ratedNet" NetAmountWon::class.java, NetAmountLost::class.java -> "computableResults.ratedNet"
NumberOfRebuy::class.java -> "result.numberOfRebuy" NumberOfRebuy::class.java -> "numberOfRebuy"
TournamentNumberOfPlayer::class.java -> "tournamentNumberOfPlayers" TournamentNumberOfPlayer::class.java -> "tournamentNumberOfPlayers"
TournamentFinalPosition::class.java -> "result.tournamentFinalPosition" TournamentFinalPosition::class.java -> "tournamentFinalPosition"
TournamentFee::class.java -> "tournamentEntryFee" TournamentFee::class.java -> "tournamentEntryFee"
StartedFromDate::class.java, StartedToDate::class.java, EndedFromDate::class.java, EndedToDate::class.java -> "startDate" StartedFromDate::class.java, StartedToDate::class.java, EndedFromDate::class.java, EndedToDate::class.java -> "startDate"
AnyDayOfWeek::class.java, IsWeekEnd::class.java, IsWeekDay::class.java -> "dayOfWeek" AnyDayOfWeek::class.java, IsWeekEnd::class.java, IsWeekDay::class.java -> "dayOfWeek"
@ -118,7 +116,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
Duration::class.java -> "netDuration" Duration::class.java -> "netDuration"
CustomFieldListQuery::class.java -> "customFieldEntries.id" CustomFieldListQuery::class.java -> "customFieldEntries.id"
CustomFieldAmountQuery::class.java, CustomFieldNumberQuery::class.java -> "customFieldEntries.numericValue" CustomFieldAmountQuery::class.java, CustomFieldNumberQuery::class.java -> "customFieldEntries.numericValue"
CustomFieldQuery::class.java -> "customFieldEntries.customFields.id" CustomFieldQuery::class.java -> "customFieldEntries.customField.id"
DateNotNull::class.java -> "startDate" DateNotNull::class.java -> "startDate"
EndDateNotNull::class.java -> "endDate" EndDateNotNull::class.java -> "endDate"
BiggestBetNotNull::class.java -> "cgBiggestBet" BiggestBetNotNull::class.java -> "cgBiggestBet"
@ -148,7 +146,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
val sessionType: Type val sessionType: Type
get() { return Type.values()[this.type] } get() { return Type.values()[this.type] }
// The result of the main user // Not used anymore
var result: Result? = null var result: Result? = null
@LinkingObjects("session") @LinkingObjects("session")
@ -209,7 +207,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
this.endDate = null this.endDate = null
} }
this.dateChanged() this.dateChanged()
this.computeStats() this.addToComputeQueue()
} }
/** /**
@ -230,7 +228,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
this.computeNetDuration() this.computeNetDuration()
this.dateChanged() this.dateChanged()
this.defineDefaultTournamentBuyinIfNecessary() this.defineDefaultTournamentBuyinIfNecessary()
this.computeStats() this.addToComputeQueue()
} }
/** /**
@ -240,7 +238,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
set(value) { set(value) {
field = value field = value
this.computeNetDuration() this.computeNetDuration()
this.computeStats() this.addToComputeQueue()
} }
/** /**
@ -252,10 +250,6 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* The start date of the break * The start date of the break
*/ */
override var pauseDate: Date? = null override var pauseDate: Date? = null
set(value) {
field = value
// this.updateRowRepresentation()
}
// The session set containing the sessions, which can contain multiple endedSessions // The session set containing the sessions, which can contain multiple endedSessions
var sessionSet: SessionSet? = null var sessionSet: SessionSet? = null
@ -268,7 +262,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
set(value) { set(value) {
field = value field = value
this.generateStakes() this.generateStakes()
this.computeStats() this.addToComputeQueue()
// this.updateRowRepresentation() // this.updateRowRepresentation()
} }
@ -296,7 +290,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
set(value) { set(value) {
if (value > 0) { if (value > 0) {
field = value field = value
this.computeStats() this.addToComputeQueue()
} }
} }
@ -314,16 +308,13 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
// The small blind value // The small blind value
var cgOldSmallBlind: Double? = null var cgOldSmallBlind: Double? = null
set(value) {
field = value
}
// The big blind value // The big blind value
var cgOldBigBlind: Double? = null var cgOldBigBlind: Double? = null
set(value) { set(value) {
field = value field = value
this.computeStats() this.addToComputeQueue()
this.result?.computeNumberOfRebuy() this.computeNumberOfRebuy()
} }
// var blinds: String? = null // var blinds: String? = null
@ -334,8 +325,8 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
field = value field = value
this.generateStakes() this.generateStakes()
this.defineHighestBet() this.defineHighestBet()
this.computeStats() this.addToComputeQueue()
this.result?.computeNumberOfRebuy() this.computeNumberOfRebuy()
} }
var cgBlinds: String? = null var cgBlinds: String? = null
@ -343,8 +334,8 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
field = cleanupBlinds(value) field = cleanupBlinds(value)
this.generateStakes() this.generateStakes()
this.defineHighestBet() this.defineHighestBet()
this.computeStats() this.addToComputeQueue()
this.result?.computeNumberOfRebuy() this.computeNumberOfRebuy()
} }
var cgBiggestBet: Double? = null var cgBiggestBet: Double? = null
@ -357,7 +348,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
var tournamentEntryFee: Double? = null var tournamentEntryFee: Double? = null
set(value) { set(value) {
field = value field = value
this.result?.computeNumberOfRebuy() this.computeNumberOfRebuy()
} }
// The total number of players who participated in the tournament // The total number of players who participated in the tournament
@ -379,9 +370,131 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
var handsCount: Int? = null var handsCount: Int? = null
set(value) { set(value) {
field = value field = value
this.computeStats() this.addToComputeQueue()
}
/**
* The buyin amount
*/
var buyin: Double? = null
set(value) {
field = value
this.computeNumberOfRebuy()
this.computeNet(true)
}
/**
* The cashed out amount
*/
var cashout: Double? = null
set(value) {
field = value
this.computeNet(true)
}
/**
* The net result
*/
var netResult: Double? = null
set(value) {
field = value
this.computeNet(false)
}
/**
* The pre-computed net (readonly)
*/
var net: Double = 0.0
private set
/**
* Tips
*/
var tips: Double? = null
set(value) {
field = value
this.addToComputeQueue()
}
// The transactions associated with the Result, impacting the result
var transactions: RealmList<Transaction> = RealmList()
set(value) {
field = value
this.computeNet()
}
// The tournament final position, if applicable
var tournamentFinalPosition: Int? = null
// Number of rebuys
var numberOfRebuy: Double? = null
//////////////////////////////
// Computes the Net
fun computeNet(withBuyin: Boolean? = null) {
val transactionsSum = transactions.sumOf { it.amount }
// choose the method to compute the net
var useBuyin = withBuyin ?: true
if (withBuyin == null) {
if (netResult != null) {
useBuyin = false
} else if (buyin != null || cashout != null) {
useBuyin = true
} else {
if (this.isCashGame() && !this.isLive) {
useBuyin = false
}
}
}
if (useBuyin) {
val buyin = this.buyin ?: 0.0
val cashOut = this.cashout ?: 0.0
this.net = cashOut - buyin + transactionsSum
} else {
val netResult = this.netResult ?: 0.0
this.net = netResult + transactionsSum
} }
// Precompute results
this.addToComputeQueue()
}
// Computes the number of rebuy
fun computeNumberOfRebuy() {
if (this.isCashGame()) {
this.cgBiggestBet?.let { bb ->
if (bb > 0.0) {
this.numberOfRebuy = (this.buyin ?: 0.0) / (bb * 100.0)
} else {
this.numberOfRebuy = null
}
}
} else {
this.tournamentEntryFee?.let { entryFee ->
if (entryFee > 0.0) {
this.numberOfRebuy = (this.buyin ?: 0.0) / entryFee
} else {
this.numberOfRebuy = null
}
}
}
}
val isPositive: Int
get() {
return if (this.isTournament()) {
if ((this.cashout ?: -1.0) >= 0.0) 1 else 0 // if cashout is null we want to count a negative session
} else {
if (this.net >= 0.0) 1 else 0
}
}
//////////////////////////////
fun bankrollHasBeenUpdated() { fun bankrollHasBeenUpdated() {
this.generateStakes() this.generateStakes()
} }
@ -391,11 +504,15 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* Should be called when the start / end date are changed * Should be called when the start / end date are changed
*/ */
private fun dateChanged() { private fun dateChanged() {
if (this.endDate != null) {
SessionSetManager.updateTimeline(this) DataManager.sessionDateChanged(this)
} else if (this.sessionSet != null) {
SessionSetManager.removeFromTimeline(this) // if (this.endDate != null) {
} // SessionSetManager.updateTimeline(this)
// } else if (this.sessionSet != null) {
// SessionSetManager.removeFromTimeline(this)
// }
// this.updateRowRepresentation() // this.updateRowRepresentation()
} }
@ -435,9 +552,9 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
val bbNet: BB val bbNet: BB
get() { get() {
val bb = this.cgBiggestBet val bb = this.cgBiggestBet
val result = this.result // val result = this.result
return if (bb != null && result != null) { return if (bb != null) {
result.net / bb this.net / bb
} else { } else {
0.0 0.0
} }
@ -468,9 +585,16 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
@Ignore @Ignore
override var amount: Double = 0.0 override var amount: Double = 0.0
get() { get() {
return this.result?.net ?: 0.0 return this.net
} }
/**
* Pre-compute various statIds
*/
private fun addToComputeQueue() {
DataManager.sessionToCompute(this)
}
/** /**
* Pre-compute various statIds * Pre-compute various statIds
*/ */
@ -501,17 +625,13 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
val numberOfHandsPerHour: Double val numberOfHandsPerHour: Double
get() { get() {
val tableSize = this.tableSize ?: 9 // 9 is the default table size if null val tableSize = this.tableSize ?: 9 // 9 is the default table size if null
val config = UserConfig.getConfiguration(this.realm) val dealtHandsPerHour = if (this.isLive) UserConfigObserver.liveDealtHandsPerHour else UserConfigObserver.onlineDealtHandsPerHour
val playerHandsPerHour = if (this.isLive) config.liveDealtHandsPerHour else config.onlineDealtHandsPerHour return this.numberOfTables * dealtHandsPerHour / tableSize.toDouble()
return this.numberOfTables * playerHandsPerHour / tableSize.toDouble()
} }
val hourlyRate: Double val hourlyRate: Double
get() { get() {
this.result?.let { result -> return this.net / this.hourlyDuration
return result.net / this.hourlyDuration
}
throw ModelException("Session should have an existing Result relationship")
} }
private val bbHourlyRate: BB private val bbHourlyRate: BB
@ -529,12 +649,12 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
val hasBuyin: Boolean val hasBuyin: Boolean
get() { get() {
return this.result?.buyin != null return this.buyin != null
} }
val hasNetResult: Boolean val hasNetResult: Boolean
get() { get() {
return this.result?.netResult != null return this.netResult != null
} }
// Manageable // Manageable
@ -557,31 +677,29 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* Start or continue a session * Start or continue a session
*/ */
fun startOrContinue() { fun startOrContinue() {
realm.executeTransaction { when (val state = getState()) {
when (val state = getState()) { SessionState.PENDING, SessionState.PLANNED -> {
SessionState.PENDING, SessionState.PLANNED -> { this.startDate = Date()
this.startDate = Date() this.defineDefaultTournamentBuyinIfNecessary()
this.defineDefaultTournamentBuyinIfNecessary() }
} SessionState.PAUSED -> {
SessionState.PAUSED -> { val pauseDate = this.pauseDate
val pauseDate = this.pauseDate if (pauseDate != null) {
if (pauseDate != null) { this.breakDuration += Date().time - pauseDate.time
this.breakDuration += Date().time - pauseDate.time } else {
} else { throw PAIllegalStateException("When resuming, the pause date must be set")
throw PAIllegalStateException("When resuming, the pause date must be set")
}
this.pauseDate = null
}
else -> {
throw PAIllegalStateException("unmanaged session state: $state")
} }
this.pauseDate = null
}
else -> {
throw PAIllegalStateException("unmanaged session state: $state")
} }
} }
} }
private fun defineDefaultTournamentBuyinIfNecessary() { private fun defineDefaultTournamentBuyinIfNecessary() {
if (this.tournamentEntryFee != null && this.result?.buyin == null) { if (this.tournamentEntryFee != null && this.buyin == null) {
this.result?.buyin = this.tournamentEntryFee this.buyin = this.tournamentEntryFee
} }
} }
@ -589,13 +707,11 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* Pause a session * Pause a session
*/ */
fun pause() { fun pause() {
realm.executeTransaction { when (val state = getState()) {
when (val state = getState()) { SessionState.STARTED -> {
SessionState.STARTED -> { this.pauseDate = Date()
this.pauseDate = Date()
}
else -> throw PAIllegalStateException("Pausing a session in an unmanaged state: $state")
} }
else -> throw PAIllegalStateException("Pausing a session in an unmanaged state: $state")
} }
} }
@ -603,13 +719,11 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* Stop a session * Stop a session
*/ */
fun stop(context: Context) { fun stop(context: Context) {
realm.executeTransaction { when (val state = getState()) {
when (val state = getState()) { SessionState.STARTED, SessionState.PAUSED -> {
SessionState.STARTED, SessionState.PAUSED -> { this.end()
this.end()
}
else -> throw Exception("Stopping session in unmanaged state: $state")
} }
else -> throw Exception("Stopping session in unmanaged state: $state")
} }
cancelStopNotification(context) cancelStopNotification(context)
} }
@ -618,12 +732,10 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* Restart a session * Restart a session
*/ */
fun restart() { fun restart() {
realm.executeTransaction { this.pauseDate = null
this.pauseDate = null this.startDate = Date()
this.startDate = Date() this.endDate = null
this.endDate = null this.breakDuration = 0L
this.breakDuration = 0L
}
} }
/** /**
@ -688,10 +800,11 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
if (isValid) { if (isValid) {
// CrashLogging.log("Deletes session. Id = ${this.id}") // CrashLogging.log("Deletes session. Id = ${this.id}")
realm.executeTransaction {
// realm.executeTransaction {
cleanup() cleanup()
deleteFromRealm() deleteFromRealm()
} // }
} else { } else {
CrashLogging.log("Attempt to delete an invalid session") CrashLogging.log("Attempt to delete an invalid session")
} }
@ -704,10 +817,10 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
// Updates the timeline // Updates the timeline
this.sessionSet?.let { this.sessionSet?.let {
SessionSetManager.removeFromTimeline(this) DataManager.removeFromTimeline(this)
} }
// cleanup unnecessary related objects // cleanup unnecessary related objects
this.result?.deleteFromRealm() // this.deleteFromRealm()
this.computableResults?.deleteAllFromRealm() this.computableResults?.deleteAllFromRealm()
} }
@ -738,7 +851,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
return "Session ${this.creationDate}" return "Session ${this.creationDate}"
} }
override fun updateValue(value: Any?, row: RowRepresentable) { fun updateValue(value: Any?, row: RowRepresentable) {
when (row) { when (row) {
SessionPropertiesRow.BANKROLL -> bankroll = value as Bankroll? SessionPropertiesRow.BANKROLL -> bankroll = value as Bankroll?
@ -757,38 +870,50 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000 this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000
} }
SessionPropertiesRow.BUY_IN -> { SessionPropertiesRow.BUY_IN -> {
val localResult = getOrCreateResult() // val localResult = getOrCreateResult()
localResult.buyin = value as Double? this.buyin = value as Double?
} }
SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> { SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> {
val localResult = getOrCreateResult() // val localResult = getOrCreateResult()
localResult.cashout = value as Double? val cashOut = value as Double?
this.cashout = cashOut
if (cashOut != null) {
this.end()
}
} }
SessionPropertiesRow.NET_RESULT -> { SessionPropertiesRow.NET_RESULT -> {
val localResult = getOrCreateResult() // val localResult = getOrCreateResult()
localResult.netResult = value as Double? val netResult = value as Double?
this.netResult = netResult
if (netResult != null) {
this.end()
}
} }
SessionPropertiesRow.COMMENT -> comment = value as String? ?: "" SessionPropertiesRow.COMMENT -> comment = value as String? ?: ""
SessionPropertiesRow.END_DATE -> if (value is Date?) { SessionPropertiesRow.END_DATE -> if (value is Date?) {
this.endDate = value this.endDate = value
} }
SessionPropertiesRow.GAME -> { SessionPropertiesRow.GAME -> {
if (value is ArrayList<*>) { when (value) {
limit = try { is ArrayList<*> -> {
(value[0] as Int?) limit = try {
} catch (e: Exception) { (value[0] as Int?)
null } catch (e: Exception) {
null
}
game = try {
(value[1] as Game?)
} catch (e: Exception) {
null
}
} }
game = try { is Game -> {
(value[1] as Game?) game = value
} catch (e: Exception) { }
null null -> {
limit = null
game = null
} }
} else if (value is Game) {
game = value
} else if (value == null) {
limit = null
game = null
} }
} }
SessionPropertiesRow.INITIAL_BUY_IN -> { SessionPropertiesRow.INITIAL_BUY_IN -> {
@ -803,21 +928,21 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
} }
} }
SessionPropertiesRow.POSITION -> { SessionPropertiesRow.POSITION -> {
val localResult = if (result != null) result as Result else realm.createObject(Result::class.java) // val localResult = if (result != null) result as Result else realm.createObject(Result::class.java)
if (value is Double) { if (value is Double) {
localResult.tournamentFinalPosition = value.toInt() this.tournamentFinalPosition = value.toInt()
} else { } else {
localResult.tournamentFinalPosition = null this.tournamentFinalPosition = null
} }
result = localResult // result = localResult
} }
SessionPropertiesRow.START_DATE -> if (value is Date) { SessionPropertiesRow.START_DATE -> if (value is Date) {
this.startDate = value this.startDate = value
} }
SessionPropertiesRow.TABLE_SIZE -> tableSize = value as Int? SessionPropertiesRow.TABLE_SIZE -> tableSize = value as Int?
SessionPropertiesRow.TIPS -> { SessionPropertiesRow.TIPS -> {
val localResult = getOrCreateResult() // val localResult = getOrCreateResult()
localResult.tips = value as Double? this.tips = value as Double?
} }
SessionPropertiesRow.TOURNAMENT_NAME -> tournamentName = value as TournamentName? SessionPropertiesRow.TOURNAMENT_NAME -> tournamentName = value as TournamentName?
SessionPropertiesRow.TOURNAMENT_TYPE -> tournamentType = (value as TournamentType?)?.ordinal SessionPropertiesRow.TOURNAMENT_TYPE -> tournamentType = (value as TournamentType?)?.ordinal
@ -842,6 +967,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
if (value != null) { if (value != null) {
val customFieldEntry = CustomFieldEntry() val customFieldEntry = CustomFieldEntry()
customFieldEntry.numericValue = value as Double? customFieldEntry.numericValue = value as Double?
customFieldEntry.customField = row
customFieldEntries.add(customFieldEntry) customFieldEntries.add(customFieldEntry)
row.entries.add(customFieldEntry) row.entries.add(customFieldEntry)
} }
@ -857,14 +983,25 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
} }
private fun getOrCreateResult(): Result { // private fun customFieldEntries(realm: Realm, customField: CustomField): List<CustomFieldEntry> {
return this.result //
?: run { //// val cfEntries = customField.entries
val result = realm.createObject(Result::class.java) //// val sessionEntries = this.customFieldEntries
this.result = result ////
result //// val entries = realm.where<CustomFieldEntry>()
} //// .`in`()
} //
// return listOf()
// }
// private fun getOrCreateResult(): Result {
// return this.result
// ?: run {
// val result = realm.createObject(Result::class.java)
// this.result = result
// result
// }
// }
// Stat Entry // Stat Entry
@ -879,40 +1016,34 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
override fun formattedValue(stat: Stat): TextFormat { override fun formattedValue(stat: Stat): TextFormat {
this.result?.let { result -> val value: Double? = when (stat) {
Stat.NET_RESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> this.net
val value: Double? = when (stat) { Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> 1.0
Stat.NET_RESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> result.net Stat.AVERAGE_BUYIN -> this.buyin
Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> 1.0 Stat.ROI -> {
Stat.AVERAGE_BUYIN -> result.buyin this.buyin?.let {
Stat.ROI -> { Stat.returnOnInvestment(this.net, it)
result.buyin?.let { } ?: run {
Stat.returnOnInvestment(result.net, it) null
} ?: run {
null
}
} }
Stat.HOURLY_RATE_BB -> this.bbHourlyRate
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> Stat.netBBPer100Hands(
this.bbNet,
this.estimatedHands
)
Stat.AVERAGE_NET_BB, Stat.BB_NET_RESULT -> this.bbNet
Stat.HOURLY_DURATION, Stat.AVERAGE_HOURLY_DURATION -> this.netDuration.toDouble()
Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate
Stat.HANDS_PLAYED -> this.estimatedHands
Stat.WIN_RATIO -> null
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
value?.let {
return stat.textFormat(it, currency = currency)
} ?: run {
return TextFormat(NULL_TEXT)
} }
Stat.HOURLY_RATE_BB -> this.bbHourlyRate
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> Stat.netBBPer100Hands(
this.bbNet,
this.estimatedHands
)
Stat.AVERAGE_NET_BB, Stat.BB_NET_RESULT -> this.bbNet
Stat.HOURLY_DURATION, Stat.AVERAGE_HOURLY_DURATION -> this.netDuration.toDouble()
Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate
Stat.HANDS_PLAYED -> this.estimatedHands
Stat.WIN_RATIO -> null
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
value?.let {
return stat.textFormat(it, currency = currency)
} ?: run { } ?: run {
throw PAIllegalStateException("Asking for statIds on Session without Result") return TextFormat(NULL_TEXT)
} }
} }
@ -965,25 +1096,25 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
@Ignore @Ignore
override val realmObjectClass: Class<out Identifiable> = Session::class.java override val realmObjectClass: Class<out Identifiable> = Session::class.java
fun charSequenceForRow(row: RowRepresentable, context: Context): String { fun charSequenceForRow(row: RowRepresentable, context: Context, realm: Realm): String {
return when (row) { return when (row) {
SessionPropertiesRow.BANKROLL -> bankroll?.name ?: NULL_TEXT SessionPropertiesRow.BANKROLL -> bankroll?.name ?: NULL_TEXT
SessionPropertiesRow.STAKES -> getFormattedStakes() SessionPropertiesRow.STAKES -> getFormattedStakes()
SessionPropertiesRow.BREAK_TIME -> if (this.breakDuration > 0.0) this.breakDuration.toMinutes() else NULL_TEXT SessionPropertiesRow.BREAK_TIME -> if (this.breakDuration > 0.0) this.breakDuration.toMinutes() else NULL_TEXT
SessionPropertiesRow.BUY_IN -> this.result?.buyin?.toCurrency(currency) ?: NULL_TEXT SessionPropertiesRow.BUY_IN -> this.buyin?.toCurrency(currency) ?: NULL_TEXT
SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> this.result?.cashout?.toCurrency(currency) ?: NULL_TEXT SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> this.cashout?.toCurrency(currency) ?: NULL_TEXT
SessionPropertiesRow.NET_RESULT -> this.result?.netResult?.toCurrency(currency) ?: NULL_TEXT SessionPropertiesRow.NET_RESULT -> this.netResult?.toCurrency(currency) ?: NULL_TEXT
SessionPropertiesRow.COMMENT -> if (this.comment.isNotEmpty()) this.comment else NULL_TEXT SessionPropertiesRow.COMMENT -> if (this.comment.isNotEmpty()) this.comment else NULL_TEXT
SessionPropertiesRow.END_DATE -> this.endDate?.shortDateTime() ?: NULL_TEXT SessionPropertiesRow.END_DATE -> this.endDate?.shortDateTime() ?: NULL_TEXT
SessionPropertiesRow.GAME -> getFormattedGame() SessionPropertiesRow.GAME -> getFormattedGame()
SessionPropertiesRow.INITIAL_BUY_IN -> tournamentEntryFee?.toCurrency(currency) ?: NULL_TEXT SessionPropertiesRow.INITIAL_BUY_IN -> tournamentEntryFee?.toCurrency(currency) ?: NULL_TEXT
SessionPropertiesRow.LOCATION -> location?.name ?: NULL_TEXT SessionPropertiesRow.LOCATION -> location?.name ?: NULL_TEXT
SessionPropertiesRow.PLAYERS -> tournamentNumberOfPlayers?.toString() ?: NULL_TEXT SessionPropertiesRow.PLAYERS -> tournamentNumberOfPlayers?.toString() ?: NULL_TEXT
SessionPropertiesRow.POSITION -> result?.tournamentFinalPosition?.toString() ?: NULL_TEXT SessionPropertiesRow.POSITION -> this.tournamentFinalPosition?.toString() ?: NULL_TEXT
SessionPropertiesRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT SessionPropertiesRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT
SessionPropertiesRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: NULL_TEXT SessionPropertiesRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: NULL_TEXT
SessionPropertiesRow.TIPS -> result?.tips?.toCurrency(currency) ?: NULL_TEXT SessionPropertiesRow.TIPS -> this.tips?.toCurrency(currency) ?: NULL_TEXT
SessionPropertiesRow.TOURNAMENT_TYPE -> { SessionPropertiesRow.TOURNAMENT_TYPE -> {
this.tournamentType?.let { this.tournamentType?.let {
TournamentType.values()[it].localizedTitle(context) TournamentType.values()[it].localizedTitle(context)
@ -1005,10 +1136,14 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
} }
} }
SessionPropertiesRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT SessionPropertiesRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT
SessionPropertiesRow.HANDS -> this.handHistories?.size.toString() SessionPropertiesRow.HANDS -> {
val handHistories = realm.where(HandHistory::class.java).equalTo("session.id", this.id).findAll()
handHistories.size.toString()
}
SessionPropertiesRow.HANDS_COUNT -> this.handsCountFormatted(context) SessionPropertiesRow.HANDS_COUNT -> this.handsCountFormatted(context)
SessionPropertiesRow.NUMBER_OF_TABLES -> this.numberOfTables.toString() SessionPropertiesRow.NUMBER_OF_TABLES -> this.numberOfTables.toString()
is CustomField -> { is CustomField -> {
// Timber.d("entries count = ${customFieldEntries.size}")
customFieldEntries.find { it.customField?.id == row.id }?.let { customFieldEntry -> customFieldEntries.find { it.customField?.id == row.id }?.let { customFieldEntry ->
return customFieldEntry.getFormattedValue(currency) return customFieldEntry.getFormattedValue(currency)
} }
@ -1024,12 +1159,12 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
} }
fun clearBuyinCashedOut() { fun clearBuyinCashedOut() {
this.result?.buyin = null this.buyin = null
this.result?.cashout = null this.cashout = null
} }
fun clearNetResult() { fun clearNetResult() {
this.result?.netResult = null this.netResult = null
} }
private fun cleanupBlinds(blinds: String?): String? { private fun cleanupBlinds(blinds: String?): String? {

@ -19,7 +19,7 @@ import java.text.DateFormat
import java.util.* import java.util.*
open class SessionSet() : RealmObject(), Timed, Filterable { open class SessionSet : RealmObject(), Timed, Filterable {
@PrimaryKey @PrimaryKey
override var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
@ -61,6 +61,7 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
override var netDuration: Long = 0L override var netDuration: Long = 0L
fun computeStats() { fun computeStats() {
// Timber.d("compute > session count = ${this.sessions?.size}")
this.ratedNet = this.sessions?.sumOf { it.computableResult?.ratedNet ?: 0.0 } ?: 0.0 this.ratedNet = this.sessions?.sumOf { it.computableResult?.ratedNet ?: 0.0 } ?: 0.0
this.estimatedHands = this.sessions?.sumOf { it.estimatedHands } ?: 0.0 this.estimatedHands = this.sessions?.sumOf { it.estimatedHands } ?: 0.0
this.bbNet = this.sessions?.sumOf { it.bbNet } ?: 0.0 this.bbNet = this.sessions?.sumOf { it.bbNet } ?: 0.0
@ -75,7 +76,7 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
var ratedNet: Double = 0.0 var ratedNet: Double = 0.0
val hourlyRate: Double private val hourlyRate: Double
get() { get() {
return this.ratedNet / this.hourlyDuration return this.ratedNet / this.hourlyDuration
} }
@ -84,7 +85,7 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
var bbNet: BB = 0.0 var bbNet: BB = 0.0
val bbHourlyRate: BB private val bbHourlyRate: BB
get() { get() {
return this.bbNet / this.hourlyDuration return this.bbNet / this.hourlyDuration
} }
@ -100,14 +101,13 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
companion object { companion object {
fun newInstance(realm: Realm) : SessionSet { fun newInstance(realm: Realm): SessionSet {
val sessionSet = SessionSet() return realm.copyToRealm(SessionSet())
return realm.copyToRealm(sessionSet)
} }
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { fun fieldNameForQueryType(queryCondition: Class <out QueryCondition>): String? {
Session.fieldNameForQueryType(queryCondition)?.let { Session.fieldNameForQueryType(queryCondition)?.let { fieldName ->
return "sessions.$it" return "sessions.$fieldName"
} }
return null return null
} }

@ -2,15 +2,18 @@ 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.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.util.UUID_SEPARATOR import net.pokeranalytics.android.util.UUID_SEPARATOR
import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.findById
import timber.log.Timber
import java.util.* import java.util.*
open class UserConfig : RealmObject() { open class UserConfig : RealmObject() {
companion object { companion object {
// these values are buffered to avoid the use of Realm instance
fun getConfiguration(realm: Realm): UserConfig { fun getConfiguration(realm: Realm): UserConfig {
realm.where(UserConfig::class.java).findFirst()?.let { config -> realm.where(UserConfig::class.java).findFirst()?.let { config ->
return config return config
@ -29,8 +32,12 @@ open class UserConfig : RealmObject() {
var transactionTypeIds: String = "" var transactionTypeIds: String = ""
fun setTransactionTypeIds(transactionTypes: Set<TransactionType>) { // fun setTransactionTypeIds(transactionTypes: Set<TransactionType>) {
this.transactionTypeIds = transactionTypes.joinToString(UUID_SEPARATOR) { it.id } // this.transactionTypeIds = transactionTypes.joinToString(UUID_SEPARATOR) { it.id }
// }
fun setTransactionTypeIds(transactionTypeIds: Set<String>) {
this.transactionTypeIds = transactionTypeIds.joinToString(UUID_SEPARATOR)
} }
fun transactionTypes(realm: Realm): List<TransactionType> { fun transactionTypes(realm: Realm): List<TransactionType> {
@ -38,4 +45,40 @@ open class UserConfig : RealmObject() {
return ids.mapNotNull { realm.findById(it) } return ids.mapNotNull { realm.findById(it) }
} }
} }
object UserConfigObserver {
private var userConfig: RealmResults<UserConfig>? = null
var liveDealtHandsPerHour: Int = 0
var onlineDealtHandsPerHour: Int = 0
init {
val realm = Realm.getDefaultInstance()
this.updateValues(realm)
this.userConfig = realm.where(UserConfig::class.java).findAll()
this.userConfig?.addChangeListener { results ->
this.updateValues(results.realm)
val userConfig = UserConfig.getConfiguration(results.realm)
this.liveDealtHandsPerHour = userConfig.liveDealtHandsPerHour
this.onlineDealtHandsPerHour = userConfig.onlineDealtHandsPerHour
}
realm.close()
}
fun create() { }
private fun updateValues(realm: Realm) {
val userConfig = UserConfig.getConfiguration(realm)
this.liveDealtHandsPerHour = userConfig.liveDealtHandsPerHour
this.onlineDealtHandsPerHour = userConfig.onlineDealtHandsPerHour
Timber.d("UserConfigObserver values updated: live = ${this.liveDealtHandsPerHour}, online = ${this.onlineDealtHandsPerHour}")
}
}

@ -198,8 +198,16 @@ open class HandHistory : RealmObject(), Deletable, RowRepresentable, Filterable,
handSetup.ante?.let { this.ante = it } handSetup.ante?.let { this.ante = it }
handSetup.blinds?.let { this.blinds = it } handSetup.blinds?.let { this.blinds = it }
this.session = handSetup.session handSetup.session?.let { session ->
this.date = this.session?.handHistoryAutomaticDate ?: Date() this.date = session.handHistoryAutomaticDate
session.realm?.let { realm ->
this.session = realm.copyFromRealm(session)
} ?: run {
this.session = session
}
} ?: run {
this.date = Date()
}
this.createActions(handSetup) this.createActions(handSetup)
} }
@ -313,13 +321,7 @@ open class HandHistory : RealmObject(), Deletable, RowRepresentable, Filterable,
* Creates and affect a PlayerSetup at the given [positionIndex] * Creates and affect a PlayerSetup at the given [positionIndex]
*/ */
fun createPlayerSetup(positionIndex: Int): PlayerSetup { fun createPlayerSetup(positionIndex: Int): PlayerSetup {
val playerSetup = PlayerSetup()
val playerSetup = if (this.realm != null) {
this.realm.createObject(PlayerSetup::class.java) }
else {
PlayerSetup()
}
playerSetup.position = positionIndex playerSetup.position = positionIndex
this.playerSetups.add(playerSetup) this.playerSetups.add(playerSetup)
return playerSetup return playerSetup

@ -16,7 +16,7 @@ class DataUtils {
fun sessionCount(realm: Realm, startDate: Date, endDate: Date?, net: Double): Int { fun sessionCount(realm: Realm, startDate: Date, endDate: Date?, net: Double): Int {
var sessionQuery = realm.where(Session::class.java) var sessionQuery = realm.where(Session::class.java)
.equalTo("startDate", startDate) .equalTo("startDate", startDate)
.equalTo("result.net", net) .equalTo("net", net)
endDate?.let { endDate?.let {
sessionQuery = sessionQuery.equalTo("endDate", it) sessionQuery = sessionQuery.equalTo("endDate", it)

@ -11,11 +11,11 @@ import net.pokeranalytics.android.ui.view.rows.SessionPropertiesRow
* Returns all significant parameters concatenated in a String * Returns all significant parameters concatenated in a String
* Not suitable for display * Not suitable for display
*/ */
private fun Session.parameterRepresentation(context: Context): String { private fun Session.parameterRepresentation(context: Context, realm: Realm): String {
var representation = "" var representation = ""
this.significantFields().forEach { this.significantFields().forEach {
representation += this.charSequenceForRow(it, context) representation += this.charSequenceForRow(it, context, realm)
} }
return representation return representation
@ -73,15 +73,20 @@ class FavoriteSessionFinder {
/** /**
* Copies the favorite session parameters on the [session] * Copies the favorite session parameters on the [session]
*/ */
fun copyParametersFromFavoriteSession(session: Session, location: Location?, context: Context) { fun copyParametersFromFavoriteSession(session: Session, location: Location?, context: Context, realm: Realm) {
val favoriteSession = favoriteSession(session.type, location, session.realm, context) val favoriteSession = favoriteSession(session.type, location, realm, context)
favoriteSession?.let { fav -> favoriteSession?.let { fav ->
session.limit = fav.limit session.limit = fav.limit
session.game = fav.game fav.game?.let { game ->
session.bankroll = fav.bankroll session.game = realm.copyFromRealm(game)
}
fav.bankroll?.let { br ->
session.bankroll = realm.copyFromRealm(br)
}
session.tableSize = fav.tableSize session.tableSize = fav.tableSize
when (session.type) { when (session.type) {
@ -112,7 +117,7 @@ class FavoriteSessionFinder {
val counters = hashMapOf<String, Counter>() val counters = hashMapOf<String, Counter>()
lastSessions.forEach { session -> lastSessions.forEach { session ->
val representation = session.parameterRepresentation(context) val representation = session.parameterRepresentation(context, realm)
val counter = counters[representation] val counter = counters[representation]
if (counter != null) { if (counter != null) {
counter.increment() counter.increment()

@ -1,209 +0,0 @@
package net.pokeranalytics.android.model.utils
import io.realm.RealmQuery
import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import kotlin.math.max
class CorruptSessionSetException(message: String) : Exception(message)
/**
* The manager is in charge of updating the abstract concept of timeline,
* representing the sequenced time frames where the user plays.
*/
class SessionSetManager {
companion object {
/**
* Updates the global timeline using the updated [session]
*/
fun updateTimeline(session: Session) {
if (!session.realm.isInTransaction) {
throw PAIllegalStateException("realm should be in transaction at this point")
}
if (session.startDate == null) {
throw ModelException("Start date should never be null here")
}
if (session.endDate == null) {
throw ModelException("End date should never be null here")
}
val sessionSets = this.matchingSets(session)
cleanupSessionSets(session, sessionSets)
}
private fun matchingSets(session: Session) : RealmResults<SessionSet> {
val realm = session.realm
val endDate = session.endDate!! // tested above
val startDate = session.startDate!!
val query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java)
query
.lessThanOrEqualTo("startDate", startDate)
.greaterThanOrEqualTo("endDate", startDate)
.or()
.lessThanOrEqualTo("startDate", endDate)
.greaterThanOrEqualTo("endDate", endDate)
.or()
.greaterThanOrEqualTo("startDate", startDate)
.lessThanOrEqualTo("endDate", endDate)
return query.findAll()
}
/**
* Multiple session sets update:
* Merges or splits session sets
* Does that by deleting then recreating
*/
private fun cleanupSessionSets(session: Session, sessionSets: RealmResults<SessionSet>) {
// get all endedSessions from sets
val allImpactedSessions = mutableSetOf<Session>()
sessionSets.forEach { set ->
set.sessions?.asIterable()?.let { allImpactedSessions.addAll(it) }
}
allImpactedSessions.add(session)
// delete all sets
sessionSets.deleteAllFromRealm()
allImpactedSessions.forEach { impactedSession ->
val sets = matchingSets(impactedSession)
this.updateTimeFrames(sets, impactedSession)
}
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}")
}
/**
* Update the global timeline using the impacted [sessionSets] and the updated [session]
*/
private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, session: Session) {
when (sessionSets.size) {
0 -> this.createOrUpdateSessionSet(session)
else -> this.mergeSessionGroups(session, sessionSets)
}
}
/**
* Creates or update the session set for the [session]
*/
private fun createOrUpdateSessionSet(session: Session) {
val set = session.sessionSet
if (set != null) {
set.startDate = session.startDate!! // tested above
set.endDate = session.endDate!!
} else {
this.createSessionSet(session)
}
}
/**
* Create a set and affect it to the [session]
*/
private fun createSessionSet(session: Session) {
val set: SessionSet = SessionSet.newInstance(session.realm)
set.startDate = session.startDate!!
set.endDate = session.endDate!!
set.breakDuration = session.breakDuration
session.sessionSet = set
set.computeStats()
}
/**
* Multiple session sets update:
* Merges all sets into one (delete all then create a new one)
*/
private fun mergeSessionGroups(session: Session, sessionSets: RealmResults<SessionSet>) {
var startDate = session.startDate!!
var endDate = session.endDate!!
// get all endedSessions from sets
val sessions = mutableSetOf<Session>()
sessionSets.forEach { set ->
set.sessions?.asIterable()?.let { sessions.addAll(it) }
}
// find earlier and later dates from all sets
sessions.forEach { s ->
if (s.startDate != null && s.endDate != null) {
val start = s.startDate!!
val end = s.endDate!!
if (start.before(startDate)) {
startDate = start
}
if (end.after(endDate)) {
endDate = end
}
} else {
throw CorruptSessionSetException("Set contains unfinished sessions!")
}
}
// delete all sets
sessionSets.deleteAllFromRealm()
// Create a new set
val set: SessionSet = SessionSet.newInstance(session.realm)
set.startDate = startDate
set.endDate = endDate
// Add the session linked to this timeframe to the new sessionGroup
session.sessionSet = set
// Add all orphan endedSessions
sessions.forEach { s ->
s.sessionSet = set
set.breakDuration = max(set.breakDuration, s.breakDuration)
}
set.computeStats()
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}")
}
/**
* Removes the [session] from the timeline
*/
fun removeFromTimeline(session: Session) {
if (!session.realm.isInTransaction) {
throw PAIllegalStateException("realm should be in transaction at this point")
}
val sessionSet = session.sessionSet
if (sessionSet != null) {
val sessions = mutableSetOf<Session>()
sessionSet.sessions?.asIterable()?.let { sessions.addAll(it) }
sessions.remove(session)
sessionSet.deleteFromRealm()
sessions.forEach {
updateTimeline(it)
}
}
}
}
}

@ -6,14 +6,13 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import io.realm.RealmResults
import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.NewPerformanceListener import net.pokeranalytics.android.calculus.NewPerformanceListener
import net.pokeranalytics.android.calculus.DataManager
import net.pokeranalytics.android.databinding.ActivityHomeBinding import net.pokeranalytics.android.databinding.ActivityHomeBinding
import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.activity.components.BaseActivity import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.adapter.HomePagerAdapter import net.pokeranalytics.android.ui.adapter.HomePagerAdapter
@ -41,13 +40,12 @@ class HomeActivity : BaseActivity(), NewPerformanceListener {
} }
} }
private lateinit var currencies: RealmResults<Currency>
private var homePagerAdapter: HomePagerAdapter? = null private var homePagerAdapter: HomePagerAdapter? = null
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item -> private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
if (binding.viewPager.currentItem == Tab.REPORTS.identifier) { if (binding.viewPager.currentItem == Tab.REPORTS.identifier) {
this.paApplication.reportWhistleBlower?.clearNotifications() DataManager.reportWhistleBlower?.clearNotifications()
} }
when (item.itemId) { when (item.itemId) {
@ -95,32 +93,16 @@ class HomeActivity : BaseActivity(), NewPerformanceListener {
} }
} }
observeRealmObjects()
initUI() initUI()
checkFirstLaunch() checkFirstLaunch()
this.paApplication.reportWhistleBlower?.addListener(this) DataManager.reportWhistleBlower?.addListener(this)
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
this.paApplication.reportWhistleBlower?.removeListener(this) DataManager.reportWhistleBlower?.removeListener(this)
}
private fun observeRealmObjects() {
val realm = getRealm()
// observe currency changes
this.currencies = realm.where(Currency::class.java).findAll()
this.currencies.addChangeListener { currencies, _ ->
realm.executeTransaction {
currencies.forEach {
it.refreshRelatedRatedValues()
}
}
}
} }
/** /**
@ -163,12 +145,10 @@ class HomeActivity : BaseActivity(), NewPerformanceListener {
} }
override fun newBestPerformanceHandler() { override fun newBestPerformanceHandler() {
if (Preferences.showInAppBadges(this)) { if (Preferences.showInAppBadges(this)) {
binding.navigation.getOrCreateBadge(R.id.navigation_reports).isVisible = true binding.navigation.getOrCreateBadge(R.id.navigation_reports).isVisible = true
binding.navigation.getOrCreateBadge(R.id.navigation_reports).number = 1 binding.navigation.getOrCreateBadge(R.id.navigation_reports).number = 1
} }
} }
private fun lookForCalendarBadge() { private fun lookForCalendarBadge() {

@ -9,14 +9,13 @@ import android.provider.MediaStore
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.util.ImageUtils import net.pokeranalytics.android.util.ImageUtils
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.*
open class MediaActivity : BaseActivity() { open class MediaActivity : BaseActivity() {
@ -52,12 +51,12 @@ open class MediaActivity : BaseActivity() {
val filesList = ArrayList<File>() val filesList = ArrayList<File>()
GlobalScope.launch { CoroutineScope(context = Dispatchers.IO).launch {
if (tempFile != null) { if (tempFile != null) {
tempFile?.let { tempFile?.let {
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
filesList.add(it) filesList.add(it)
getPictures(filesList) getPictures(filesList)
} }
} }
@ -65,7 +64,7 @@ open class MediaActivity : BaseActivity() {
data.clipData?.let { clipData -> data.clipData?.let { clipData ->
try { try {
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
isLoadingNewPictures() isLoadingNewPictures()
} }
@ -78,7 +77,7 @@ open class MediaActivity : BaseActivity() {
filesList.add(photoFile) filesList.add(photoFile)
} }
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
getPictures(filesList) getPictures(filesList)
} }
@ -90,7 +89,7 @@ open class MediaActivity : BaseActivity() {
data.data?.let { uri -> data.data?.let { uri ->
try { try {
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
isLoadingNewPictures() isLoadingNewPictures()
} }
@ -98,7 +97,7 @@ open class MediaActivity : BaseActivity() {
val photoFile = ImageUtils.createTempImageFile(this@MediaActivity) val photoFile = ImageUtils.createTempImageFile(this@MediaActivity)
ImageUtils.copyInputStreamToFile(inputStream!!, photoFile) ImageUtils.copyInputStreamToFile(inputStream!!, photoFile)
filesList.add(photoFile) filesList.add(photoFile)
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
getPictures(filesList) getPictures(filesList)
} }
@ -176,13 +175,6 @@ open class MediaActivity : BaseActivity() {
} }
} }
// // Test if we have the permission
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// selectedChoice = SELECTED_CHOICE_TAKE_PICTURE
// askForStoragePermission()
// return
// }
} }
@ -206,21 +198,6 @@ open class MediaActivity : BaseActivity() {
} }
} }
// Test if we have the permission
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// selectedChoice = SELECTED_CHOICE_SELECT_PICTURE
// askForStoragePermission()
// return
// }
//
// this.multiplePictures = multiplePictures
//
// val galleryIntent = Intent()
// galleryIntent.type = "image/*"
// galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiplePictures)
// galleryIntent.action = Intent.ACTION_GET_CONTENT
// startActivityForResult(galleryIntent, REQUEST_CODE_SELECT_PICTURE)
} }
/** /**

@ -8,10 +8,11 @@ import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.DataManager
import net.pokeranalytics.android.databinding.FragmentImportBinding import net.pokeranalytics.android.databinding.FragmentImportBinding
import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.util.csv.CSVImporter import net.pokeranalytics.android.util.csv.CSVImporter
@ -63,7 +64,6 @@ class ImportFragment : RealmFragment(), ImportDelegate {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
this.initUI() this.initUI()
this.startImport() this.startImport()
} }
@ -91,14 +91,14 @@ class ImportFragment : RealmFragment(), ImportDelegate {
private fun startImport() { private fun startImport() {
this.parentActivity?.paApplication?.reportWhistleBlower?.pause() DataManager.reportWhistleBlower?.pause()
this.importer = CSVImporter(uri, requireContext()) this.importer = CSVImporter(uri, requireContext())
this.importer.delegate = this this.importer.delegate = this
CoroutineScope(coroutineContext).launch { CoroutineScope(coroutineContext).launch {
val coroutine = GlobalScope.async { val coroutine = CoroutineScope(context = Dispatchers.IO).async {
val s = Date() val s = Date()
Timber.d(">>> Start Import...") Timber.d(">>> Start Import...")
@ -142,7 +142,7 @@ class ImportFragment : RealmFragment(), ImportDelegate {
} }
private fun end() { private fun end() {
this.parentActivity?.paApplication?.reportWhistleBlower?.resume() DataManager.reportWhistleBlower?.resume()
activity?.finish() activity?.finish()
} }

@ -26,6 +26,7 @@ import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.Performance import net.pokeranalytics.android.model.realm.Performance
import net.pokeranalytics.android.model.realm.ReportSetup import net.pokeranalytics.android.model.realm.ReportSetup
import net.pokeranalytics.android.calculus.DataManager
import net.pokeranalytics.android.ui.activity.ReportCreationActivity import net.pokeranalytics.android.ui.activity.ReportCreationActivity
import net.pokeranalytics.android.ui.activity.components.ReportActivity import net.pokeranalytics.android.ui.activity.components.ReportActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.activity.components.RequestCode
@ -186,13 +187,13 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
ReportCreationActivity.newInstanceForResult(this, requireContext()) ReportCreationActivity.newInstanceForResult(this, requireContext())
} }
this.paApplication?.reportWhistleBlower?.addListener(this) DataManager.reportWhistleBlower?.addListener(this)
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
this.paApplication?.reportWhistleBlower?.removeListener(this) DataManager.reportWhistleBlower?.removeListener(this)
} }
// Rows // Rows
@ -265,7 +266,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
override fun boolForRow(row: RowRepresentable): Boolean { override fun boolForRow(row: RowRepresentable): Boolean {
val reportRow = row as PerformanceRow val reportRow = row as PerformanceRow
return Preferences.showInAppBadges(requireContext()) return Preferences.showInAppBadges(requireContext())
&& (this.paApplication?.reportWhistleBlower?.has(reportRow.performance.id) ?: false) && (DataManager.reportWhistleBlower?.has(reportRow.performance.id) ?: false)
} }
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
@ -321,7 +322,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
val report = Calculator.computeStats(realm, options = options) val report = Calculator.computeStats(realm, options = options)
Timber.d("launchComputation: ${System.currentTimeMillis() - startDate.time}ms") // Timber.d("launchComputation: ${System.currentTimeMillis() - startDate.time}ms")
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
if (!isDetached) { if (!isDetached) {
@ -334,7 +335,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
} }
override fun newBestPerformanceHandler() { override fun newBestPerformanceHandler() {
Timber.d("newBestPerformanceHandler called") // Timber.d("newBestPerformanceHandler called")
activity?.runOnUiThread { activity?.runOnUiThread {
this.dataListAdapter.notifyDataSetChanged() this.dataListAdapter.notifyDataSetChanged()

@ -50,6 +50,7 @@ import net.pokeranalytics.android.util.billing.IAPProducts
import net.pokeranalytics.android.util.billing.PurchaseListener import net.pokeranalytics.android.util.billing.PurchaseListener
import net.pokeranalytics.android.util.csv.ProductCSVDescriptors import net.pokeranalytics.android.util.csv.ProductCSVDescriptors
import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted
import net.pokeranalytics.android.util.extensions.writeAsync
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -187,17 +188,17 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep
private fun updateMainCurrency(currencyCode: String, rate: Double) { private fun updateMainCurrency(currencyCode: String, rate: Double) {
Preferences.setCurrencyCode(currencyCode, requireContext()) Preferences.setCurrencyCode(currencyCode, requireContext())
val realm = Realm.getDefaultInstance()
realm.executeTransaction { getRealm().writeAsync { asyncRealm ->
realm.where(Currency::class.java).findAll().forEach { currency -> asyncRealm.where(Currency::class.java).findAll().forEach { currency ->
currency.rate = (currency.rate ?: 1.0) * rate currency.rate = (currency.rate ?: 1.0) * rate
} }
realm.where(Session::class.java).findAll().forEach { session -> asyncRealm.where(Session::class.java).findAll().forEach { session ->
session.bankrollHasBeenUpdated() session.bankrollHasBeenUpdated()
} }
} }
realm.close()
settingsAdapterRow.refreshRow(SettingsRow.CURRENCY) settingsAdapterRow.refreshRow(SettingsRow.CURRENCY)
} }

@ -9,7 +9,7 @@ import android.view.*
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -29,7 +29,6 @@ import net.pokeranalytics.android.ui.modules.filter.FilterActivityRequestCode
import net.pokeranalytics.android.ui.modules.filter.FilterableType import net.pokeranalytics.android.ui.modules.filter.FilterableType
import net.pokeranalytics.android.ui.modules.filter.FiltersActivity import net.pokeranalytics.android.ui.modules.filter.FiltersActivity
import net.pokeranalytics.android.ui.modules.settings.TransactionFilterActivity import net.pokeranalytics.android.ui.modules.settings.TransactionFilterActivity
import timber.log.Timber
import java.util.* import java.util.*
class StatisticsFragment : FilterableFragment(), RealmAsyncListener { class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
@ -75,6 +74,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
addRealmChangeListener(this, UserConfig::class.java) addRealmChangeListener(this, UserConfig::class.java)
addRealmChangeListener(this, ComputableResult::class.java) addRealmChangeListener(this, ComputableResult::class.java)
addRealmChangeListener(this, SessionSet::class.java)
addRealmChangeListener(this, Transaction::class.java) addRealmChangeListener(this, Transaction::class.java)
} }
@ -160,9 +160,9 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
CoroutineScope(coroutineContext).launch { CoroutineScope(coroutineContext).launch {
val async = GlobalScope.async { val async = CoroutineScope(context = Dispatchers.IO).async {
val s = Date() val s = Date()
Timber.d(">>> start...") // Timber.d(">>> start...")
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.refresh() realm.refresh()
@ -174,7 +174,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
val e = Date() val e = Date()
val duration = (e.time - s.time) / 1000.0 val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds") // Timber.d(">>> ended in $duration seconds")
} }
async.await() async.await()
@ -222,7 +222,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
Stat.AVERAGE_BUYIN Stat.AVERAGE_BUYIN
) )
if (QueryCondition.IsCash.queryWith(realm.where(Result::class.java).isNotNull("tips")).count() > 0) { if (QueryCondition.IsCash.queryWith(realm.where(Session::class.java).isNotNull("tips")).count() > 0) {
cgStats.add(Stat.TOTAL_TIPS) cgStats.add(Stat.TOTAL_TIPS)
} }
@ -235,13 +235,13 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_GAMES,
Stat.AVERAGE_BUYIN Stat.AVERAGE_BUYIN
) )
if (QueryCondition.IsTournament.queryWith(realm.where(Result::class.java).isNotNull("tips")).count() > 0) { if (QueryCondition.IsTournament.queryWith(realm.where(Session::class.java).isNotNull("tips")).count() > 0) {
tStats.add(Stat.TOTAL_TIPS) tStats.add(Stat.TOTAL_TIPS)
} }
val tSessionGroup = ComputableGroup(Query(QueryCondition.IsTournament).merge(query), tStats) val tSessionGroup = ComputableGroup(Query(QueryCondition.IsTournament).merge(query), tStats)
Timber.d(">>>>> Start computations...") // Timber.d(">>>>> Start computations...")
val options = Calculator.Options() val options = Calculator.Options()
val computedStats = mutableListOf<Stat>() val computedStats = mutableListOf<Stat>()

@ -114,7 +114,7 @@ class Top10Fragment : RealmFragment(), RowRepresentableDataSource, RowRepresenta
this.positiveSessions = getRealm().where<Session>() this.positiveSessions = getRealm().where<Session>()
.isNotNull("startDate") .isNotNull("startDate")
.isNotNull("endDate") .isNotNull("endDate")
.greaterThan("result.net", 0.0) .greaterThan("net", 0.0)
.findAll() .findAll()
updateTop10() updateTop10()

@ -8,15 +8,16 @@ import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import io.realm.RealmObject import io.realm.RealmObject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.util.extensions.writeAsync
/** /**
* Deletable Item Fragment * Deletable Item Fragment
@ -56,7 +57,7 @@ abstract class DeletableItemFragment : RealmFragment() {
val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName)
itemToDeleteId?.let { id -> itemToDeleteId?.let { id ->
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
delay(300) delay(300)
deleteItem(dataListAdapter, deletableItems(), id) deleteItem(dataListAdapter, deletableItems(), id)
} }
@ -88,8 +89,8 @@ abstract class DeletableItemFragment : RealmFragment() {
if (itemToDelete.isValidForDelete(this.getRealm())) { if (itemToDelete.isValidForDelete(this.getRealm())) {
deletedItem = getRealm().copyFromRealm(itemToDelete) deletedItem = getRealm().copyFromRealm(itemToDelete)
lastDeletedItemPosition = itemPosition lastDeletedItemPosition = itemPosition
getRealm().executeTransaction { getRealm().writeAsync { asyncRealm ->
itemToDelete.deleteDependencies(it) itemToDelete.deleteDependencies(asyncRealm)
itemToDelete.deleteFromRealm() itemToDelete.deleteFromRealm()
} }
itemHasBeenReInserted = false itemHasBeenReInserted = false
@ -117,9 +118,9 @@ abstract class DeletableItemFragment : RealmFragment() {
snackBar?.setAction(R.string.cancel) { snackBar?.setAction(R.string.cancel) {
if (!itemHasBeenReInserted) { if (!itemHasBeenReInserted) {
itemHasBeenReInserted = true itemHasBeenReInserted = true
getRealm().executeTransaction { realm -> getRealm().writeAsync { asyncRealm ->
deletedItem?.let { deletedItem?.let {
val item = realm.copyToRealmOrUpdate(it) val item = asyncRealm.copyToRealmOrUpdate(it)
updateUIAfterUndoDeletion(item) updateUIAfterUndoDeletion(item)
} }
} }

@ -9,8 +9,8 @@ import android.view.*
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -140,7 +140,7 @@ open class FilterableFragment : RealmFragment(), FilterHandler {
viewGroup.removeAllViews() viewGroup.removeAllViews()
viewGroup.addView(layoutCurrentFilter) viewGroup.addView(layoutCurrentFilter)
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
delay(300) delay(300)
viewGroup.visibility = View.VISIBLE viewGroup.visibility = View.VISIBLE
} }
@ -153,7 +153,7 @@ open class FilterableFragment : RealmFragment(), FilterHandler {
*/ */
private fun hideSelectedFilter() { private fun hideSelectedFilter() {
view?.findViewById<ViewGroup>(R.id.selectedFilter).let { view?.findViewById<ViewGroup>(R.id.selectedFilter).let {
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
it?.visibility = View.GONE it?.visibility = View.GONE
} }
} }

@ -9,7 +9,6 @@ import io.realm.RealmModel
import io.realm.RealmResults import io.realm.RealmResults
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import timber.log.Timber
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
interface RealmAsyncListener { interface RealmAsyncListener {
@ -70,7 +69,7 @@ open class RealmFragment : BaseFragment() {
this.changeListener = listener this.changeListener = listener
val results = this.realm.where(clazz).findAllAsync() val results = this.realm.where(clazz).findAllAsync()
results.addChangeListener { t, _ -> results.addChangeListener { t, _ ->
Timber.d("Realm changes: ${realmResults?.size}, $this") // Timber.d("Realm changes: ${realmResults?.size}, $this")
this.changeListener?.asyncListenedEntityChange(t.realm) this.changeListener?.asyncListenedEntityChange(t.realm)
} }
this.observedRealmResults.add(results) this.observedRealmResults.add(results)

@ -7,6 +7,7 @@ import android.view.ViewGroup
import androidx.core.view.get import androidx.core.view.get
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import io.realm.RealmObject
import net.pokeranalytics.android.databinding.BottomSheetGameListBinding import net.pokeranalytics.android.databinding.BottomSheetGameListBinding
import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -38,10 +39,11 @@ class BottomSheetListGameFragment : BottomSheetListFragment() {
} }
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.model.realmData?.let { this.model.realmData?.let { results ->
val selectedData = it[position] val selectedData = results[position]
selectedData?.let { data -> (selectedData as? RealmObject)?.let { data ->
this.model.someValues[1] = data val obj = results.realm.copyFromRealm(data)
this.model.someValues[1] = obj
this.onRowValueChanged() this.onRowValueChanged()
// this.delegate.onRowValueChanged(values, this.row) // this.delegate.onRowValueChanged(values, this.row)
dismiss() dismiss()

@ -17,6 +17,7 @@ import net.pokeranalytics.android.ui.modules.data.DataManagerFragment
import net.pokeranalytics.android.ui.viewmodel.ReportViewModel import net.pokeranalytics.android.ui.viewmodel.ReportViewModel
import net.pokeranalytics.android.ui.viewmodel.ViewModelHolder import net.pokeranalytics.android.ui.viewmodel.ViewModelHolder
import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.writeAsync
abstract class AbstractReportFragment : DataManagerFragment() { abstract class AbstractReportFragment : DataManagerFragment() {
@ -84,7 +85,7 @@ abstract class AbstractReportFragment : DataManagerFragment() {
saveReport(nameEditText.text.toString()) saveReport(nameEditText.text.toString())
dialog.dismiss() dialog.dismiss()
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(requireContext(), e.localizedMessage, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), e.localizedMessage, Toast.LENGTH_LONG).show()
} }
} }
.setNegativeButton(R.string.cancel) { dialog, _ -> .setNegativeButton(R.string.cancel) { dialog, _ ->
@ -105,36 +106,34 @@ abstract class AbstractReportFragment : DataManagerFragment() {
} }
this.reportViewModel.title = name this.reportViewModel.title = name
val rs = this.model.item as ReportSetup getRealm().writeAsync { asyncRealm ->
getRealm().executeTransaction { realm ->
val rs = this.model.item as ReportSetup
val firstSave = (this.model.primaryKey == null)
if (firstSave) { val options = this.selectedReport.options
val options = this.selectedReport.options rs.name = name
rs.name = name rs.display = this.reportViewModel.reportDisplay?.ordinal
rs.display = this.reportViewModel.reportDisplay?.ordinal ?: throw PAIllegalStateException("Display not set") ?: throw PAIllegalStateException("Display not set")
options.stats.forEach { rs.statIds.clear()
rs.statIds.add(it.uniqueIdentifier) rs.statIds.addAll(options.stats.map { it.uniqueIdentifier })
}
options.criterias.forEach { criteria -> rs.criteriaIds.clear()
when (criteria) { options.criterias.forEach { criteria ->
is CustomFieldCriteria -> rs.criteriaCustomFieldIds.add(criteria.customFieldId) when (criteria) {
else -> rs.criteriaIds.add(criteria.uniqueIdentifier) is CustomFieldCriteria -> rs.criteriaCustomFieldIds.add(criteria.customFieldId)
} else -> rs.criteriaIds.add(criteria.uniqueIdentifier)
} }
}
options.filterId?.let { id -> options.filterId?.let { id ->
rs.filter = realm.findById(id) rs.filter = asyncRealm.findById(id)
}
realm.copyToRealmOrUpdate(rs)
} else {
rs.name = name
realm.insertOrUpdate(rs)
} }
asyncRealm.copyToRealmOrUpdate(rs)
this.model.primaryKey = rs.id
} }
this.model.primaryKey = rs.id
this.deleteButtonShouldAppear = true this.deleteButtonShouldAppear = true
setToolbarTitle(this.reportViewModel.title) setToolbarTitle(this.reportViewModel.title)
} }

@ -8,7 +8,7 @@ import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -30,7 +30,6 @@ import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rows.StatRow import net.pokeranalytics.android.ui.view.rows.StatRow
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat import net.pokeranalytics.android.util.TextFormat
import timber.log.Timber
import java.util.* import java.util.*
open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentableDataSource, CoroutineScope, open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentableDataSource, CoroutineScope,
@ -203,12 +202,12 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab
showLoader() showLoader()
GlobalScope.launch(coroutineContext) { CoroutineScope(context = Dispatchers.IO).launch(coroutineContext) {
var report: Report? = null var report: Report? = null
val test = GlobalScope.async { val test = CoroutineScope(context = Dispatchers.IO).async {
val s = Date() val s = Date()
Timber.d(">>> start...") // Timber.d(">>> start...")
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.refresh() realm.refresh()
@ -221,7 +220,7 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab
val e = Date() val e = Date()
val duration = (e.time - s.time) / 1000.0 val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds") // Timber.d(">>> ended in $duration seconds")
} }
test.await() test.await()

@ -11,8 +11,8 @@ import com.github.mikephil.charting.data.LineDataSet
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.AggregationType import net.pokeranalytics.android.calculus.AggregationType
@ -30,7 +30,6 @@ import net.pokeranalytics.android.ui.extensions.showWithAnimation
import net.pokeranalytics.android.ui.fragment.GraphFragment import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.graph.Graph import net.pokeranalytics.android.ui.graph.Graph
import net.pokeranalytics.android.ui.helpers.AppReviewManager import net.pokeranalytics.android.ui.helpers.AppReviewManager
import timber.log.Timber
import java.util.* import java.util.*
@ -163,10 +162,10 @@ class ProgressReportFragment : AbstractReportFragment() {
graphContainer.hideWithAnimation() graphContainer.hideWithAnimation()
progressBar.showWithAnimation() progressBar.showWithAnimation()
GlobalScope.launch { CoroutineScope(context = Dispatchers.IO).launch {
val s = Date() val s = Date()
Timber.d(">>> start...") // Timber.d(">>> start...")
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
@ -178,7 +177,7 @@ class ProgressReportFragment : AbstractReportFragment() {
val e = Date() val e = Date()
val duration = (e.time - s.time) / 1000.0 val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds") // Timber.d(">>> ended in $duration seconds")
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
setGraphData(report, aggregationType) setGraphData(report, aggregationType)

@ -9,8 +9,8 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.data.LineDataSet
import io.realm.RealmResults import io.realm.RealmResults
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -35,8 +35,6 @@ import net.pokeranalytics.android.ui.view.rows.BankrollTotalRow
import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.util.extensions.sorted import net.pokeranalytics.android.util.extensions.sorted
import timber.log.Timber import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList
interface BankrollRowRepresentable : RowRepresentable { interface BankrollRowRepresentable : RowRepresentable {
var bankrollId: String? var bankrollId: String?
@ -88,7 +86,7 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour
if (requestCode == RequestCode.BANKROLL_DETAILS.value && resultCode == Activity.RESULT_OK) { if (requestCode == RequestCode.BANKROLL_DETAILS.value && resultCode == Activity.RESULT_OK) {
val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName)
itemToDeleteId?.let { id -> itemToDeleteId?.let { id ->
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
delay(300) delay(300)
deleteItem(dataListAdapter, bankrolls, id) deleteItem(dataListAdapter, bankrolls, id)

@ -13,8 +13,8 @@ import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.data.LineDataSet
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
@ -167,7 +167,7 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
this.model.computedResults?.let { computedResults -> this.model.computedResults?.let { computedResults ->
GlobalScope.launch { CoroutineScope(context = Dispatchers.IO).launch {
val startDate = Date() val startDate = Date()

@ -11,7 +11,6 @@ import io.realm.Realm
import io.realm.RealmModel import io.realm.RealmModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
@ -39,7 +38,6 @@ 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.ui.view.rows.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.util.extensions.* import net.pokeranalytics.android.util.extensions.*
import timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.set import kotlin.collections.set
@ -348,16 +346,16 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
binding.progressBar.showWithAnimation() binding.progressBar.showWithAnimation()
binding.recyclerView.hideWithAnimation() binding.recyclerView.hideWithAnimation()
GlobalScope.launch { CoroutineScope(context = Dispatchers.IO).launch {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.refresh() // realm.refresh()
launchStatComputation(realm) launchStatComputation(realm)
realm.close() realm.close()
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
displayData() displayData()
} }
} }
@ -490,7 +488,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
sortedMonthlyReports = monthlyReports.toSortedMap(compareByDescending { it }) sortedMonthlyReports = monthlyReports.toSortedMap(compareByDescending { it })
sortedYearlyReports = yearlyReports.toSortedMap(compareByDescending { it }) sortedYearlyReports = yearlyReports.toSortedMap(compareByDescending { it })
Timber.d("Computation: ${System.currentTimeMillis() - startDate.time}ms") // Timber.d("Computation: ${System.currentTimeMillis() - startDate.time}ms")
} }
@ -498,7 +496,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
* Display data * Display data
*/ */
private fun displayData() { private fun displayData() {
Timber.d("displayData") // Timber.d("displayData")
if (context == null) { return } // required because of launchAsyncStatComputation if (context == null) { return } // required because of launchAsyncStatComputation
@ -594,8 +592,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
} }
} }
Timber.d("Display data: ${System.currentTimeMillis() - startDate.time}ms") // Timber.d("Display data: ${System.currentTimeMillis() - startDate.time}ms")
Timber.d("Rows: ${rows.size}") // Timber.d("Rows: ${rows.size}")
this.calendarAdapter.notifyDataSetChanged() this.calendarAdapter.notifyDataSetChanged()

@ -12,7 +12,6 @@ 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.extensions.* import net.pokeranalytics.android.util.extensions.*
import java.util.* import java.util.*
import kotlin.collections.HashMap
class GridCalendarViewModel : ViewModel(), RowRepresentableDataSource { class GridCalendarViewModel : ViewModel(), RowRepresentableDataSource {
@ -64,8 +63,8 @@ class GridCalendarViewModel : ViewModel(), RowRepresentableDataSource {
while (tmpDate.time <= lastDate.time) { while (tmpDate.time <= lastDate.time) {
val result = groupedSessions[tmpDate]?.let { bucket -> val result = groupedSessions[tmpDate]?.let { bucket ->
if (bucket.sumByDouble { if (bucket.sumOf {
it.result?.net ?: 0.0 it?.net ?: 0.0
} > 0.0) CellResult.POSITIVE else CellResult.NEGATIVE } > 0.0) CellResult.POSITIVE else CellResult.NEGATIVE
} ?: run { } ?: run {
CellResult.EMPTY CellResult.EMPTY

@ -16,6 +16,8 @@ import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel
import net.pokeranalytics.android.util.extensions.writeAsync
import timber.log.Timber
open class DataManagerFragment : RealmFragment() { open class DataManagerFragment : RealmFragment() {
@ -96,13 +98,26 @@ open class DataManagerFragment : RealmFragment() {
val status = savable.getSaveValidityStatus(realm = this.getRealm()) val status = savable.getSaveValidityStatus(realm = this.getRealm())
when (status) { when (status) {
SaveValidityStatus.VALID -> { SaveValidityStatus.VALID -> {
this.getRealm().executeTransaction {
val managedItem = it.copyToRealmOrUpdate(this.model.item) val realm = getRealm()
if (managedItem is Savable) { val item = this.model.item
val uniqueIdentifier = managedItem.id Timber.d("save")
finishActivityWithResult(uniqueIdentifier) realm.writeAsync { asyncRealm ->
} Timber.d("execute async")
asyncRealm.copyToRealmOrUpdate(item)
}
if (item is Savable) {
val uniqueIdentifier = item.id
finishActivityWithResult(uniqueIdentifier)
} }
// this.getRealm().executeTransaction {
// val managedItem = it.copyToRealmOrUpdate(this.model.item)
// if (managedItem is Savable) {
// val uniqueIdentifier = managedItem.id
// finishActivityWithResult(uniqueIdentifier)
// }
// }
onDataSaved() onDataSaved()
} }
else -> { else -> {

@ -11,12 +11,13 @@ import io.realm.RealmModel
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.util.CrashLogging
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowUpdatable import net.pokeranalytics.android.ui.view.RowUpdatable
import net.pokeranalytics.android.util.CrashLogging
import net.pokeranalytics.android.util.extensions.writeAsync
open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegate { open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegate {
@ -67,16 +68,25 @@ open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegat
} }
override fun onRowValueChanged(value: Any?, row: RowRepresentable) { override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
getRealm().executeTransaction { (this.model.item as RowUpdatable).updateValue(value, row)
this.writeIfPossible(value, row)
rowRepresentableAdapter.refreshRow(row)
}
private fun writeIfPossible(value: Any?, row: RowRepresentable) {
if (!this.isUpdating) {
return
}
getRealm().writeAsync { asyncRealm ->
try { try {
(this.model.item as RowUpdatable).updateValue(value, row) asyncRealm.copyToRealmOrUpdate(this.model.item)
} catch (e: Exception) { } catch (e: Exception) {
CrashLogging.log("Exception caught: row = $row, value=$value, class=${this.javaClass}") CrashLogging.log("Exception caught: row = $row, value=$value, class=${this.javaClass}")
throw e throw e
} }
} }
rowRepresentableAdapter.refreshRow(row)
} }
/** /**

@ -13,8 +13,8 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -215,7 +215,7 @@ class PlayerDataFragment : EditableDataFragment(), StaticRowRepresentableDataSou
when (row) { when (row) {
is Comment -> { is Comment -> {
if (row.isValidForDelete(getRealm())) { if (row.isValidForDelete(getRealm())) {
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
delay(300) delay(300)
showAlertDialog(requireContext(), messageResId = R.string.are_you_sure_you_want_to_delete, showCancelButton = true, positiveAction = { showAlertDialog(requireContext(), messageResId = R.string.are_you_sure_you_want_to_delete, showCancelButton = true, positiveAction = {
playerModel.deleteComment(row) playerModel.deleteComment(row)

@ -16,6 +16,7 @@ import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.isSameDay import net.pokeranalytics.android.util.extensions.isSameDay
import net.pokeranalytics.android.util.extensions.mediumDate import net.pokeranalytics.android.util.extensions.mediumDate
import net.pokeranalytics.android.util.extensions.writeAsync
import java.util.* import java.util.*
class PlayerDataViewModel : DataManagerViewModel(), StaticRowRepresentableDataSource { class PlayerDataViewModel : DataManagerViewModel(), StaticRowRepresentableDataSource {
@ -131,9 +132,11 @@ class PlayerDataViewModel : DataManagerViewModel(), StaticRowRepresentableDataSo
*/ */
fun cleanupComments() { // called when saving the custom field fun cleanupComments() { // called when saving the custom field
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction {
this.commentsToDelete.forEach { // entries are out of realm val ids = this.commentsToDelete.map { it.id }
realm.where<Comment>().equalTo("id", it.id).findFirst()?.deleteFromRealm() realm.writeAsync { asyncRealm ->
ids.forEach { id -> // entries are out of realm
asyncRealm.where<Comment>().equalTo("id", id).findFirst()?.deleteFromRealm()
} }
} }
realm.close() realm.close()

@ -2,8 +2,8 @@ package net.pokeranalytics.android.ui.modules.data
import android.content.Context import android.content.Context
import io.realm.kotlin.where import io.realm.kotlin.where
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
@ -141,7 +141,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
when (val next = rows[index + 1]) { when (val next = rows[index + 1]) {
TransactionPropertiesRow.DATE, TransactionPropertiesRow.COMMENT -> {} TransactionPropertiesRow.DATE, TransactionPropertiesRow.COMMENT -> {}
else -> { else -> {
GlobalScope.launch(Dispatchers.Main) { CoroutineScope(context = Dispatchers.Main).launch {
delay(200) delay(200)
onRowSelected(0, next) onRowSelected(0, next)
} }

@ -39,12 +39,13 @@ import net.pokeranalytics.android.ui.modules.filter.FiltersActivity
import net.pokeranalytics.android.ui.modules.handhistory.HandHistoryActivity import net.pokeranalytics.android.ui.modules.handhistory.HandHistoryActivity
import net.pokeranalytics.android.ui.modules.session.SessionActivity import net.pokeranalytics.android.ui.modules.session.SessionActivity
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.URL import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.billing.AppGuard import net.pokeranalytics.android.util.billing.AppGuard
import net.pokeranalytics.android.util.billing.PurchaseListener import net.pokeranalytics.android.util.billing.PurchaseListener
import net.pokeranalytics.android.util.extensions.count import net.pokeranalytics.android.util.extensions.count
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.writeAsync
import java.util.* import java.util.*
class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseListener { class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseListener {
@ -494,9 +495,14 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
* Delete selected transaction * Delete selected transaction
*/ */
private fun deleteSelectedTransaction() { private fun deleteSelectedTransaction() {
getRealm().executeTransaction {
selectedTransaction?.deleteFromRealm() this.selectedTransaction?.id?.let { transactionId ->
getRealm().writeAsync { asyncRealm ->
val transaction = asyncRealm.findById<Transaction>(transactionId)
transaction?.deleteFromRealm()
}
} }
selectedTransactionPosition = -1 selectedTransactionPosition = -1
} }

@ -20,9 +20,7 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.SessionRowView import net.pokeranalytics.android.ui.view.SessionRowView
import net.pokeranalytics.android.util.extensions.getMonthAndYear import net.pokeranalytics.android.util.extensions.getMonthAndYear
import timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.HashMap
/** /**
@ -182,7 +180,7 @@ class FeedSessionRowRepresentableAdapter(
allSessions.clear() allSessions.clear()
allSessions.addAll(this.pendingSessions) allSessions.addAll(this.pendingSessions)
allSessions.addAll(this.startedSessions) allSessions.addAll(this.startedSessions)
Timber.d("Update session list, total count = ${allSessions.size}") // Timber.d("Update session list, total count = ${allSessions.size}")
val headersPositions = HashMap<Int, Date?>() val headersPositions = HashMap<Int, Date?>()
@ -210,7 +208,7 @@ class FeedSessionRowRepresentableAdapter(
} }
sortedHeaders = headersPositions.toSortedMap() sortedHeaders = headersPositions.toSortedMap()
Timber.d("Create viewTypesPositions in: ${System.currentTimeMillis() - start}ms") // Timber.d("Create viewTypesPositions in: ${System.currentTimeMillis() - start}ms")
} }
/** /**

@ -9,8 +9,8 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewAnimationUtils import android.view.ViewAnimationUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.databinding.ActivityNewDataBinding import net.pokeranalytics.android.databinding.ActivityNewDataBinding
@ -105,7 +105,8 @@ class NewDataMenuActivity : BaseActivity() {
val intent = Intent() val intent = Intent()
intent.putExtra(IntentKey.CHOICE.keyName, choice) intent.putExtra(IntentKey.CHOICE.keyName, choice)
setResult(RESULT_OK, intent) setResult(RESULT_OK, intent)
GlobalScope.launch(Dispatchers.Main) {
CoroutineScope(context = Dispatchers.Main).launch {
delay(200) delay(200)
hideMenu() hideMenu()
} }

@ -234,17 +234,17 @@ open class FilterDetailsFragment : RealmFragment(), RowRepresentableDelegate {
//TODO: Save currentFilter details data //TODO: Save currentFilter details data
Timber.d("Save data for queryWith: ${currentFilter?.id}") Timber.d("Save data for queryWith: ${currentFilter?.id}")
this.model.selectedRows.forEach { // this.model.selectedRows.forEach {
Timber.d("Selected rows: $it") // Timber.d("Selected rows: $it")
} // }
this.activityModel.selectedCategoryRow?.let { category -> this.activityModel.selectedCategoryRow?.let { category ->
getRealm().executeTransaction { // getRealm().executeTransaction {
currentFilter?.remove(category) currentFilter?.remove(category)
val validConditions = this.model.selectedRows.filter { it.queryCondition != null } val validConditions = this.model.selectedRows.filter { it.queryCondition != null }
currentFilter?.createOrUpdateFilterConditions(validConditions) currentFilter?.createOrUpdateFilterConditions(validConditions)
} // }
} }
currentFilter?.filterConditions?.forEach { currentFilter?.filterConditions?.forEach {

@ -4,11 +4,12 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.where
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.enumerations.IntIdentifiable import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import net.pokeranalytics.android.util.enumerations.IntSearchable import net.pokeranalytics.android.util.enumerations.IntSearchable
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.writeAsync
enum class FilterActivityRequestCode { enum class FilterActivityRequestCode {
SELECT_FILTER, SELECT_FILTER,
@ -45,10 +46,9 @@ interface FilterHandler {
fun saveFilter(context: Context, filterId: String) { fun saveFilter(context: Context, filterId: String) {
Preferences.setActiveFilterId(filterId, context) Preferences.setActiveFilterId(filterId, context)
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction { executeRealm -> realm.writeAsync { asyncRealm ->
currentFilter(context, executeRealm)?.let { currentFilter(context, asyncRealm)?.let {
it.useCount++ it.useCount++
} }
} }
@ -61,8 +61,9 @@ interface FilterHandler {
} }
fun currentFilter(context: Context, realm: Realm): Filter? { fun currentFilter(context: Context, realm: Realm): Filter? {
return Preferences.getActiveFilterId(context)?.let { return Preferences.getActiveFilterId(context)?.let { id ->
realm.where<Filter>().equalTo("id", it).findFirst() realm.findById(id)
// realm.where<Filter>().equalTo("id", it).findFirst()
} ?: run { } ?: run {
null null
} }

@ -15,12 +15,12 @@ import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
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.extensions.px
import net.pokeranalytics.android.ui.fragment.components.RealmFragment 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.rows.FilterCategoryRow import net.pokeranalytics.android.ui.view.rows.FilterCategoryRow
import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.extensions.sorted import net.pokeranalytics.android.util.extensions.sorted
import net.pokeranalytics.android.util.extensions.writeAsync
open class FiltersFragment : RealmFragment(), RowRepresentableDelegate { open class FiltersFragment : RealmFragment(), RowRepresentableDelegate {
@ -218,16 +218,13 @@ open class FiltersFragment : RealmFragment(), RowRepresentableDelegate {
* Validate the updates of the queryWith * Validate the updates of the queryWith
*/ */
private fun validateUpdates() { private fun validateUpdates() {
val currentFilter = this.model.currentFilter val filter = this.model.currentFilter
filter?.let { currentFilter ->
getRealm().executeTransaction { realm -> getRealm().writeAsync { asyncRealm ->
currentFilter?.let { asyncRealm.copyToRealmOrUpdate(currentFilter)
it.name = it.query.getName(requireContext()) }
realm.copyToRealmOrUpdate(it) }
} val filterId = filter?.id ?: ""
}
val filterId = currentFilter?.id ?: ""
finishActivityWithResult(filterId) finishActivityWithResult(filterId)
} }
@ -239,9 +236,9 @@ open class FiltersFragment : RealmFragment(), RowRepresentableDelegate {
val filterCopy = this.model.filterCopy val filterCopy = this.model.filterCopy
val filterId = filterCopy?.id ?: "" val filterId = filterCopy?.id ?: ""
getRealm().executeTransaction { realm -> getRealm().writeAsync { asyncRealm ->
filterCopy?.let { filterCopy?.let {
realm.copyToRealmOrUpdate(it) asyncRealm.copyToRealmOrUpdate(it)
} }
} }
finishActivityWithResult(filterId) finishActivityWithResult(filterId)

@ -3,11 +3,14 @@ package net.pokeranalytics.android.ui.modules.filter
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.modules.datalist.DataListFragment import net.pokeranalytics.android.ui.modules.datalist.DataListFragment
import net.pokeranalytics.android.ui.modules.filter.FilterHandler.Companion.INTENT_FILTER_UPDATE_FILTER_UI import net.pokeranalytics.android.ui.modules.filter.FilterHandler.Companion.INTENT_FILTER_UPDATE_FILTER_UI
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.writeAsync
open class FiltersListFragment : DataListFragment() { open class FiltersListFragment : DataListFragment() {
@ -15,12 +18,19 @@ open class FiltersListFragment : DataListFragment() {
override fun onRowValueChanged(value: Any?, row: RowRepresentable) { override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
when (row) { when (row) {
is Filter -> { is Filter -> {
getRealm().executeTransaction { val filterId = row.id
row.updateValue(value, row) // val filter = row.realm.copyFromRealm(row)
} // row.updateValue(value, row)
val index = this.model.items.indexOf(row) getRealm().writeAsync ({ asyncRealm ->
this.dataListAdapter.notifyItemChanged(index) asyncRealm.findById<Filter>(filterId)?.let { filter ->
updateFilterUIIfNecessary(requireContext(), row.id) filter.updateValue(value, filter)
// asyncRealm.copyToRealmOrUpdate(filter)
} ?: throw PAIllegalStateException("missing filter: $filterId")
}, {
val index = this.model.items.indexOf(row)
this.dataListAdapter.notifyItemChanged(index)
updateFilterUIIfNecessary(requireContext(), row.id)
} )
} }
else -> super.onRowValueChanged(value, row) else -> super.onRowValueChanged(value, row)
} }

@ -35,6 +35,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.util.CrashLogging import net.pokeranalytics.android.util.CrashLogging
import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.writeAsync
import timber.log.Timber import timber.log.Timber
@ -203,7 +204,8 @@ class EditorFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListen
val playerId = data?.getStringExtra(BaseFragment.BundleKey.PRIMARY_KEY.value) val playerId = data?.getStringExtra(BaseFragment.BundleKey.PRIMARY_KEY.value)
?: throw PAIllegalStateException("Primary key not set where as activity has finished") ?: throw PAIllegalStateException("Primary key not set where as activity has finished")
getRealm().findById<Player>(playerId)?.let { player -> getRealm().findById<Player>(playerId)?.let { player ->
this.model.playerSelected(player) val unmanagedPlayer = player.realm.copyFromRealm(player)
this.model.playerSelected(unmanagedPlayer)
} ?: throw PAIllegalStateException("Player (id=$playerId) not found") } ?: throw PAIllegalStateException("Player (id=$playerId) not found")
this.editorAdapter.notifyDataSetChanged() this.editorAdapter.notifyDataSetChanged()
} }
@ -684,10 +686,9 @@ class EditorFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListen
*/ */
private fun deleteHand() { private fun deleteHand() {
getRealm().findById<HandHistory>(this.model.handHistory.id)?.let { hh -> getRealm().writeAsync { asyncRealm ->
getRealm().executeTransaction { val hh = asyncRealm.findById<HandHistory>(this.model.handHistory.id)
hh.deleteFromRealm() hh?.deleteFromRealm()
}
} }
this.activity?.finish() this.activity?.finish()

@ -5,10 +5,6 @@ import android.text.InputType
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.handhistory.HandSetup import net.pokeranalytics.android.model.handhistory.HandSetup
@ -27,12 +23,11 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.util.CrashLogging import net.pokeranalytics.android.util.CrashLogging
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.formatted import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.writeAsync
import timber.log.Timber import timber.log.Timber
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.ParseException import java.text.ParseException
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass import kotlin.reflect.KClass
enum class HHKeyboard { enum class HHKeyboard {
@ -49,9 +44,6 @@ interface PlayerSetupCreationListener {
class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer, ActionListListener { class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer, ActionListListener {
private val coroutineContext: CoroutineContext
get() = Dispatchers.Main
/*** /***
* The hand history * The hand history
*/ */
@ -641,33 +633,33 @@ class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer
* Saves the current hand state in the database * Saves the current hand state in the database
*/ */
fun save(realm: Realm) { fun save(realm: Realm) {
realm.executeTransaction {
this.handHistory.actions.clear()
val actions = this.sortedActions.map { it.action }
this.handHistory.actions.addAll(actions)
if (!this.handHistory.isManaged) {
realm.copyToRealmOrUpdate(this.handHistory)
}
}
this.defineWinnerPositions()
}
private fun defineWinnerPositions() { this.handHistory.actions.clear()
val actions = this.sortedActions.map { it.action }
this.handHistory.actions.addAll(actions)
val hhId = this.handHistory.id realm.writeAsync { asyncRealm ->
GlobalScope.launch(coroutineContext) { this.handHistory.defineWinnerPositions()
val c = GlobalScope.async { asyncRealm.copyToRealmOrUpdate(this.handHistory)
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
realm.findById<HandHistory>(hhId)?.defineWinnerPositions()
}
realm.close()
}
c.await()
} }
// this.defineWinnerPositions()
} }
// private fun defineWinnerPositions() {
//
// val hhId = this.handHistory.id
// GlobalScope.launch(coroutineContext) {
// val c = GlobalScope.async {
// val realm = Realm.getDefaultInstance()
// realm.executeTransaction {
// realm.findById<HandHistory>(hhId)?.defineWinnerPositions()
// }
// realm.close()
// }
// c.await()
// }
//
// }
// Card Centralizer // Card Centralizer
@ -906,8 +898,8 @@ class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer
fun changeStraddleSelection(positions: LinkedHashSet<Position>) { fun changeStraddleSelection(positions: LinkedHashSet<Position>) {
if (positions.isEmpty()) { if (positions.isEmpty()) {
this.firstStraddlePosition = null this.firstStraddlePosition = null
this.handSetup.clearStraddles() this.handSetup.clearStraddles()
} else { } else {
if (this.firstStraddlePosition == null) { if (this.firstStraddlePosition == null) {
@ -1013,16 +1005,16 @@ class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer
fun playerSelected(player: Player) { fun playerSelected(player: Player) {
// Remove all use of the selected player // Remove all use of the selected player
this.handHistory.playerSetups.filter { it.player == player }.forEach { this.handHistory.playerSetups.filter { it.player?.id == player.id }.forEach {
it.player = null it.player = null
} }
// Affects the player to the selected position // Affects the player to the selected position
this.tappedPlayerPositionIndex?.let { positionIndex -> this.tappedPlayerPositionIndex?.let { positionIndex ->
player.realm.executeTransaction { // player.realm.executeTransaction {
val ps = this.handHistory.playerSetupForPosition(positionIndex) ?: this.handHistory.createPlayerSetup(positionIndex) val ps = this.handHistory.playerSetupForPosition(positionIndex) ?: this.handHistory.createPlayerSetup(positionIndex)
ps.player = player ps.player = player
} // }
} ?: throw PAIllegalStateException("Click position not set for player selection") } ?: throw PAIllegalStateException("Click position not set for player selection")
} }
@ -1036,20 +1028,20 @@ class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer
/*** /***
* Tries to deletes the row at the given [position] * Tries to deletes the row at the given [position]
*/ */
fun deleteIfPossible(position: Int) { // fun deleteIfPossible(position: Int) {
when (val row = this.rowRepresentables[position]) { // when (val row = this.rowRepresentables[position]) {
is PlayerSetupRow -> { // is PlayerSetupRow -> {
val playerSetup = this.handHistory.playerSetupForPosition(row.positionIndex) ?: throw PAIllegalStateException("Attempt to delete an null object") // val playerSetup = this.handHistory.playerSetupForPosition(row.positionIndex) ?: throw PAIllegalStateException("Attempt to delete an null object")
this.handHistory.playerSetups.remove(playerSetup) // this.handHistory.playerSetups.remove(playerSetup)
//
this.handHistory.realm?.let { // this.handHistory.realm?.let {
it.executeTransaction { // it.executeTransaction {
playerSetup.deleteFromRealm() // playerSetup.deleteFromRealm()
} // }
} // }
} // }
} // }
} // }
fun removePlayerSetup(positionIndex: Int) { fun removePlayerSetup(positionIndex: Int) {
val ps = this.handHistory.playerSetupForPosition(positionIndex) val ps = this.handHistory.playerSetupForPosition(positionIndex)

@ -13,7 +13,10 @@ import android.provider.MediaStore
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.arthenica.ffmpegkit.FFmpegKit import com.arthenica.ffmpegkit.FFmpegKit
import io.realm.Realm import io.realm.Realm
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.handhistory.HandHistory import net.pokeranalytics.android.model.realm.handhistory.HandHistory
@ -70,8 +73,9 @@ class ReplayExportService : Service() {
private fun startGIFExport() { private fun startGIFExport() {
GlobalScope.launch(coroutineContext) { CoroutineScope(context = coroutineContext).launch {
val c = GlobalScope.async {
val c = async {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.refresh() realm.refresh()
@ -148,8 +152,8 @@ class ReplayExportService : Service() {
val start = Date().time val start = Date().time
GlobalScope.launch(coroutineContext) { CoroutineScope(context = coroutineContext).launch {
val async = GlobalScope.async { val async = async {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
@ -287,8 +291,8 @@ class ReplayExportService : Service() {
private fun startGIFExportPreQ() { private fun startGIFExportPreQ() {
GlobalScope.launch(coroutineContext) { CoroutineScope(context = coroutineContext).launch {
val c = GlobalScope.async { val c = async {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.refresh() realm.refresh()
@ -349,8 +353,8 @@ class ReplayExportService : Service() {
private fun startFFMPEGVideoExportPreQ() { private fun startFFMPEGVideoExportPreQ() {
GlobalScope.launch(coroutineContext) { CoroutineScope(context = coroutineContext).launch {
val async = GlobalScope.async { val async = async {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")

@ -13,7 +13,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -28,6 +28,7 @@ import net.pokeranalytics.android.model.extensions.getState
import net.pokeranalytics.android.model.extensions.scheduleStopNotification import net.pokeranalytics.android.model.extensions.scheduleStopNotification
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.model.utils.FavoriteSessionFinder import net.pokeranalytics.android.model.utils.FavoriteSessionFinder
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
@ -185,29 +186,31 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
if (sessionRealm != null) { if (sessionRealm != null) {
if (this.model.duplicate) { // duplicate session if (this.model.duplicate) { // duplicate session
realm.executeTransaction { realm.writeAsync { asyncRealm ->
val session = sessionRealm.duplicate() asyncRealm.findById<Session>(sessionId)?.duplicate()?.let { duplicate ->
currentSession = session currentSession = asyncRealm.copyFromRealm(duplicate)
}
// val session = sessionRealm.duplicate()
// currentSession = session
} }
sessionHasBeenUserCustomized = false sessionHasBeenUserCustomized = false
} else { // show existing session } else { // show existing session
currentSession = sessionRealm currentSession = realm.copyFromRealm(sessionRealm)
sessionHasBeenUserCustomized = true sessionHasBeenUserCustomized = true
} }
} else { } else {
throw PAIllegalStateException("Session cannot be null here, session id = $sessionId") throw PAIllegalStateException("Session cannot be null here, session id = $sessionId")
} }
} else { // create new session } else { // create new session
realm.executeTransaction { executeRealm -> currentSession = Session.newInstance(realm, this.model.isTournament)
currentSession = Session.newInstance(executeRealm, this.model.isTournament) FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, null, requireContext(), realm)
FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, null, requireContext())
}
// Find the nearest location around the user // Find the nearest location around the user
parentActivity?.findNearestLocation { parentActivity?.findNearestLocation {
it?.let { location -> it?.let { location ->
realm.executeTransaction { executeRealm -> realm.writeAsync { asyncRealm ->
val realmLocation = executeRealm.findById<Location>(location.id) val realmLocation = asyncRealm.findById<Location>(location.id)
FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, realmLocation, requireContext()) FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, realmLocation, requireContext(), asyncRealm)
currentSession.location = realmLocation currentSession.location = realmLocation
} }
@ -217,6 +220,10 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
sessionHasBeenUserCustomized = false sessionHasBeenUserCustomized = false
} }
// load everything and have some SessionManager links it all
//
} }
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
@ -249,7 +256,8 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
showBottomSheet(row, this, data, false, session.currency) showBottomSheet(row, this, data, false, session.currency)
} }
SessionPropertiesRow.HANDS -> { SessionPropertiesRow.HANDS -> {
val hhIds = session.handHistories?.map { it.id }?.toTypedArray() val handHistories = getRealm().where(HandHistory::class.java).equalTo("session.id", session.id).findAll()
val hhIds = handHistories?.map { it.id }?.toTypedArray()
DataListActivity.newInstance(this, LiveData.HAND_HISTORY, false, hhIds, false) DataListActivity.newInstance(this, LiveData.HAND_HISTORY, false, hhIds, false)
} }
else -> showBottomSheet(row, this, data, currentCurrency = session.currency) else -> showBottomSheet(row, this, data, currentCurrency = session.currency)
@ -259,9 +267,8 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
override fun onRowValueChanged(value: Any?, row: RowRepresentable) { override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
this.sessionHasBeenUserCustomized = true this.sessionHasBeenUserCustomized = true
try { try {
getRealm().executeTransaction { this.currentSession.updateValue(value, row)
this.currentSession.updateValue(value, row) this.writeChanges()
}
} catch (e: PAIllegalStateException) { } catch (e: PAIllegalStateException) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
return return
@ -279,6 +286,25 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
} }
private fun writeChanges() {
Timber.d("write changes")
getRealm().writeAsync ({ asyncRealm ->
Timber.d("start transaction")
asyncRealm.copyToRealmOrUpdate(this.currentSession)
Timber.d("yupyup")
}, { // onSuccess we retrieved the object because it might have been changed (i.e. session set added). Not retrieving it can for example copy it without its sessionset, writing null in db whereas it should have it
Timber.d("onSuccess")
val id = this.currentSession.id
getRealm().refresh()
getRealm().findById<Session>(id)?.let { session ->
this.currentSession = getRealm().copyFromRealm(session)
Timber.d("Session retrieved, set = ${this.currentSession.sessionSet}, br = ${session.bankroll}")
} ?: throw PAIllegalStateException("session not found")
})
}
/** /**
* Update the UI with the session data * Update the UI with the session data
* Should be called after the initialization of the session * Should be called after the initialization of the session
@ -393,11 +419,14 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
} }
currentSession.startOrContinue() currentSession.startOrContinue()
this.writeChanges()
binding.recyclerView.smoothScrollToPosition(0) binding.recyclerView.smoothScrollToPosition(0)
} }
SessionState.STARTED -> { SessionState.STARTED -> {
currentSession.pause() currentSession.pause()
this.writeChanges()
} }
else -> { else -> {
} }
@ -405,6 +434,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
updateSessionUI() updateSessionUI()
} }
private fun computeOptimalDuration() { private fun computeOptimalDuration() {
Timber.d("Start optimal duration finding attempt...") Timber.d("Start optimal duration finding attempt...")
@ -414,7 +444,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
var optimalDuration: Double? = null var optimalDuration: Double? = null
val cr = GlobalScope.async { val cr = CoroutineScope(context = Dispatchers.IO).async {
optimalDuration = CashGameOptimalDurationCalculator.start(isLive) optimalDuration = CashGameOptimalDurationCalculator.start(isLive)
} }
cr.await() cr.await()
@ -426,7 +456,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
currentSession.scheduleStopNotification(requireContext(), delay) currentSession.scheduleStopNotification(requireContext(), delay)
val formattedDuration = (it / 3600 / 1000).formattedHourlyDuration() val formattedDuration = (it / 3600 / 1000).formattedHourlyDuration()
Timber.d("Setting stop notification in: $formattedDuration") // Timber.d("Setting stop notification in: $formattedDuration")
val message = requireContext().getString(R.string.stop_notification_in_, formattedDuration) val message = requireContext().getString(R.string.stop_notification_in_, formattedDuration)
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show() Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
} }
@ -482,7 +512,17 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
val bankrollId = this.currentSession.bankroll?.id val bankrollId = this.currentSession.bankroll?.id
this.currentSession.delete() val id = currentSession.id
getRealm().writeAsync ({ asyncRealm ->
asyncRealm.findById<Session>(id)?.let { session ->
session.cleanup()
session.deleteFromRealm()
} ?: throw PAIllegalStateException("session not found")
}, {
Timber.d("delete successful")
// getRealm().refresh()
})
bankrollId?.let { bankrollId?.let {
BankrollReportManager.notifyBankrollReportImpact(bankrollId) BankrollReportManager.notifyBankrollReportImpact(bankrollId)
} }
@ -495,7 +535,11 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
override fun onBackPressed() { override fun onBackPressed() {
super.onBackPressed() super.onBackPressed()
if (!sessionHasBeenUserCustomized) { if (!sessionHasBeenUserCustomized) {
currentSession.delete()
val id = currentSession.id
getRealm().writeAsync { asyncRealm ->
asyncRealm.findById<Session>(id)?.delete()
}
} }
} }
@ -526,7 +570,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
} }
override fun charSequenceForRow(row: RowRepresentable, context: Context): String { override fun charSequenceForRow(row: RowRepresentable, context: Context): String {
return this.currentSession.charSequenceForRow(row, context) return this.currentSession.charSequenceForRow(row, context, getRealm())
} }
override fun actionIconForRow(row: RowRepresentable): Int? { override fun actionIconForRow(row: RowRepresentable): Int? {
@ -593,18 +637,18 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
mapOf( mapOf(
"bb" to session.cgBiggestBet, "bb" to session.cgBiggestBet,
"fee" to session.tournamentEntryFee, "fee" to session.tournamentEntryFee,
"ratedBuyin" to session.result?.buyin "ratedBuyin" to session.buyin
) )
) )
SessionPropertiesRow.BREAK_TIME -> row.editingDescriptors(mapOf()) SessionPropertiesRow.BREAK_TIME -> row.editingDescriptors(mapOf())
SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> row.editingDescriptors( SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> row.editingDescriptors(
mapOf( mapOf(
"defaultValue" to session.result?.cashout "defaultValue" to session.cashout
) )
) )
SessionPropertiesRow.NET_RESULT -> row.editingDescriptors( SessionPropertiesRow.NET_RESULT -> row.editingDescriptors(
mapOf( mapOf(
"defaultValue" to session.result?.netResult "defaultValue" to session.netResult
) )
) )
SessionPropertiesRow.COMMENT -> row.editingDescriptors( SessionPropertiesRow.COMMENT -> row.editingDescriptors(
@ -624,7 +668,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
) )
SessionPropertiesRow.POSITION -> row.editingDescriptors( SessionPropertiesRow.POSITION -> row.editingDescriptors(
mapOf( mapOf(
"defaultValue" to session.result?.tournamentFinalPosition "defaultValue" to session.tournamentFinalPosition
) )
) )
SessionPropertiesRow.HANDS_COUNT -> row.editingDescriptors( SessionPropertiesRow.HANDS_COUNT -> row.editingDescriptors(
@ -641,7 +685,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
mapOf( mapOf(
"sb" to session.cgBiggestBet?.round(), "sb" to session.cgBiggestBet?.round(),
"bb" to session.cgBiggestBet?.round(), "bb" to session.cgBiggestBet?.round(),
"tips" to session.result?.tips "tips" to session.tips
) )
) )
is CustomField -> { is CustomField -> {
@ -663,16 +707,15 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
override fun resultCaptureTypeSelected(resultCaptureType: ResultCaptureType, applyBankroll: Boolean) { override fun resultCaptureTypeSelected(resultCaptureType: ResultCaptureType, applyBankroll: Boolean) {
getRealm().executeTransaction { // cleanup existing results when (resultCaptureType) {
when (resultCaptureType) { ResultCaptureType.NET_RESULT -> {
ResultCaptureType.NET_RESULT -> { this.currentSession.clearBuyinCashedOut()
this.currentSession.clearBuyinCashedOut() }
} ResultCaptureType.BUYIN_CASHED_OUT -> {
ResultCaptureType.BUYIN_CASHED_OUT -> { this.currentSession.clearNetResult()
this.currentSession.clearNetResult()
}
} }
} }
this.writeChanges()
this.model.resultCaptureType = resultCaptureType this.model.resultCaptureType = resultCaptureType
if (applyBankroll) { if (applyBankroll) {

@ -17,7 +17,6 @@ import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rows.SeparatorRow import net.pokeranalytics.android.ui.view.rows.SeparatorRow
import net.pokeranalytics.android.ui.view.rows.SessionPropertiesRow import net.pokeranalytics.android.ui.view.rows.SessionPropertiesRow
import net.pokeranalytics.android.util.extensions.sorted import net.pokeranalytics.android.util.extensions.sorted
import java.util.ArrayList
class SessionViewModel : ViewModel() { class SessionViewModel : ViewModel() {
@ -52,7 +51,7 @@ class SessionViewModel : ViewModel() {
fun updatedRowRepresentationForCurrentState(session: Session, realm: Realm, context: Context) { fun updatedRowRepresentationForCurrentState(session: Session, realm: Realm, context: Context) {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
val result = session.result // val result = session.result
val currency = session.currency val currency = session.currency
// Headers // Headers
@ -62,7 +61,7 @@ class SessionViewModel : ViewModel() {
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = session.getFormattedDuration(), title = session.getFormattedDuration(),
valueTextFormat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = session.currency).textFormat valueTextFormat = ComputedStat(Stat.NET_RESULT, session.net, currency = session.currency).textFormat
) )
) )
rows.add(SeparatorRow()) rows.add(SeparatorRow())
@ -72,7 +71,7 @@ class SessionViewModel : ViewModel() {
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
resId = R.string.pause, resId = R.string.pause,
valueTextFormat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency).textFormat valueTextFormat = ComputedStat(Stat.NET_RESULT, session.net, currency = currency).textFormat
) )
) )
rows.add(SeparatorRow()) rows.add(SeparatorRow())
@ -82,7 +81,7 @@ class SessionViewModel : ViewModel() {
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = session.getFormattedDuration(), title = session.getFormattedDuration(),
valueTextFormat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency).textFormat valueTextFormat = ComputedStat(Stat.NET_RESULT, session.net, currency = currency).textFormat
) )
) )
rows.add( rows.add(
@ -100,11 +99,14 @@ class SessionViewModel : ViewModel() {
} }
// Rows // Rows
rows.addAll(SessionPropertiesRow.getRows(session, this.resultCaptureType, context)) rows.addAll(SessionPropertiesRow.getRows(session, this.resultCaptureType, context, realm))
// Add custom fields // Add custom fields
rows.add(SeparatorRow()) rows.add(SeparatorRow())
rows.addAll(realm.sorted<CustomField>())
val customFields = realm.sorted<CustomField>()
val unmanaged = customFields.map { realm.copyFromRealm(it) }
rows.addAll(unmanaged)
this.rows = rows this.rows = rows
} }

@ -10,6 +10,7 @@ import net.pokeranalytics.android.databinding.FragmentDealtHandsConfigBinding
import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.UserConfig import net.pokeranalytics.android.model.realm.UserConfig
import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.util.extensions.writeAsync
class DealtHandsPerHourFragment : RealmFragment() { class DealtHandsPerHourFragment : RealmFragment() {
@ -57,19 +58,19 @@ class DealtHandsPerHourFragment : RealmFragment() {
private fun save() { private fun save() {
getRealm().executeTransaction { realm -> getRealm().writeAsync { asyncRealm ->
val userConfig = UserConfig.getConfiguration(realm) val userConfig = UserConfig.getConfiguration(asyncRealm)
this.liveValue.text.toString().toIntOrNull()?.let { liveDealtHandsPerHour -> this.liveValue.text.toString().toIntOrNull()?.let { liveDealtHandsPerHour ->
userConfig.liveDealtHandsPerHour = liveDealtHandsPerHour userConfig.liveDealtHandsPerHour = liveDealtHandsPerHour
} }
this.onlineValue.text.toString().toIntOrNull()?.let { onlineDealtHandsPerHour -> this.onlineValue.text.toString().toIntOrNull()?.let { onlineDealtHandsPerHour ->
userConfig.onlineDealtHandsPerHour = onlineDealtHandsPerHour userConfig.onlineDealtHandsPerHour = onlineDealtHandsPerHour
} }
realm.copyToRealmOrUpdate(userConfig) asyncRealm.copyToRealmOrUpdate(userConfig)
// update all precomputed hand counts // update all precomputed hand counts
realm.where(ComputableResult::class.java).findAll().forEach { cr -> asyncRealm.where(ComputableResult::class.java).findAll().forEach { cr ->
cr.session?.let { session -> cr.session?.let { session ->
cr.estimatedHands = session.estimatedHands cr.estimatedHands = session.estimatedHands
} }

@ -16,6 +16,7 @@ import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.RealmFragment 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.RowViewType
import net.pokeranalytics.android.util.extensions.writeAsync
class TransactionFilterFragment : RealmFragment(), StaticRowRepresentableDataSource, class TransactionFilterFragment : RealmFragment(), StaticRowRepresentableDataSource,
RowRepresentableDelegate { RowRepresentableDelegate {
@ -96,10 +97,13 @@ class TransactionFilterFragment : RealmFragment(), StaticRowRepresentableDataSou
} }
private fun save() { private fun save() {
getRealm().executeTransaction { realm -> val ids = this.model.selectedTransactionTypes.map { it.id }
val userConfig = UserConfig.getConfiguration(realm)
userConfig.setTransactionTypeIds(this.model.selectedTransactionTypes) // this.model.selectedTransactionTypes.joinToString(UUID_SEPARATOR) { it.id }
realm.copyToRealmOrUpdate(userConfig) getRealm().writeAsync { asyncRealm ->
val userConfig = UserConfig.getConfiguration(asyncRealm)
userConfig.setTransactionTypeIds(ids.toSet())
asyncRealm.copyToRealmOrUpdate(userConfig)
} }
this.activity?.finish() this.activity?.finish()
} }

@ -511,9 +511,8 @@ enum class RowViewType(private var layoutRes: Int) : ViewIdentifier {
if (row is Session) { if (row is Session) {
itemView.findViewById<AppCompatTextView>(R.id.gameResult)?.let { gameResult -> itemView.findViewById<AppCompatTextView>(R.id.gameResult)?.let { gameResult ->
val result = row.result?.net ?: 0.0
val formattedStat = val formattedStat =
ComputedStat(Stat.NET_RESULT, result, currency = row.currency).textFormat ComputedStat(Stat.NET_RESULT, row.net, currency = row.currency).textFormat
gameResult.setTextFormat(formattedStat, itemView.context) gameResult.setTextFormat(formattedStat, itemView.context)
} }

@ -147,10 +147,8 @@ class SessionRowView : FrameLayout {
this.infoIcon.isVisible = false this.infoIcon.isVisible = false
this.infoTitle.isVisible = false this.infoTitle.isVisible = false
session.result?.net?.let { netResult -> val stat = ComputedStat(Stat.NET_RESULT, session.net, currency = session.currency)
val stat = ComputedStat(Stat.NET_RESULT, netResult, currency = session.currency) this.gameResult.setTextFormat(stat.textFormat, context)
this.gameResult.setTextFormat(stat.textFormat, context)
}
// val formattedStat = ComputedStat(Stat.NET_RESULT, result, currency = session.currency).format() // val formattedStat = ComputedStat(Stat.NET_RESULT, result, currency = session.currency).format()
} }

@ -2,6 +2,7 @@ package net.pokeranalytics.android.ui.view.rows
import android.content.Context import android.content.Context
import android.text.InputType import android.text.InputType
import io.realm.Realm
import io.realm.RealmResults import io.realm.RealmResults
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.TournamentType import net.pokeranalytics.android.model.TournamentType
@ -9,6 +10,7 @@ import net.pokeranalytics.android.model.extensions.SessionState
import net.pokeranalytics.android.model.extensions.getState import net.pokeranalytics.android.model.extensions.getState
import net.pokeranalytics.android.model.realm.ResultCaptureType import net.pokeranalytics.android.model.realm.ResultCaptureType
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
@ -48,7 +50,7 @@ enum class SessionPropertiesRow : RowRepresentable {
/** /**
* Return the rows to display for the current session state * Return the rows to display for the current session state
*/ */
fun getRows(session: Session, resultCaptureType: ResultCaptureType?, context: Context): List<RowRepresentable> { fun getRows(session: Session, resultCaptureType: ResultCaptureType?, context: Context, realm: Realm): List<RowRepresentable> {
val state = session.getState() val state = session.getState()
when (session.type) { when (session.type) {
Session.Type.TOURNAMENT.ordinal -> { Session.Type.TOURNAMENT.ordinal -> {
@ -77,6 +79,8 @@ enum class SessionPropertiesRow : RowRepresentable {
TIPS)) TIPS))
fields.add(SeparatorRow()) fields.add(SeparatorRow())
fields.add(COMMENT) fields.add(COMMENT)
if (session.handHistories?.isNotEmpty() == true) { if (session.handHistories?.isNotEmpty() == true) {
fields.add(HANDS) fields.add(HANDS)
} }
@ -133,7 +137,9 @@ enum class SessionPropertiesRow : RowRepresentable {
// PROPERTIES // PROPERTIES
fields.add(SeparatorRow()) fields.add(SeparatorRow())
fields.add(COMMENT) fields.add(COMMENT)
if (session.handHistories?.isNotEmpty() == true) {
val handHistories = realm.where(HandHistory::class.java).equalTo("session.id", session.id).findAll()
if (handHistories?.isNotEmpty() == true) {
fields.add(HANDS) fields.add(HANDS)
} }
fields.add(SeparatorRow()) fields.add(SeparatorRow())

@ -3,6 +3,7 @@ package net.pokeranalytics.android.ui.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException
@ -211,17 +212,32 @@ class BottomSheetViewModel(var row: RowRepresentable) : ViewModel() {
} }
return null return null
} }
BottomSheetType.MULTI_SELECTION -> this.selectedRows BottomSheetType.MULTI_SELECTION -> this.selectedRowsRealmCopy()
BottomSheetType.NUMERIC_TEXT -> this.doubleValue BottomSheetType.NUMERIC_TEXT -> this.doubleValue
BottomSheetType.GRID -> this.defaultSize BottomSheetType.GRID -> this.defaultSize
BottomSheetType.DOUBLE_LIST, BottomSheetType.LIST_GAME -> this.someValues BottomSheetType.DOUBLE_LIST, BottomSheetType.LIST_GAME -> this.someValues
BottomSheetType.LIST_STATIC -> this.selectedRows.firstOrNull() BottomSheetType.LIST_STATIC -> this.selectedRowsRealmCopy().firstOrNull()
BottomSheetType.SUM -> this.doubleValue BottomSheetType.SUM -> this.doubleValue
BottomSheetType.CASH_GAME_STAKES -> Stakes(this.secondStringValue, this.ante) BottomSheetType.CASH_GAME_STAKES -> Stakes(this.secondStringValue, this.ante)
else -> null else -> null
} }
} }
private fun selectedRowsRealmCopy(): List<*> {
val realmObjects = this.selectedRows.filterIsInstance<RealmObject>()
return if (realmObjects.isNotEmpty()) {
realmObjects.map { obj ->
if (obj.isManaged) {
obj.realm.copyFromRealm(obj)
} else {
obj
}
}
} else {
this.selectedRows
}
}
fun isSelected(row: RowRepresentable): Boolean { fun isSelected(row: RowRepresentable): Boolean {
return this.selectedRows.contains(row) return this.selectedRows.contains(row)
} }
@ -243,9 +259,17 @@ class BottomSheetViewModel(var row: RowRepresentable) : ViewModel() {
} }
fun rowSelected(position: Int): RowRepresentable? { fun rowSelected(position: Int): RowRepresentable? {
return when(this.row.bottomSheetType) { when(this.row.bottomSheetType) {
BottomSheetType.LIST -> this.realmData?.get(position) BottomSheetType.LIST -> {
BottomSheetType.LIST_STATIC -> this.staticRows[position] this.realmData?.realm?.let { realm ->
val item = this.realmData?.get(position) as? RealmObject
item?.let {
return realm.copyFromRealm(it) as? RowRepresentable
}
}
return null
}
BottomSheetType.LIST_STATIC -> return this.staticRows[position]
else -> throw PAIllegalStateException("row selected for unmanaged bottom sheet type") else -> throw PAIllegalStateException("row selected for unmanaged bottom sheet type")
} }
} }

@ -40,7 +40,7 @@ class BackupOperator(var context: Context) {
fun backupIfNecessary() { fun backupIfNecessary() {
Timber.d(">>> backupIfNecessary") // Timber.d(">>> backupIfNecessary")
Preferences.getBackupEmail(context)?.let { email -> Preferences.getBackupEmail(context)?.let { email ->
this.backupSessionsIfNecessary(email) this.backupSessionsIfNecessary(email)

@ -34,14 +34,6 @@ class FakeDataManager {
val bankroll = realm.where<Bankroll>().findAll().firstOrNull() val bankroll = realm.where<Bankroll>().findAll().firstOrNull()
val locations = realm.where<Location>().findAll() val locations = realm.where<Location>().findAll()
if (locations.size == 0) {
realm.executeTransaction {
listOf("Bellagio", "Aria", "Borgata").map {
realm.getOrCreate<Location>(it)
}
}
}
// Test endedSessions // Test endedSessions
Timber.d("*** Start creating ${numberOfSessions} fake computables...") Timber.d("*** Start creating ${numberOfSessions} fake computables...")
@ -50,10 +42,16 @@ class FakeDataManager {
realm.beginTransaction() realm.beginTransaction()
if (locations.size == 0) {
listOf("Bellagio", "Aria", "Borgata").map {
realm.getOrCreate<Location>(it)
}
}
for (index in 1..numberOfSessions) { for (index in 1..numberOfSessions) {
if (index % commitFrequency == 0) { if (index % commitFrequency == 0) {
Timber.d("****** committing at ${index} computables...") Timber.d("****** committing at ${index} sessions...")
realm.commitTransaction() realm.commitTransaction()
realm.beginTransaction() realm.beginTransaction()
} }
@ -82,10 +80,8 @@ class FakeDataManager {
session.tableSize = (2..10).random() session.tableSize = (2..10).random()
val buyin = buyinList.random() val buyin = buyinList.random()
session.result?.let { result -> session.buyin = buyinList.random()
result.buyin = buyinList.random() session.cashout = resultsList.random() + buyin
result.cashout = resultsList.random() + buyin
}
if (isTournament) { if (isTournament) {
session.tournamentEntryFee = buyin session.tournamentEntryFee = buyin
@ -99,6 +95,8 @@ class FakeDataManager {
// session.cgSmallBlind = bigBlind / 2.0 // session.cgSmallBlind = bigBlind / 2.0
} }
realm.copyToRealmOrUpdate(session)
} }
realm.commitTransaction() realm.commitTransaction()

@ -7,6 +7,7 @@ import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.ObjectIdentifier import net.pokeranalytics.android.model.interfaces.ObjectIdentifier
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.writeAsync
import org.apache.commons.csv.CSVRecord import org.apache.commons.csv.CSVRecord
/** /**
@ -70,8 +71,8 @@ abstract class DataCSVDescriptor<T : Identifiable>(source: DataSource, vararg el
if (realm.isInTransaction) { if (realm.isInTransaction) {
this.deleteInsertedFromRealm(realm) this.deleteInsertedFromRealm(realm)
} else { } else {
realm.executeTransaction { realm.writeAsync { asyncRealm ->
this.deleteInsertedFromRealm(realm) this.deleteInsertedFromRealm(asyncRealm)
} }
} }

@ -26,7 +26,7 @@ abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
vararg elements: CSVField) vararg elements: CSVField)
: DataCSVDescriptor<T>(source, *elements) { : DataCSVDescriptor<T>(source, *elements) {
var noSessionImport: Boolean = false private var noSessionImport: Boolean = false
init { init {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
@ -46,7 +46,7 @@ abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
protected fun parseSession(realm: Realm, record: CSVRecord, context: Context): Session? { protected fun parseSession(realm: Realm, record: CSVRecord, context: Context): Session? {
val isTournament = isTournament ?: false val isTournament = isTournament ?: false
val session = Session.newInstance(realm, isTournament, managed = false) val session = Session.newInstance(realm, isTournament)
var startDate: Date? = null var startDate: Date? = null
var endDate: Date? = null var endDate: Date? = null
@ -127,13 +127,13 @@ abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
} }
is SessionField.Buyin -> { is SessionField.Buyin -> {
val buyin = field.parse(value) val buyin = field.parse(value)
session.result?.buyin = buyin session.buyin = buyin
if (session.type == Session.Type.TOURNAMENT.ordinal) { if (session.type == Session.Type.TOURNAMENT.ordinal) {
session.tournamentEntryFee = buyin session.tournamentEntryFee = buyin
} else {} } else {}
} }
is SessionField.CashedOut -> session.result?.cashout = field.parse(value) is SessionField.CashedOut -> session.cashout = field.parse(value)
is SessionField.NetResult -> session.result?.netResult = field.parse(value) is SessionField.NetResult -> session.netResult = field.parse(value)
is SessionField.SessionType -> { is SessionField.SessionType -> {
Session.Type.getValueFromString(value)?.let { type -> Session.Type.getValueFromString(value)?.let { type ->
session.type = type.ordinal session.type = type.ordinal
@ -143,7 +143,7 @@ abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
is SessionField.NumberOfTables -> session.numberOfTables = field.parse(value) ?: 1 is SessionField.NumberOfTables -> session.numberOfTables = field.parse(value) ?: 1
is SessionField.Addon -> additionalBuyins += field.parse(value) ?: 0.0 is SessionField.Addon -> additionalBuyins += field.parse(value) ?: 0.0
is SessionField.Rebuy -> additionalBuyins += field.parse(value) ?: 0.0 is SessionField.Rebuy -> additionalBuyins += field.parse(value) ?: 0.0
is SessionField.Tips -> session.result?.tips = field.parse(value) is SessionField.Tips -> session.tips = field.parse(value)
is SessionField.HandsCount -> session.handsCount = field.parse(value) is SessionField.HandsCount -> session.handsCount = field.parse(value)
is SessionField.Break -> { is SessionField.Break -> {
field.parse(value)?.let { field.parse(value)?.let {
@ -191,7 +191,7 @@ abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
session.cgAnte = field.parse(value) session.cgAnte = field.parse(value)
} }
is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value) is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value)
is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition = is SessionField.TournamentPosition -> session.tournamentFinalPosition =
field.parse(value) field.parse(value)
is SessionField.TournamentName -> { is SessionField.TournamentName -> {
if (value.isNotEmpty()) { if (value.isNotEmpty()) {
@ -232,6 +232,7 @@ abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
Timber.d("N>> create: $number") Timber.d("N>> create: $number")
val entry = realm.copyToRealm(CustomFieldEntry()) val entry = realm.copyToRealm(CustomFieldEntry())
entry.numericValue = number entry.numericValue = number
entry.customField = customField
customField.entries.add(entry) customField.entries.add(entry)
session.customFieldEntries.add(entry) session.customFieldEntries.add(entry)
@ -255,12 +256,12 @@ abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
val bankroll = Bankroll.getOrCreate(realm, bankrollName, isLive, currencyCode, currencyRate) val bankroll = Bankroll.getOrCreate(realm, bankrollName, isLive, currencyCode, currencyRate)
session.bankroll = bankroll session.bankroll = bankroll
session.result?.buyin?.let { session.buyin?.let {
session.result?.buyin = it + additionalBuyins session.buyin = it + additionalBuyins
} }
val net = session.result?.net val net = session.net
if (startDate != null && net != null) { // valid session if (startDate != null) { // valid session
// session already in realm, we'd love not put it in Realm before doing the check // session already in realm, we'd love not put it in Realm before doing the check
val count = DataUtils.sessionCount(realm, startDate!!, endDate, net) val count = DataUtils.sessionCount(realm, startDate!!, endDate, net)
if (this.noSessionImport || count == 0) { if (this.noSessionImport || count == 0) {

@ -46,10 +46,10 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el
is SessionField.SessionType -> Session.Type.values()[data.type].value is SessionField.SessionType -> Session.Type.values()[data.type].value
is SessionField.Live -> field.format(data.isLive) is SessionField.Live -> field.format(data.isLive)
is SessionField.NumberOfTables -> field.format(data.numberOfTables) is SessionField.NumberOfTables -> field.format(data.numberOfTables)
is SessionField.Buyin -> field.format(data.result?.buyin) is SessionField.Buyin -> field.format(data.buyin)
is SessionField.CashedOut -> field.format(data.result?.cashout) is SessionField.CashedOut -> field.format(data.cashout)
is SessionField.NetResult -> field.format(data.result?.netResult) is SessionField.NetResult -> field.format(data.netResult)
is SessionField.Tips -> field.format(data.result?.tips) is SessionField.Tips -> field.format(data.tips)
is SessionField.HandsCount -> field.format(data.handsCount) is SessionField.HandsCount -> field.format(data.handsCount)
is SessionField.LimitType -> { is SessionField.LimitType -> {
data.limit?.let { limit -> data.limit?.let { limit ->
@ -76,7 +76,7 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el
is SessionField.TournamentFeatures -> field.format(data.tournamentFeatures) is SessionField.TournamentFeatures -> field.format(data.tournamentFeatures)
is SessionField.TournamentEntryFee -> field.format(data.tournamentEntryFee) is SessionField.TournamentEntryFee -> field.format(data.tournamentEntryFee)
is SessionField.TournamentNumberOfPlayers -> field.format(data.tournamentNumberOfPlayers) is SessionField.TournamentNumberOfPlayers -> field.format(data.tournamentNumberOfPlayers)
is SessionField.TournamentPosition -> field.format(data.result?.tournamentFinalPosition) is SessionField.TournamentPosition -> field.format(data.tournamentFinalPosition)
is SessionField.Comment -> data.comment is SessionField.Comment -> data.comment
is SessionField.NumberCustomField -> { is SessionField.NumberCustomField -> {
val entry = data.customFieldEntries.find { it.customField?.id == field.customField.id } val entry = data.customFieldEntries.find { it.customField?.id == field.customField.id }

@ -7,6 +7,8 @@ import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.UsageCountable import net.pokeranalytics.android.model.interfaces.UsageCountable
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import timber.log.Timber
import java.util.*
fun <T : RealmModel>Realm.count(clazz: Class<T>) : Long { fun <T : RealmModel>Realm.count(clazz: Class<T>) : Long {
return this.where(clazz).count() return this.where(clazz).count()
@ -91,13 +93,52 @@ inline fun <reified C : RealmModel> Realm.sorted(editableOnly: Boolean = false,
return this.sorted(C::class.java, editableOnly, omitId = omitId) return this.sorted(C::class.java, editableOnly, omitId = omitId)
} }
fun Realm.writeAsync(handler: (Realm) -> (Unit)) {
Timber.d("Start write...")
this.executeTransactionAsync { asyncRealm ->
handler(asyncRealm)
}
// this.executeTransactionAsync({ asyncRealm ->
// handler(asyncRealm)
// }, { // success
//
// }, { error -> // error
// Timber.w("Realm write error: $error")
// })
}
fun Realm.writeAsync(handler: (Realm) -> (Unit), success: () -> (Unit)) {
Timber.d("Start write with success/error...")
val s = Date()
this.executeTransactionAsync({ asyncRealm ->
handler(asyncRealm)
Timber.d("REALM execution ended")
}, { // success
val e = Date()
val duration = e.time - s.time
Timber.d("//// transaction duration = $duration")
Timber.d("SUCCESS!")
success()
}, { error -> // error
Timber.d("Realm write error: $error")
})
}
/** /**
* Updates the useCount variable of the CountableUsage entity * Updates the useCount variable of the CountableUsage entity
*/ */
fun <T : RealmModel>Realm.updateUsageCount(clazz: Class<T>) { fun <T : RealmModel>Realm.updateUsageCount(clazz: Class<T>) {
val results = this.where(clazz).findAll() this.writeAsync { asyncRealm ->
this.executeTransaction { val results = asyncRealm.where(clazz).findAll()
results.forEach { countableUsage -> results.forEach { countableUsage ->
val countable = (countableUsage as UsageCountable) val countable = (countableUsage as UsageCountable)
@ -106,7 +147,7 @@ fun <T : RealmModel>Realm.updateUsageCount(clazz: Class<T>) {
TournamentFeature::class -> "tournamentFeatures.id" TournamentFeature::class -> "tournamentFeatures.id"
else -> "${clazz.simpleName.decapitalize()}.id" else -> "${clazz.simpleName.decapitalize()}.id"
} }
val count = it.where(countable.ownerClass).contains(fieldName, countable.id).count().toInt() val count = asyncRealm.where(countable.ownerClass).contains(fieldName, countable.id).count().toInt()
countable.useCount = count countable.useCount = count
} }
} }

@ -25,7 +25,6 @@
android:id="@+id/newTransaction" android:id="@+id/newTransaction"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
@ -103,7 +102,6 @@
android:id="@+id/newCashGame" android:id="@+id/newCashGame"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginTop="8dp"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"

Loading…
Cancel
Save