diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt index c98eb4b4..ac31a69d 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt @@ -186,13 +186,13 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() { Assert.fail("No std100 stat") } - results.computedStat(Stat.MAXIMUM_NETRESULT)?.let { + results.computedStat(Stat.MAXIMUM_NET_RESULT)?.let { assertEquals(300.0, it.value, delta) } ?: run { Assert.fail("No MAXIMUM_NETRESULT") } - results.computedStat(Stat.MINIMUM_NETRESULT)?.let { + results.computedStat(Stat.MINIMUM_NET_RESULT)?.let { assertEquals(-100.0, it.value, delta) } ?: run { Assert.fail("No MINIMUM_NETRESULT") diff --git a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt index ad608090..3981ae4a 100644 --- a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt +++ b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt @@ -14,7 +14,9 @@ import net.pokeranalytics.android.calculus.ReportWhistleBlower import net.pokeranalytics.android.model.migrations.Patcher 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.SessionManager import net.pokeranalytics.android.util.* import net.pokeranalytics.android.util.billing.AppGuard import timber.log.Timber @@ -44,6 +46,11 @@ class PokerAnalyticsApplication : Application() { UserDefaults.init(this) + if (BuildConfig.DEBUG) { + // Logs + Timber.plant(PokerAnalyticsLogs()) + } + // AppGuard / Billing services AppGuard.load(this.applicationContext) @@ -51,8 +58,8 @@ class PokerAnalyticsApplication : Application() { Realm.init(this) val realmConfiguration = RealmConfiguration.Builder() .name(Realm.DEFAULT_REALM_NAME) - .schemaVersion(14) - .allowWritesOnUiThread(true) + .schemaVersion(15) +// .allowWritesOnUiThread(true) .migration(PokerAnalyticsMigration()) .initialData(Seed(this)) .build() @@ -63,23 +70,19 @@ class PokerAnalyticsApplication : Application() { CrashLogging.log("App onCreate. Locales = $locales") } - if (BuildConfig.DEBUG) { - // Logs - Timber.plant(PokerAnalyticsLogs()) - } Timber.d("SDK version = ${Build.VERSION.SDK_INT}") if (BuildConfig.DEBUG) { Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}") Timber.d("Realm path = ${Realm.getDefaultInstance().path}") - -// this.createFakeSessions() } // Patch Patcher.patchAll(this) - // Report + // Processors + SessionManager.create() + UserConfigObserver.create() this.reportWhistleBlower = ReportWhistleBlower(this.applicationContext) // Backups diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt b/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt index 112093d9..92c51b6b 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt @@ -10,9 +10,14 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import net.pokeranalytics.android.calculus.optimalduration.CashGameOptimalDurationCalculator 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.model.realm.Session 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.writeAsync import timber.log.Timber import kotlin.coroutines.CoroutineContext @@ -24,7 +29,7 @@ interface NewPerformanceListener { class ReportWhistleBlower(var context: Context) { private var sessions: RealmResults? = null - private var results: RealmResults? = null +// private var results: RealmResults? = null private var currentTask: ReportTask? = null @@ -45,10 +50,10 @@ class ReportWhistleBlower(var context: Context) { requestReportLaunch() } - this.results = realm.where(Result::class.java).findAll() - this.results?.addChangeListener { _ -> - requestReportLaunch() - } +// this.results = realm.where(Result::class.java).findAll() +// this.results?.addChangeListener { _ -> +// requestReportLaunch() +// } realm.close() } @@ -71,8 +76,8 @@ class ReportWhistleBlower(var context: Context) { this.timer?.cancel() val launchStart = 100L - val timer = object: CountDownTimer(launchStart, launchStart) { - override fun onTick(p0: Long) { } + val timer = object : CountDownTimer(launchStart, launchStart) { + override fun onTick(p0: Long) {} override fun onFinish() { launchReportTask() @@ -212,7 +217,10 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co customField?.let { 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 val bestComputedResults = result.max(stat) @@ -226,7 +234,11 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co var storePerf = true currentPerf?.let { currentPerf.name?.let { name -> - if (computedResults.group.query.getName(this.context, nameSeparator) == name) { + if (computedResults.group.query.getName( + this.context, + nameSeparator + ) == name + ) { storePerf = false } } @@ -237,12 +249,13 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co } if (storePerf) { - realm.executeTransaction { - currentPerf.name = performanceName - currentPerf.objectId = performanceQuery.objectId - currentPerf.customFieldId = customField?.id + currentPerf.name = performanceName + currentPerf.objectId = performanceQuery.objectId + currentPerf.customFieldId = customField?.id + realm.writeAsync { asyncRealm -> + asyncRealm.copyToRealm(currentPerf) + this.whistleBlower.notify(currentPerf) } - this.whistleBlower.notify(currentPerf) } } @@ -256,16 +269,20 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co customField?.id, null ) - realm.executeTransaction { it.copyToRealm(performance) } - this.whistleBlower.notify(performance) + realm.writeAsync { asyncRealm -> + asyncRealm.copyToRealm(performance) + this.whistleBlower.notify(performance) + } + } } ?: run { // if there is no max but a now irrelevant Performance, we delete it Timber.d("NO best computed value, current perf = $currentPerf ") currentPerf?.let { perf -> - realm.executeTransaction { + realm.writeAsync { asyncRealm -> Timber.d("Delete perf: stat = ${perf.stat}, report = ${perf.reportId}") - perf.deleteFromRealm() + asyncRealm.findById(perf.id)?.deleteFromRealm() +// perf.deleteFromRealm() } } } @@ -274,9 +291,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 { var storePerf = true @@ -288,30 +313,38 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co } if (storePerf) { - realm.executeTransaction { - perf.name = formattedDuration - perf.value = duration + perf.name = formattedDuration + perf.value = duration + realm.writeAsync { asyncRealm -> + asyncRealm.copyToRealm(perf) } } } if (storePerf) { - val perf = Performance(staticReport, key, name = formattedDuration, value = duration) - realm.executeTransaction { it.copyToRealm(perf) } - this.whistleBlower.notify(perf) + val perf = + Performance(staticReport, key, name = formattedDuration, value = duration) + realm.writeAsync { asyncRealm -> + asyncRealm.copyToRealm(perf) + this.whistleBlower.notify(perf) + } } } ?: run { // no duration performance?.let { perf -> - realm.executeTransaction { - perf.deleteFromRealm() // delete if the perf exists + realm.writeAsync { asyncRealm -> + asyncRealm.findById(perf.id)?.deleteFromRealm() } } } } - private fun performancesQuery(realm: Realm, staticReport: StaticReport, key: PerformanceKey): RealmQuery { + private fun performancesQuery( + realm: Realm, + staticReport: StaticReport, + key: PerformanceKey + ): RealmQuery { return realm.where(Performance::class.java) .equalTo("reportId", staticReport.uniqueIdentifier) .equalTo("key", key.value) diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt index 6ff628a0..3e9cc8ff 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt @@ -89,7 +89,7 @@ class BankrollCalculator { this.computeRiskOfRuin(report, result) } else { - val results = Filter.queryOn(realm, baseQuery) + val results = Filter.queryOn(realm, baseQuery) report.netResult = results.sum("net").toDouble() } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt index 211589e0..3b3b3542 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt @@ -2,8 +2,8 @@ package net.pokeranalytics.android.calculus.bankroll import io.realm.Realm import io.realm.RealmResults +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.async import kotlinx.coroutines.launch import net.pokeranalytics.android.model.realm.Bankroll @@ -68,10 +68,10 @@ object BankrollReportManager { } // otherwise compute it - GlobalScope.launch(coroutineContext) { + CoroutineScope(context = coroutineContext).launch { var report: BankrollReport? = null - val coroutine = GlobalScope.async { + val coroutine = CoroutineScope(context = Dispatchers.IO).async { val s = Date() Timber.d(">>>>> start computing bankroll...") diff --git a/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt b/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt index 964a9b26..6237df76 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/Criteria.kt @@ -87,7 +87,7 @@ sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, Row if (this is ValueCustomFields) { val realm = Realm.getDefaultInstance() - val distincts = realm.where().equalTo("customFields.id", this.customFieldId).findAll().sort("numericValue", Sort.ASCENDING) + val distincts = realm.where().equalTo("customField.id", this.customFieldId).findAll().sort("numericValue", Sort.ASCENDING) realm.close() val objects = mutableListOf() diff --git a/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt b/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt index e31ef53a..cc03e990 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt @@ -51,12 +51,17 @@ enum class LiveData : Localizable { } fun updateOrCreate(realm: Realm, primaryKey: String?): Deletable { - val proxyItem: Deletable? = this.getData(realm, primaryKey) - proxyItem?.let { - return realm.copyFromRealm(it) - } ?: run { - return this.newEntity() - } + + val data = this.getData(realm, primaryKey) + data?.let { return data } + return this.newEntity() + +// val proxyItem: Deletable? = this.getData(realm, primaryKey) +// proxyItem?.let { +// return realm.copyFromRealm(it) +// } ?: run { +// return this.newEntity() +// } } private fun newEntity(): Deletable { @@ -68,7 +73,7 @@ enum class LiveData : Localizable { primaryKey?.let { val t = realm.findById(this.relatedEntity, it) t?.let { - proxyItem = t + proxyItem = realm.copyFromRealm(t) // make an unmanaged object } } return proxyItem diff --git a/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt b/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt index eb09435d..66c655cc 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt @@ -3,7 +3,10 @@ package net.pokeranalytics.android.model.filter import io.realm.RealmModel import io.realm.RealmResults 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 /** @@ -63,7 +66,7 @@ class FilterHelper { ComputableResult::class.java -> ComputableResult.fieldNameForQueryType(queryCondition) SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition) Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition) - Result::class.java -> Result.fieldNameForQueryType(queryCondition) +// Result::class.java -> Result.fieldNameForQueryType(queryCondition) else -> { CrashLogging.logException(PAIllegalStateException("Filterable type fields are not defined for condition ${queryCondition::class}, class ${T::class}")) null diff --git a/app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt b/app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt index fc154bae..39075d64 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt @@ -9,9 +9,10 @@ import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.handhistory.HandHistory import net.pokeranalytics.android.model.utils.Seed -import net.pokeranalytics.android.model.utils.SessionSetManager +import net.pokeranalytics.android.model.utils.SessionManager import net.pokeranalytics.android.util.BLIND_SEPARATOR import net.pokeranalytics.android.util.Preferences +import net.pokeranalytics.android.util.extensions.writeAsync import java.text.NumberFormat class Patcher { @@ -70,8 +71,8 @@ class Patcher { val transactionTypes = TransactionType.Value.values() - realm.executeTransaction { - Seed.createDefaultTransactionTypes(transactionTypes, context, realm) + realm.writeAsync { asyncRealm -> + Seed.createDefaultTransactionTypes(transactionTypes, context, asyncRealm) } realm.close() @@ -80,19 +81,17 @@ class Patcher { private fun patchBreaks() { val realm = Realm.getDefaultInstance() - val sets = realm.where(SessionSet::class.java).findAll() - val sessions = Filter.queryOn(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(asyncRealm, Query(QueryCondition.IsCash)) + sets.forEach { it.computeStats() } sessions.forEach { it.generateStakes() it.defineHighestBet() - } - results.forEach { it.computeNumberOfRebuy() } } @@ -103,8 +102,8 @@ class Patcher { private fun patchDefaultTransactionTypes(context: Context) { val realm = Realm.getDefaultInstance() - realm.executeTransaction { - val tts = realm.where(TransactionType::class.java).findAll() + realm.writeAsync { asyncRealm -> + val tts = asyncRealm.where(TransactionType::class.java).findAll() tts.forEach { tt -> tt.kind?.let { kind -> val value = TransactionType.Value.values()[kind] @@ -117,8 +116,8 @@ class Patcher { private fun patchStakes() { val realm = Realm.getDefaultInstance() - realm.executeTransaction { - val sessions = realm.where(Session::class.java).findAll() + realm.writeAsync { asyncRealm -> + val sessions = asyncRealm.where(Session::class.java).findAll() sessions.forEach { session -> val blinds = arrayListOf(session.cgOldSmallBlind, session.cgOldBigBlind).filterNotNull() 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 -> val blinds = arrayListOf(hh.oldSmallBlind, hh.oldBigBlind).filterNotNull() val blindsFormatted = blinds.map { NumberFormat.getInstance().format(it) } @@ -143,8 +142,8 @@ class Patcher { private fun patchNegativeLimits() { val realm = Realm.getDefaultInstance() - realm.executeTransaction { - val sessions = realm.where(Session::class.java).lessThan("limit", 0).findAll() + realm.writeAsync { asyncRealm -> + val sessions = asyncRealm.where(Session::class.java).lessThan("limit", 0).findAll() sessions.forEach { session -> session.limit = null } @@ -154,10 +153,10 @@ class Patcher { private fun cleanBlindsFilters() { val realm = Realm.getDefaultInstance() - realm.executeTransaction { - val blindFilterConditions = realm.where(FilterCondition::class.java).equalTo("filterName", "AnyBlind").findAll() + realm.writeAsync { asyncRealm -> + val blindFilterConditions = asyncRealm.where(FilterCondition::class.java).equalTo("filterName", "AnyBlind").findAll() 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() } realm.close() @@ -170,11 +169,12 @@ class Patcher { private fun patchSessionSet() { val realm = Realm.getDefaultInstance() - realm.executeTransaction { - realm.where(SessionSet::class.java).findAll().deleteAllFromRealm() - val sessions = realm.where(Session::class.java).isNotNull("startDate").isNotNull("endDate").findAll() + realm.writeAsync { asyncRealm -> + asyncRealm.where(SessionSet::class.java).findAll().deleteAllFromRealm() + val sessions = asyncRealm.where(Session::class.java) + .isNotNull("startDate").isNotNull("endDate").findAll() sessions.forEach { session -> - SessionSetManager.updateTimeline(session) + SessionManager.updateTimeline(session) } } realm.close() @@ -187,8 +187,8 @@ class Patcher { */ private fun patchComputableResults() { val realm = Realm.getDefaultInstance() - realm.executeTransaction { - val crs = realm.where(ComputableResult::class.java).findAll() + realm.writeAsync { asyncRealm -> + val crs = asyncRealm.where(ComputableResult::class.java).findAll() crs.forEach { cr -> cr.session?.let { cr.updateWith(it) } } @@ -210,8 +210,8 @@ class Patcher { private fun patchZeroTable() { val realm = Realm.getDefaultInstance() val zero = 0 - val sessions = realm.where().equalTo("numberOfTables", zero).findAll() - realm.executeTransaction { + realm.writeAsync { asyncRealm -> + val sessions = asyncRealm.where().equalTo("numberOfTables", zero).findAll() sessions.forEach { s -> s.numberOfTables = 1 } @@ -221,8 +221,8 @@ class Patcher { private fun patchRatedAmounts() { val realm = Realm.getDefaultInstance() - val transactions = realm.where().findAll() - realm.executeTransaction { + realm.writeAsync { asyncRealm -> + val transactions = asyncRealm.where().findAll() transactions.forEach { t -> t.computeRatedAmount() } diff --git a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt index 93f7d03c..54bcc527 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt @@ -1,6 +1,7 @@ package net.pokeranalytics.android.model.migrations import io.realm.DynamicRealm +import io.realm.DynamicRealmObject import io.realm.RealmMigration import net.pokeranalytics.android.exceptions.PAIllegalStateException import timber.log.Timber @@ -335,6 +336,47 @@ class PokerAnalyticsMigration : RealmMigration { 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("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 { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt index 28a6be15..29c9db70 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt @@ -28,12 +28,10 @@ open class ComputableResult : RealmObject(), Filterable { val rate = session.bankroll?.currency?.rate ?: 1.0 - session.result?.let { result -> - this.ratedNet = result.net * rate - this.isPositive = result.isPositive - this.ratedBuyin = (result.buyin ?: 0.0) * rate - this.ratedTips = (result.tips ?: 0.0) * rate - } + this.ratedNet = session.net * rate + this.isPositive = session.isPositive + this.ratedBuyin = (session.buyin ?: 0.0) * rate + this.ratedTips = (session.tips ?: 0.0) * rate this.bbNet = session.bbNet this.hasBigBlind = if (session.cgBiggestBet != null) 1 else 0 diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt index 98b11a0c..2b9c8575 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt @@ -5,6 +5,7 @@ import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.util.UserDefaults +import timber.log.Timber import java.util.* open class Currency : RealmObject() { @@ -42,15 +43,16 @@ open class Currency : RealmObject() { fun refreshRelatedRatedValues() { + Timber.d("refreshRelatedRatedValues of $code") val rate = this.rate ?: DEFAULT_RATE val query = this.realm.where(ComputableResult::class.java) query.`in`("session.bankroll.currency.id", arrayOf(this.id)) val cResults = query.findAll() cResults.forEach { computable -> - computable.session?.result?.net?.let { + computable.session?.net?.let { computable.ratedNet = it * rate } - computable.session?.result?.buyin?.let { + computable.session?.buyin?.let { computable.ratedBuyin = it * rate } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt index 15b55812..cef637c1 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt @@ -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.SimpleRow import net.pokeranalytics.android.util.enumerations.IntIdentifiable -import timber.log.Timber +import net.pokeranalytics.android.util.extensions.writeAsync import java.util.* -import kotlin.collections.ArrayList 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 } - override fun deleteDependencies(realm: Realm) { if (isValid) { - val entries = realm.where().equalTo("customFields.id", id).findAll() + val entries = realm.where().equalTo("customField.id", id).findAll() entries.deleteAllFromRealm() } } @@ -230,6 +228,7 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana */ fun addEntry(): CustomFieldEntry { val entry = CustomFieldEntry() + entry.customField = this this.entries.add(entry) sortEntries() updateRowRepresentation() @@ -250,9 +249,11 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana fun cleanupEntries() { // called when saving the custom field val realm = Realm.getDefaultInstance() - realm.executeTransaction { - this.entriesToDelete.forEach { // entries are out of realm - realm.where().equalTo("id", it.id).findFirst()?.deleteFromRealm() + + val ids = this.entriesToDelete.map { it.id } + realm.writeAsync { asyncRealm -> + ids.forEach { id -> // entries are out of realm + asyncRealm.where().equalTo("id", id).findFirst()?.deleteFromRealm() } } realm.close() @@ -261,37 +262,16 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana fun getOrCreateEntry(realm: Realm, value: String): CustomFieldEntry { this.entries.find { it.value == value }?.let { - Timber.d("L>> get") return it } ?: run { - Timber.d("L>> create") val entry = realm.copyToRealm(CustomFieldEntry()) + entry.customField = this entry.value = value this.entries.add(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().equalTo("id", it.id).findFirst()?.deleteFromRealm() -// } -// entriesToDelete.clear() -// } -// } - /** * Returns a comparison criteria based on this custom field */ diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt index 5c889291..e25883ac 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt @@ -4,9 +4,7 @@ import android.content.Context import android.text.InputType import io.realm.Realm import io.realm.RealmObject -import io.realm.RealmResults import io.realm.annotations.Ignore -import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey import io.realm.kotlin.where import net.pokeranalytics.android.R @@ -43,13 +41,14 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable, R /** * The inverse relationship with CustomField */ - @LinkingObjects("entries") - val customFields: RealmResults? = null +// @LinkingObjects("entries") +// val customFields: RealmResults? = null - val customField: CustomField? - get() { - return this.customFields?.first() - } + var customField: CustomField? = null + +// get() { +// return this.customFields?.first() +// } /** * The string value of the entry diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt index c258c30f..d83a9fd0 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt @@ -9,7 +9,10 @@ import net.pokeranalytics.android.R import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Query 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.modules.filter.FilterableType import net.pokeranalytics.android.ui.view.* @@ -56,18 +59,11 @@ open class Filter : RealmObject(), RowRepresentable, RowUpdatable, Deletable, Us override val imageClickable: Boolean? get() = true - @PrimaryKey override var id = UUID.randomUUID().toString() // the queryWith name var name: String = "" - get() { - if (field.isEmpty()) { - return this.query.defaultName - } - return field - } override var useCount: Int = 0 @@ -109,7 +105,7 @@ open class Filter : RealmObject(), RowRepresentable, RowUpdatable, Deletable, Us val previousCondition = filterConditions.filter { it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator } - filterConditions.removeAll(previousCondition) + filterConditions.removeAll(previousCondition.toSet()) filterConditions.add(newFilterCondition) } } @@ -118,7 +114,7 @@ open class Filter : RealmObject(), RowRepresentable, RowUpdatable, Deletable, Us fun remove(filterCategoryRow: FilterCategoryRow) { val sections = filterCategoryRow.filterSectionRows.map { it.name } val savedSections = filterConditions.filter { sections.contains(it.sectionName) } - this.filterConditions.removeAll(savedSections) + this.filterConditions.removeAll(savedSections.toSet()) } fun countBy(filterCategoryRow: FilterCategoryRow): Int { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Performance.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Performance.kt index 65ae3e53..eba2063b 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Performance.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Performance.kt @@ -2,9 +2,11 @@ package net.pokeranalytics.android.model.realm import io.realm.Realm import io.realm.RealmObject +import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey import net.pokeranalytics.android.calculus.Stat 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.util.NULL_TEXT import net.pokeranalytics.android.util.extensions.lookupForNameInAllTablesById @@ -16,10 +18,13 @@ interface PerformanceKey { val value: Int } -open class Performance() : RealmObject() { +open class Performance() : RealmObject(), Identifiable { @PrimaryKey - var id: String = UUID.randomUUID().toString() + override var id: String = UUID.randomUUID().toString() + + @Ignore + override val realmObjectClass: Class = Performance::class.java constructor( report: StaticReport, diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt index a088a991..eaaf63d4 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt @@ -6,9 +6,9 @@ import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey -import net.pokeranalytics.android.calculus.calcul.ReportDisplay import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.calculus.calcul.ReportDisplay import net.pokeranalytics.android.model.Criteria import net.pokeranalytics.android.model.interfaces.Deletable 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.RowViewType import net.pokeranalytics.android.util.extensions.findById +import timber.log.Timber import java.util.* @@ -67,6 +68,7 @@ open class ReportSetup : RealmObject(), RowRepresentable, Deletable { */ fun options(realm: Realm, reportDisplay: ReportDisplay): Calculator.Options { + Timber.d("stats = ${this.statIds.size}") val stats = this.statIds.map { Stat.valueByIdentifier(it) } // Comparison criteria diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt index 7298726d..30fa5203 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt @@ -9,12 +9,13 @@ import io.realm.annotations.RealmClass import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.QueryCondition + @RealmClass open class Result : RealmObject(), Filterable { companion object { - fun fieldNameForQueryType(queryCondition: Class < out QueryCondition>): String? { + fun fieldNameForQueryType(queryCondition: Class ): String? { Session.fieldNameForQueryType(queryCondition)?.let { return "sessions.$it" } @@ -22,6 +23,7 @@ open class Result : RealmObject(), Filterable { } } + /** * The buyin amount */ @@ -29,6 +31,7 @@ open class Result : RealmObject(), Filterable { set(value) { field = value this.computeNumberOfRebuy() +// SessionManager.sessionNetChanged(this.session) this.computeNet(true) } @@ -39,9 +42,6 @@ open class Result : RealmObject(), Filterable { set(value) { field = value this.computeNet(true) - if (value != null) { - this.session?.end() - } } /** @@ -49,20 +49,8 @@ open class Result : RealmObject(), Filterable { */ var netResult: Double? = null 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 this.computeNet(false) - if (value != null) { - this.session?.end() - } } /** @@ -105,14 +93,14 @@ open class Result : RealmObject(), Filterable { val isPositive: Int get() { 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 { if (this.net >= 0.0) 1 else 0 } } // Computes the Net - private fun computeNet(withBuyin: Boolean? = null) { + fun computeNet(withBuyin: Boolean? = null) { val transactionsSum = transactions.sumOf { it.amount } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index 2164c764..d2cc66b1 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -13,7 +13,6 @@ import io.realm.kotlin.where import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.StatFormattingException -import net.pokeranalytics.android.exceptions.ModelException import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.Stakes @@ -27,7 +26,7 @@ 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.realm.handhistory.HandHistory -import net.pokeranalytics.android.model.utils.SessionSetManager +import net.pokeranalytics.android.model.utils.SessionManager import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException import net.pokeranalytics.android.ui.graph.Graph import net.pokeranalytics.android.ui.view.* @@ -37,6 +36,7 @@ import net.pokeranalytics.android.util.extensions.hourMinute import net.pokeranalytics.android.util.extensions.shortDateTime import net.pokeranalytics.android.util.extensions.toCurrency import net.pokeranalytics.android.util.extensions.toMinutes +import timber.log.Timber import java.text.DateFormat import java.text.NumberFormat import java.text.ParseException @@ -45,7 +45,7 @@ import java.util.Currency typealias BB = Double -open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Timed, +open class Session : RealmObject(), Savable, RowRepresentable, Timed, TimeFilterable, Filterable, DatedBankrollGraphEntry, StakesHolder { enum class Type(val value: String) { @@ -67,24 +67,23 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim 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() - session.result = Result() - if (bankroll != null) { - session.bankroll = bankroll - } else { - session.bankroll = realm.where().findFirst() + + val br: Bankroll? = bankroll ?: realm.where().findFirst() + br?.let { + session.bankroll = realm.copyFromRealm(br) } - 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.game = realm.where(Game::class.java).equalTo("shortName", "HE").findFirst() - return if (managed) { - realm.copyToRealm(session) - } else { - session + val game = realm.where(Game::class.java).equalTo("shortName", "HE").findFirst() + game?.let { + session.game = realm.copyFromRealm(game) } + + return session } fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { @@ -118,7 +117,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim Duration::class.java -> "netDuration" CustomFieldListQuery::class.java -> "customFieldEntries.id" CustomFieldAmountQuery::class.java, CustomFieldNumberQuery::class.java -> "customFieldEntries.numericValue" - CustomFieldQuery::class.java -> "customFieldEntries.customFields.id" + CustomFieldQuery::class.java -> "customFieldEntries.customField.id" DateNotNull::class.java -> "startDate" EndDateNotNull::class.java -> "endDate" BiggestBetNotNull::class.java -> "cgBiggestBet" @@ -148,7 +147,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim val sessionType: Type get() { return Type.values()[this.type] } - // The result of the main user + // Not used anymore var result: Result? = null @LinkingObjects("session") @@ -209,7 +208,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim this.endDate = null } this.dateChanged() - this.computeStats() + this.addToComputeQueue() } /** @@ -230,7 +229,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim this.computeNetDuration() this.dateChanged() this.defineDefaultTournamentBuyinIfNecessary() - this.computeStats() + this.addToComputeQueue() } /** @@ -240,7 +239,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim set(value) { field = value this.computeNetDuration() - this.computeStats() + this.addToComputeQueue() } /** @@ -268,7 +267,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim set(value) { field = value this.generateStakes() - this.computeStats() + this.addToComputeQueue() // this.updateRowRepresentation() } @@ -296,7 +295,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim set(value) { if (value > 0) { field = value - this.computeStats() + this.addToComputeQueue() } } @@ -314,16 +313,13 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim // The small blind value var cgOldSmallBlind: Double? = null - set(value) { - field = value - } // The big blind value var cgOldBigBlind: Double? = null set(value) { field = value - this.computeStats() - this.result?.computeNumberOfRebuy() + this.addToComputeQueue() + this.computeNumberOfRebuy() } // var blinds: String? = null @@ -334,8 +330,8 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim field = value this.generateStakes() this.defineHighestBet() - this.computeStats() - this.result?.computeNumberOfRebuy() + this.addToComputeQueue() + this.computeNumberOfRebuy() } var cgBlinds: String? = null @@ -343,8 +339,8 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim field = cleanupBlinds(value) this.generateStakes() this.defineHighestBet() - this.computeStats() - this.result?.computeNumberOfRebuy() + this.addToComputeQueue() + this.computeNumberOfRebuy() } var cgBiggestBet: Double? = null @@ -357,7 +353,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim var tournamentEntryFee: Double? = null set(value) { field = value - this.result?.computeNumberOfRebuy() + this.computeNumberOfRebuy() } // The total number of players who participated in the tournament @@ -379,9 +375,132 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim var handsCount: Int? = null set(value) { field = value - this.computeStats() + this.addToComputeQueue() + } + + /** + * The buyin amount + */ + var buyin: Double? = null + set(value) { + field = value + this.computeNumberOfRebuy() +// SessionManager.sessionNetChanged(this.session) + 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 = 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() + this.sessionSet?.computeStats() + } + + // 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() { this.generateStakes() } @@ -391,11 +510,15 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim * Should be called when the start / end date are changed */ private fun dateChanged() { - if (this.endDate != null) { - SessionSetManager.updateTimeline(this) - } else if (this.sessionSet != null) { - SessionSetManager.removeFromTimeline(this) - } + + SessionManager.sessionDateChanged(this) + +// if (this.endDate != null) { +// SessionSetManager.updateTimeline(this) +// } else if (this.sessionSet != null) { +// SessionSetManager.removeFromTimeline(this) +// } + // this.updateRowRepresentation() } @@ -435,9 +558,9 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim val bbNet: BB get() { val bb = this.cgBiggestBet - val result = this.result - return if (bb != null && result != null) { - result.net / bb +// val result = this.result + return if (bb != null) { + this.net / bb } else { 0.0 } @@ -468,9 +591,16 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim @Ignore override var amount: Double = 0.0 get() { - return this.result?.net ?: 0.0 + return this.net } + /** + * Pre-compute various statIds + */ + private fun addToComputeQueue() { + SessionManager.sessionToCompute(this) + } + /** * Pre-compute various statIds */ @@ -501,17 +631,13 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim val numberOfHandsPerHour: Double get() { val tableSize = this.tableSize ?: 9 // 9 is the default table size if null - val config = UserConfig.getConfiguration(this.realm) - val playerHandsPerHour = if (this.isLive) config.liveDealtHandsPerHour else config.onlineDealtHandsPerHour - return this.numberOfTables * playerHandsPerHour / tableSize.toDouble() + val dealtHandsPerHour = if (this.isLive) UserConfigObserver.liveDealtHandsPerHour else UserConfigObserver.onlineDealtHandsPerHour + return this.numberOfTables * dealtHandsPerHour / tableSize.toDouble() } val hourlyRate: Double get() { - this.result?.let { result -> - return result.net / this.hourlyDuration - } - throw ModelException("Session should have an existing Result relationship") + return this.net / this.hourlyDuration } private val bbHourlyRate: BB @@ -529,12 +655,12 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim val hasBuyin: Boolean get() { - return this.result?.buyin != null + return this.buyin != null } val hasNetResult: Boolean get() { - return this.result?.netResult != null + return this.netResult != null } // Manageable @@ -557,31 +683,29 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim * Start or continue a session */ fun startOrContinue() { - realm.executeTransaction { - when (val state = getState()) { - SessionState.PENDING, SessionState.PLANNED -> { - this.startDate = Date() - this.defineDefaultTournamentBuyinIfNecessary() - } - SessionState.PAUSED -> { - val pauseDate = this.pauseDate - if (pauseDate != null) { - this.breakDuration += Date().time - pauseDate.time - } else { - throw PAIllegalStateException("When resuming, the pause date must be set") - } - this.pauseDate = null - } - else -> { - throw PAIllegalStateException("unmanaged session state: $state") + when (val state = getState()) { + SessionState.PENDING, SessionState.PLANNED -> { + this.startDate = Date() + this.defineDefaultTournamentBuyinIfNecessary() + } + SessionState.PAUSED -> { + val pauseDate = this.pauseDate + if (pauseDate != null) { + this.breakDuration += Date().time - pauseDate.time + } else { + throw PAIllegalStateException("When resuming, the pause date must be set") } + this.pauseDate = null + } + else -> { + throw PAIllegalStateException("unmanaged session state: $state") } } } private fun defineDefaultTournamentBuyinIfNecessary() { - if (this.tournamentEntryFee != null && this.result?.buyin == null) { - this.result?.buyin = this.tournamentEntryFee + if (this.tournamentEntryFee != null && this.buyin == null) { + this.buyin = this.tournamentEntryFee } } @@ -589,13 +713,11 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim * Pause a session */ fun pause() { - realm.executeTransaction { - when (val state = getState()) { - SessionState.STARTED -> { - this.pauseDate = Date() - } - else -> throw PAIllegalStateException("Pausing a session in an unmanaged state: $state") + when (val state = getState()) { + SessionState.STARTED -> { + this.pauseDate = Date() } + else -> throw PAIllegalStateException("Pausing a session in an unmanaged state: $state") } } @@ -603,13 +725,11 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim * Stop a session */ fun stop(context: Context) { - realm.executeTransaction { - when (val state = getState()) { - SessionState.STARTED, SessionState.PAUSED -> { - this.end() - } - else -> throw Exception("Stopping session in unmanaged state: $state") + when (val state = getState()) { + SessionState.STARTED, SessionState.PAUSED -> { + this.end() } + else -> throw Exception("Stopping session in unmanaged state: $state") } cancelStopNotification(context) } @@ -618,12 +738,10 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim * Restart a session */ fun restart() { - realm.executeTransaction { - this.pauseDate = null - this.startDate = Date() - this.endDate = null - this.breakDuration = 0L - } + this.pauseDate = null + this.startDate = Date() + this.endDate = null + this.breakDuration = 0L } /** @@ -688,10 +806,11 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim if (isValid) { // CrashLogging.log("Deletes session. Id = ${this.id}") - realm.executeTransaction { + +// realm.executeTransaction { cleanup() deleteFromRealm() - } +// } } else { CrashLogging.log("Attempt to delete an invalid session") } @@ -704,10 +823,10 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim // Updates the timeline this.sessionSet?.let { - SessionSetManager.removeFromTimeline(this) + SessionManager.removeFromTimeline(this) } // cleanup unnecessary related objects - this.result?.deleteFromRealm() +// this.deleteFromRealm() this.computableResults?.deleteAllFromRealm() } @@ -738,7 +857,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim return "Session ${this.creationDate}" } - override fun updateValue(value: Any?, row: RowRepresentable) { + fun updateValue(value: Any?, row: RowRepresentable, realm: Realm) { when (row) { SessionPropertiesRow.BANKROLL -> bankroll = value as Bankroll? @@ -757,38 +876,50 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000 } SessionPropertiesRow.BUY_IN -> { - val localResult = getOrCreateResult() - localResult.buyin = value as Double? +// val localResult = getOrCreateResult() + this.buyin = value as Double? } SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> { - val localResult = getOrCreateResult() - localResult.cashout = value as Double? +// val localResult = getOrCreateResult() + val cashOut = value as Double? + this.cashout = cashOut + if (cashOut != null) { + this.end() + } } SessionPropertiesRow.NET_RESULT -> { - val localResult = getOrCreateResult() - localResult.netResult = value as Double? +// val localResult = getOrCreateResult() + val netResult = value as Double? + this.netResult = netResult + if (netResult != null) { + this.end() + } } SessionPropertiesRow.COMMENT -> comment = value as String? ?: "" SessionPropertiesRow.END_DATE -> if (value is Date?) { this.endDate = value } SessionPropertiesRow.GAME -> { - if (value is ArrayList<*>) { - limit = try { - (value[0] as Int?) - } catch (e: Exception) { - null + when (value) { + is ArrayList<*> -> { + limit = try { + (value[0] as Int?) + } catch (e: Exception) { + null + } + game = try { + (value[1] as Game?) + } catch (e: Exception) { + null + } } - game = try { - (value[1] as Game?) - } catch (e: Exception) { - null + is Game -> { + game = value + } + null -> { + limit = null + game = null } - } else if (value is Game) { - game = value - } else if (value == null) { - limit = null - game = null } } SessionPropertiesRow.INITIAL_BUY_IN -> { @@ -803,21 +934,21 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim } } 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) { - localResult.tournamentFinalPosition = value.toInt() + this.tournamentFinalPosition = value.toInt() } else { - localResult.tournamentFinalPosition = null + this.tournamentFinalPosition = null } - result = localResult +// result = localResult } SessionPropertiesRow.START_DATE -> if (value is Date) { this.startDate = value } SessionPropertiesRow.TABLE_SIZE -> tableSize = value as Int? SessionPropertiesRow.TIPS -> { - val localResult = getOrCreateResult() - localResult.tips = value as Double? +// val localResult = getOrCreateResult() + this.tips = value as Double? } SessionPropertiesRow.TOURNAMENT_NAME -> tournamentName = value as TournamentName? SessionPropertiesRow.TOURNAMENT_TYPE -> tournamentType = (value as TournamentType?)?.ordinal @@ -842,6 +973,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim if (value != null) { val customFieldEntry = CustomFieldEntry() customFieldEntry.numericValue = value as Double? + customFieldEntry.customField = row customFieldEntries.add(customFieldEntry) row.entries.add(customFieldEntry) } @@ -857,15 +989,26 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim } - private fun getOrCreateResult(): Result { - return this.result - ?: run { - val result = realm.createObject(Result::class.java) - this.result = result - result - } + private fun customFieldEntries(realm: Realm, customField: CustomField): List { + +// val cfEntries = customField.entries +// val sessionEntries = this.customFieldEntries +// +// val entries = realm.where() +// .`in`() + + return listOf() } +// private fun getOrCreateResult(): Result { +// return this.result +// ?: run { +// val result = realm.createObject(Result::class.java) +// this.result = result +// result +// } +// } + // Stat Entry override fun entryTitle(context: Context): String { @@ -879,15 +1022,15 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim override fun formattedValue(stat: Stat): TextFormat { - this.result?.let { result -> +// this.result?.let { result -> val value: Double? = when (stat) { - Stat.NET_RESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> result.net + Stat.NET_RESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> this.net Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> 1.0 - Stat.AVERAGE_BUYIN -> result.buyin + Stat.AVERAGE_BUYIN -> this.buyin Stat.ROI -> { - result.buyin?.let { - Stat.returnOnInvestment(result.net, it) + this.buyin?.let { + Stat.returnOnInvestment(this.net, it) } ?: run { null } @@ -911,9 +1054,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim return TextFormat(NULL_TEXT) } - } ?: run { - throw PAIllegalStateException("Asking for statIds on Session without Result") - } +// } } @@ -971,19 +1112,19 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim SessionPropertiesRow.BANKROLL -> bankroll?.name ?: NULL_TEXT SessionPropertiesRow.STAKES -> getFormattedStakes() 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.CASHED_OUT, SessionPropertiesRow.PRIZE -> this.result?.cashout?.toCurrency(currency) ?: NULL_TEXT - SessionPropertiesRow.NET_RESULT -> this.result?.netResult?.toCurrency(currency) ?: NULL_TEXT + SessionPropertiesRow.BUY_IN -> this.buyin?.toCurrency(currency) ?: NULL_TEXT + SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> this.cashout?.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.END_DATE -> this.endDate?.shortDateTime() ?: NULL_TEXT SessionPropertiesRow.GAME -> getFormattedGame() SessionPropertiesRow.INITIAL_BUY_IN -> tournamentEntryFee?.toCurrency(currency) ?: NULL_TEXT SessionPropertiesRow.LOCATION -> location?.name ?: 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.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 -> { this.tournamentType?.let { TournamentType.values()[it].localizedTitle(context) @@ -1009,6 +1150,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim SessionPropertiesRow.HANDS_COUNT -> this.handsCountFormatted(context) SessionPropertiesRow.NUMBER_OF_TABLES -> this.numberOfTables.toString() is CustomField -> { + Timber.d("entries count = ${customFieldEntries.size}") customFieldEntries.find { it.customField?.id == row.id }?.let { customFieldEntry -> return customFieldEntry.getFormattedValue(currency) } @@ -1024,12 +1166,12 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim } fun clearBuyinCashedOut() { - this.result?.buyin = null - this.result?.cashout = null + this.buyin = null + this.cashout = null } fun clearNetResult() { - this.result?.netResult = null + this.netResult = null } private fun cleanupBlinds(blinds: String?): String? { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/UserConfig.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/UserConfig.kt index 96094c1f..22ed1445 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/UserConfig.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/UserConfig.kt @@ -2,15 +2,18 @@ package net.pokeranalytics.android.model.realm import io.realm.Realm import io.realm.RealmObject +import io.realm.RealmResults import io.realm.annotations.PrimaryKey import net.pokeranalytics.android.util.UUID_SEPARATOR import net.pokeranalytics.android.util.extensions.findById +import timber.log.Timber import java.util.* open class UserConfig : RealmObject() { companion object { + // these values are buffered to avoid the use of Realm instance fun getConfiguration(realm: Realm): UserConfig { realm.where(UserConfig::class.java).findFirst()?.let { config -> return config @@ -29,8 +32,12 @@ open class UserConfig : RealmObject() { var transactionTypeIds: String = "" - fun setTransactionTypeIds(transactionTypes: Set) { - this.transactionTypeIds = transactionTypes.joinToString(UUID_SEPARATOR) { it.id } +// fun setTransactionTypeIds(transactionTypes: Set) { +// this.transactionTypeIds = transactionTypes.joinToString(UUID_SEPARATOR) { it.id } +// } + + fun setTransactionTypeIds(transactionTypeIds: Set) { + this.transactionTypeIds = transactionTypeIds.joinToString(UUID_SEPARATOR) } fun transactionTypes(realm: Realm): List { @@ -38,4 +45,40 @@ open class UserConfig : RealmObject() { return ids.mapNotNull { realm.findById(it) } } -} \ No newline at end of file +} + +object UserConfigObserver { + + private var userConfig: RealmResults? = 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}") + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/HandHistory.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/HandHistory.kt index 6ec84ade..1a41419d 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/HandHistory.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/HandHistory.kt @@ -313,13 +313,7 @@ open class HandHistory : RealmObject(), Deletable, RowRepresentable, Filterable, * Creates and affect a PlayerSetup at the given [positionIndex] */ fun createPlayerSetup(positionIndex: Int): PlayerSetup { - - val playerSetup = if (this.realm != null) { - this.realm.createObject(PlayerSetup::class.java) } - else { - PlayerSetup() - } - + val playerSetup = PlayerSetup() playerSetup.position = positionIndex this.playerSetups.add(playerSetup) return playerSetup diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt index fe40b2bb..c2a01596 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt @@ -73,15 +73,20 @@ class FavoriteSessionFinder { /** * 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 -> session.limit = fav.limit - session.game = fav.game - session.bankroll = fav.bankroll + fav.game?.let { game -> + session.game = realm.copyFromRealm(game) + } + fav.bankroll?.let { br -> + session.bankroll = realm.copyFromRealm(br) + } + session.tableSize = fav.tableSize when (session.type) { diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionManager.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionManager.kt new file mode 100644 index 00000000..1a0a47b8 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionManager.kt @@ -0,0 +1,273 @@ +package net.pokeranalytics.android.model.utils + +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.Session +import net.pokeranalytics.android.model.realm.SessionSet +import net.pokeranalytics.android.util.extensions.findById +import net.pokeranalytics.android.util.extensions.writeAsync +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 SessionManager { + + private var sessions: RealmResults? = null +// private var results: RealmResults? = null + + private var dateModifiedSessionIds: MutableSet = mutableSetOf() + private var netModifiedSessionIds: MutableSet = mutableSetOf() + private var statsToComputeSessionIds: MutableSet = mutableSetOf() + + init { + + val realm = Realm.getDefaultInstance() + + this.sessions = realm.where(Session::class.java).findAll() + this.sessions?.addChangeListener { results -> + + if (this.dateModifiedSessionIds.isNotEmpty()) { + results.realm.writeAsync { asyncRealm -> + for (sessionId in dateModifiedSessionIds) { + asyncRealm.findById(sessionId)?.let { session -> + if (session.endDate != null) { + this.updateTimeline(session) + } else if (session.sessionSet != null) { + this.removeFromTimeline(session) + } + } + } + this.dateModifiedSessionIds.clear() + } + } + + if (this.netModifiedSessionIds.isNotEmpty()) { + results.realm.writeAsync { asyncRealm -> + for (sessionId in netModifiedSessionIds) { + asyncRealm.findById(sessionId)?.computeNet(false) + } + this.dateModifiedSessionIds.clear() + } + } + + if (this.statsToComputeSessionIds.isNotEmpty()) { + results.realm.writeAsync { asyncRealm -> + for (sessionId in statsToComputeSessionIds) { + asyncRealm.findById(sessionId)?.computeStats() + } + this.statsToComputeSessionIds.clear() + } + } + + } + + realm.close() + } + + fun create() { + // empty, juste called to make sure the singleton is initialized + } + + fun sessionDateChanged(session: Session) { + this.dateModifiedSessionIds.add(session.id) + } + fun sessionNetChanged(session: Session) { + this.netModifiedSessionIds.add(session.id) + } + fun sessionToCompute(session: Session) { + this.statsToComputeSessionIds.add(session.id) + } + + /** + * 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 { + val realm = session.realm + val endDate = session.endDate!! // tested above + val startDate = session.startDate!! + + val query: RealmQuery = 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) { + + // get all endedSessions from sets + val allImpactedSessions = mutableSetOf() + 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, 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) { + + var startDate = session.startDate!! + var endDate = session.endDate!! + + // get all endedSessions from sets + val sessions = mutableSetOf() + 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() + sessionSet.sessions?.asIterable()?.let { sessions.addAll(it) } + sessions.remove(session) + + sessionSet.deleteFromRealm() + + sessions.forEach { + updateTimeline(it) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt deleted file mode 100644 index 598cba3b..00000000 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt +++ /dev/null @@ -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 { - val realm = session.realm - val endDate = session.endDate!! // tested above - val startDate = session.startDate!! - - val query: RealmQuery = 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) { - - // get all endedSessions from sets - val allImpactedSessions = mutableSetOf() - 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, 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) { - - var startDate = session.startDate!! - var endDate = session.endDate!! - - // get all endedSessions from sets - val sessions = mutableSetOf() - 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() - sessionSet.sessions?.asIterable()?.let { sessions.addAll(it) } - sessions.remove(session) - - sessionSet.deleteFromRealm() - - sessions.forEach { - updateTimeline(it) - } - } - } - - } - -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt index 59fe47b9..690dc909 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt @@ -21,6 +21,7 @@ import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.billing.AppGuard import net.pokeranalytics.android.util.extensions.findAll import net.pokeranalytics.android.util.extensions.isSameMonth +import net.pokeranalytics.android.util.extensions.writeAsync import java.util.* enum class Tab(var identifier: Int) { @@ -114,11 +115,15 @@ class HomeActivity : BaseActivity(), NewPerformanceListener { // observe currency changes this.currencies = realm.where(Currency::class.java).findAll() - this.currencies.addChangeListener { currencies, _ -> - realm.executeTransaction { - currencies.forEach { - it.refreshRelatedRatedValues() + this.currencies.addChangeListener { _, _ -> + + realm.writeAsync { asyncRealm -> + + val currencies = asyncRealm.where(Currency::class.java).findAll() + currencies.forEach { currency -> + currency.refreshRelatedRatedValues() } + } } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt index 141012b7..c8a54b48 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt @@ -9,14 +9,13 @@ import android.provider.MediaStore import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.pokeranalytics.android.util.ImageUtils import timber.log.Timber import java.io.File import java.io.IOException -import java.util.* open class MediaActivity : BaseActivity() { @@ -52,12 +51,12 @@ open class MediaActivity : BaseActivity() { val filesList = ArrayList() - GlobalScope.launch { + CoroutineScope(context = Dispatchers.IO).launch { if (tempFile != null) { tempFile?.let { - GlobalScope.launch(Dispatchers.Main) { - filesList.add(it) + CoroutineScope(context = Dispatchers.Main).launch { + filesList.add(it) getPictures(filesList) } } @@ -65,7 +64,7 @@ open class MediaActivity : BaseActivity() { data.clipData?.let { clipData -> try { - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { isLoadingNewPictures() } @@ -78,7 +77,7 @@ open class MediaActivity : BaseActivity() { filesList.add(photoFile) } - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { getPictures(filesList) } @@ -90,7 +89,7 @@ open class MediaActivity : BaseActivity() { data.data?.let { uri -> try { - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { isLoadingNewPictures() } @@ -98,7 +97,7 @@ open class MediaActivity : BaseActivity() { val photoFile = ImageUtils.createTempImageFile(this@MediaActivity) ImageUtils.copyInputStreamToFile(inputStream!!, photoFile) filesList.add(photoFile) - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { 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) } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt index d842c3db..7b862799 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt @@ -8,7 +8,7 @@ import android.view.ViewGroup import android.widget.TextView import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch import net.pokeranalytics.android.R @@ -98,7 +98,7 @@ class ImportFragment : RealmFragment(), ImportDelegate { CoroutineScope(coroutineContext).launch { - val coroutine = GlobalScope.async { + val coroutine = CoroutineScope(context = Dispatchers.IO).async { val s = Date() Timber.d(">>> Start Import...") diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt index 2e29525a..2db20b5e 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt @@ -50,6 +50,7 @@ import net.pokeranalytics.android.util.billing.IAPProducts import net.pokeranalytics.android.util.billing.PurchaseListener import net.pokeranalytics.android.util.csv.ProductCSVDescriptors import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted +import net.pokeranalytics.android.util.extensions.writeAsync import timber.log.Timber import java.io.File import java.io.IOException @@ -187,17 +188,17 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep private fun updateMainCurrency(currencyCode: String, rate: Double) { Preferences.setCurrencyCode(currencyCode, requireContext()) - val realm = Realm.getDefaultInstance() - realm.executeTransaction { - realm.where(Currency::class.java).findAll().forEach { currency -> + + getRealm().writeAsync { asyncRealm -> + asyncRealm.where(Currency::class.java).findAll().forEach { currency -> 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() } } - realm.close() + settingsAdapterRow.refreshRow(SettingsRow.CURRENCY) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt index b32e041e..a83c36c7 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt @@ -9,7 +9,7 @@ import android.view.* import androidx.appcompat.widget.Toolbar import io.realm.Realm import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch import net.pokeranalytics.android.R @@ -160,7 +160,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener { CoroutineScope(coroutineContext).launch { - val async = GlobalScope.async { + val async = CoroutineScope(context = Dispatchers.IO).async { val s = Date() Timber.d(">>> start...") @@ -222,7 +222,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener { 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) } @@ -235,7 +235,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener { Stat.NUMBER_OF_GAMES, 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) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt index a527b94c..b62f00d8 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt @@ -8,15 +8,16 @@ import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import com.google.android.material.snackbar.Snackbar import io.realm.RealmObject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.PAIllegalStateException 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.modules.datalist.DataListActivity +import net.pokeranalytics.android.util.extensions.writeAsync /** * Deletable Item Fragment @@ -56,7 +57,7 @@ abstract class DeletableItemFragment : RealmFragment() { val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) itemToDeleteId?.let { id -> - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { delay(300) deleteItem(dataListAdapter, deletableItems(), id) } @@ -88,8 +89,8 @@ abstract class DeletableItemFragment : RealmFragment() { if (itemToDelete.isValidForDelete(this.getRealm())) { deletedItem = getRealm().copyFromRealm(itemToDelete) lastDeletedItemPosition = itemPosition - getRealm().executeTransaction { - itemToDelete.deleteDependencies(it) + getRealm().writeAsync { asyncRealm -> + itemToDelete.deleteDependencies(asyncRealm) itemToDelete.deleteFromRealm() } itemHasBeenReInserted = false @@ -117,9 +118,9 @@ abstract class DeletableItemFragment : RealmFragment() { snackBar?.setAction(R.string.cancel) { if (!itemHasBeenReInserted) { itemHasBeenReInserted = true - getRealm().executeTransaction { realm -> + getRealm().writeAsync { asyncRealm -> deletedItem?.let { - val item = realm.copyToRealmOrUpdate(it) + val item = asyncRealm.copyToRealmOrUpdate(it) updateUIAfterUndoDeletion(item) } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/FilterableFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/FilterableFragment.kt index ccfcd16c..1bfafad6 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/FilterableFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/FilterableFragment.kt @@ -9,8 +9,8 @@ import android.view.* import android.widget.ImageView import android.widget.TextView import androidx.appcompat.widget.Toolbar +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.pokeranalytics.android.R @@ -140,7 +140,7 @@ open class FilterableFragment : RealmFragment(), FilterHandler { viewGroup.removeAllViews() viewGroup.addView(layoutCurrentFilter) - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { delay(300) viewGroup.visibility = View.VISIBLE } @@ -153,7 +153,7 @@ open class FilterableFragment : RealmFragment(), FilterHandler { */ private fun hideSelectedFilter() { view?.findViewById(R.id.selectedFilter).let { - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { it?.visibility = View.GONE } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt index edf77cd6..08ebd811 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup import androidx.core.view.get import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.chip.Chip +import io.realm.RealmObject import net.pokeranalytics.android.databinding.BottomSheetGameListBinding import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter @@ -38,10 +39,11 @@ class BottomSheetListGameFragment : BottomSheetListFragment() { } override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) { - this.model.realmData?.let { - val selectedData = it[position] - selectedData?.let { data -> - this.model.someValues[1] = data + this.model.realmData?.let { results -> + val selectedData = results[position] + (selectedData as? RealmObject)?.let { data -> + val obj = results.realm.copyFromRealm(data) + this.model.someValues[1] = obj this.onRowValueChanged() // this.delegate.onRowValueChanged(values, this.row) dismiss() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt index 1fd8e151..6dfc9930 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt @@ -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.ViewModelHolder import net.pokeranalytics.android.util.extensions.findById +import net.pokeranalytics.android.util.extensions.writeAsync abstract class AbstractReportFragment : DataManagerFragment() { @@ -84,7 +85,7 @@ abstract class AbstractReportFragment : DataManagerFragment() { saveReport(nameEditText.text.toString()) dialog.dismiss() } 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, _ -> @@ -105,36 +106,34 @@ abstract class AbstractReportFragment : DataManagerFragment() { } this.reportViewModel.title = name - val rs = this.model.item as ReportSetup - getRealm().executeTransaction { realm -> - - val firstSave = (this.model.primaryKey == null) - if (firstSave) { - val options = this.selectedReport.options - rs.name = name - rs.display = this.reportViewModel.reportDisplay?.ordinal ?: throw PAIllegalStateException("Display not set") - options.stats.forEach { - rs.statIds.add(it.uniqueIdentifier) - } - options.criterias.forEach { criteria -> - when (criteria) { - is CustomFieldCriteria -> rs.criteriaCustomFieldIds.add(criteria.customFieldId) - else -> rs.criteriaIds.add(criteria.uniqueIdentifier) - } + getRealm().writeAsync { asyncRealm -> + + val rs = this.model.item as ReportSetup + + val options = this.selectedReport.options + rs.name = name + rs.display = this.reportViewModel.reportDisplay?.ordinal + ?: throw PAIllegalStateException("Display not set") + rs.statIds.clear() + rs.statIds.addAll(options.stats.map { it.uniqueIdentifier }) + + rs.criteriaIds.clear() + options.criterias.forEach { criteria -> + when (criteria) { + is CustomFieldCriteria -> rs.criteriaCustomFieldIds.add(criteria.customFieldId) + else -> rs.criteriaIds.add(criteria.uniqueIdentifier) } + } - options.filterId?.let { id -> - rs.filter = realm.findById(id) - } - realm.copyToRealmOrUpdate(rs) - } else { - rs.name = name - realm.insertOrUpdate(rs) + options.filterId?.let { id -> + rs.filter = asyncRealm.findById(id) } + asyncRealm.copyToRealmOrUpdate(rs) + + this.model.primaryKey = rs.id } - this.model.primaryKey = rs.id this.deleteButtonShouldAppear = true setToolbarTitle(this.reportViewModel.title) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt index 4fddc017..5daf23ba 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt @@ -8,7 +8,7 @@ import android.widget.Toast import androidx.recyclerview.widget.LinearLayoutManager import io.realm.Realm import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch import net.pokeranalytics.android.R @@ -203,10 +203,10 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab showLoader() - GlobalScope.launch(coroutineContext) { + CoroutineScope(context = Dispatchers.IO).launch(coroutineContext) { var report: Report? = null - val test = GlobalScope.async { + val test = CoroutineScope(context = Dispatchers.IO).async { val s = Date() Timber.d(">>> start...") diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt index 63601a7f..1903b7c1 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt @@ -11,8 +11,8 @@ import com.github.mikephil.charting.data.LineDataSet import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup import io.realm.Realm +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.AggregationType @@ -163,7 +163,7 @@ class ProgressReportFragment : AbstractReportFragment() { graphContainer.hideWithAnimation() progressBar.showWithAnimation() - GlobalScope.launch { + CoroutineScope(context = Dispatchers.IO).launch { val s = Date() Timber.d(">>> start...") diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollFragment.kt index 7ca3d0a7..cada0af9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollFragment.kt @@ -9,8 +9,8 @@ import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import com.github.mikephil.charting.data.LineDataSet import io.realm.RealmResults +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch 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.util.extensions.sorted import timber.log.Timber -import java.util.* -import kotlin.collections.ArrayList interface BankrollRowRepresentable : RowRepresentable { var bankrollId: String? @@ -88,7 +86,7 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour if (requestCode == RequestCode.BANKROLL_DETAILS.value && resultCode == Activity.RESULT_OK) { val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) itemToDeleteId?.let { id -> - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { delay(300) deleteItem(dataListAdapter, bankrolls, id) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt index 210b26ba..5d6e2c9d 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt @@ -13,8 +13,8 @@ import com.github.mikephil.charting.data.BarDataSet import com.github.mikephil.charting.data.LineDataSet import com.google.android.material.tabs.TabLayout import io.realm.Realm +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.Calculator @@ -167,7 +167,7 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource this.model.computedResults?.let { computedResults -> - GlobalScope.launch { + CoroutineScope(context = Dispatchers.IO).launch { val startDate = Date() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt index 02591d28..c46d50f9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt @@ -11,7 +11,6 @@ import io.realm.Realm import io.realm.RealmModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.Calculator @@ -348,7 +347,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable binding.progressBar.showWithAnimation() binding.recyclerView.hideWithAnimation() - GlobalScope.launch { + CoroutineScope(context = Dispatchers.IO).launch { val realm = Realm.getDefaultInstance() realm.refresh() @@ -357,7 +356,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable realm.close() - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { displayData() } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/GridCalendarViewModel.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/GridCalendarViewModel.kt index a1f64175..fffb7d5e 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/GridCalendarViewModel.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/GridCalendarViewModel.kt @@ -12,7 +12,6 @@ import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.util.extensions.* import java.util.* -import kotlin.collections.HashMap class GridCalendarViewModel : ViewModel(), RowRepresentableDataSource { @@ -64,8 +63,8 @@ class GridCalendarViewModel : ViewModel(), RowRepresentableDataSource { while (tmpDate.time <= lastDate.time) { val result = groupedSessions[tmpDate]?.let { bucket -> - if (bucket.sumByDouble { - it.result?.net ?: 0.0 + if (bucket.sumOf { + it?.net ?: 0.0 } > 0.0) CellResult.POSITIVE else CellResult.NEGATIVE } ?: run { CellResult.EMPTY diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/DataManagerFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/DataManagerFragment.kt index a7c78bd8..4764c42a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/DataManagerFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/DataManagerFragment.kt @@ -16,6 +16,8 @@ import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.modules.datalist.DataListActivity import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel +import net.pokeranalytics.android.util.extensions.writeAsync +import timber.log.Timber open class DataManagerFragment : RealmFragment() { @@ -96,13 +98,26 @@ open class DataManagerFragment : RealmFragment() { val status = savable.getSaveValidityStatus(realm = this.getRealm()) when (status) { SaveValidityStatus.VALID -> { - this.getRealm().executeTransaction { - val managedItem = it.copyToRealmOrUpdate(this.model.item) - if (managedItem is Savable) { - val uniqueIdentifier = managedItem.id - finishActivityWithResult(uniqueIdentifier) - } + + val realm = getRealm() + val item = this.model.item + Timber.d("save") + 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() } else -> { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataFragment.kt index 002abb66..de66fefb 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataFragment.kt @@ -11,12 +11,13 @@ import io.realm.RealmModel import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.PAIllegalStateException 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.RowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.view.RowRepresentable 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 { @@ -67,16 +68,25 @@ open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegat } 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 { - (this.model.item as RowUpdatable).updateValue(value, row) + asyncRealm.copyToRealmOrUpdate(this.model.item) } catch (e: Exception) { CrashLogging.log("Exception caught: row = $row, value=$value, class=${this.javaClass}") throw e } - } - rowRepresentableAdapter.refreshRow(row) } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataFragment.kt index 7421d7f5..e2b5e00d 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataFragment.kt @@ -13,8 +13,8 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.pokeranalytics.android.R @@ -215,7 +215,7 @@ class PlayerDataFragment : EditableDataFragment(), StaticRowRepresentableDataSou when (row) { is Comment -> { if (row.isValidForDelete(getRealm())) { - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { delay(300) showAlertDialog(requireContext(), messageResId = R.string.are_you_sure_you_want_to_delete, showCancelButton = true, positiveAction = { playerModel.deleteComment(row) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataViewModel.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataViewModel.kt index 00dc11aa..f3fde4b2 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataViewModel.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataViewModel.kt @@ -16,6 +16,7 @@ import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.extensions.isSameDay import net.pokeranalytics.android.util.extensions.mediumDate +import net.pokeranalytics.android.util.extensions.writeAsync import java.util.* class PlayerDataViewModel : DataManagerViewModel(), StaticRowRepresentableDataSource { @@ -131,9 +132,11 @@ class PlayerDataViewModel : DataManagerViewModel(), StaticRowRepresentableDataSo */ fun cleanupComments() { // called when saving the custom field val realm = Realm.getDefaultInstance() - realm.executeTransaction { - this.commentsToDelete.forEach { // entries are out of realm - realm.where().equalTo("id", it.id).findFirst()?.deleteFromRealm() + + val ids = this.commentsToDelete.map { it.id } + realm.writeAsync { asyncRealm -> + ids.forEach { id -> // entries are out of realm + asyncRealm.where().equalTo("id", id).findFirst()?.deleteFromRealm() } } realm.close() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/TransactionDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/TransactionDataFragment.kt index a66f67bb..4b432857 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/TransactionDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/TransactionDataFragment.kt @@ -2,8 +2,8 @@ package net.pokeranalytics.android.ui.modules.data import android.content.Context import io.realm.kotlin.where +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager @@ -141,7 +141,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa when (val next = rows[index + 1]) { TransactionPropertiesRow.DATE, TransactionPropertiesRow.COMMENT -> {} else -> { - GlobalScope.launch(Dispatchers.Main) { + CoroutineScope(context = Dispatchers.Main).launch { delay(200) onRowSelected(0, next) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedFragment.kt index 20033333..0cb711d0 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedFragment.kt @@ -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.session.SessionActivity 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.URL import net.pokeranalytics.android.util.billing.AppGuard import net.pokeranalytics.android.util.billing.PurchaseListener import net.pokeranalytics.android.util.extensions.count +import net.pokeranalytics.android.util.extensions.findById +import net.pokeranalytics.android.util.extensions.writeAsync import java.util.* class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseListener { @@ -494,9 +495,14 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis * Delete selected transaction */ private fun deleteSelectedTransaction() { - getRealm().executeTransaction { - selectedTransaction?.deleteFromRealm() + + this.selectedTransaction?.id?.let { transactionId -> + getRealm().writeAsync { asyncRealm -> + val transaction = asyncRealm.findById(transactionId) + transaction?.deleteFromRealm() + } } + selectedTransactionPosition = -1 } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt index dd0630d2..00fb2e5d 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt @@ -9,8 +9,8 @@ import android.os.Build import android.os.Bundle import android.view.View import android.view.ViewAnimationUtils +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.pokeranalytics.android.databinding.ActivityNewDataBinding @@ -105,7 +105,8 @@ class NewDataMenuActivity : BaseActivity() { val intent = Intent() intent.putExtra(IntentKey.CHOICE.keyName, choice) setResult(RESULT_OK, intent) - GlobalScope.launch(Dispatchers.Main) { + + CoroutineScope(context = Dispatchers.Main).launch { delay(200) hideMenu() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsFragment.kt index 74c684d6..358795d8 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsFragment.kt @@ -240,11 +240,11 @@ open class FilterDetailsFragment : RealmFragment(), RowRepresentableDelegate { this.activityModel.selectedCategoryRow?.let { category -> - getRealm().executeTransaction { +// getRealm().executeTransaction { currentFilter?.remove(category) val validConditions = this.model.selectedRows.filter { it.queryCondition != null } currentFilter?.createOrUpdateFilterConditions(validConditions) - } +// } } currentFilter?.filterConditions?.forEach { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterHandler.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterHandler.kt index dadb2a24..eb13217c 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterHandler.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterHandler.kt @@ -4,11 +4,12 @@ import android.content.Context import android.content.Intent import androidx.fragment.app.Fragment import io.realm.Realm -import io.realm.kotlin.where import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.enumerations.IntIdentifiable import net.pokeranalytics.android.util.enumerations.IntSearchable +import net.pokeranalytics.android.util.extensions.findById +import net.pokeranalytics.android.util.extensions.writeAsync enum class FilterActivityRequestCode { SELECT_FILTER, @@ -45,10 +46,9 @@ interface FilterHandler { fun saveFilter(context: Context, filterId: String) { Preferences.setActiveFilterId(filterId, context) - val realm = Realm.getDefaultInstance() - realm.executeTransaction { executeRealm -> - currentFilter(context, executeRealm)?.let { + realm.writeAsync { asyncRealm -> + currentFilter(context, asyncRealm)?.let { it.useCount++ } } @@ -61,8 +61,9 @@ interface FilterHandler { } fun currentFilter(context: Context, realm: Realm): Filter? { - return Preferences.getActiveFilterId(context)?.let { - realm.where().equalTo("id", it).findFirst() + return Preferences.getActiveFilterId(context)?.let { id -> + realm.findById(id) +// realm.where().equalTo("id", it).findFirst() } ?: run { null } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersFragment.kt index a69109b0..93d372f2 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersFragment.kt @@ -15,12 +15,12 @@ import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter 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.view.RowRepresentable import net.pokeranalytics.android.ui.view.rows.FilterCategoryRow import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.extensions.sorted +import net.pokeranalytics.android.util.extensions.writeAsync open class FiltersFragment : RealmFragment(), RowRepresentableDelegate { @@ -218,16 +218,13 @@ open class FiltersFragment : RealmFragment(), RowRepresentableDelegate { * Validate the updates of the queryWith */ private fun validateUpdates() { - val currentFilter = this.model.currentFilter - - getRealm().executeTransaction { realm -> - currentFilter?.let { - it.name = it.query.getName(requireContext()) - realm.copyToRealmOrUpdate(it) - } - } - - val filterId = currentFilter?.id ?: "" + val filter = this.model.currentFilter + filter?.let { currentFilter -> + getRealm().writeAsync { asyncRealm -> + asyncRealm.copyToRealmOrUpdate(currentFilter) + } + } + val filterId = filter?.id ?: "" finishActivityWithResult(filterId) } @@ -239,9 +236,9 @@ open class FiltersFragment : RealmFragment(), RowRepresentableDelegate { val filterCopy = this.model.filterCopy val filterId = filterCopy?.id ?: "" - getRealm().executeTransaction { realm -> + getRealm().writeAsync { asyncRealm -> filterCopy?.let { - realm.copyToRealmOrUpdate(it) + asyncRealm.copyToRealmOrUpdate(it) } } finishActivityWithResult(filterId) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersListFragment.kt index 01050708..68261049 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersListFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersListFragment.kt @@ -3,11 +3,14 @@ package net.pokeranalytics.android.ui.modules.filter import android.app.Activity import android.content.Context import android.content.Intent +import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.realm.Filter 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.view.RowRepresentable import net.pokeranalytics.android.util.Preferences +import net.pokeranalytics.android.util.extensions.findById +import net.pokeranalytics.android.util.extensions.writeAsync open class FiltersListFragment : DataListFragment() { @@ -15,12 +18,19 @@ open class FiltersListFragment : DataListFragment() { override fun onRowValueChanged(value: Any?, row: RowRepresentable) { when (row) { is Filter -> { - getRealm().executeTransaction { - row.updateValue(value, row) - } - val index = this.model.items.indexOf(row) - this.dataListAdapter.notifyItemChanged(index) - updateFilterUIIfNecessary(requireContext(), row.id) + val filterId = row.id +// val filter = row.realm.copyFromRealm(row) +// row.updateValue(value, row) + getRealm().writeAsync ({ asyncRealm -> + asyncRealm.findById(filterId)?.let { filter -> + 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) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/editor/EditorFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/editor/EditorFragment.kt index 165e6ffd..0c87a345 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/editor/EditorFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/editor/EditorFragment.kt @@ -35,6 +35,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.util.CrashLogging import net.pokeranalytics.android.util.extensions.findById +import net.pokeranalytics.android.util.extensions.writeAsync import timber.log.Timber @@ -203,7 +204,8 @@ class EditorFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListen val playerId = data?.getStringExtra(BaseFragment.BundleKey.PRIMARY_KEY.value) ?: throw PAIllegalStateException("Primary key not set where as activity has finished") getRealm().findById(playerId)?.let { player -> - this.model.playerSelected(player) + val unmanagedPlayer = player.realm.copyFromRealm(player) + this.model.playerSelected(unmanagedPlayer) } ?: throw PAIllegalStateException("Player (id=$playerId) not found") this.editorAdapter.notifyDataSetChanged() } @@ -684,10 +686,9 @@ class EditorFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListen */ private fun deleteHand() { - getRealm().findById(this.model.handHistory.id)?.let { hh -> - getRealm().executeTransaction { - hh.deleteFromRealm() - } + getRealm().writeAsync { asyncRealm -> + val hh = asyncRealm.findById(this.model.handHistory.id) + hh?.deleteFromRealm() } this.activity?.finish() diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt index 11f7b6ff..270f5d27 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt @@ -5,10 +5,6 @@ import android.text.InputType import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel 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.exceptions.PAIllegalStateException 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.rows.CustomizableRowRepresentable 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.writeAsync import timber.log.Timber import java.text.DecimalFormat import java.text.ParseException -import kotlin.coroutines.CoroutineContext import kotlin.reflect.KClass enum class HHKeyboard { @@ -49,9 +44,6 @@ interface PlayerSetupCreationListener { class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer, ActionListListener { - private val coroutineContext: CoroutineContext - get() = Dispatchers.Main - /*** * The hand history */ @@ -641,33 +633,33 @@ class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer * Saves the current hand state in the database */ 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 - GlobalScope.launch(coroutineContext) { - val c = GlobalScope.async { - val realm = Realm.getDefaultInstance() - realm.executeTransaction { - realm.findById(hhId)?.defineWinnerPositions() - } - realm.close() - } - c.await() + realm.writeAsync { asyncRealm -> + this.handHistory.defineWinnerPositions() + asyncRealm.copyToRealmOrUpdate(this.handHistory) } - - } +// 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(hhId)?.defineWinnerPositions() +// } +// realm.close() +// } +// c.await() +// } +// +// } // Card Centralizer @@ -906,8 +898,8 @@ class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer fun changeStraddleSelection(positions: LinkedHashSet) { if (positions.isEmpty()) { - this.firstStraddlePosition = null - this.handSetup.clearStraddles() + this.firstStraddlePosition = null + this.handSetup.clearStraddles() } else { if (this.firstStraddlePosition == null) { @@ -1013,16 +1005,16 @@ class EditorViewModel : ViewModel(), RowRepresentableDataSource, CardCentralizer fun playerSelected(player: 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 } // Affects the player to the selected position this.tappedPlayerPositionIndex?.let { positionIndex -> - player.realm.executeTransaction { +// player.realm.executeTransaction { val ps = this.handHistory.playerSetupForPosition(positionIndex) ?: this.handHistory.createPlayerSetup(positionIndex) ps.player = player - } +// } } ?: 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] */ - fun deleteIfPossible(position: Int) { - when (val row = this.rowRepresentables[position]) { - is PlayerSetupRow -> { - val playerSetup = this.handHistory.playerSetupForPosition(row.positionIndex) ?: throw PAIllegalStateException("Attempt to delete an null object") - this.handHistory.playerSetups.remove(playerSetup) - - this.handHistory.realm?.let { - it.executeTransaction { - playerSetup.deleteFromRealm() - } - } - } - } - } +// fun deleteIfPossible(position: Int) { +// when (val row = this.rowRepresentables[position]) { +// is PlayerSetupRow -> { +// val playerSetup = this.handHistory.playerSetupForPosition(row.positionIndex) ?: throw PAIllegalStateException("Attempt to delete an null object") +// this.handHistory.playerSetups.remove(playerSetup) +// +// this.handHistory.realm?.let { +// it.executeTransaction { +// playerSetup.deleteFromRealm() +// } +// } +// } +// } +// } fun removePlayerSetup(positionIndex: Int) { val ps = this.handHistory.playerSetupForPosition(positionIndex) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt index b7625bc3..7c50312a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt @@ -13,7 +13,10 @@ import android.provider.MediaStore import androidx.core.content.FileProvider import com.arthenica.ffmpegkit.FFmpegKit 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.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.realm.handhistory.HandHistory @@ -70,8 +73,9 @@ class ReplayExportService : Service() { private fun startGIFExport() { - GlobalScope.launch(coroutineContext) { - val c = GlobalScope.async { + CoroutineScope(context = coroutineContext).launch { + + val c = async { val realm = Realm.getDefaultInstance() realm.refresh() @@ -148,8 +152,8 @@ class ReplayExportService : Service() { val start = Date().time - GlobalScope.launch(coroutineContext) { - val async = GlobalScope.async { + CoroutineScope(context = coroutineContext).launch { + val async = async { val realm = Realm.getDefaultInstance() val handHistory = realm.findById(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") @@ -287,8 +291,8 @@ class ReplayExportService : Service() { private fun startGIFExportPreQ() { - GlobalScope.launch(coroutineContext) { - val c = GlobalScope.async { + CoroutineScope(context = coroutineContext).launch { + val c = async { val realm = Realm.getDefaultInstance() realm.refresh() @@ -349,8 +353,8 @@ class ReplayExportService : Service() { private fun startFFMPEGVideoExportPreQ() { - GlobalScope.launch(coroutineContext) { - val async = GlobalScope.async { + CoroutineScope(context = coroutineContext).launch { + val async = async { val realm = Realm.getDefaultInstance() val handHistory = realm.findById(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId") diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt index 97b0a670..9750a4ed 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt @@ -13,7 +13,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch import net.pokeranalytics.android.R @@ -185,29 +185,31 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr if (sessionRealm != null) { if (this.model.duplicate) { // duplicate session - realm.executeTransaction { - val session = sessionRealm.duplicate() - currentSession = session + realm.writeAsync { asyncRealm -> + asyncRealm.findById(sessionId)?.duplicate()?.let { duplicate -> + currentSession = asyncRealm.copyFromRealm(duplicate) + } +// val session = sessionRealm.duplicate() +// currentSession = session } sessionHasBeenUserCustomized = false } else { // show existing session - currentSession = sessionRealm + currentSession = realm.copyFromRealm(sessionRealm) sessionHasBeenUserCustomized = true } } else { throw PAIllegalStateException("Session cannot be null here, session id = $sessionId") } } else { // create new session - realm.executeTransaction { executeRealm -> - currentSession = Session.newInstance(executeRealm, this.model.isTournament) - FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, null, requireContext()) - } + currentSession = Session.newInstance(realm, this.model.isTournament) + FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, null, requireContext(), realm) + // Find the nearest location around the user parentActivity?.findNearestLocation { it?.let { location -> - realm.executeTransaction { executeRealm -> - val realmLocation = executeRealm.findById(location.id) - FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, realmLocation, requireContext()) + realm.writeAsync { asyncRealm -> + val realmLocation = asyncRealm.findById(location.id) + FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, realmLocation, requireContext(), asyncRealm) currentSession.location = realmLocation } @@ -217,6 +219,10 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr sessionHasBeenUserCustomized = false } + + // load everything and have some SessionManager links it all + // + } override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) { @@ -259,9 +265,8 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr override fun onRowValueChanged(value: Any?, row: RowRepresentable) { this.sessionHasBeenUserCustomized = true try { - getRealm().executeTransaction { - this.currentSession.updateValue(value, row) - } + this.currentSession.updateValue(value, row, getRealm()) + this.writeChanges() } catch (e: PAIllegalStateException) { Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() return @@ -279,6 +284,12 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr } + private fun writeChanges() { + getRealm().writeAsync { asyncRealm -> + asyncRealm.copyToRealmOrUpdate(this.currentSession) + } + } + /** * Update the UI with the session data * Should be called after the initialization of the session @@ -393,11 +404,14 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr } currentSession.startOrContinue() + this.writeChanges() + binding.recyclerView.smoothScrollToPosition(0) } SessionState.STARTED -> { currentSession.pause() + this.writeChanges() } else -> { } @@ -405,6 +419,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr updateSessionUI() } + private fun computeOptimalDuration() { Timber.d("Start optimal duration finding attempt...") @@ -414,7 +429,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr var optimalDuration: Double? = null - val cr = GlobalScope.async { + val cr = CoroutineScope(context = Dispatchers.IO).async { optimalDuration = CashGameOptimalDurationCalculator.start(isLive) } cr.await() @@ -482,7 +497,11 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr val bankrollId = this.currentSession.bankroll?.id - this.currentSession.delete() + val id = currentSession.id + getRealm().writeAsync { asyncRealm -> + asyncRealm.findById(id)?.delete() + } + bankrollId?.let { BankrollReportManager.notifyBankrollReportImpact(bankrollId) } @@ -495,7 +514,11 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr override fun onBackPressed() { super.onBackPressed() if (!sessionHasBeenUserCustomized) { - currentSession.delete() + + val id = currentSession.id + getRealm().writeAsync { asyncRealm -> + asyncRealm.findById(id)?.delete() + } } } @@ -593,18 +616,18 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr mapOf( "bb" to session.cgBiggestBet, "fee" to session.tournamentEntryFee, - "ratedBuyin" to session.result?.buyin + "ratedBuyin" to session.buyin ) ) SessionPropertiesRow.BREAK_TIME -> row.editingDescriptors(mapOf()) SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> row.editingDescriptors( mapOf( - "defaultValue" to session.result?.cashout + "defaultValue" to session.cashout ) ) SessionPropertiesRow.NET_RESULT -> row.editingDescriptors( mapOf( - "defaultValue" to session.result?.netResult + "defaultValue" to session.netResult ) ) SessionPropertiesRow.COMMENT -> row.editingDescriptors( @@ -624,7 +647,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr ) SessionPropertiesRow.POSITION -> row.editingDescriptors( mapOf( - "defaultValue" to session.result?.tournamentFinalPosition + "defaultValue" to session.tournamentFinalPosition ) ) SessionPropertiesRow.HANDS_COUNT -> row.editingDescriptors( @@ -641,7 +664,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr mapOf( "sb" to session.cgBiggestBet?.round(), "bb" to session.cgBiggestBet?.round(), - "tips" to session.result?.tips + "tips" to session.tips ) ) is CustomField -> { @@ -663,16 +686,15 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr override fun resultCaptureTypeSelected(resultCaptureType: ResultCaptureType, applyBankroll: Boolean) { - getRealm().executeTransaction { // cleanup existing results - when (resultCaptureType) { - ResultCaptureType.NET_RESULT -> { - this.currentSession.clearBuyinCashedOut() - } - ResultCaptureType.BUYIN_CASHED_OUT -> { - this.currentSession.clearNetResult() - } + when (resultCaptureType) { + ResultCaptureType.NET_RESULT -> { + this.currentSession.clearBuyinCashedOut() + } + ResultCaptureType.BUYIN_CASHED_OUT -> { + this.currentSession.clearNetResult() } } + this.writeChanges() this.model.resultCaptureType = resultCaptureType if (applyBankroll) { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionViewModel.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionViewModel.kt index bb05602f..d897b826 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionViewModel.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionViewModel.kt @@ -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.SessionPropertiesRow import net.pokeranalytics.android.util.extensions.sorted -import java.util.ArrayList class SessionViewModel : ViewModel() { @@ -52,7 +51,7 @@ class SessionViewModel : ViewModel() { fun updatedRowRepresentationForCurrentState(session: Session, realm: Realm, context: Context) { val rows = ArrayList() - val result = session.result +// val result = session.result val currency = session.currency // Headers @@ -62,7 +61,7 @@ class SessionViewModel : ViewModel() { CustomizableRowRepresentable( RowViewType.HEADER_TITLE_AMOUNT_BIG, 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()) @@ -72,7 +71,7 @@ class SessionViewModel : ViewModel() { CustomizableRowRepresentable( RowViewType.HEADER_TITLE_AMOUNT_BIG, 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()) @@ -82,7 +81,7 @@ class SessionViewModel : ViewModel() { CustomizableRowRepresentable( RowViewType.HEADER_TITLE_AMOUNT_BIG, 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( diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/settings/DealtHandsPerHourFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/settings/DealtHandsPerHourFragment.kt index c73bbfac..cddf4053 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/settings/DealtHandsPerHourFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/settings/DealtHandsPerHourFragment.kt @@ -10,6 +10,7 @@ import net.pokeranalytics.android.databinding.FragmentDealtHandsConfigBinding import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.UserConfig import net.pokeranalytics.android.ui.fragment.components.RealmFragment +import net.pokeranalytics.android.util.extensions.writeAsync class DealtHandsPerHourFragment : RealmFragment() { @@ -57,19 +58,19 @@ class DealtHandsPerHourFragment : RealmFragment() { 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 -> userConfig.liveDealtHandsPerHour = liveDealtHandsPerHour } this.onlineValue.text.toString().toIntOrNull()?.let { onlineDealtHandsPerHour -> userConfig.onlineDealtHandsPerHour = onlineDealtHandsPerHour } - realm.copyToRealmOrUpdate(userConfig) + asyncRealm.copyToRealmOrUpdate(userConfig) // 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.estimatedHands = session.estimatedHands } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/settings/TransactionFilterFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/settings/TransactionFilterFragment.kt index 85f82ac2..04160fde 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/settings/TransactionFilterFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/settings/TransactionFilterFragment.kt @@ -16,6 +16,7 @@ import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType +import net.pokeranalytics.android.util.extensions.writeAsync class TransactionFilterFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { @@ -96,10 +97,13 @@ class TransactionFilterFragment : RealmFragment(), StaticRowRepresentableDataSou } private fun save() { - getRealm().executeTransaction { realm -> - val userConfig = UserConfig.getConfiguration(realm) - userConfig.setTransactionTypeIds(this.model.selectedTransactionTypes) - realm.copyToRealmOrUpdate(userConfig) + val ids = this.model.selectedTransactionTypes.map { it.id } + +// this.model.selectedTransactionTypes.joinToString(UUID_SEPARATOR) { it.id } + getRealm().writeAsync { asyncRealm -> + val userConfig = UserConfig.getConfiguration(asyncRealm) + userConfig.setTransactionTypeIds(ids.toSet()) + asyncRealm.copyToRealmOrUpdate(userConfig) } this.activity?.finish() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt index ef2eefd7..f2d6da3a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt @@ -511,9 +511,8 @@ enum class RowViewType(private var layoutRes: Int) : ViewIdentifier { if (row is Session) { itemView.findViewById(R.id.gameResult)?.let { gameResult -> - val result = row.result?.net ?: 0.0 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) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt index d3a0bcc8..e2af96e4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt @@ -147,10 +147,8 @@ class SessionRowView : FrameLayout { this.infoIcon.isVisible = false this.infoTitle.isVisible = false - session.result?.net?.let { netResult -> - val stat = ComputedStat(Stat.NET_RESULT, netResult, currency = session.currency) - this.gameResult.setTextFormat(stat.textFormat, context) - } + val stat = ComputedStat(Stat.NET_RESULT, session.net, currency = session.currency) + this.gameResult.setTextFormat(stat.textFormat, context) // val formattedStat = ComputedStat(Stat.NET_RESULT, result, currency = session.currency).format() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/BottomSheetViewModel.kt b/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/BottomSheetViewModel.kt index d8302698..f6ce2370 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/BottomSheetViewModel.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/BottomSheetViewModel.kt @@ -3,6 +3,7 @@ package net.pokeranalytics.android.ui.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.realm.RealmList +import io.realm.RealmObject import io.realm.RealmResults import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException @@ -211,17 +212,26 @@ class BottomSheetViewModel(var row: RowRepresentable) : ViewModel() { } return null } - BottomSheetType.MULTI_SELECTION -> this.selectedRows + BottomSheetType.MULTI_SELECTION -> this.selectedRowsRealmCopy() BottomSheetType.NUMERIC_TEXT -> this.doubleValue BottomSheetType.GRID -> this.defaultSize 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.CASH_GAME_STAKES -> Stakes(this.secondStringValue, this.ante) else -> null } } + private fun selectedRowsRealmCopy(): List<*> { + val realmObjects = this.selectedRows.filterIsInstance() + return if (realmObjects.isNotEmpty()) { + realmObjects.map { it.realm.copyFromRealm(it) } + } else { + this.selectedRows + } + } + fun isSelected(row: RowRepresentable): Boolean { return this.selectedRows.contains(row) } @@ -243,9 +253,17 @@ class BottomSheetViewModel(var row: RowRepresentable) : ViewModel() { } fun rowSelected(position: Int): RowRepresentable? { - return when(this.row.bottomSheetType) { - BottomSheetType.LIST -> this.realmData?.get(position) - BottomSheetType.LIST_STATIC -> this.staticRows[position] + when(this.row.bottomSheetType) { + BottomSheetType.LIST -> { + 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") } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt b/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt index c13ecc34..cd6685fd 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt @@ -8,6 +8,7 @@ import net.pokeranalytics.android.model.realm.Game import net.pokeranalytics.android.model.realm.Location import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.util.extensions.getOrCreate +import net.pokeranalytics.android.util.extensions.writeAsync import timber.log.Timber import java.util.* @@ -35,9 +36,9 @@ class FakeDataManager { val locations = realm.where().findAll() if (locations.size == 0) { - realm.executeTransaction { + realm.writeAsync { asyncRealm -> listOf("Bellagio", "Aria", "Borgata").map { - realm.getOrCreate(it) + asyncRealm.getOrCreate(it) } } } @@ -82,10 +83,8 @@ class FakeDataManager { session.tableSize = (2..10).random() val buyin = buyinList.random() - session.result?.let { result -> - result.buyin = buyinList.random() - result.cashout = resultsList.random() + buyin - } + session.buyin = buyinList.random() + session.cashout = resultsList.random() + buyin if (isTournament) { session.tournamentEntryFee = buyin diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt index aad81d12..d45c5de8 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -7,6 +7,7 @@ import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.ObjectIdentifier import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.util.extensions.findById +import net.pokeranalytics.android.util.extensions.writeAsync import org.apache.commons.csv.CSVRecord /** @@ -70,8 +71,8 @@ abstract class DataCSVDescriptor(source: DataSource, vararg el if (realm.isInTransaction) { this.deleteInsertedFromRealm(realm) } else { - realm.executeTransaction { - this.deleteInsertedFromRealm(realm) + realm.writeAsync { asyncRealm -> + this.deleteInsertedFromRealm(asyncRealm) } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt index 63adcb5a..65a1e263 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt @@ -26,7 +26,7 @@ abstract class PACSVDescriptor(source: DataSource, vararg elements: CSVField) : DataCSVDescriptor(source, *elements) { - var noSessionImport: Boolean = false + private var noSessionImport: Boolean = false init { val realm = Realm.getDefaultInstance() @@ -46,7 +46,7 @@ abstract class PACSVDescriptor(source: DataSource, protected fun parseSession(realm: Realm, record: CSVRecord, context: Context): Session? { val isTournament = isTournament ?: false - val session = Session.newInstance(realm, isTournament, managed = false) + val session = Session.newInstance(realm, isTournament) var startDate: Date? = null var endDate: Date? = null @@ -127,13 +127,13 @@ abstract class PACSVDescriptor(source: DataSource, } is SessionField.Buyin -> { val buyin = field.parse(value) - session.result?.buyin = buyin + session.buyin = buyin if (session.type == Session.Type.TOURNAMENT.ordinal) { session.tournamentEntryFee = buyin } else {} } - is SessionField.CashedOut -> session.result?.cashout = field.parse(value) - is SessionField.NetResult -> session.result?.netResult = field.parse(value) + is SessionField.CashedOut -> session.cashout = field.parse(value) + is SessionField.NetResult -> session.netResult = field.parse(value) is SessionField.SessionType -> { Session.Type.getValueFromString(value)?.let { type -> session.type = type.ordinal @@ -143,7 +143,7 @@ abstract class PACSVDescriptor(source: DataSource, is SessionField.NumberOfTables -> session.numberOfTables = field.parse(value) ?: 1 is SessionField.Addon -> 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.Break -> { field.parse(value)?.let { @@ -191,7 +191,7 @@ abstract class PACSVDescriptor(source: DataSource, session.cgAnte = field.parse(value) } is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value) - is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition = + is SessionField.TournamentPosition -> session.tournamentFinalPosition = field.parse(value) is SessionField.TournamentName -> { if (value.isNotEmpty()) { @@ -232,6 +232,7 @@ abstract class PACSVDescriptor(source: DataSource, Timber.d("N>> create: $number") val entry = realm.copyToRealm(CustomFieldEntry()) entry.numericValue = number + entry.customField = customField customField.entries.add(entry) session.customFieldEntries.add(entry) @@ -255,10 +256,10 @@ abstract class PACSVDescriptor(source: DataSource, val bankroll = Bankroll.getOrCreate(realm, bankrollName, isLive, currencyCode, currencyRate) session.bankroll = bankroll - session.result?.buyin?.let { - session.result?.buyin = it + additionalBuyins + session.buyin?.let { + session.buyin = it + additionalBuyins } - val net = session.result?.net + val net = session.net if (startDate != null && net != null) { // valid session // session already in realm, we'd love not put it in Realm before doing the check diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt index 4f617718..b4f59b1c 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -46,10 +46,10 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el is SessionField.SessionType -> Session.Type.values()[data.type].value is SessionField.Live -> field.format(data.isLive) is SessionField.NumberOfTables -> field.format(data.numberOfTables) - is SessionField.Buyin -> field.format(data.result?.buyin) - is SessionField.CashedOut -> field.format(data.result?.cashout) - is SessionField.NetResult -> field.format(data.result?.netResult) - is SessionField.Tips -> field.format(data.result?.tips) + is SessionField.Buyin -> field.format(data.buyin) + is SessionField.CashedOut -> field.format(data.cashout) + is SessionField.NetResult -> field.format(data.netResult) + is SessionField.Tips -> field.format(data.tips) is SessionField.HandsCount -> field.format(data.handsCount) is SessionField.LimitType -> { 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.TournamentEntryFee -> field.format(data.tournamentEntryFee) 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.NumberCustomField -> { val entry = data.customFieldEntries.find { it.customField?.id == field.customField.id } diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt index 21d1236e..e5327fe1 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt @@ -7,6 +7,7 @@ import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.UsageCountable import net.pokeranalytics.android.model.realm.* +import timber.log.Timber fun Realm.count(clazz: Class) : Long { return this.where(clazz).count() @@ -91,13 +92,43 @@ inline fun Realm.sorted(editableOnly: Boolean = false, 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...") + + this.executeTransactionAsync({ asyncRealm -> + handler(asyncRealm) + }, { // success + success() + }, { error -> // error + Timber.w("Realm write error: $error") + }) +} + + /** * Updates the useCount variable of the CountableUsage entity */ fun Realm.updateUsageCount(clazz: Class) { - val results = this.where(clazz).findAll() - this.executeTransaction { + this.writeAsync { asyncRealm -> + val results = asyncRealm.where(clazz).findAll() results.forEach { countableUsage -> val countable = (countableUsage as UsageCountable) @@ -106,7 +137,7 @@ fun Realm.updateUsageCount(clazz: Class) { TournamentFeature::class -> "tournamentFeatures.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 } }