Compare commits

...

26 Commits

  1. 4
      app/build.gradle
  2. 3
      app/src/androidTest/java/net/pokeranalytics/android/performanceTests/PerfsInstrumentedUnitTest.kt
  3. 4
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt
  4. 14
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  5. 2
      app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt
  6. 48
      app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt
  7. 8
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  8. 22
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt
  9. 12
      app/src/main/java/net/pokeranalytics/android/calculus/optimalduration/CashGameOptimalDurationCalculator.kt
  10. 1
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  11. 1
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  12. 27
      app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
  13. 2
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  14. 55
      app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt
  15. 9
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  16. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Comment.kt
  17. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  18. 26
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt
  19. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt
  20. 6
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  21. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt
  22. 9
      app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt
  23. 4
      app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt
  24. 91
      app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt
  25. 252
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  26. 27
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  27. 288
      app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt
  28. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt
  29. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentName.kt
  30. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  31. 14
      app/src/main/java/net/pokeranalytics/android/model/realm/UserConfig.kt
  32. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/HandHistory.kt
  33. 14
      app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt
  34. 412
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt
  35. 10
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  36. 6
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/BaseActivity.kt
  37. 13
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt
  38. 9
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt
  39. 5
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportCreationFragment.kt
  40. 23
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt
  41. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  42. 42
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt
  43. 5
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BaseFragment.kt
  44. 24
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt
  45. 36
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt
  46. 12
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetDataListFragment.kt
  47. 21
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetDataMultiSelectionFragment.kt
  48. 53
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt
  49. 9
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt
  50. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetStaticListFragment.kt
  51. 18
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt
  52. 28
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt
  53. 13
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt
  54. 3
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt
  55. 51
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt
  56. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/CustomFieldDataFragment.kt
  57. 55
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/DataManagerFragment.kt
  58. 21
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataFragment.kt
  59. 24
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/TransactionDataFragment.kt
  60. 6
      app/src/main/java/net/pokeranalytics/android/ui/modules/datalist/DataListFragment.kt
  61. 41
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedFragment.kt
  62. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedHandHistoryRowRepresentableAdapter.kt
  63. 10
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedSessionRowRepresentableAdapter.kt
  64. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedTransactionRowRepresentableAdapter.kt
  65. 7
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt
  66. 42
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsFragment.kt
  67. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsViewModel.kt
  68. 3
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterHandler.kt
  69. 30
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterViewModel.kt
  70. 28
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersFragment.kt
  71. 6
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersListFragment.kt
  72. 16
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/editor/EditorFragment.kt
  73. 87
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt
  74. 571
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt
  75. 191
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt
  76. 10
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionViewModel.kt
  77. 26
      app/src/main/java/net/pokeranalytics/android/ui/modules/settings/DealtHandsPerHourFragment.kt
  78. 14
      app/src/main/java/net/pokeranalytics/android/ui/modules/settings/TransactionFilterFragment.kt
  79. 25
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  80. 8
      app/src/main/java/net/pokeranalytics/android/ui/view/rows/StaticReport.kt
  81. 34
      app/src/main/java/net/pokeranalytics/android/ui/viewmodel/BottomSheetViewModel.kt
  82. 12
      app/src/main/java/net/pokeranalytics/android/ui/viewmodel/DataManagerViewModel.kt
  83. 4
      app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt
  84. 6
      app/src/main/java/net/pokeranalytics/android/util/Global.kt
  85. 9
      app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt
  86. 12
      app/src/main/java/net/pokeranalytics/android/util/ListExtensions.kt
  87. 2
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt
  88. 18
      app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt
  89. 4
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt
  90. 16
      app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt
  91. 3
      app/src/main/res/layout/activity_home.xml
  92. 4
      app/src/main/res/layout/fragment_editable_data.xml

@ -37,8 +37,8 @@ android {
applicationId "net.pokeranalytics.android"
minSdkVersion 23
targetSdkVersion 32
versionCode 146
versionName "6.0.3"
versionCode 147
versionName "6.0.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

@ -39,8 +39,7 @@ class PerfsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
Seed(app).execute(realm)
realm.commitTransaction()
FakeDataManager.createFakeSessions(5000) {success ->
FakeDataManager.createFakeSessions(5000) { success ->
if (success) {

@ -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")

@ -7,13 +7,15 @@ import com.google.firebase.FirebaseApp
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.where
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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.utils.Seed
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.util.CrashLogging
import net.pokeranalytics.android.util.FakeDataManager
import net.pokeranalytics.android.util.PokerAnalyticsLogs
@ -52,8 +54,7 @@ class PokerAnalyticsApplication : Application() {
Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(14)
.allowWritesOnUiThread(true)
.schemaVersion(15)
.migration(PokerAnalyticsMigration())
.initialData(Seed(this))
.build()
@ -82,15 +83,12 @@ class PokerAnalyticsApplication : Application() {
// Report
this.reportWhistleBlower = ReportWhistleBlower(this.applicationContext)
SessionSetManager.configure()
// Infos
val locale = Locale.getDefault()
CrashLogging.log("Country: ${locale.country}, language: ${locale.language}")
// Realm.getDefaultInstance().executeTransaction {
// it.delete(Performance::class.java)
// }
}
/**
@ -103,7 +101,7 @@ class PokerAnalyticsApplication : Application() {
realm.close()
if (sessionsCount < 10) {
GlobalScope.launch {
CoroutineScope(Dispatchers.Default).launch {
FakeDataManager.createFakeSessions(500)
}
}

@ -18,7 +18,7 @@ class CurrencyConverterApi {
companion object {
val json = Json { ignoreUnknownKeys = true }
private val json = Json { ignoreUnknownKeys = true }
fun currencyRate(fromCurrency: String, toCurrency: String, context: Context, callback: (Double?, VolleyError?) -> (Unit)) {

@ -9,13 +9,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.pokeranalytics.android.calculus.optimalduration.CashGameOptimalDurationCalculator
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveOnline
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.view.rows.StaticReport
import net.pokeranalytics.android.util.Global
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import timber.log.Timber
import kotlin.coroutines.CoroutineContext
interface NewPerformanceListener {
fun newBestPerformanceHandler()
@ -25,6 +25,7 @@ class ReportWhistleBlower(var context: Context) {
private var sessions: RealmResults<Session>? = null
private var results: RealmResults<Result>? = null
private var sessionSets: RealmResults<SessionSet>? = null
private var currentTask: ReportTask? = null
@ -32,7 +33,7 @@ class ReportWhistleBlower(var context: Context) {
private val listeners: MutableList<NewPerformanceListener> = mutableListOf()
private var paused: Boolean = false
private var paused: Boolean = true
private var timer: CountDownTimer? = null
@ -50,6 +51,11 @@ class ReportWhistleBlower(var context: Context) {
requestReportLaunch()
}
this.sessionSets = realm.where(SessionSet::class.java).findAll()
this.sessionSets?.addChangeListener { _ ->
requestReportLaunch()
}
realm.close()
}
@ -62,7 +68,7 @@ class ReportWhistleBlower(var context: Context) {
}
fun requestReportLaunch() {
Timber.d(">>> Launch report")
// Timber.d(">>> Launch report")
if (paused) {
return
@ -131,19 +137,20 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
private var cancelled = false
private val coroutineContext: CoroutineContext
get() = Dispatchers.Default
fun start() {
launchReports()
}
fun cancel() {
Timber.d("Reportwhistleblower task CANCEL")
this.cancelled = true
}
private fun launchReports() {
CoroutineScope(coroutineContext).launch {
Timber.d("====== Report whistleblower launch batch...")
CoroutineScope(Dispatchers.Default).launch {
val realm = Realm.getDefaultInstance()
@ -157,7 +164,8 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
// CustomField
val customFields = realm.where(CustomField::class.java)
.equalTo("type", CustomField.Type.LIST.uniqueIdentifier).findAll()
.equalTo("type", CustomField.Type.LIST.uniqueIdentifier)
.findAll()
for (customField in customFields) {
if (cancelled) {
break
@ -171,7 +179,7 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
private fun launchReport(realm: Realm, report: StaticReport) {
Timber.d(">>> launch report = $report")
// Timber.d(">>> launch report = $report")
when (report) {
StaticReport.OptimalDuration -> launchOptimalDuration(realm, report)
@ -200,9 +208,9 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
val nameSeparator = " "
for (stat in result.options.stats) {
for (stat in staticReport.performanceStats) {
Timber.d("analyse stat: $stat for report: $staticReport")
// Timber.d("analyse stat: $stat for report: $staticReport")
// Get current performance
var query = performancesQuery(realm, staticReport, stat)
@ -221,7 +229,8 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
val performanceQuery = computedResults.group.query
val performanceName = performanceQuery.getName(this.context, nameSeparator)
Timber.d("Best computed = $performanceName, ${computedResults.computedStat(Stat.NET_RESULT)?.value}")
val count = computedResults.computedStat(Stat.NUMBER_OF_GAMES)?.value?.toInt() ?: throw PAIllegalStateException("Number of games not found")
val notify = !(stat.hasEarlyVariance && count < 10)
var storePerf = true
currentPerf?.let {
@ -242,7 +251,10 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
currentPerf.objectId = performanceQuery.objectId
currentPerf.customFieldId = customField?.id
}
this.whistleBlower.notify(currentPerf)
if (notify) {
this.whistleBlower.notify(currentPerf)
}
}
}
@ -257,14 +269,16 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
null
)
realm.executeTransaction { it.copyToRealm(performance) }
this.whistleBlower.notify(performance)
if (notify) {
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 ")
// Timber.d("NO best computed value, current perf = $currentPerf ")
currentPerf?.let { perf ->
realm.executeTransaction {
Timber.d("Delete perf: stat = ${perf.stat}, report = ${perf.reportId}")
// Timber.d("Delete perf: stat = ${perf.stat}, report = ${perf.reportId}")
perf.deleteFromRealm()
}
}

@ -319,6 +319,14 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
}
}
val hasEarlyVariance: Boolean
get() {
return when (this) {
HOURLY_RATE, AVERAGE, AVERAGE_NET_BB, AVERAGE_BUYIN, AVERAGE_HOURLY_DURATION -> true
else -> false
}
}
private val hasProgressValues: Boolean
get() {
return when (this) {

@ -2,10 +2,7 @@ package net.pokeranalytics.android.calculus.bankroll
import io.realm.Realm
import io.realm.RealmResults
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Transaction
@ -15,9 +12,6 @@ import kotlin.coroutines.CoroutineContext
object BankrollReportManager {
private val coroutineContext: CoroutineContext
get() = Dispatchers.Main
private var reports: MutableMap<String?, BankrollReport> = mutableMapOf()
private var computableResults: RealmResults<ComputableResult>
@ -68,12 +62,12 @@ object BankrollReportManager {
}
// otherwise compute it
GlobalScope.launch(coroutineContext) {
CoroutineScope(Dispatchers.Main).launch {
var report: BankrollReport? = null
val coroutine = GlobalScope.async {
val s = Date()
Timber.d(">>>>> start computing bankroll...")
val coroutine = CoroutineScope(Dispatchers.Default).async {
// val s = Date()
// Timber.d(">>>>> start computing bankroll...")
val realm = Realm.getDefaultInstance()
@ -82,9 +76,9 @@ object BankrollReportManager {
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>>>> ended in $duration seconds")
// val e = Date()
// val duration = (e.time - s.time) / 1000.0
// Timber.d(">>>>> ended in $duration seconds")
}
coroutine.await()

@ -64,8 +64,8 @@ class CashGameOptimalDurationCalculator {
var end: Double? = null
var validBuckets = 0
val hkeys = sessionsByDuration.keys.map { it / 3600 / 1000.0 }.sorted()
Timber.d("Stop notif > keys: $hkeys ")
// val hkeys = sessionsByDuration.keys.map { it / 3600 / 1000.0 }.sorted()
// Timber.d("Stop notif > keys: $hkeys ")
for (key in sessionsByDuration.keys.sorted()) {
val sessionCount = sessionsByDuration[key]?.size ?: 0
if (start == null && sessionCount >= minimumValidityCount) {
@ -76,15 +76,15 @@ class CashGameOptimalDurationCalculator {
validBuckets++
}
}
Timber.d("Stop notif > validBuckets: $validBuckets ")
// Timber.d("Stop notif > validBuckets: $validBuckets ")
if (!(start != null && end != null && (end - start) >= intervalValidity)) {
Timber.d("Stop notif > invalid setup: $start / $end ")
// Timber.d("Stop notif > invalid setup: $start / $end ")
return null
}
// define if we have enough sessions
if (sessions.size < 50) {
Timber.d("Stop notif > not enough sessions: ${sessions.size} ")
// Timber.d("Stop notif > not enough sessions: ${sessions.size} ")
return null
}
@ -134,7 +134,7 @@ class CashGameOptimalDurationCalculator {
return bestDuration
}
Timber.d("Stop notif > not found, best duration: $bestDuration")
// Timber.d("Stop notif > not found, best duration: $bestDuration")
realm.close()
return null
}

@ -11,6 +11,7 @@ class ConfigurationException(message: String) : Exception(message)
class EnumIdentifierNotFoundException(message: String) : Exception(message)
class MisconfiguredSavableEnumException(message: String) : Exception(message)
class PAIllegalStateException(message: String) : Exception(message)
class PADataModelException(message: String) : Exception(message)
sealed class PokerAnalyticsException(message: String) : Exception(message) {
object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition")

@ -68,7 +68,6 @@ sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, Row
realm.findById(CustomField::class.java, this.customFieldId)?.entries?.forEach {
objects.add(QueryCondition.CustomFieldListQuery(it))
}
objects.sort()
realm.close()
return objects.map { Query(it) }
}

@ -8,7 +8,7 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.modules.data.*
import net.pokeranalytics.android.ui.modules.handhistory.HandHistoryActivity
import net.pokeranalytics.android.ui.view.Localizable
import net.pokeranalytics.android.util.extensions.findById
@ -30,7 +30,11 @@ enum class LiveData : Localizable {
PLAYER,
HAND_HISTORY;
var subType:Int? = null
var subType: Int? = null
fun instanceFromOrdinal(ordinal: Int): LiveData {
return values()[ordinal]
}
val relatedEntity: Class<out Deletable>
get() {
@ -52,10 +56,10 @@ enum class LiveData : Localizable {
fun updateOrCreate(realm: Realm, primaryKey: String?): Deletable {
val proxyItem: Deletable? = this.getData(realm, primaryKey)
proxyItem?.let {
return realm.copyFromRealm(it)
return proxyItem?.let {
realm.copyFromRealm(it)
} ?: run {
return this.newEntity()
this.newEntity()
}
}
@ -186,4 +190,17 @@ enum class LiveData : Localizable {
}
}
val dataFragment: EditableDataFragment
get() {
return when (this) {
BANKROLL -> BankrollDataFragment()
LOCATION -> LocationDataFragment()
TRANSACTION -> TransactionDataFragment()
CUSTOM_FIELD -> CustomFieldDataFragment()
TRANSACTION_TYPE -> TransactionTypeDataFragment()
PLAYER -> PlayerDataFragment()
else -> EditableDataFragment()
}
}
}

@ -723,7 +723,7 @@ sealed class QueryCondition : RowRepresentable {
constructor(customFieldEntry: CustomFieldEntry) : this() {
this.setObject(customFieldEntry)
this.customFieldId = customFieldEntry.customField?.id
this.customFieldId = customFieldEntry.managedCustomField?.id
?: throw PokerAnalyticsException.QueryValueMapUnexpectedValue
}

@ -68,10 +68,9 @@ class Patcher {
private fun patchMissingTransactionTypes(context: Context) {
val realm = Realm.getDefaultInstance()
val transactionTypes = TransactionType.Value.values()
realm.executeTransaction {
Seed.createDefaultTransactionTypes(transactionTypes, context, realm)
realm.executeTransactionAsync { asyncRealm ->
val transactionTypes = TransactionType.Value.values()
Seed.createDefaultTransactionTypes(transactionTypes, context, asyncRealm)
}
realm.close()
@ -80,11 +79,12 @@ class Patcher {
private fun patchBreaks() {
val realm = Realm.getDefaultInstance()
val sets = realm.where(SessionSet::class.java).findAll()
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsCash))
val results = realm.where(Result::class.java).findAll()
realm.executeTransactionAsync { asyncRealm ->
val sets = asyncRealm.where(SessionSet::class.java).findAll()
val sessions = Filter.queryOn<Session>(asyncRealm, Query(QueryCondition.IsCash))
val results = asyncRealm.where(Result::class.java).findAll()
realm.executeTransaction {
sets.forEach {
it.computeStats()
}
@ -98,13 +98,12 @@ class Patcher {
}
realm.close()
}
private fun patchDefaultTransactionTypes(context: Context) {
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
val tts = realm.where(TransactionType::class.java).findAll()
realm.executeTransactionAsync { 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.executeTransactionAsync { 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.executeTransactionAsync { 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.executeTransactionAsync { 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,9 +169,9 @@ 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.executeTransactionAsync { 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)
}
@ -187,8 +186,8 @@ class Patcher {
*/
private fun patchComputableResults() {
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
val crs = realm.where(ComputableResult::class.java).findAll()
realm.executeTransactionAsync { asyncRealm ->
val crs = asyncRealm.where(ComputableResult::class.java).findAll()
crs.forEach { cr ->
cr.session?.let { cr.updateWith(it) }
}
@ -210,8 +209,8 @@ class Patcher {
private fun patchZeroTable() {
val realm = Realm.getDefaultInstance()
val zero = 0
val sessions = realm.where<Session>().equalTo("numberOfTables", zero).findAll()
realm.executeTransaction {
realm.executeTransactionAsync { asyncRealm ->
val sessions = asyncRealm.where<Session>().equalTo("numberOfTables", zero).findAll()
sessions.forEach { s ->
s.numberOfTables = 1
}
@ -221,8 +220,8 @@ class Patcher {
private fun patchRatedAmounts() {
val realm = Realm.getDefaultInstance()
val transactions = realm.where<Transaction>().findAll()
realm.executeTransaction {
realm.executeTransactionAsync { asyncRealm ->
val transactions = asyncRealm.where<Transaction>().findAll()
transactions.forEach { t ->
t.computeRatedAmount()
}

@ -335,6 +335,15 @@ class PokerAnalyticsMigration : RealmMigration {
currentVersion++
}
// Migrate to version 15
if (currentVersion == 14) {
schema.get("Result")?.let { crs ->
crs.addField("id", String::class.java).setRequired("id", true)
crs.addPrimaryKey("id")
}
currentVersion++
}
}
override fun equals(other: Any?): Boolean {

@ -42,7 +42,7 @@ open class Comment : RealmObject(), Manageable, RowRepresentable, RowUpdatable {
}
override fun getDisplayName(context: Context): String {
return if (content.isNotEmpty()) content else NULL_TEXT
return content.ifEmpty { NULL_TEXT }
}
// override fun startEditing(dataSource: Any?, parent: Fragment?) {

@ -22,7 +22,7 @@ open class ComputableResult : RealmObject(), Filterable {
var session: Session? = null
var ratedTips: Double = 0.0
private var ratedTips: Double = 0.0
fun updateWith(session: Session) {
@ -38,8 +38,12 @@ open class ComputableResult : RealmObject(), Filterable {
this.bbNet = session.bbNet
this.hasBigBlind = if (session.cgBiggestBet != null) 1 else 0
this.estimatedHands = session.estimatedHands
this.bbPer100Hands =
session.bbNet / (session.numberOfHandsPerHour * session.hourlyDuration) * 100
this.bbPer100Hands = if (this.estimatedHands > 0.0) {
session.bbNet / this.estimatedHands * 100
} else {
0.0
}
}

@ -139,10 +139,6 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana
}
}
override fun isValidForSave(): Boolean {
return super.isValidForSave()
}
override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
return when (status) {
SaveValidityStatus.DATA_INVALID -> R.string.cf_empty_field_error
@ -250,7 +246,7 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana
fun cleanupEntries() { // called when saving the custom field
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
realm.executeTransactionAsync {
this.entriesToDelete.forEach { // entries are out of realm
realm.where<CustomFieldEntry>().equalTo("id", it.id).findFirst()?.deleteFromRealm()
}
@ -272,26 +268,6 @@ open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameMana
}
}
/**
* Clean the entries if the type is not a list & remove the deleted entries from realm
*/
// fun cleanEntries(realm: Realm) {
// realm.executeTransaction {
//
// if (!isListType) {
// entriesToDelete.addAll(entries)
// entries.clear()
// }
//
// // @TODO
// entriesToDelete.forEach {
// Timber.d("Delete entry: V=${it.value} N=${it.numericValue} / ID=${it.id}")
// realm.where<CustomFieldEntry>().equalTo("id", it.id).findFirst()?.deleteFromRealm()
// }
// entriesToDelete.clear()
// }
// }
/**
* Returns a comparison criteria based on this custom field
*/

@ -22,6 +22,7 @@ import net.pokeranalytics.android.ui.view.RowUpdatable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.toCurrency
import timber.log.Timber
import java.text.NumberFormat
import java.util.*
import java.util.Currency
@ -46,7 +47,7 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable, R
@LinkingObjects("entries")
val customFields: RealmResults<CustomField>? = null
val customField: CustomField?
val managedCustomField: CustomField?
get() {
return this.customFields?.first()
}
@ -89,7 +90,7 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable, R
}
override fun getDisplayName(context: Context): String {
return if (value.isNotEmpty()) value else NULL_TEXT
return value.ifEmpty { NULL_TEXT }
}
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
@ -136,8 +137,8 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable, R
/**
* Return the amount
*/
fun getFormattedValue(currency: Currency? = null): String {
return when (customField?.type) {
fun getFormattedValue(parentCustomField: CustomField, currency: Currency? = null): String {
return when (parentCustomField.type) {
CustomField.Type.AMOUNT.uniqueIdentifier -> {
numericValue?.toCurrency(currency) ?: run { NULL_TEXT }
}
@ -145,6 +146,7 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable, R
NumberFormat.getInstance().format(this.numericValue)
}
else -> {
Timber.d("FORMATTED = $value")
value
}
}

@ -77,7 +77,7 @@ open class Filter : RealmObject(), RowRepresentable, RowUpdatable, Deletable, Us
var filterConditions: RealmList<FilterCondition> = RealmList()
private set
private var filterableTypeUniqueIdentifier: Int? = null
var filterableTypeUniqueIdentifier: Int? = null
val filterableType: FilterableType
get() {
@ -109,7 +109,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 +118,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 {

@ -63,16 +63,6 @@ open class FilterCondition() : RealmObject() {
}
}
fun <T> getv(clazz: Class<T>) : T {
return when (clazz) {
Int::class -> intValue ?: 0
Double::class -> doubleValue?: 0.0
Date::class -> dateValue ?: Date()
String::class -> stringValue ?: ""
else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue
} as T
}
inline fun <reified T> getValue(): T {
return when (T::class) {
Int::class -> intValue ?: 0

@ -50,13 +50,6 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
@Ignore
override val ownerClass: Class<out RealmModel> = Session::class.java
fun getNotNullShortName() : String {
this.shortName?.let {
return it
}
return this.name
}
override fun getDisplayName(context: Context): String {
return this.name
}
@ -71,7 +64,7 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
SimpleRow.NAME -> this.name.ifEmpty { NULL_TEXT }
GamePropertiesRow.SHORT_NAME -> this.shortName ?: NULL_TEXT
else -> return super.charSequenceForRow(row, context, 0)
}

@ -83,7 +83,7 @@ open class Player : RealmObject(), NameManageable, Savable, Deletable, StaticRow
tag: Int
): CharSequence {
return when (row) {
PlayerPropertiesRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
PlayerPropertiesRow.NAME -> this.name.ifEmpty { NULL_TEXT }
else -> return super.charSequenceForRow(row, context, 0)
}
}
@ -174,7 +174,7 @@ open class Player : RealmObject(), NameManageable, Savable, Deletable, StaticRow
*/
fun cleanupComments() { // called when saving the custom field
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
realm.executeTransactionAsync {
this.commentsToDelete.forEach { // entries are out of realm
realm.where<Comment>().equalTo("id", it.id).findFirst()?.deleteFromRealm()
}

@ -3,18 +3,20 @@ package net.pokeranalytics.android.model.realm
import io.realm.RealmList
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.annotations.RealmClass
import net.pokeranalytics.android.exceptions.PADataModelException
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import java.util.*
@RealmClass
open class Result : RealmObject(), Filterable {
companion object {
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition>): String? {
fun fieldNameForQueryType(queryCondition: Class <out QueryCondition>): String? {
Session.fieldNameForQueryType(queryCondition)?.let {
return "sessions.$it"
}
@ -22,13 +24,17 @@ open class Result : RealmObject(), Filterable {
}
}
@PrimaryKey
var id = UUID.randomUUID().toString()
/**
* The buyin amount
*/
var buyin: Double? = null
set(value) {
field = value
this.computeNumberOfRebuy()
// this.computeNumberOfRebuy()
this.computeNet(true)
}
@ -39,9 +45,6 @@ open class Result : RealmObject(), Filterable {
set(value) {
field = value
this.computeNet(true)
if (value != null) {
this.session?.end()
}
}
/**
@ -49,20 +52,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()
}
}
/**
@ -75,10 +66,6 @@ open class Result : RealmObject(), Filterable {
* Tips
*/
var tips: Double? = null
set(value) {
field = value
this.session?.computeStats()
}
// The transactions associated with the Result, impacting the result
var transactions: RealmList<Transaction> = RealmList()
@ -91,21 +78,23 @@ open class Result : RealmObject(), Filterable {
var tournamentFinalPosition: Int? = null
// Number of rebuys
var numberOfRebuy: Double? = null
private var numberOfRebuy: Double? = null
@LinkingObjects("result")
private val sessions: RealmResults<Session>? = null
@Ignore
val session: Session? = this.sessions?.firstOrNull()
private val managedSession: Session
get() {
return this.sessions?.firstOrNull() ?: throw PADataModelException("Unmanaged Result")
}
/**
* Returns 1 if the session is positive
*/
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
return if (managedSession.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
}
@ -124,11 +113,9 @@ open class Result : RealmObject(), Filterable {
} else if (buyin != null || cashout != null) {
useBuyin = true
} else {
this.session?.let { session ->
if (session.isCashGame() && !session.isLive) {
useBuyin = false
}
}
if (this.managedSession.isCashGame() && !this.managedSession.isLive) {
useBuyin = false
}
}
}
@ -142,31 +129,29 @@ open class Result : RealmObject(), Filterable {
}
// Precompute results
this.session?.computeStats()
this.session?.sessionSet?.computeStats()
// this.managedSession.computeStats()
// this.managedSession.sessionSet?.computeStats()
}
// Computes the number of rebuy
fun computeNumberOfRebuy() {
this.session?.let {
if (it.isCashGame()) {
it.cgBiggestBet?.let { bb ->
if (bb > 0.0) {
this.numberOfRebuy = (this.buyin ?: 0.0) / (bb * 100.0)
} else {
this.numberOfRebuy = null
}
}
} else {
it.tournamentEntryFee?.let { entryFee ->
if (entryFee > 0.0) {
this.numberOfRebuy = (this.buyin ?: 0.0) / entryFee
} else {
this.numberOfRebuy = null
}
}
}
}
if (this.managedSession.isCashGame()) {
this.managedSession.cgBiggestBet?.let { bb ->
if (bb > 0.0) {
this.numberOfRebuy = (this.buyin ?: 0.0) / (bb * 100.0)
} else {
this.numberOfRebuy = null
}
}
} else {
this.managedSession.tournamentEntryFee?.let { entryFee ->
if (entryFee > 0.0) {
this.numberOfRebuy = (this.buyin ?: 0.0) / entryFee
} else {
this.numberOfRebuy = null
}
}
}
}
// @todo tips?

@ -67,24 +67,32 @@ 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
session.bankroll = realm.copyFromRealm(bankroll)
} else {
session.bankroll = realm.where<Bankroll>().findFirst()
realm.where<Bankroll>().findFirst()?.let { br ->
session.bankroll = realm.copyFromRealm(br)
}
}
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
realm.where(Game::class.java)
.equalTo("shortName", "HE").findFirst()?.let {
session.game = realm.copyFromRealm(it)
}
return session
// return if (managed) {
// realm.copyToRealm(session)
// } else {
// session
// }
}
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? {
@ -157,6 +165,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
@Ignore
val computableResult: ComputableResult? = this.computableResults?.firstOrNull()
// Timed interface
override var dayOfWeek: Int? = null
@ -204,12 +213,12 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
this.updateTimeParameter(field)
this.computeNetDuration()
// nullifies enddate when setting the start date after the end date
// nullifies the endDate when setting the start date after the end date
if (value != null && this.endDate != null && value.after(this.endDate)) {
this.endDate = null
}
this.dateChanged()
this.computeStats()
// this.computeStats()
}
/**
@ -230,7 +239,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
this.computeNetDuration()
this.dateChanged()
this.defineDefaultTournamentBuyinIfNecessary()
this.computeStats()
// this.computeStats()
}
/**
@ -240,7 +249,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
set(value) {
field = value
this.computeNetDuration()
this.computeStats()
// this.computeStats()
}
/**
@ -252,10 +261,6 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* The start date of the break
*/
override var pauseDate: Date? = null
set(value) {
field = value
// this.updateRowRepresentation()
}
// The session set containing the sessions, which can contain multiple endedSessions
var sessionSet: SessionSet? = null
@ -268,7 +273,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
set(value) {
field = value
this.generateStakes()
this.computeStats()
// this.computeStats()
// this.updateRowRepresentation()
}
@ -296,7 +301,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
set(value) {
if (value > 0) {
field = value
this.computeStats()
// this.computeStats()
}
}
@ -314,16 +319,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.result?.computeNumberOfRebuy()
}
// var blinds: String? = null
@ -334,8 +336,8 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
field = value
this.generateStakes()
this.defineHighestBet()
this.computeStats()
this.result?.computeNumberOfRebuy()
// this.computeStats()
// this.result?.computeNumberOfRebuy()
}
var cgBlinds: String? = null
@ -343,8 +345,8 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
field = cleanupBlinds(value)
this.generateStakes()
this.defineHighestBet()
this.computeStats()
this.result?.computeNumberOfRebuy()
// this.computeStats()
// this.result?.computeNumberOfRebuy()
}
var cgBiggestBet: Double? = null
@ -355,10 +357,6 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
// The entry fee of the tournament
var tournamentEntryFee: Double? = null
set(value) {
field = value
this.result?.computeNumberOfRebuy()
}
// The total number of players who participated in the tournament
var tournamentNumberOfPlayers: Int? = null
@ -377,28 +375,29 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
// The number of hands played during the sessions
var handsCount: Int? = null
set(value) {
field = value
this.computeStats()
}
fun bankrollHasBeenUpdated() {
this.generateStakes()
}
/**
* Manages impacts on SessionSets
* 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)
}
// this.updateRowRepresentation()
SessionSetManager.sessionDateChanged(this)
}
// /**
// * Manages impacts on SessionSets
// * Should be called when the start / end date are changed
// */
// private fun dateChanged() {
// SessionSetManager.updatedSession(this)
//// if (this.endDate != null) {
//// SessionSetManager.updateTimeline(this)
//// } else if (this.sessionSet != null) {
//// SessionSetManager.removeFromTimeline(this)
//// }
//// this.updateRowRepresentation()
// }
/**
* Returns a non-null date for the session
*/
@ -436,7 +435,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
get() {
val bb = this.cgBiggestBet
val result = this.result
return if (bb != null && result != null) {
return if (bb != null && bb > 0.0 && result != null) {
result.net / bb
} else {
0.0
@ -471,6 +470,11 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
return this.result?.net ?: 0.0
}
fun preCompute() {
this.computeStats()
this.result?.computeNumberOfRebuy()
}
/**
* Pre-compute various statIds
*/
@ -493,16 +497,21 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
}
}
this.sessionSet?.computeStats()
}
/**
* Approximates the number of hands played per hour at the table
*/
val numberOfHandsPerHour: Double
private 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
var playerHandsPerHour = 0
UserConfig.getConfiguration(this.realm) { config ->
playerHandsPerHour = if (this.isLive) config.liveDealtHandsPerHour else config.onlineDealtHandsPerHour
}
return this.numberOfTables * playerHandsPerHour / tableSize.toDouble()
}
@ -557,7 +566,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* Start or continue a session
*/
fun startOrContinue() {
realm.executeTransaction {
// realm.executeTransaction {
when (val state = getState()) {
SessionState.PENDING, SessionState.PLANNED -> {
this.startDate = Date()
@ -576,7 +585,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
throw PAIllegalStateException("unmanaged session state: $state")
}
}
}
// }
}
private fun defineDefaultTournamentBuyinIfNecessary() {
@ -589,28 +598,28 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* Pause a session
*/
fun pause() {
realm.executeTransaction {
// realm.executeTransaction {
when (val state = getState()) {
SessionState.STARTED -> {
this.pauseDate = Date()
}
else -> throw PAIllegalStateException("Pausing a session in an unmanaged state: $state")
}
}
// }
}
/**
* Stop a session
*/
fun stop(context: Context) {
realm.executeTransaction {
// realm.executeTransaction {
when (val state = getState()) {
SessionState.STARTED, SessionState.PAUSED -> {
this.end()
}
else -> throw Exception("Stopping session in unmanaged state: $state")
}
}
// }
cancelStopNotification(context)
}
@ -618,12 +627,12 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
* Restart a session
*/
fun restart() {
realm.executeTransaction {
// realm.executeTransaction {
this.pauseDate = null
this.startDate = Date()
this.endDate = null
this.breakDuration = 0L
}
// }
}
/**
@ -660,7 +669,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
/**
* Return the game titleResId
* Example: NL Holdem
* Example: NL Hold'em
*/
fun getFormattedGame(): String {
var gameTitle = ""
@ -672,46 +681,13 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
if (game != null) {
gameTitle += game?.name
}
return if (gameTitle.isNotBlank()) gameTitle else NULL_TEXT
return gameTitle.ifBlank { NULL_TEXT }
}
fun getFormattedStakes(): String {
return this.cgStakes?.let { StakesHolder.readableStakes(it) } ?: run { NULL_TEXT }
//
// val formattedBlinds = StakesHolder.formattedBlinds(this.cgBlinds, this.currency)
// val formattedAntes = StakesHolder.formattedAnte(this.cgAnte, this.currency)
//
// return StakesHolder.formattedStakes(formattedBlinds, formattedAntes)
//
//
// val components = arrayListOf<String>()
// this.formattedBlinds?.let { components.add(it) }
// this.formattedAnte?.let { components.add("($it)") }
//
// return if (components.isNotEmpty()) {
// components.joinToString(" ")
// } else {
// NULL_TEXT
// }
}
// fun formatBlinds() {
// blinds = null
// if (cgBigBlind == null) return
// cgBigBlind?.let { bb ->
// val sb = cgSmallBlind ?: bb / 2.0
// val preFormattedBlinds = "${sb.formatted}/${bb.round()}"
// println("<<<<<< bb.toCurrency(currency) : ${bb.toCurrency(currency)}")
// println("<<<<<< preFormattedBlinds : $preFormattedBlinds")
// val regex = Regex("-?\\d+(\\.\\d+)?")
// blinds = bb.toCurrency(currency).replace(regex, preFormattedBlinds)
// println("<<<<<< blinds = $blinds")
// }
// }
// LifeCycle
/**
@ -721,10 +697,10 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
CrashLogging.log("Deletes session. Id = ${this.id}")
if (isValid) {
realm.executeTransaction {
// realm.executeTransaction {
cleanup()
deleteFromRealm()
}
// }
} else {
CrashLogging.log("Attempt to delete an invalid session")
}
@ -741,7 +717,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
}
// cleanup unnecessary related objects
this.result?.deleteFromRealm()
this.computableResults?.deleteAllFromRealm()
this.computableResult?.deleteFromRealm()
}
@ -776,32 +752,12 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
when (row) {
SessionPropertiesRow.BANKROLL -> bankroll = value as Bankroll?
SessionPropertiesRow.STAKES -> if (value is Stakes) {
if (value.ante != null) {
this.cgAnte = value.ante
}
if (value.blinds != null) {
this.cgBlinds = value.blinds
}
// cgSmallBlind = try {
// (value[0] as String? ?: "0").toDouble()
// } catch (e: Exception) {
// null
// }
//
// cgBigBlind = try {
// (value[1] as String? ?: "0").toDouble()
// } catch (e: Exception) {
// null
// }
//
// cgBigBlind?.let {
// if (cgSmallBlind == null || cgSmallBlind == 0.0) {
// cgSmallBlind = it / 2.0
// }
// }
} else if (value == null) {
this.cgBlinds = null
this.cgAnte = null
@ -812,15 +768,20 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
SessionPropertiesRow.BUY_IN -> {
val localResult = getOrCreateResult()
localResult.buyin = value as Double?
// this.updateRowRepresentation()
}
SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE -> {
val localResult = getOrCreateResult()
localResult.cashout = value as Double?
if (value != null) {
this.end()
}
}
SessionPropertiesRow.NET_RESULT -> {
val localResult = getOrCreateResult()
localResult.netResult = value as Double?
if (value != null) {
this.end()
}
}
SessionPropertiesRow.COMMENT -> comment = value as String? ?: ""
SessionPropertiesRow.END_DATE -> if (value is Date?) {
@ -876,20 +837,23 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
SessionPropertiesRow.TOURNAMENT_NAME -> tournamentName = value as TournamentName?
SessionPropertiesRow.TOURNAMENT_TYPE -> tournamentType = (value as TournamentType?)?.ordinal
SessionPropertiesRow.TOURNAMENT_FEATURE -> {
this.tournamentFeatures.clear()
value?.let {
tournamentFeatures = RealmList()
tournamentFeatures.addAll((it as ArrayList<TournamentFeature>))
} ?: run {
tournamentFeatures.removeAll(this.tournamentFeatures)
tournamentFeatures.addAll((it as List<TournamentFeature>))
}
}
SessionPropertiesRow.HANDS_COUNT -> handsCount = (value as Double?)?.toInt()
SessionPropertiesRow.NUMBER_OF_TABLES -> this.numberOfTables = (value as Double?)?.toInt() ?: 1
is CustomField -> {
customFieldEntries.filter { it.customField?.id == row.id }.let {
customFieldEntries.removeAll(it)
}
val entryIds = row.entries.map { it.id }
val entries = this.customFieldEntries.intersectBy(entryIds) { it.id }
this.customFieldEntries.removeAll(entries)
// customFieldEntries.filter { it.customField?.id == row.id }.let {
// customFieldEntries.removeAll(it.toSet())
// }
when (row.type) {
CustomField.Type.AMOUNT.uniqueIdentifier,
CustomField.Type.NUMBER.uniqueIdentifier -> {
@ -914,10 +878,11 @@ 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
}
val result = Result()
// result.inverseSession = WeakReference(this)
this.result = result
result
}
}
// Stat Entry
@ -1028,7 +993,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
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.COMMENT -> if (this.comment.isNotEmpty()) this.comment else NULL_TEXT
SessionPropertiesRow.COMMENT -> this.comment.ifEmpty { NULL_TEXT }
SessionPropertiesRow.END_DATE -> this.endDate?.shortDateTime() ?: NULL_TEXT
SessionPropertiesRow.GAME -> getFormattedGame()
SessionPropertiesRow.INITIAL_BUY_IN -> tournamentEntryFee?.toCurrency(currency) ?: NULL_TEXT
@ -1063,8 +1028,10 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
SessionPropertiesRow.HANDS_COUNT -> this.handsCountFormatted(context)
SessionPropertiesRow.NUMBER_OF_TABLES -> this.numberOfTables.toString()
is CustomField -> {
customFieldEntries.find { it.customField?.id == row.id }?.let { customFieldEntry ->
return customFieldEntry.getFormattedValue(currency)
val entryIds = this.customFieldEntries.map { it.id }
val entries = row.entries.intersectBy(entryIds) { it.id }
entries.firstOrNull()?.let { customFieldEntry ->
return customFieldEntry.getFormattedValue(row, currency)
}
return NULL_TEXT
}
@ -1086,33 +1053,6 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
this.result?.netResult = null
}
/// Stakes
// fun generateStakes() {
//
// if (this.cgAnte == null && this.cgAnte == null) {
// this.cgStakes = null
// return
// }
//
// val components = arrayListOf<String>()
//
// this.cgBlinds?.let { components.add("${cbBlinds}${it}") }
// this.cgAnte?.let { components.add("${cbAnte}${it.formatted}") }
//
// val code = this.bankroll?.currency?.code ?: UserDefaults.currency.currencyCode
// components.add("${cbCode}${code}")
//
// this.cgStakes = components.joinToString(cbSeparator)
// }
//
// fun defineHighestBet() {
// val bets = arrayListOf<Double>()
// this.cgAnte?.let { bets.add(it) }
// bets.addAll(this.blindValues)
// this.cgBiggestBet = bets.maxOrNull()
// }
private fun cleanupBlinds(blinds: String?): String? {
if (blinds == null) {

@ -15,11 +15,12 @@ import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import kotlin.math.min
import java.text.DateFormat
import java.util.*
import kotlin.math.max
open class SessionSet() : RealmObject(), Timed, Filterable {
open class SessionSet : RealmObject(), Timed, Filterable {
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -64,7 +65,7 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
this.ratedNet = this.sessions?.sumOf { it.computableResult?.ratedNet ?: 0.0 } ?: 0.0
this.estimatedHands = this.sessions?.sumOf { it.estimatedHands } ?: 0.0
this.bbNet = this.sessions?.sumOf { it.bbNet } ?: 0.0
this.breakDuration = this.sessions?.max("breakDuration")?.toLong() ?: 0L
updateBreakDuration()
}
/**
@ -75,7 +76,7 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
var ratedNet: Double = 0.0
val hourlyRate: Double
private val hourlyRate: Double
get() {
return this.ratedNet / this.hourlyDuration
}
@ -84,7 +85,7 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
var bbNet: BB = 0.0
val bbHourlyRate: BB
private val bbHourlyRate: BB
get() {
return this.bbNet / this.hourlyDuration
}
@ -142,5 +143,21 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
@Ignore
override val realmObjectClass: Class<out Identifiable> = SessionSet::class.java
private fun updateBreakDuration() {
var longestNetDuration = 0L
var maxBreakDuration = 0L
this.sessions?.let { sessions ->
for (session in sessions) {
longestNetDuration = max(longestNetDuration, session.netDuration)
maxBreakDuration = max(session.breakDuration, maxBreakDuration)
}
}
val maxSetBreak = endDate.time - startDate.time - longestNetDuration
this.breakDuration = min(maxBreakDuration, maxSetBreak)
}
}

@ -1,288 +0,0 @@
//package net.pokeranalytics.android.model.realm
//
//import io.realm.RealmObject
//import io.realm.RealmQuery
//import io.realm.RealmResults
//import io.realm.annotations.Ignore
//import io.realm.annotations.LinkingObjects
//import net.pokeranalytics.android.exceptions.ModelException
//import timber.log.Timber
//import java.util.*
//
//open class TimeFrame : RealmObject() {
//
// // A start date
// var startDate: Date = Date()
// private set(value) {
// field = value
// this.computeNetDuration()
// }
//
// // An end date
// var endDate: Date? = null
// private set(value) {
// field = value
// this.computeNetDuration()
// }
//
// // The latest pause date
// var pauseDate: Date? = null
// set(value) {
// field?.let {
// if (value == null && field != null) {
// breakDuration += Date().time - it.time
// }
// }
// field = value
// this.computeNetDuration()
// }
//
// // The break netDuration
// var breakDuration: Long = 0L
// set(value) {
// field = value
// this.computeNetDuration()
// }
//
// // the total netDuration
// var netDuration: Long = 0L
// private set
//
// var hourlyDuration: Double = 0.0
// get() {
// return this.netDuration / 3600000.0 // 3.6 millions of milliseconds
// }
//
// // Session
// @LinkingObjects("timeFrame")
// private val endedSessions: RealmResults<Session>? = null // we should have only one session
//
// @Ignore
// var session: Session? = null
// get() = if (this.endedSessions != null && this.endedSessions.isEmpty()) null else this.endedSessions?.first()
//
// // Group
// @LinkingObjects("timeFrame")
// private val sets: RealmResults<SessionSet>? = null // we should have only one sessionGroup
//
// @Ignore
// var set: SessionSet? = null
// get() = this.sets?.first()
//
// fun setStart(startDate: Date) {
// this.startDate = startDate
// this.session?.let {
// this.notifySessionDateChange(it)
// }
// }
//
// fun setEnd(endDate: Date?) {
// this.endDate = endDate
// this.session?.let {
// this.notifySessionDateChange(it)
// }
// }
//
// fun setDate(startDate: Date, endDate: Date?) {
// this.startDate = startDate
// this.endDate = endDate
//
// this.session?.let {
// this.notifySessionDateChange(it)
// }
// }
//
// /**
// * Computes the net netDuration of the session
// */
// private fun computeNetDuration() {
// var endDate: Date = this.endDate ?: Date()
// this.netDuration = endDate.time - this.startDate.time - this.breakDuration
// }
//
// /**
// * Queries all time frames that might be impacted by the date change
// * Makes all necessary changes to keep sequential time frames
// */
// fun notifySessionDateChange(owner: Session) {
//
// var query: RealmQuery<SessionSet> = this.realm.where(SessionSet::class.java)
// query.isNotNull("timeFrame")
//
//// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}")
//
// val sets = realm.where(SessionSet::class.java).findAll()
//// Timber.d("set count = ${sets.size}")
//
// if (this.endDate == null) {
// query.greaterThanOrEqualTo("timeFrame.startDate", this.startDate)
// .or()
// .greaterThanOrEqualTo("timeFrame.endDate", this.startDate)
// .or()
// .isNull("timeFrame.endDate")
// } else {
// val endDate = this.endDate!!
// query
// .lessThanOrEqualTo("timeFrame.startDate", this.startDate)
// .greaterThanOrEqualTo("timeFrame.endDate", this.startDate)
// .or()
// .lessThanOrEqualTo("timeFrame.startDate", endDate)
// .greaterThanOrEqualTo("timeFrame.endDate", endDate)
// .or()
// .greaterThanOrEqualTo("timeFrame.startDate", this.startDate)
// .lessThanOrEqualTo("timeFrame.endDate", endDate)
// .or()
// .isNull("timeFrame.endDate")
// .lessThanOrEqualTo("timeFrame.startDate", endDate)
// }
//
// val sessionGroups = query.findAll()
//
// this.updateTimeFrames(sessionGroups, owner)
//
// }
//
// /**
// * Update Time frames from sets
// */
// private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, owner: Session) {
//
// when (sessionSets.size) {
// 0 -> this.createOrUpdateSessionSet(owner)
// 1 -> this.updateSessionGroup(owner, sessionSets.first()!!)
// else -> this.mergeSessionGroups(owner, sessionSets)
// }
//
// }
//
// /**
// * Creates the session sessionGroup when the session has none
// */
// private fun createOrUpdateSessionSet(owner: Session) {
//
// val set = owner.sessionSet
// if (set != null) {
// set.timeFrame?.startDate = this.startDate
// set.timeFrame?.endDate = this.endDate
// } else {
// this.createSessionSet(owner)
// }
//
//// Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}")
// Timber.d("netDuration 1 = : ${set?.timeFrame?.netDuration}")
//
// }
//
// fun createSessionSet(owner: Session) {
// val set: SessionSet = SessionSet.newInstanceForResult(this.realm)
// set.timeFrame?.let {
// it.startDate = this.startDate
// it.endDate = this.endDate
// } ?: run {
// throw ModelException("TimeFrame should never be null here")
// }
//
// owner.sessionSet = set
// }
//
//
// /**
// * Single SessionSet update, the session might be the owner
// * Changes the sessionGroup timeframe using the current timeframe dates
// */
// private fun updateSessionGroup(owner: Session, sessionSet: SessionSet) {
//
// var timeFrame: TimeFrame = sessionSet.timeFrame!! // tested in the query
//// timeFrame.setDate(this.startDate, this.endDate)
//
// val sisterSessions = sessionSet.endedSessions!! // shouldn't crash ever
//
// // if we have only one session in the set and that it corresponds to the set
// if (sessionSet.endedSessions?.size == 1 && sessionSet.endedSessions?.first() == owner) {
// timeFrame.setDate(this.startDate, this.endDate)
// } else { // there are 2+ endedSessions to manage and possible splits
//
// val endDate = this.endDate
//
// // case where all endedSessions are over but the set is not, we might have a split, so we delete the set and save everything again
// if (endDate != null && sisterSessions.all { it.timeFrame?.endDate != null } && timeFrame.endDate == null) {
// var endedSessions = mutableListOf<Session>(owner)
// sessionSet.endedSessions?.forEach { endedSessions.add(it) }
// sessionSet.deleteFromRealm()
// endedSessions.forEach { it.timeFrame?.notifySessionDateChange(it) }
// } else {
//
// if (this.startDate.before(timeFrame.startDate)) {
// timeFrame.startDate = this.startDate
// }
// if (endDate != null && timeFrame.endDate != null && endDate.after(timeFrame.endDate)) {
// timeFrame.endDate = endDate
// } else if (endDate == null) {
// timeFrame.endDate = null
// }
//
// owner.sessionSet = sessionSet
//
//// Timber.d("sd = : ${sessionSet.timeFrame?.startDate}, ed = ${sessionSet.timeFrame?.endDate}")
// Timber.d("netDuration 2 = : ${sessionSet.timeFrame?.netDuration}")
// }
//
// }
//
// }
//
// /**
// * Multiple session sets update:
// * Merges all sets into one (delete all then create a new one)
// */
// private fun mergeSessionGroups(owner: Session, sessionSets: RealmResults<SessionSet>) {
//
// var startDate: Date = this.startDate
// var endDate: Date? = this.endDate
//
// // find earlier and later dates from all sets
// val timeFrames = sessionSets.mapNotNull { it.timeFrame }
// timeFrames.forEach { tf ->
// if (tf.startDate.before(startDate)) {
// startDate = tf.startDate
// }
//
// endDate?.let { ed ->
// tf.endDate?.let { tfed ->
// if (tfed.after(ed)) {
// endDate = tfed
// }
// }
// } ?: run {
// endDate = tf.endDate
// }
//
// }
//
// // get all endedSessions from sets
// var endedSessions = mutableSetOf<Session>()
// sessionSets.forEach { set ->
// set.endedSessions?.asIterable()?.let { endedSessions.addAll(it) }
// }
//
// // delete all sets
// sessionSets.deleteAllFromRealm()
//
// // Create a new sets
// val set: SessionSet = SessionSet.newInstanceForResult(this.realm)
// set.timeFrame?.let {
// it.setDate(startDate, endDate)
// } ?: run {
// throw ModelException("TimeFrame should never be null here")
// }
//
// // Add the session linked to this timeframe to the new sessionGroup
// owner.sessionSet = set
//
// // Add all orphan endedSessions
// endedSessions.forEach { it.sessionSet = set }
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}")
//
// }
//
//}

@ -62,7 +62,7 @@ open class TournamentFeature : RealmObject(), RowRepresentable, RowUpdatable, Na
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
SimpleRow.NAME -> this.name.ifEmpty { NULL_TEXT }
else -> return super.charSequenceForRow(row, context, 0)
}
}

@ -61,7 +61,7 @@ open class TournamentName : RealmObject(), NameManageable, StaticRowRepresentabl
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
SimpleRow.NAME -> this.name.ifEmpty { NULL_TEXT }
else -> return super.charSequenceForRow(row, context,0)
}
}

@ -79,7 +79,7 @@ open class Transaction : RealmObject(), RowRepresentable, RowUpdatable, Manageab
}
// The amount of the transaction
var ratedAmount: Double = 0.0
private var ratedAmount: Double = 0.0
// The date of the transaction
override var date: Date = Date()

@ -11,7 +11,19 @@ open class UserConfig : RealmObject() {
companion object {
fun getConfiguration(realm: Realm): UserConfig {
fun getConfiguration(realm: Realm?, handler: (UserConfig) -> (Unit)) {
if (realm != null) {
handler(userConfiguration(realm))
} else {
val r = Realm.getDefaultInstance()
handler(userConfiguration(r))
r.close()
}
}
private fun userConfiguration(realm: Realm): UserConfig {
realm.where(UserConfig::class.java).findFirst()?.let { config ->
return config
}

@ -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
@ -562,7 +556,7 @@ open class HandHistory : RealmObject(), Deletable, RowRepresentable, Filterable,
this.winnerPots.clear()
this.winnerPots.addAll(wonPots)
Timber.d("Pot won: ${this.winnerPots.size} for positions: ${this.winnerPots.map {it.position}} ")
Timber.d("Pot won: ${this.winnerPots.size} for positions: ${this.winnerPots.map { it.position }} ")
}
/***

@ -73,15 +73,21 @@ class FavoriteSessionFinder {
/**
* Copies the favorite session parameters on the [session]
*/
fun copyParametersFromFavoriteSession(session: Session, location: Location?, context: Context) {
fun copyParametersFromFavoriteSession(realm: Realm, session: Session, location: Location?, context: Context) {
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 {
session.game = realm.copyFromRealm(it)
}
fav.bankroll?.let {
session.bankroll = realm.copyFromRealm(it)
}
session.tableSize = fav.tableSize
when (session.type) {

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

@ -113,11 +113,11 @@ class HomeActivity : BaseActivity(), NewPerformanceListener {
val realm = getRealm()
// observe currency changes
this.currencies = realm.where(Currency::class.java).findAll()
this.currencies.addChangeListener { currencies, _ ->
realm.executeTransaction {
currencies.forEach {
it.refreshRelatedRatedValues()
this.currencies = realm.where(Currency::class.java).findAllAsync()
this.currencies.addChangeListener { _, _ ->
realm.executeTransactionAsync { asyncRealm ->
asyncRealm.where(Currency::class.java).findAll().forEach { currency ->
currency.refreshRelatedRatedValues()
}
}
}

@ -129,6 +129,12 @@ abstract class BaseActivity : AppCompatActivity() {
fragmentTransaction.commit()
}
fun addFragmentWithBackStack(fragment: Fragment, containerId: Int) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.add(containerId, fragment)
fragmentTransaction.addToBackStack(fragment.javaClass.toString())
fragmentTransaction.commit()
}
/**
* Return the realm instance

@ -9,6 +9,7 @@ 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
@ -52,11 +53,11 @@ open class MediaActivity : BaseActivity() {
val filesList = ArrayList<File>()
GlobalScope.launch {
CoroutineScope(Dispatchers.Default).launch {
if (tempFile != null) {
tempFile?.let {
GlobalScope.launch(Dispatchers.Main) {
CoroutineScope(Dispatchers.Main).launch {
filesList.add(it)
getPictures(filesList)
}
@ -65,7 +66,7 @@ open class MediaActivity : BaseActivity() {
data.clipData?.let { clipData ->
try {
GlobalScope.launch(Dispatchers.Main) {
CoroutineScope(Dispatchers.Main).launch {
isLoadingNewPictures()
}
@ -78,7 +79,7 @@ open class MediaActivity : BaseActivity() {
filesList.add(photoFile)
}
GlobalScope.launch(Dispatchers.Main) {
CoroutineScope(Dispatchers.Main).launch {
getPictures(filesList)
}
@ -90,7 +91,7 @@ open class MediaActivity : BaseActivity() {
data.data?.let { uri ->
try {
GlobalScope.launch(Dispatchers.Main) {
CoroutineScope(Dispatchers.Main).launch {
isLoadingNewPictures()
}
@ -98,7 +99,7 @@ open class MediaActivity : BaseActivity() {
val photoFile = ImageUtils.createTempImageFile(this@MediaActivity)
ImageUtils.copyInputStreamToFile(inputStream!!, photoFile)
filesList.add(photoFile)
GlobalScope.launch(Dispatchers.Main) {
CoroutineScope(Dispatchers.Main).launch {
getPictures(filesList)
}

@ -6,10 +6,7 @@ import android.view.View
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.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.databinding.FragmentImportBinding
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
@ -89,9 +86,9 @@ class ImportFragment : RealmFragment(), ImportDelegate {
this.importer = CSVImporter(inputStream)
this.importer.delegate = this
CoroutineScope(coroutineContext).launch {
CoroutineScope(Dispatchers.Main).launch {
val coroutine = GlobalScope.async {
val coroutine = CoroutineScope(Dispatchers.Default).async {
val s = Date()
Timber.d(">>> Start Import...")

@ -118,11 +118,6 @@ class ReportCreationFragment : RealmFragment(), RowRepresentableDataSource, RowR
if (this.assistant.step == Assistant.Step.FINALIZE) {
// getRealm().executeTransaction {
// val rs = this.assistant.options.reportSetup("test")
// it.insert(rs)
// }
// launch report
this.finishActivityWithOptions(this.assistant.options, this.assistant.reportDisplay)

@ -11,13 +11,11 @@ import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm
import io.realm.RealmResults
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.NewPerformanceListener
import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.calcul.ReportDisplay
import net.pokeranalytics.android.databinding.FragmentReportsBinding
@ -60,10 +58,7 @@ data class ReportSection(val report: StaticReport, var performances: MutableList
}
data class PerformanceRow(val performance: Performance, val report: StaticReport): RowRepresentable {
override val resId: Int? = this.performance.resId
override val viewType: Int = RowViewType.TITLE_BADGE_VALUE.identifier
}
@ -71,6 +66,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
private lateinit var reportSetups: RealmResults<ReportSetup>
private lateinit var performances: RealmResults<Performance>
private var adapterRows = mutableListOf<RowRepresentable>()
override fun deletableItems(): List<Deletable> {
@ -131,7 +127,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName)
itemToDeleteId?.let { id ->
CoroutineScope(coroutineContext).launch {
CoroutineScope(Dispatchers.Default).launch {
delay(300)
deleteItem(dataListAdapter, reportSetups, id)
}
@ -152,12 +148,12 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
* Init data
*/
private fun initData() {
this.reportSetups = getRealm().where(ReportSetup::class.java).findAll().sort("name")
this.reportSetups = getRealm().where(ReportSetup::class.java).findAllAsync().sort("name")
this.reportSetups.addChangeListener { _, _ ->
this.updateRows()
}
this.performances = getRealm().where(Performance::class.java).findAll()
this.performances = getRealm().where(Performance::class.java).findAllAsync()
this.performances.addChangeListener { _, _ ->
this.updateRows()
}
@ -313,17 +309,14 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
showLoader()
CoroutineScope(coroutineContext).launch {
CoroutineScope(Dispatchers.Default).launch {
val startDate = Date()
val realm = Realm.getDefaultInstance()
realm.refresh()
val report = Calculator.computeStats(realm, options = options)
Timber.d("launchComputation: ${System.currentTimeMillis() - startDate.time}ms")
launch(Dispatchers.Main) {
CoroutineScope(Dispatchers.Main).launch {
if (!isDetached) {
hideLoader()
ReportActivity.newInstanceForResult(this@ReportsFragment, report, reportDisplay, reportName)

@ -185,12 +185,12 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep
Preferences.setCurrencyCode(currencyCode, requireContext())
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
realm.where(Currency::class.java).findAll().forEach { currency ->
realm.executeTransactionAsync { execRealm->
execRealm.where(Currency::class.java).findAll().forEach { currency ->
currency.rate = (currency.rate ?: 1.0) * rate
}
realm.where(Session::class.java).findAll().forEach { session ->
execRealm.where(Session::class.java).findAll().forEach { session ->
session.bankrollHasBeenUpdated()
}
}

@ -8,8 +8,9 @@ import android.os.Bundle
import android.view.*
import androidx.appcompat.widget.Toolbar
import io.realm.Realm
import io.realm.RealmModel
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
@ -30,7 +31,6 @@ import net.pokeranalytics.android.ui.modules.filter.FilterableType
import net.pokeranalytics.android.ui.modules.filter.FiltersActivity
import net.pokeranalytics.android.ui.modules.settings.TransactionFilterActivity
import timber.log.Timber
import java.util.*
class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
@ -76,6 +76,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
addRealmChangeListener(this, UserConfig::class.java)
addRealmChangeListener(this, ComputableResult::class.java)
addRealmChangeListener(this, Transaction::class.java)
addRealmChangeListener(this, SessionSet::class.java)
}
private fun initUI() {
@ -99,10 +100,11 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
private fun setTransactionFilterItemColor() {
context?.let {
val userConfig = UserConfig.getConfiguration(getRealm())
val color = if (userConfig.transactionTypeIds.isNotEmpty()) R.color.red else R.color.white
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.transactionFilterMenuItem?.iconTintList = ColorStateList.valueOf(it.getColor(color))
UserConfig.getConfiguration(getRealm()) { userConfig ->
val color = if (userConfig.transactionTypeIds.isNotEmpty()) R.color.red else R.color.white
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.transactionFilterMenuItem?.iconTintList = ColorStateList.valueOf(it.getColor(color))
}
}
}
}
@ -146,11 +148,13 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
// Business
override fun asyncListenedEntityChange(realm: Realm) {
override fun asyncListenedEntityChange(realm: Realm, clazz: Class<out RealmModel>) {
if (isAdded) { // Fixes: java.lang.IllegalStateException Fragment StatisticsFragment{9d3e5ec} not attached to a context.
launchStatComputation()
setTransactionFilterItemColor()
}
}
/**
@ -158,11 +162,11 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
*/
private fun launchStatComputation() {
CoroutineScope(coroutineContext).launch {
CoroutineScope(Dispatchers.Default).launch {
val async = GlobalScope.async {
val s = Date()
Timber.d(">>> start...")
val async = async {
// val s = Date()
// Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
realm.refresh()
@ -172,15 +176,17 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds")
// val e = Date()
// val duration = (e.time - s.time) / 1000.0
// Timber.d(">>> ended in $duration seconds")
}
async.await()
if (isAdded && !isDetached) {
tableReportFragment.showResults()
launch(Dispatchers.Main) {
if (isAdded && !isDetached) {
tableReportFragment.showResults()
}
}
}
}
@ -247,7 +253,9 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
computedStats.addAll(tStats)
options.stats = computedStats
options.includedTransactions = UserConfig.getConfiguration(realm).transactionTypes(realm)
UserConfig.getConfiguration(realm) { userConfig ->
options.includedTransactions = userConfig.transactionTypes(realm)
}
return Calculator.computeGroups(realm, listOf(allSessionGroup, cgSessionGroup, tSessionGroup), options)
}

@ -109,6 +109,11 @@ abstract class BaseFragment : Fragment() {
view?.findViewById<Toolbar>(R.id.toolbar)?.let { toolbar ->
parentActivity?.setSupportActionBar(toolbar)
}
context?.getColor(R.color.kaki_darkest)?.let { color ->
view?.setBackgroundColor(color)
}
}
/**

@ -8,15 +8,13 @@ import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar
import io.realm.RealmObject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
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.util.extensions.findById
/**
* Deletable Item Fragment
@ -56,7 +54,7 @@ abstract class DeletableItemFragment : RealmFragment() {
val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName)
itemToDeleteId?.let { id ->
GlobalScope.launch(Dispatchers.Main) {
CoroutineScope(Dispatchers.Main).launch {
delay(300)
deleteItem(dataListAdapter, deletableItems(), id)
}
@ -84,14 +82,21 @@ abstract class DeletableItemFragment : RealmFragment() {
if (itemToDelete is RealmObject && itemPosition != -1) {
val itemClass = itemToDelete.realmObjectClass
// Check if the object is valid for the deletion
if (itemToDelete.isValidForDelete(this.getRealm())) {
deletedItem = getRealm().copyFromRealm(itemToDelete)
lastDeletedItemPosition = itemPosition
getRealm().executeTransaction {
itemToDelete.deleteDependencies(it)
itemToDelete.deleteFromRealm()
getRealm().executeTransactionAsync { asyncRealm ->
val item = asyncRealm.findById(itemClass, itemId) as? Deletable
item?.let {
item.deleteDependencies(asyncRealm)
(item as RealmObject).deleteFromRealm()
}
}
itemHasBeenReInserted = false
updateUIAfterDeletion(itemId, itemPosition)
showUndoSnackBar()
@ -117,7 +122,8 @@ abstract class DeletableItemFragment : RealmFragment() {
snackBar?.setAction(R.string.cancel) {
if (!itemHasBeenReInserted) {
itemHasBeenReInserted = true
getRealm().executeTransaction { realm ->
getRealm().executeTransactionAsync { realm ->
deletedItem?.let {
val item = realm.copyToRealmOrUpdate(it)
updateUIAfterUndoDeletion(item)

@ -7,20 +7,15 @@ import android.view.ViewGroup
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmResults
import kotlinx.coroutines.Dispatchers
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import timber.log.Timber
import kotlin.coroutines.CoroutineContext
import net.pokeranalytics.android.util.Global
interface RealmAsyncListener {
fun asyncListenedEntityChange(realm: Realm)
fun asyncListenedEntityChange(realm: Realm, clazz: Class<out RealmModel>)
}
open class RealmFragment : BaseFragment() {
val coroutineContext: CoroutineContext
get() = Dispatchers.Main
/**
* A realm instance
*/
@ -41,15 +36,14 @@ open class RealmFragment : BaseFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
realm = Realm.getDefaultInstance()
this.observedEntities.forEach {
val realmResults = realm.where(it).findAll()
realmResults.addChangeListener { t, _ ->
this.entitiesChanged(it, t)
}
this.observedRealmResults.add(realmResults)
}
// this.observedEntities.forEach {
// val realmResults = realm.where(it).findAllAsync()
// realmResults.addChangeListener { t, _ ->
// this.entitiesChanged(it, t)
// }
//
// this.observedRealmResults.add(realmResults)
// }
return super.onCreateView(inflater, container, savedInstanceState)
}
@ -70,8 +64,10 @@ open class RealmFragment : BaseFragment() {
this.changeListener = listener
val results = this.realm.where(clazz).findAllAsync()
results.addChangeListener { t, _ ->
Timber.d("Realm changes: ${realmResults?.size}, $this")
this.changeListener?.asyncListenedEntityChange(t.realm)
// Timber.d("Realm changes: ${realmResults?.size}, $this")
if (Global.LAUNCH_ASYNC_LISTENERS) {
this.changeListener?.asyncListenedEntityChange(t.realm, clazz)
}
}
this.observedRealmResults.add(results)
}
@ -90,11 +86,11 @@ open class RealmFragment : BaseFragment() {
/**
* A list of RealmModel classes to observe
*/
open val observedEntities: List<Class<out RealmModel>> = listOf()
// open val observedEntities: List<Class<out RealmModel>> = listOf()
/**
* The method called when a change happened in any RealmResults
*/
open fun entitiesChanged(clazz: Class<out RealmModel>, results: RealmResults<out RealmModel>) {}
// open fun entitiesChanged(clazz: Class<out RealmModel>, results: RealmResults<out RealmModel>) {}
}

@ -13,7 +13,7 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
open class BottomSheetDataListFragment : BottomSheetFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
private var _binding: BottomSheetListBinding? = null
private val binding get() = _binding!!
@ -62,18 +62,8 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable
}
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.onRowSelected(position)
dismiss()
// this.viewModel.realmData?.let {
// val selectedData = it[position]
// selectedData?.let {data ->
// this.viewModel.onRowValueChanged(data)
//// this.delegate.onRowValueChanged(data, this.row)
// dismiss()
// }
// }
// super.onRowSelected(position, row, tag)
}
/**

@ -2,22 +2,16 @@ package net.pokeranalytics.android.ui.fragment.components.bottomsheet
import android.app.Activity
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.realm.RealmModel
import net.pokeranalytics.android.databinding.BottomSheetDoubleEditTextBinding
import net.pokeranalytics.android.databinding.BottomSheetListBinding
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
/**
* Manage multiple items selection in a bottom sheet list
*/
open class BottomSheetMultiSelectionFragment : BottomSheetListFragment() {
open class BottomSheetDataMultiSelectionFragment : BottomSheetDataListFragment() {
override fun viewTypeForPosition(position: Int): Int {
return RowViewType.TITLE_CHECK.ordinal
@ -27,11 +21,14 @@ open class BottomSheetMultiSelectionFragment : BottomSheetListFragment() {
if (requestCode == REQUEST_CODE_ADD_NEW_OBJECT && resultCode == Activity.RESULT_OK && data != null) {
val dataType = data.getIntExtra(EditableDataActivity.IntentKey.DATA_TYPE.keyName, 0)
val primaryKey = data.getStringExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName)
val pokerAnalyticsActivity = activity as BaseActivity
val liveDataType = LiveData.values()[dataType]
val proxyItem: RealmModel? = liveDataType.getData(pokerAnalyticsActivity.getRealm(), primaryKey)
this.model.selectedRows.add(proxyItem as RowRepresentable)
this.refreshRow(proxyItem as RowRepresentable)
val realm = (activity as BaseActivity).getRealm()
liveDataType.getData(realm, primaryKey)?.let { proxyItem ->
val copy = realm.copyFromRealm(proxyItem)
this.model.selectedRows.add(copy as RowRepresentable)
this.refreshRow(copy as RowRepresentable)
}
// dataAdapter.refreshRow(proxyItem as RowRepresentable)
}
}

@ -18,13 +18,16 @@ import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.BaseFragment
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rows.SessionPropertiesRow
import net.pokeranalytics.android.ui.view.rows.TransactionPropertiesRow
import net.pokeranalytics.android.ui.viewmodel.AddedDataViewModel
import net.pokeranalytics.android.ui.viewmodel.BottomSheetViewModel
import net.pokeranalytics.android.ui.viewmodel.BottomSheetViewModelFactory
import timber.log.Timber
import java.util.*
class BottomSheetConfig(var row: RowRepresentable,
@ -48,6 +51,10 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
private var _binding: FragmentBottomSheetBinding? = null
private val binding get() = _binding!!
protected open val addedDataViewModel: AddedDataViewModel by lazy {
ViewModelProvider(requireActivity()).get(AddedDataViewModel::class.java)
}
companion object {
private var config: BottomSheetConfig? = null
@ -75,11 +82,11 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
private fun newInstance(bottomSheetType: BottomSheetType): BottomSheetFragment {
return when (bottomSheetType) {
BottomSheetType.NONE -> BottomSheetFragment()
BottomSheetType.LIST -> BottomSheetListFragment()
BottomSheetType.LIST -> BottomSheetDataListFragment()
BottomSheetType.LIST_STATIC -> BottomSheetStaticListFragment()
BottomSheetType.LIST_GAME -> BottomSheetListGameFragment()
BottomSheetType.DOUBLE_LIST -> BottomSheetListGameFragment()
BottomSheetType.MULTI_SELECTION -> BottomSheetMultiSelectionFragment()
BottomSheetType.MULTI_SELECTION -> BottomSheetDataMultiSelectionFragment()
BottomSheetType.GRID -> BottomSheetTableSizeGridFragment()
BottomSheetType.EDIT_TEXT -> BottomSheetEditTextFragment()
BottomSheetType.EDIT_TEXT_MULTI_LINES -> BottomSheetEditTextMultiLinesFragment()
@ -95,6 +102,7 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
//TODO: When dependency 'com.google.android.material:material:1.1.0' will be available in stable version, upgrade and remove that
activity?.setTheme(R.style.PokerAnalyticsTheme)
_binding = FragmentBottomSheetBinding.inflate(inflater, container, false)
inflateContentView(inflater, binding.root)
return binding.root
@ -169,10 +177,18 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
val primaryKey = data.getStringExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName)
val pokerAnalyticsActivity = activity as BaseActivity
val liveDataType = LiveData.values()[dataType]
this.model.addedData = liveDataType.getData(pokerAnalyticsActivity.getRealm(), primaryKey)
this.onRowValueChanged()
val realm = pokerAnalyticsActivity.getRealm()
liveDataType.getData(realm, primaryKey)?.let {
this.model.addedData = realm.copyFromRealm(it)
this.onRowValueChanged()
dismiss()
} ?: run {
Timber.w("Data not found with primary key = $primaryKey")
}
// this.model.addedData = liveDataType.getData(pokerAnalyticsActivity.getRealm(), primaryKey)
// this.delegate.onRowValueChanged(proxyItem, this.row)
dismiss()
}
}
@ -224,11 +240,22 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
else -> throw PAIllegalStateException("row $it does not have an associated LiveData value")
}
EditableDataActivity.newInstanceForResult(
this,
liveData,
requestCode = REQUEST_CODE_ADD_NEW_OBJECT
)
val fragment = liveData.dataFragment
//
this.addedDataViewModel.dataForAdd = true
val bundle = Bundle()
bundle.putInt(BaseFragment.BundleKey.DATA_TYPE.value, liveData.ordinal)
fragment.arguments = bundle
(this.activity as BaseActivity).addFragmentWithBackStack(fragment, R.id.container)
dismiss()
// EditableDataActivity.newInstanceForResult(
// this,
// liveData,
// requestCode = REQUEST_CODE_ADD_NEW_OBJECT
// )
true
}
@ -251,9 +278,9 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
return this.model.rowRepresentableEditDescriptors
}
private fun getValue(): Any? {
return this.model.getValue()
}
// private fun getValue(): Any? {
// return this.model.getValue()
// }
private fun onClear() {
this.delegate?.onRowValueChanged(null, this.model.row)

@ -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.RealmModel
import net.pokeranalytics.android.databinding.BottomSheetGameListBinding
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -17,7 +18,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
* Bottom Sheet List Game Fragment
* Display a list of game + chips to choose the game limit
*/
class BottomSheetListGameFragment : BottomSheetListFragment() {
class BottomSheetListGameFragment : BottomSheetDataListFragment() {
private var _binding: BottomSheetGameListBinding? = null
private val binding get() = _binding!!
@ -38,10 +39,10 @@ class BottomSheetListGameFragment : BottomSheetListFragment() {
}
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.model.realmData?.let {
val selectedData = it[position]
this.model.realmData?.let { realmResults ->
val selectedData = realmResults[position]
selectedData?.let { data ->
this.model.someValues[1] = data
this.model.someValues[1] = realmResults.realm.copyFromRealm(data as RealmModel)
this.onRowValueChanged()
// this.delegate.onRowValueChanged(values, this.row)
dismiss()

@ -45,9 +45,7 @@ class BottomSheetStaticListFragment : BottomSheetFragment(), StaticRowRepresenta
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.model.selectedRows.add(row)
this.onRowValueChanged()
// this.delegate.onRowValueChanged(row, this.row)
dismiss()
// super.onRowSelected(position, row, tag)
}
/**

@ -121,9 +121,12 @@ abstract class AbstractReportFragment : DataManagerFragment() {
this.reportViewModel.title = name
val rs = this.model.item as ReportSetup
getRealm().executeTransaction { realm ->
val setupId = rs.id
val firstSave = (this.model.primaryKey == null)
getRealm().executeTransactionAsync { realm ->
val firstSave = (this.model.primaryKey == null)
if (firstSave) {
val options = this.selectedReport.options
rs.name = name
@ -141,15 +144,18 @@ abstract class AbstractReportFragment : DataManagerFragment() {
options.filterId?.let { id ->
rs.filter = realm.findById(id)
}
realm.copyToRealmOrUpdate(rs)
} else {
rs.name = name
realm.insertOrUpdate(rs)
// realm.copyToRealmOrUpdate(rs)
} else {
realm.findById<ReportSetup>(setupId)?.let { reportSetup ->
reportSetup.name = name
realm.insertOrUpdate(reportSetup)
}
}
}
this.model.primaryKey = rs.id
this.model.primaryKey = setupId
this.deleteButtonShouldAppear = true
setToolbarTitle(this.reportViewModel.title)
}

@ -7,16 +7,13 @@ import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.calcul.ReportDisplay
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.calcul.ReportDisplay
import net.pokeranalytics.android.databinding.FragmentComposableTableReportBinding
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.ui.activity.components.ReportActivity
@ -30,15 +27,10 @@ import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rows.StatRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import timber.log.Timber
import java.util.*
open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentableDataSource, CoroutineScope,
open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentableDataSource,
RowRepresentableDelegate {
// override val coroutineContext: CoroutineContext
// get() = Dispatchers.Main
companion object {
/**
@ -211,12 +203,12 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab
showLoader()
GlobalScope.launch(coroutineContext) {
CoroutineScope(Dispatchers.Default).launch {
var report: Report? = null
val test = GlobalScope.async {
val s = Date()
Timber.d(">>> start...")
val test = async {
// val s = Date()
// Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
realm.refresh()
@ -227,9 +219,9 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds")
// val e = Date()
// val duration = (e.time - s.time) / 1000.0
// Timber.d(">>> ended in $duration seconds")
}
test.await()

@ -11,6 +11,7 @@ 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
@ -169,10 +170,10 @@ class ProgressReportFragment : AbstractReportFragment() {
graphContainer.hideWithAnimation()
progressBar.showWithAnimation()
GlobalScope.launch {
CoroutineScope(Dispatchers.Default).launch {
val s = Date()
Timber.d(">>> start...")
// val s = Date()
// Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
@ -183,9 +184,9 @@ class ProgressReportFragment : AbstractReportFragment() {
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds")
// val e = Date()
// val duration = (e.time - s.time) / 1000.0
// Timber.d(">>> ended in $duration seconds")
launch(Dispatchers.Main) {
setGraphData(report, aggregationType)

@ -13,6 +13,7 @@ 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
@ -167,7 +168,7 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
this.model.computedResults?.let { computedResults ->
GlobalScope.launch {
CoroutineScope(Dispatchers.Default).launch {
val startDate = Date()

@ -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
@ -23,9 +22,7 @@ import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.model.realm.UserConfig
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -39,12 +36,11 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.util.extensions.*
import timber.log.Timber
import java.util.*
import kotlin.collections.set
class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentableDataSource,
class CalendarFragment : RealmFragment(), StaticRowRepresentableDataSource,
RowRepresentableDelegate, RealmAsyncListener {
enum class TimeFilter {
@ -101,13 +97,15 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initData()
initUI()
initData()
addRealmChangeListener(this, UserConfig::class.java)
addRealmChangeListener(this, ComputableResult::class.java)
addRealmChangeListener(this, Transaction::class.java)
addRealmChangeListener(this, SessionSet::class.java)
}
private var transactionFilterMenuItem: MenuItem? = null
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@ -133,11 +131,10 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
private fun setTransactionFilterItemColor() {
context?.let {
val userConfig = UserConfig.getConfiguration(getRealm())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.transactionFilterMenuItem?.let { item ->
val color = if (userConfig.transactionTypeIds.isNotEmpty()) R.color.red else R.color.white
item.iconTintList = ColorStateList.valueOf(it.getColor(color))
UserConfig.getConfiguration(getRealm()) { userConfig ->
val color = if (userConfig.transactionTypeIds.isNotEmpty()) R.color.red else R.color.white
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.transactionFilterMenuItem?.iconTintList = ColorStateList.valueOf(it.getColor(color))
}
}
}
@ -211,9 +208,6 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
}
}
override val observedEntities: List<Class<out RealmModel>> = listOf(ComputableResult::class.java)
// Business
/**
@ -348,7 +342,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
binding.progressBar.showWithAnimation()
binding.recyclerView.hideWithAnimation()
GlobalScope.launch {
CoroutineScope(Dispatchers.Default).launch {
val realm = Realm.getDefaultInstance()
realm.refresh()
@ -357,7 +351,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
realm.close()
GlobalScope.launch(Dispatchers.Main) {
launch(Dispatchers.Main) {
displayData()
}
}
@ -368,7 +362,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
val calendar = Calendar.getInstance()
calendar.time = Date().startOfMonth()
val startDate = Date()
// val startDate = Date()
val requiredStats: List<Stat> =
listOf(
@ -378,7 +372,12 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
Stat.STANDARD_DEVIATION_HOURLY
)
val transactionTypes = UserConfig.getConfiguration(realm).transactionTypes(realm)
var transactionTypes = listOf<TransactionType>()
UserConfig.getConfiguration(realm) { userConfig ->
transactionTypes = userConfig.transactionTypes(realm)
}
// val transactionTypes = UserConfig.getConfiguration(realm).transactionTypes(realm)
// All
val allOptions = Calculator.Options(
@ -490,7 +489,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
sortedMonthlyReports = monthlyReports.toSortedMap(compareByDescending { it })
sortedYearlyReports = yearlyReports.toSortedMap(compareByDescending { it })
Timber.d("Computation: ${System.currentTimeMillis() - startDate.time}ms")
// Timber.d("Computation: ${System.currentTimeMillis() - startDate.time}ms")
}
@ -498,11 +497,11 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
* Display data
*/
private fun displayData() {
Timber.d("displayData")
// Timber.d("displayData")
if (context == null) { return } // required because of launchAsyncStatComputation
val startDate = Date()
// val startDate = Date()
datesForRows.clear()
rows.clear()
@ -594,8 +593,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
}
}
Timber.d("Display data: ${System.currentTimeMillis() - startDate.time}ms")
Timber.d("Rows: ${rows.size}")
// Timber.d("Display data: ${System.currentTimeMillis() - startDate.time}ms")
// Timber.d("Rows: ${rows.size}")
this.calendarAdapter.notifyDataSetChanged()
@ -604,11 +603,13 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
}
override fun asyncListenedEntityChange(realm: Realm) {
override fun asyncListenedEntityChange(realm: Realm, clazz: Class<out RealmModel>) {
if (isAdded) { // Fixes: java.lang.IllegalStateException Fragment StatisticsFragment{9d3e5ec} not attached to a context.
launchAsyncStatComputation()
setTransactionFilterItemColor()
}
}
private fun showGridCalendar() {

@ -133,7 +133,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> if (customField.name.isNotEmpty()) customField.name else NULL_TEXT
SimpleRow.NAME -> customField.name.ifEmpty { NULL_TEXT }
else -> super.charSequenceForRow(row, context, 0)
}
}

@ -5,7 +5,6 @@ import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider
@ -15,6 +14,7 @@ import net.pokeranalytics.android.model.interfaces.Savable
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.AddedDataViewModel
import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel
open class DataManagerFragment : RealmFragment() {
@ -23,12 +23,11 @@ open class DataManagerFragment : RealmFragment() {
ViewModelProvider(this).get(modelClass)
}
open val modelClass: Class<out DataManagerViewModel> = DataManagerViewModel::class.java
protected open val addedDataViewModel: AddedDataViewModel by lazy {
ViewModelProvider(requireActivity()).get(AddedDataViewModel::class.java)
}
// lateinit var item: Deletable
// protected lateinit var liveDataType: LiveData
// protected var primaryKey: String? = null
// protected var dataType: Int? = null
open val modelClass: Class<out DataManagerViewModel> = DataManagerViewModel::class.java
var deleteButtonShouldAppear = false
set(value) {
@ -53,10 +52,22 @@ open class DataManagerFragment : RealmFragment() {
menu.clear()
inflater.inflate(R.menu.toolbar_editable_data, menu)
this.editableMenu = menu
setMenuListeners()
updateMenuUI()
super.onCreateOptionsMenu(menu, inflater)
}
private fun setMenuListeners() {
editableMenu?.findItem(R.id.delete)?.setOnMenuItemClickListener {
deleteData()
return@setOnMenuItemClickListener true
}
editableMenu?.findItem(R.id.save)?.setOnMenuItemClickListener {
saveData()
return@setOnMenuItemClickListener true
}
}
/**
* Update menu UI
*/
@ -65,13 +76,13 @@ open class DataManagerFragment : RealmFragment() {
editableMenu?.findItem(R.id.save)?.isVisible = this.saveButtonShouldAppear
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save -> saveData()
R.id.delete -> deleteData()
}
return true
}
// override fun onOptionsItemSelected(item: MenuItem): Boolean {
// when (item.itemId) {
// R.id.save -> saveData()
// R.id.delete -> deleteData()
// }
// return true
// }
/**
* Init data
@ -96,14 +107,20 @@ 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)
}
this.getRealm().executeTransactionAsync { asyncRealm ->
asyncRealm.insertOrUpdate(savable)
}
onDataSaved()
if (this.addedDataViewModel.dataForAdd) {
this.addedDataViewModel.data.value = savable
this.parentActivity?.supportFragmentManager?.popBackStack()
} else {
val uniqueIdentifier = savable.id
finishActivityWithResult(uniqueIdentifier)
}
}
else -> {
val message = savable.getFailedSaveMessage(status)

@ -18,7 +18,6 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowUpdatable
open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegate {
lateinit var rowRepresentableAdapter: RowRepresentableAdapter
@ -43,7 +42,6 @@ open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegat
this.model.primaryKey = this.arguments?.getString(BundleKey.PRIMARY_KEY.value)
this.model.loadItemWithRealm(getRealm())
}
open fun indexOfFirstRowToSelect(): Int {
@ -67,15 +65,20 @@ open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegat
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
getRealm().executeTransaction {
try {
(this.model.item as RowUpdatable).updateValue(value, row)
} catch (e: Exception) {
CrashLogging.log("Exception caught: row = $row, value=$value, class=${this.javaClass}")
throw e
}
(this.model.item as RowUpdatable).updateValue(value, row)
if (this.model.primaryKey != null) {
getRealm().executeTransactionAsync { asyncRealm ->
try {
asyncRealm.insertOrUpdate(this.model.item)
} catch (e: Exception) {
CrashLogging.log("Exception caught: row = $row, value=$value, class=${this.javaClass}")
throw e
}
}
}
rowRepresentableAdapter.refreshRow(row)
}

@ -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
@ -33,6 +33,11 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
return this.model.item as Transaction
}
override fun onDestroyView() {
super.onDestroyView()
this.addedDataViewModel.data.removeObservers(this)
}
override fun initData() {
super.initData()
@ -40,9 +45,19 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
if (this.transaction.bankroll == null) {
val bankrolls = getRealm().where<Bankroll>().findAll()
if (bankrolls.size == 1) {
this.transaction.bankroll = bankrolls.first()
bankrolls.first()?.let { br ->
this.transaction.bankroll = getRealm().copyFromRealm(br)
}
}
}
this.addedDataViewModel.data.observeForever {
if (this.addedDataViewModel.dataForAdd) {
this.addedDataViewModel.dataForAdd = false
this.onRowValueChanged(it, this.addedDataViewModel.dataIdentifier)
}
}
}
override fun indexOfFirstRowToSelect(): Int {
@ -102,6 +117,9 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
}
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.addedDataViewModel.dataIdentifier = row
when (row) {
TransactionPropertiesRow.DATE -> DateTimePickerManager.create(
requireContext(),
@ -141,7 +159,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
when (val next = rows[index + 1]) {
TransactionPropertiesRow.DATE, TransactionPropertiesRow.COMMENT -> {}
else -> {
GlobalScope.launch(Dispatchers.Main) {
CoroutineScope(Dispatchers.Main).launch {
delay(200)
onRowSelected(0, next)
}

@ -81,10 +81,14 @@ open class DataListFragment : DeletableItemFragment(), RowRepresentableDelegate
getRealm().where(this.model.identifiableClass)
.`in`("id", itemIds)
.sort(this.model.dataType.sortFields, this.model.dataType.sortOrders)
.findAll()
.findAllAsync()
} else {
this.retrieveItems(getRealm())
}
items.addChangeListener { _, _ ->
this.dataListAdapter.notifyDataSetChanged()
}
this.model.setItemsList(items)
}

@ -12,6 +12,7 @@ import com.android.billingclient.api.Purchase
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import com.google.android.material.tabs.TabLayout
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmResults
import io.realm.Sort
@ -30,6 +31,7 @@ import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.extensions.openUrl
import net.pokeranalytics.android.ui.fragment.components.FilterableFragment
import net.pokeranalytics.android.ui.fragment.components.RealmAsyncListener
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.modules.filter.FilterActivityRequestCode
@ -44,9 +46,10 @@ 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 java.util.*
class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseListener {
class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseListener, RealmAsyncListener {
private enum class Tab {
SESSIONS,
@ -83,14 +86,7 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
private var badgeDrawable: BadgeDrawable? = null
override val observedEntities: List<Class<out RealmModel>> =
listOf(Session::class.java, Transaction::class.java, HandHistory::class.java)
override fun entitiesChanged(
clazz: Class<out RealmModel>,
results: RealmResults<out RealmModel>
) {
super.entitiesChanged(clazz, results)
override fun asyncListenedEntityChange(realm: Realm, clazz: Class<out RealmModel>) {
when (clazz.kotlin) {
Session::class -> {
@ -106,7 +102,6 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
this.handHistoryAdapter.notifyDataSetChanged()
}
}
}
private var _binding: FragmentFeedBinding? = null
@ -141,11 +136,7 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
when (item.itemId) {
R.id.duplicate -> {
val session = this.sessionAdapter.sessionForPosition(menuPosition)
if (session != null) {
createNewSession(true, sessionId = session.id, duplicate = true)
} else {
throw PAIllegalStateException("Session not found for duplicate at position: $menuPosition")
}
createNewSession(true, sessionId = session.id, duplicate = true)
}
}
@ -157,6 +148,10 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
AppGuard.registerListener(this)
addRealmChangeListener(this, Session::class.java)
addRealmChangeListener(this, Transaction::class.java)
addRealmChangeListener(this, HandHistory::class.java)
initUI()
initData()
}
@ -481,9 +476,8 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
// gets the first session of the adapter - the last created - to preconfigure the HH
if (this.sessionAdapter.itemCount > 0) {
this.sessionAdapter.sessionForPosition(0)?.let { session ->
HandHistoryActivity.newInstance(this, session, false)
} ?: throw PAIllegalStateException("Cannot happen")
val session = this.sessionAdapter.sessionForPosition(0)
HandHistoryActivity.newInstance(this, session, false)
} else {
HandHistoryActivity.newInstance(this)
}
@ -493,9 +487,16 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
* Delete selected transaction
*/
private fun deleteSelectedTransaction() {
getRealm().executeTransaction {
selectedTransaction?.deleteFromRealm()
selectedTransaction?.id?.let { id ->
getRealm().executeTransactionAsync { asyncRealm ->
asyncRealm.findById<Transaction>(id)?.deleteFromRealm()
}
}
// getRealm().executeTransaction {
// selectedTransaction?.deleteFromRealm()
// }
selectedTransactionPosition = -1
}

@ -137,8 +137,6 @@ class FeedHandHistoryRowRepresentableAdapter(
this.sortedHeaders = this.headersPositions.toSortedMap()
// Timber.d("]]] this.sortedHeaders = ${this.sortedHeaders}")
}
/**

@ -100,7 +100,7 @@ class FeedSessionRowRepresentableAdapter(
BindableHolder {
fun bind(title: String) {
// Title
itemView.findViewById<AppCompatTextView>(net.pokeranalytics.android.R.id.title)?.let {
itemView.findViewById<AppCompatTextView>(R.id.title)?.let {
it.text = title
}
}
@ -144,7 +144,7 @@ class FeedSessionRowRepresentableAdapter(
// If the header has no date, it's a pending session
return if (sortedHeaders[position] == null) {
context.getString(net.pokeranalytics.android.R.string.pending)
context.getString(R.string.pending)
} else {
// Else, return the formatted date
sortedHeaders[position]?.getMonthAndYear() ?: throw PAIllegalStateException("Null date should not happen there")
@ -153,14 +153,14 @@ class FeedSessionRowRepresentableAdapter(
throw PAIllegalStateException("Any position should always have a header, position = $position")
}
fun sessionForPosition(position: Int): Session? {
fun sessionForPosition(position: Int): Session {
return this.getSessionForPosition(position)
}
/**
* Get real index
*/
private fun getSessionForPosition(position: Int): Session? {
private fun getSessionForPosition(position: Int): Session {
// Row position
var headersBefore = 0
@ -182,7 +182,7 @@ class FeedSessionRowRepresentableAdapter(
allSessions.clear()
allSessions.addAll(this.pendingSessions)
allSessions.addAll(this.startedSessions)
Timber.d("Update session list, total count = ${allSessions.size}")
// Timber.d("Update session list, total count = ${allSessions.size}")
val headersPositions = HashMap<Int, Date?>()

@ -160,8 +160,6 @@ class FeedTransactionRowRepresentableAdapter(
*/
private fun checkHeaderCondition(currentCalendar: Calendar, previousYear: Int, previousMonth: Int): Boolean {
return currentCalendar.get(Calendar.YEAR) == previousYear && currentCalendar.get(Calendar.MONTH) < previousMonth || (currentCalendar.get(Calendar.YEAR) < previousYear)
}
}

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

@ -90,7 +90,7 @@ open class FilterDetailsFragment : RealmFragment(), RowRepresentableDelegate {
this.arguments?.let { bundle ->
val filter = this.activityModel.currentFilter ?: throw PAIllegalStateException("Filter is null")
val filter = this.activityModel.currentFilter
val category = bundle.getInt(BundleKey.DATA_TYPE.value)
val categoryRow = FilterCategoryRow.values()[category]
Timber.d("Category row = $categoryRow")
@ -100,7 +100,7 @@ open class FilterDetailsFragment : RealmFragment(), RowRepresentableDelegate {
} ?: throw PAIllegalStateException("Missing bundle")
Timber.d(">> Filter = ${this.activityModel.currentFilter}")
// Timber.d(">> Filter = ${this.activityModel.currentFilter}")
Timber.d("selectedRow = ${this.activityModel.selectedCategoryRow}")
this.binding.toolbar.title = this.activityModel.selectedCategoryRow?.localizedTitle(requireContext())
@ -230,26 +230,32 @@ open class FilterDetailsFragment : RealmFragment(), RowRepresentableDelegate {
*/
private fun saveData() {
val currentFilter = this.activityModel.currentFilter
val filter = this.activityModel.currentFilter
//TODO: Save currentFilter details data
Timber.d("Save data for queryWith: ${currentFilter?.id}")
this.model.selectedRows.forEach {
Timber.d("Selected rows: $it")
}
// this.activityModel.currentFilter?.id?.let { filterId ->
this.activityModel.selectedCategoryRow?.let { category ->
this.activityModel.selectedCategoryRow?.let { category ->
getRealm().executeTransaction {
currentFilter?.remove(category)
val validConditions = this.model.selectedRows.filter { it.queryCondition != null }
currentFilter?.createOrUpdateFilterConditions(validConditions)
}
}
// getRealm().executeTransactionAsync { asyncRealm ->
// asyncRealm.findById<Filter>(filterId)?.let { filter ->
filter.remove(category)
val validConditions = this.model.selectedRows.filter { it.queryCondition != null }
filter.createOrUpdateFilterConditions(validConditions)
}
// }
// }
currentFilter?.filterConditions?.forEach {
Timber.d("Condition: $it")
}
// }
// //TODO: Save currentFilter details data
// Timber.d("Save data for queryWith: ${currentFilter?.id}")
// this.model.selectedRows.forEach {
// Timber.d("Selected rows: $it")
// }
// currentFilter?.filterConditions?.forEach {
// Timber.d("Condition: $it")
// }
// finishActivityWithResult(currentFilter?.id)
}

@ -31,7 +31,7 @@ class FilterDetailsViewModel(categoryRow: FilterCategoryRow, var filter: Filter)
this.defineSelectedItems()
}
override fun adapterRows(): List<RowRepresentable>? {
override fun adapterRows(): List<RowRepresentable> {
return this.rows
}

@ -45,9 +45,8 @@ interface FilterHandler {
fun saveFilter(context: Context, filterId: String) {
Preferences.setActiveFilterId(filterId, context)
val realm = Realm.getDefaultInstance()
realm.executeTransaction { executeRealm ->
realm.executeTransactionAsync { executeRealm ->
currentFilter(context, executeRealm)?.let {
it.useCount++
}

@ -12,7 +12,7 @@ import net.pokeranalytics.android.util.extensions.findById
class FilterViewModel : ViewModel(), StaticRowRepresentableDataSource {
var currentFilter: Filter? = null
var currentFilter: Filter = Filter()
// Main
var filterableType: FilterableType? = null
@ -27,27 +27,27 @@ class FilterViewModel : ViewModel(), StaticRowRepresentableDataSource {
fun init(realm: Realm) {
if (this.currentFilter != null) { // can be called twice and we don't want that
return
}
// if (this.currentFilter != null) { // can be called twice and we don't want that
// return
// }
this.primaryKey?.let {
val filter = realm.findById<Filter>(it) ?: throw PAIllegalStateException("Can't find filter with id=$it")
this.currentFilter = realm.copyFromRealm(filter)
this.isUpdating = true
} ?: run {
this.filterableType?.uniqueIdentifier?.let {
this.currentFilter = Filter.newInstance(it) //realm.copyFromRealm(Filter.newInstanceForResult(realm, this.filterableType.ordinal))
}
this.currentFilter.filterableTypeUniqueIdentifier = this.filterableType?.uniqueIdentifier
// this.filterableType?.uniqueIdentifier?.let {
// this.currentFilter = Filter.newInstance(it) //realm.copyFromRealm(Filter.newInstanceForResult(realm, this.filterableType.ordinal))
// }
}
// Create a copy if the user cancels the updates
this.currentFilter?.let {
if (it.isValid && it.isManaged) {
this.filterCopy = realm.copyFromRealm(it)
}
}
// this.currentFilter?.let {
// if (it.isValid && it.isManaged) {
// this.filterCopy = realm.copyFromRealm(it)
// }
// }
this.categoryRows.clear()
@ -59,13 +59,13 @@ class FilterViewModel : ViewModel(), StaticRowRepresentableDataSource {
// Data source
override fun adapterRows(): List<RowRepresentable>? {
override fun adapterRows(): List<RowRepresentable> {
return this.categoryRows
}
override fun charSequenceForRow(row: RowRepresentable, context: Context, tag: Int): CharSequence {
// Return the number of selected filters for this category
val count = this.currentFilter?.countBy(row as FilterCategoryRow) ?: 0
val count = this.currentFilter.countBy(row as FilterCategoryRow)
return if (count > 0) count.toString() else ""
}

@ -15,7 +15,6 @@ 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
@ -105,7 +104,7 @@ open class FiltersFragment : RealmFragment(), RowRepresentableDelegate {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save -> validateUpdates()
R.id.save -> save()
}
return true
}
@ -156,9 +155,7 @@ open class FiltersFragment : RealmFragment(), RowRepresentableDelegate {
val categoryRow = row as FilterCategoryRow
this.model.selectedCategoryRow = categoryRow
this.model.currentFilter?.let { _ ->
(activity as FiltersActivity).showDetailsFragment(categoryRow)
}
(activity as FiltersActivity).showDetailsFragment(categoryRow)
}
/**
@ -217,18 +214,15 @@ open class FiltersFragment : RealmFragment(), RowRepresentableDelegate {
/**
* Validate the updates of the queryWith
*/
private fun validateUpdates() {
val currentFilter = this.model.currentFilter
private fun save() {
val filter = this.model.currentFilter
getRealm().executeTransaction { realm ->
currentFilter?.let {
it.name = it.query.getName(requireContext())
realm.copyToRealmOrUpdate(it)
}
}
getRealm().executeTransactionAsync { asyncRealm ->
filter.name = filter.query.getName(requireContext())
asyncRealm.insertOrUpdate(filter)
}
val filterId = currentFilter?.id ?: ""
finishActivityWithResult(filterId)
finishActivityWithResult(filter.id)
}
/**
@ -239,9 +233,9 @@ open class FiltersFragment : RealmFragment(), RowRepresentableDelegate {
val filterCopy = this.model.filterCopy
val filterId = filterCopy?.id ?: ""
getRealm().executeTransaction { realm ->
getRealm().executeTransactionAsync { realm ->
filterCopy?.let {
realm.copyToRealmOrUpdate(it)
realm.insertOrUpdate(it)
}
}
finishActivityWithResult(filterId)

@ -15,8 +15,10 @@ open class FiltersListFragment : DataListFragment() {
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
when (row) {
is Filter -> {
getRealm().executeTransaction {
row.updateValue(value, row)
row.updateValue(value, row)
getRealm().executeTransactionAsync { asyncRealm ->
asyncRealm.insertOrUpdate(row)
}
val index = this.model.items.indexOf(row)
this.dataListAdapter.notifyItemChanged(index)

@ -202,12 +202,15 @@ class EditorFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListen
if (resultCode == Activity.RESULT_OK) {
val playerId = data?.getStringExtra(BaseFragment.BundleKey.PRIMARY_KEY.value)
?: throw PAIllegalStateException("Primary key not set where as activity has finished")
getRealm().findById<Player>(playerId)?.let { player ->
this.model.playerSelected(player)
val realm = getRealm()
realm.findById<Player>(playerId)?.let { player ->
val p = realm.copyFromRealm(player)
this.model.playerSelected(p)
} ?: throw PAIllegalStateException("Player (id=$playerId) not found")
this.editorAdapter.notifyDataSetChanged()
}
}
}
@ -684,10 +687,9 @@ class EditorFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListen
*/
private fun deleteHand() {
getRealm().findById<HandHistory>(this.model.handHistory.id)?.let { hh ->
getRealm().executeTransaction {
hh.deleteFromRealm()
}
val hhId = this.model.handHistory.id
getRealm().executeTransactionAsync { asyncRealm ->
asyncRealm.findById<HandHistory>(hhId)?.deleteFromRealm()
}
this.activity?.finish()

@ -5,10 +5,7 @@ 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 kotlinx.coroutines.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.handhistory.HandSetup
@ -27,12 +24,10 @@ 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 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,30 @@ 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)
this.handHistory.actions.clear()
val actions = this.sortedActions.map { it.action }
this.handHistory.actions.addAll(actions)
if (!this.handHistory.isManaged) {
realm.copyToRealmOrUpdate(this.handHistory)
}
realm.executeTransactionAsync { asyncRealm ->
this.handHistory.defineWinnerPositions()
asyncRealm.insertOrUpdate(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<HandHistory>(hhId)?.defineWinnerPositions()
}
realm.close()
}
c.await()
}
}
// private fun defineWinnerPositions() {
//
// val hhId = this.handHistory.id
// CoroutineScope(Dispatchers.Default).launch {
// val realm = Realm.getDefaultInstance()
//
// realm.findById<HandHistory>(hhId)?.defineWinnerPositions()
// realm.executeTransactionAsync { execRealm ->
// execRealm.copyToRealmOrUpdate(handHistory)
// }
// realm.close()
// }
//
// }
// Card Centralizer
@ -1013,16 +1002,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 +1025,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)

@ -13,7 +13,9 @@ 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.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
@ -26,373 +28,323 @@ import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.util.*
import kotlin.coroutines.CoroutineContext
enum class FileType(var value: String) {
IMAGE_GIF("image/gif"),
VIDEO_MP4("video/mp4")
IMAGE_GIF("image/gif"),
VIDEO_MP4("video/mp4")
}
class ReplayExportService : Service() {
private lateinit var handHistoryId: String
private lateinit var handHistoryId: String
private val binder = LocalBinder()
private val binder = LocalBinder()
private val coroutineContext: CoroutineContext
get() = Dispatchers.Main
override fun onBind(intent: Intent?): IBinder {
return binder
}
override fun onBind(intent: Intent?): IBinder {
return binder
}
inner class LocalBinder : Binder() {
fun getService(): ReplayExportService = this@ReplayExportService
}
inner class LocalBinder : Binder() {
fun getService(): ReplayExportService = this@ReplayExportService
}
fun videoExport(handHistoryId: String) {
this@ReplayExportService.handHistoryId = handHistoryId
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startFFMPEGVideoExport()
} else {
startFFMPEGVideoExportPreQ()
}
}
fun videoExport(handHistoryId: String) {
this@ReplayExportService.handHistoryId = handHistoryId
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startFFMPEGVideoExport()
} else {
startFFMPEGVideoExportPreQ()
}
}
fun gifExport(handHistoryId: String) {
this@ReplayExportService.handHistoryId = handHistoryId
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startGIFExport()
} else {
startGIFExportPreQ()
}
}
fun gifExport(handHistoryId: String) {
this@ReplayExportService.handHistoryId = handHistoryId
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startGIFExport()
} else {
startGIFExportPreQ()
}
}
private fun startGIFExport() {
private fun startGIFExport() {
CoroutineScope(Dispatchers.Default).launch {
GlobalScope.launch(coroutineContext) {
val c = GlobalScope.async {
val realm = Realm.getDefaultInstance()
realm.refresh()
val realm = Realm.getDefaultInstance()
realm.refresh()
val handHistory = realm.findById<HandHistory>(handHistoryId)
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val context = this@ReplayExportService
val context = this@ReplayExportService
val animator = ReplayerAnimator(handHistory, true)
val animator = ReplayerAnimator(handHistory, true)
val square = 1024
val square = 1024
val width = square
val height = square
val width = square
val height = square
animator.configure(width.toFloat(), height.toFloat(), context)
animator.configure(width.toFloat(), height.toFloat(), context)
val formattedDate = Date().dateTimeFileFormatted
val fileName = "hand_${formattedDate}"
val formattedDate = Date().dateTimeFileFormatted
val fileName = "hand_${formattedDate}"
// Add a specific media item.
val resolver = applicationContext.contentResolver
// Add a specific media item.
val resolver = applicationContext.contentResolver
// Q version tested before calling the function
val imageCollection =
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
// Q version tested before calling the function
val imageCollection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val gifDetails = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, FileType.IMAGE_GIF.value)
}
val gifDetails = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, FileType.IMAGE_GIF.value)
}
val uri = resolver.insert(imageCollection, gifDetails)
val uri = resolver.insert(imageCollection, gifDetails)
if (uri != null) {
if (uri != null) {
val os = resolver.openOutputStream(uri)
val os = resolver.openOutputStream(uri)
val writer = AnimatedGIFWriter(false)
writer.prepareForWrite(os, width, height)
val writer = AnimatedGIFWriter(false)
writer.prepareForWrite(os, width, height)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
var animationCount = 0
animator.frames(context) { bitmap, count ->
var animationCount = 0
animator.frames(context) { bitmap, count ->
when {
count > 10 -> {
writer.writeFrame(os, bitmap, count * 8)
animationCount = 0
}
else -> {
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
}
animationCount++
}
}
}
writer.finishWrite(os)
when {
count > 10 -> {
writer.writeFrame(os, bitmap, count * 8)
animationCount = 0
}
else -> {
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
}
animationCount++
}
}
}
writer.finishWrite(os)
realm.close()
notifyUser(uri, FileType.IMAGE_GIF)
} else {
Timber.w("Resolver insert ended without uri...")
}
}
realm.close()
notifyUser(uri, FileType.IMAGE_GIF)
} else {
Timber.w("Resolver insert ended without uri...")
}
}
c.await()
}
}
}
private fun startFFMPEGVideoExport() {
private fun startFFMPEGVideoExport() {
CoroutineScope(Dispatchers.Default).launch {
GlobalScope.launch(coroutineContext) {
val async = GlobalScope.async {
val realm = Realm.getDefaultInstance()
val handHistory = realm.findById<HandHistory>(handHistoryId)
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val realm = Realm.getDefaultInstance()
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val context = this@ReplayExportService
val context = this@ReplayExportService
val animator = ReplayerAnimator(handHistory, true)
val animator = ReplayerAnimator(handHistory, true)
val square = 1024
val square = 1024
val width = square
val height = square
val width = square
val height = square
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
// generates all images and file descriptor
Timber.d("Generating images for video...")
val tmpDir = animator.generateVideoContent(this@ReplayExportService)
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
// generates all images and file descriptor
Timber.d("Generating images for video...")
val tmpDir = animator.generateVideoContent(this@ReplayExportService)
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
val formattedDate = Date().dateTimeFileFormatted
val fileName = "hand_${formattedDate}.mp4"
val formattedDate = Date().dateTimeFileFormatted
val fileName = "hand_${formattedDate}.mp4"
val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
?: throw PAIllegalStateException("File is invalid")
val output = "${outputDirectory.path}/$fileName"
val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) ?: throw PAIllegalStateException("File is invalid")
val output = "${outputDirectory.path}/$fileName"
Timber.d("Assembling images for video...")
Timber.d("Assembling images for video...")
val command =
"-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output"
FFmpegKit.executeAsync(command) {
val command = "-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output"
FFmpegKit.executeAsync(command) {
when {
it.returnCode.isSuccess -> {
Timber.d("FFMPEG command execution completed successfully")
}
it.returnCode.isCancel -> {
Timber.d("Command execution cancelled by user.")
}
else -> {
Timber.d("Command execution failed with rc=${it.returnCode.value} and the output below.")
}
}
when {
it.returnCode.isSuccess -> {
Timber.d("FFMPEG command execution completed successfully")
}
it.returnCode.isCancel -> {
Timber.d("Command execution cancelled by user.")
}
else -> {
Timber.d(String.format("Command execution failed with rc=%d and the output below.", it.returnCode.value))
}
}
File(dpath).delete()
tmpDir.delete()
File(dpath).delete()
tmpDir.delete()
val file = File(output)
val file = File(output)
val resolver = applicationContext.contentResolver
val resolver = applicationContext.contentResolver
// Q version tested before calling the function
val videoCollection =
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
// Q version tested before calling the function
val videoCollection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val fileDetails = ContentValues().apply {
Timber.d("set file details = $fileName")
put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, FileType.VIDEO_MP4.value)
}
val fileDetails = ContentValues().apply {
Timber.d("set file details = $fileName")
put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, FileType.VIDEO_MP4.value)
}
// copy video to nice path
resolver.insert(videoCollection, fileDetails)?.let { uri ->
// copy video to nice path
resolver.insert(videoCollection, fileDetails)?.let { uri ->
Timber.d("copy file at uri = $uri")
Timber.d("copy file at uri = $uri")
val os = resolver.openOutputStream(uri)
os?.write(file.readBytes())
os?.close()
val os = resolver.openOutputStream(uri)
os?.write(file.readBytes())
os?.close()
file.delete() // delete temp file
file.delete() // delete temp file
notifyUser(uri, FileType.VIDEO_MP4)
notifyUser(uri, FileType.VIDEO_MP4)
} ?: run {
Timber.w("Resolver insert ended without uri...")
}
} ?: run {
Timber.w("Resolver insert ended without uri...")
}
}
}
}
}
async.await()
}
}
}
private fun startGIFExportPreQ() {
// private fun startVideoExport() {
//
// GlobalScope.launch(coroutineContext) {
// val c = GlobalScope.async {
//
// val realm = Realm.getDefaultInstance()
// val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
//
// val context = this@ReplayExportService
//
// val animator = ReplayerAnimator(handHistory, true)
//
// val square = 1024
//
// val width = square
// val height = square
//
// animator.setDimension(width.toFloat(), height.toFloat())
// TableDrawer.configurePaints(context, animator)
//
// val muxer = MMediaMuxer()
// muxer.init(null, width, height, "hhVideo", "YES!")
//
// animator.frames(context) { bitmap, count ->
//
// try {
// val byteArray = bitmap.toByteArray()
// muxer.addFrame(byteArray, count, false)
// } catch (e: Exception) {
// Timber.e("error = ${e.message}")
// }
//
// }
//
// realm.close()
//
// muxer.createVideo { path ->
// notifyUser(path)
// }
//
// }
// c.await()
// }
//
// }
private fun startGIFExportPreQ() {
GlobalScope.launch(coroutineContext) {
val c = GlobalScope.async {
CoroutineScope(Dispatchers.Default).launch {
val realm = Realm.getDefaultInstance()
realm.refresh()
val realm = Realm.getDefaultInstance()
realm.refresh()
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val handHistory = realm.findById<HandHistory>(handHistoryId)
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val context = this@ReplayExportService
val context = this@ReplayExportService
val animator = ReplayerAnimator(handHistory, true)
val animator = ReplayerAnimator(handHistory, true)
val square = 1024
val square = 1024
val width = square
val height = square
val width = square
val height = square
animator.configure(width.toFloat(), height.toFloat(), context)
animator.configure(width.toFloat(), height.toFloat(), context)
val formattedDate = Date().dateTimeFileFormatted
val path = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"hand_${formattedDate}.gif"
).toString()
val formattedDate = Date().dateTimeFileFormatted
val path = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"hand_${formattedDate}.gif"
).toString()
val writer = AnimatedGIFWriter(false)
val os = FileOutputStream(path)
writer.prepareForWrite(os, width, height)
val writer = AnimatedGIFWriter(false)
val os = FileOutputStream(path)
writer.prepareForWrite(os, width, height)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
var animationCount = 0
animator.frames(context) { bitmap, count ->
var animationCount = 0
animator.frames(context) { bitmap, count ->
when {
count > 10 -> {
writer.writeFrame(os, bitmap, count * 8)
animationCount = 0
}
else -> {
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
}
animationCount++
}
}
when {
count > 10 -> {
writer.writeFrame(os, bitmap, count * 8)
animationCount = 0
}
else -> {
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
}
animationCount++
}
}
}
writer.finishWrite(os)
}
writer.finishWrite(os)
realm.close()
notifyUser(path)
realm.close()
notifyUser(path)
}
c.await()
}
}
}
}
private fun startFFMPEGVideoExportPreQ() {
private fun startFFMPEGVideoExportPreQ() {
GlobalScope.launch(coroutineContext) {
val async = GlobalScope.async {
CoroutineScope(Dispatchers.Default).launch {
val realm = Realm.getDefaultInstance()
val handHistory = realm.findById<HandHistory>(handHistoryId) ?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val realm = Realm.getDefaultInstance()
realm.refresh() // Fixes crash right below
val context = this@ReplayExportService
val handHistory = realm.findById<HandHistory>(handHistoryId)
?: throw PAIllegalStateException("HandHistory not found, id: $handHistoryId")
val animator = ReplayerAnimator(handHistory, true)
val context = this@ReplayExportService
val square = 1024
val animator = ReplayerAnimator(handHistory, true)
val width = square
val height = square
val square = 1024
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
val width = square
val height = square
// generates all images and file descriptor
Timber.d("Generating images for video...")
val tmpDir = animator.generateVideoContent(this@ReplayExportService)
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
animator.configure(width.toFloat(), height.toFloat(), this@ReplayExportService)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
val formattedDate = Date().dateTimeFileFormatted
val output = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"hand_${formattedDate}.mp4"
).path
// generates all images and file descriptor
Timber.d("Generating images for video...")
val tmpDir = animator.generateVideoContent(this@ReplayExportService)
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
Environment.getExternalStorageState(tmpDir)
val formattedDate = Date().dateTimeFileFormatted
val output = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"hand_${formattedDate}.mp4"
).path
Timber.d("Assembling images for video...")
Environment.getExternalStorageState(tmpDir)
Timber.d("Assembling images for video...")
val command = "-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output"
FFmpegKit.executeAsync(command) {
val command =
"-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output"
FFmpegKit.executeAsync(command) {
when {
it.returnCode.isSuccess -> {
Timber.d("FFMPEG command execution completed successfully")
}
it.returnCode.isCancel -> {
Timber.d("Command execution cancelled by user.")
}
else -> {
Timber.d(String.format("Command execution failed with rc=%d and the output below.", it.returnCode.value))
}
}
when {
it.returnCode.isSuccess -> {
Timber.d("FFMPEG command execution completed successfully")
}
it.returnCode.isCancel -> {
Timber.d("Command execution cancelled by user.")
}
else -> {
Timber.d("Command execution failed with rc=${it.returnCode.value} and the output below.")
}
}
// FFmpeg.executeAsync("-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -vf fps=20 -pix_fmt yuv420p $output") { id, rc ->
//
@ -403,61 +355,60 @@ class ReplayExportService : Service() {
// } else {
// Timber.d(String.format("Command execution failed with rc=%d and the output below.", rc))
// }
// Delete descriptor and image files
// Delete descriptor and image files
// tmpDir.delete()
// File(dpath).delete()
notifyUser(output)
}
notifyUser(output)
}
}
async.await()
}
}
}
}
private fun notifyUser(uri: Uri, type: FileType) {
private fun notifyUser(uri: Uri, type: FileType) {
val title = getString(R.string.video_available)
val body = getString(R.string.video_retrieval_message) + ": " + uri.path
val title = getString(R.string.video_available)
val body = getString(R.string.video_retrieval_message) + ": " + uri.path
this.showNotification(title, body, uri, type.value)
this.showNotification(title, body, uri, type.value)
}
}
private fun notifyUser(path: String) {
private fun notifyUser(path: String) {
val title = getString(R.string.video_available)
val body = getString(R.string.video_retrieval_message) + ": " + path
val title = getString(R.string.video_available)
val body = getString(R.string.video_retrieval_message) + ": " + path
Timber.d("Show local notification, path of file: ${path}")
Timber.d("Show local notification, path of file: ${path}")
val uri = FileProvider.getUriForFile(
this,
this.applicationContext.packageName.toString() + ".fileprovider",
File(path)
)
val uri = FileProvider.getUriForFile(
this,
this.applicationContext.packageName.toString() + ".fileprovider",
File(path)
)
val type = when {
path.contains("gif") -> "image/gif"
else -> "video/*"
}
val type = when {
path.contains("gif") -> "image/gif"
else -> "video/*"
}
this.showNotification(title, body, uri, type)
}
this.showNotification(title, body, uri, type)
}
private fun showNotification(title: String, body: String, uri: Uri, type: String) {
private fun showNotification(title: String, body: String, uri: Uri, type: String) {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, type)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooser = Intent.createChooser(intent, getString(R.string.open_file_with))
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, type)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooser = Intent.createChooser(intent, getString(R.string.open_file_with))
val pendingIntent = PendingIntent.getActivity(this, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT)
val pendingIntent =
PendingIntent.getActivity(this, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT)
TriggerNotification(this, title, body, pendingIntent)
TriggerNotification(this, title, body, pendingIntent)
}
}
}

@ -12,8 +12,7 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DiffUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
@ -38,6 +37,7 @@ import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.modules.handhistory.HandHistoryActivity
import net.pokeranalytics.android.ui.view.*
import net.pokeranalytics.android.ui.view.rows.SessionPropertiesRow
import net.pokeranalytics.android.ui.viewmodel.AddedDataViewModel
import net.pokeranalytics.android.util.CrashLogging
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.extensions.*
@ -48,6 +48,10 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
private lateinit var model: SessionViewModel
private val addedDataViewModel: AddedDataViewModel by lazy {
ViewModelProvider(requireActivity()).get(AddedDataViewModel::class.java)
}
companion object {
const val TIMER_DELAY = 5000L
const val REQUEST_CODE_NEW_CUSTOM_FIELD = 1000
@ -104,13 +108,21 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
super.onViewCreated(view, savedInstanceState)
loadOrCreateSession()
initUI()
initData()
}
override fun onDestroyView() {
super.onDestroyView()
this.addedDataViewModel.data.removeObservers(this)
_binding = null
}
private fun initData() {
this.addedDataViewModel.data.observeForever {
this.onRowValueChanged(it, this.addedDataViewModel.dataIdentifier)
}
}
/**
* Init UI
*/
@ -178,40 +190,44 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
if (sessionId != null) {
val sessionRealm = realm.findById<Session>(sessionId)
if (sessionRealm != null) {
sessionRealm?.let {
val copy = realm.copyFromRealm(it)
if (this.model.duplicate) { // duplicate session
realm.executeTransaction {
val session = sessionRealm.duplicate()
// realm.executeTransaction {
val session = copy.duplicate()
currentSession = session
}
// }
sessionHasBeenUserCustomized = false
} else { // show existing session
currentSession = sessionRealm
currentSession = copy
sessionHasBeenUserCustomized = true
}
} else {
throw PAIllegalStateException("Session cannot be null here, session id = $sessionId")
}
} ?: throw PAIllegalStateException("Session cannot be null here, session id = $sessionId")
} else { // create new session
realm.executeTransaction { executeRealm ->
currentSession = Session.newInstance(executeRealm, this.model.isTournament)
FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, null, requireContext())
}
currentSession = Session.newInstance(realm, this.model.isTournament)
FavoriteSessionFinder.copyParametersFromFavoriteSession(realm, currentSession, null, requireContext())
// realm.executeTransaction { executeRealm ->
// currentSession = Session.newInstance(executeRealm, this.model.isTournament)
// FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, null, requireContext())
// }
// Find the nearest location around the user
parentActivity?.findNearestLocation {
it?.let { location ->
realm.executeTransaction { executeRealm ->
val realmLocation = executeRealm.findById<Location>(location.id)
FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, realmLocation, requireContext())
currentSession.location = realmLocation
}
// realm.executeTransaction { executeRealm ->
val realmLocation = realm.findById<Location>(location.id)
FavoriteSessionFinder.copyParametersFromFavoriteSession(realm, currentSession, realmLocation, requireContext())
currentSession.location = realmLocation
// }
this.updateSessionUI(true)
}
}
sessionHasBeenUserCustomized = false
this.sessionHasBeenUserCustomized = false
}
}
@ -221,6 +237,8 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
return
}
this.addedDataViewModel.dataIdentifier = row
val session = this.currentSession
val data = this.editDescriptors(row)
@ -253,15 +271,25 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
Timber.d("value changed: $value, row: $row")
this.sessionHasBeenUserCustomized = true
try {
getRealm().executeTransaction {
this.currentSession.updateValue(value, row)
}
} catch (e: PAIllegalStateException) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
return
updateSessionThenSaveAsynchronously { session ->
session.updateValue(value, row)
}
Timber.d("val = ${this.currentSession.customFieldEntries}")
// try {
// this.currentSession.updateValue(value, row)
// getRealm().executeTransactionAsync { realm ->
// realm.copyToRealmOrUpdate(this.currentSession)
// }
// } catch (e: PAIllegalStateException) {
// Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
// return
// }
this.sessionAdapter.refreshRow(row)
when (row) {
SessionPropertiesRow.CASHED_OUT, SessionPropertiesRow.PRIZE, SessionPropertiesRow.NET_RESULT,
@ -275,6 +303,24 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
}
private fun updateSessionThenSaveAsynchronously(handler: (Session) -> (Unit)) {
try {
handler(this.currentSession)
} catch (e: PAIllegalStateException) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
return
}
getRealm().executeTransactionAsync { realm ->
val session = realm.copyToRealmOrUpdate(this.currentSession)
session.preCompute()
for (customField in this.model.customFields) {
realm.insertOrUpdate(customField)
}
}
}
/**
* Update the UI with the session data
* Should be called after the initialization of the session
@ -388,12 +434,20 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
}
}
currentSession.startOrContinue()
updateSessionThenSaveAsynchronously { session ->
session.startOrContinue()
}
// currentSession.startOrContinue()
binding.recyclerView.smoothScrollToPosition(0)
}
SessionState.STARTED -> {
currentSession.pause()
updateSessionThenSaveAsynchronously { session ->
session.pause()
}
// currentSession.pause()
}
else -> {
}
@ -406,14 +460,13 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
Timber.d("Start optimal duration finding attempt...")
val isLive = this.currentSession.isLive
CoroutineScope(coroutineContext).launch {
CoroutineScope(Dispatchers.Default).launch {
var optimalDuration: Double? = null
val cr = GlobalScope.async {
optimalDuration = CashGameOptimalDurationCalculator.start(isLive)
}
cr.await()
val optimalDuration: Double? = CashGameOptimalDurationCalculator.start(isLive)
// optimalDuration =
// val cr = GlobalScope.async {
// }
// cr.await()
if (!isDetached) {
optimalDuration?.let {
@ -421,10 +474,13 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
val delay = it.toLong() //5000L
currentSession.scheduleStopNotification(requireContext(), delay)
val formattedDuration = (it / 3600 / 1000).formattedHourlyDuration()
Timber.d("Setting stop notification in: $formattedDuration")
val message = requireContext().getString(R.string.stop_notification_in_, formattedDuration)
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
launch(Dispatchers.Main) {
val formattedDuration = (it / 3600 / 1000).formattedHourlyDuration()
Timber.d("Setting stop notification in: $formattedDuration")
val message = requireContext().getString(R.string.stop_notification_in_, formattedDuration)
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
}
}
}
}
@ -435,7 +491,9 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
* Stop the current session
*/
private fun stopSession() {
this.currentSession.stop(requireContext())
updateSessionThenSaveAsynchronously { session ->
session.stop(requireContext())
}
updateSessionUI()
}
@ -454,7 +512,9 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
* Restart timer
*/
private fun restartTimer() {
currentSession.restart()
updateSessionThenSaveAsynchronously { session ->
session.restart()
}
updateSessionUI()
}
@ -478,22 +538,30 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
val bankrollId = this.currentSession.bankroll?.id
this.currentSession.delete()
val sessionId = this.currentSession.id
getRealm().executeTransactionAsync { asyncRealm ->
asyncRealm.findById<Session>(sessionId)?.delete()
}
// this.currentSession.delete()
bankrollId?.let {
BankrollReportManager.notifyBankrollReportImpact(bankrollId)
}
activity?.finish()
}
/**
* Called when the user pressed back on the parent activity
*/
override fun onBackPressed() {
super.onBackPressed()
if (!sessionHasBeenUserCustomized) {
currentSession.delete()
}
}
// /**
// * Called when the user pressed back on the parent activity
// */
// override fun onBackPressed() {
// super.onBackPressed()
// if (!sessionHasBeenUserCustomized) {
// val sessionId = currentSession.id
// getRealm().executeTransactionAsync { asyncRealm ->
// asyncRealm.findById<Session>(sessionId)?.delete()
// }
// }
// }
//// Static Data Source
@ -641,15 +709,16 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
)
)
is CustomField -> {
val entry = row.entries.intersect(session.customFieldEntries).firstOrNull()
row.editingDescriptors(
when (row.type) {
CustomField.Type.LIST.uniqueIdentifier -> mapOf(
"defaultValue" to session.customFieldEntries.find { it.customField?.id == row.id }?.value,
"data" to row.entries
)
else -> mapOf(
"defaultValue" to session.customFieldEntries.find { it.customField?.id == row.id }?.numericValue
)
CustomField.Type.LIST.uniqueIdentifier -> {
mapOf(
"defaultValue" to entry?.value,
"data" to row.entries
)
}
else -> mapOf("defaultValue" to entry?.numericValue)
}
)
}
@ -659,7 +728,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
override fun resultCaptureTypeSelected(resultCaptureType: ResultCaptureType, applyBankroll: Boolean) {
getRealm().executeTransaction { // cleanup existing results
updateSessionThenSaveAsynchronously {
when (resultCaptureType) {
ResultCaptureType.NET_RESULT -> {
this.currentSession.clearBuyinCashedOut()

@ -46,6 +46,11 @@ class SessionViewModel : ViewModel() {
*/
var rows: List<RowRepresentable> = mutableListOf()
/**
* The list of customFields
*/
var customFields: List<CustomField> = listOf()
fun rowForPosition(position: Int): RowRepresentable {
return this.rows[position]
}
@ -104,7 +109,10 @@ class SessionViewModel : ViewModel() {
// Add custom fields
rows.add(SeparatorRow())
rows.addAll(realm.sorted<CustomField>())
val customFields = realm.sorted<CustomField>()
this.customFields = customFields.map { realm.copyFromRealm(it) }
rows.addAll(this.customFields)
this.rows = rows
}

@ -49,24 +49,28 @@ class DealtHandsPerHourFragment : RealmFragment() {
setDisplayHomeAsUpEnabled(true)
val userConfig = UserConfig.getConfiguration(this.getRealm())
this.liveValue.hint = "${userConfig.liveDealtHandsPerHour}"
this.onlineValue.hint = "${userConfig.onlineDealtHandsPerHour}"
UserConfig.getConfiguration(this.getRealm()) { userConfig ->
this.liveValue.hint = "${userConfig.liveDealtHandsPerHour}"
this.onlineValue.hint = "${userConfig.onlineDealtHandsPerHour}"
}
}
private fun save() {
getRealm().executeTransaction { realm ->
getRealm().executeTransactionAsync { realm ->
val userConfig = UserConfig.getConfiguration(realm)
this.liveValue.text.toString().toIntOrNull()?.let { liveDealtHandsPerHour ->
userConfig.liveDealtHandsPerHour = liveDealtHandsPerHour
}
this.onlineValue.text.toString().toIntOrNull()?.let { onlineDealtHandsPerHour ->
userConfig.onlineDealtHandsPerHour = onlineDealtHandsPerHour
UserConfig.getConfiguration(realm) { userConfig ->
this.liveValue.text.toString().toIntOrNull()?.let { liveDealtHandsPerHour ->
userConfig.liveDealtHandsPerHour = liveDealtHandsPerHour
}
this.onlineValue.text.toString().toIntOrNull()?.let { onlineDealtHandsPerHour ->
userConfig.onlineDealtHandsPerHour = onlineDealtHandsPerHour
}
realm.insertOrUpdate(userConfig)
}
realm.copyToRealmOrUpdate(userConfig)
// val userConfig = UserConfig.getConfiguration(realm)
// update all precomputed hand counts
realm.where(ComputableResult::class.java).findAll().forEach { cr ->

@ -90,16 +90,18 @@ class TransactionFilterFragment : RealmFragment(), StaticRowRepresentableDataSou
.findAll()
this.model.transactionTypes = transactionTypes
val userConfig = UserConfig.getConfiguration(this.getRealm())
this.model.selectedTransactionTypes = userConfig.transactionTypes(getRealm()).toMutableSet()
UserConfig.getConfiguration(this.getRealm()) { userConfig ->
this.model.selectedTransactionTypes = userConfig.transactionTypes(getRealm()).toMutableSet()
}
}
private fun save() {
getRealm().executeTransaction { realm ->
val userConfig = UserConfig.getConfiguration(realm)
userConfig.setTransactionTypeIds(this.model.selectedTransactionTypes)
realm.copyToRealmOrUpdate(userConfig)
getRealm().executeTransactionAsync { asyncRealm ->
UserConfig.getConfiguration(asyncRealm) { userConfig ->
userConfig.setTransactionTypeIds(this.model.selectedTransactionTypes)
asyncRealm.insertOrUpdate(userConfig)
}
}
this.activity?.finish()
}

@ -34,7 +34,6 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.extensions.ChipGroupExtension
import net.pokeranalytics.android.ui.extensions.px
import net.pokeranalytics.android.ui.extensions.setTextFormat
import net.pokeranalytics.android.ui.fragment.PerformanceRow
import net.pokeranalytics.android.ui.graph.Graph
import net.pokeranalytics.android.ui.graph.setStyle
import net.pokeranalytics.android.ui.modules.bankroll.BankrollRowRepresentable
@ -675,30 +674,6 @@ enum class RowViewType(private var layoutRes: Int) : ViewIdentifier {
BindableHolder {
override fun onBind(position: Int, row: RowRepresentable, adapter: RecyclerAdapter) {
if (row is PerformanceRow) {
itemView.findViewById<AppCompatTextView>(R.id.title)?.let {
it.text = row.localizedTitle(itemView.context)
}
itemView.findViewById<AppCompatTextView>(R.id.value)?.let {
it.text = adapter.dataSource.charSequenceForRow(row, itemView.context)
}
itemView.findViewById<AppCompatImageView>(R.id.badge)?.let {
it.isVisible = adapter.dataSource.boolForRow(row)
}
itemView.findViewById<AppCompatImageView>(R.id.nextArrow)?.let {
it.visibility = if (row.report.hasGraph) {
View.VISIBLE
} else {
View.GONE
}
}
val listener = View.OnClickListener {
adapter.delegate?.onRowSelected(position, row)
}
itemView.setOnClickListener(listener)
}
}
}

@ -107,6 +107,14 @@ sealed class StaticReport(override var uniqueIdentifier: Int) : RowRepresentable
}
val stats: List<Stat>
get() {
return when (this) {
OptimalDuration -> listOf(Stat.AVERAGE_NET_BB)
else -> listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.NUMBER_OF_GAMES)
}
}
val performanceStats: List<Stat>
get() {
return when (this) {
OptimalDuration -> listOf(Stat.AVERAGE_NET_BB)

@ -3,15 +3,18 @@ package net.pokeranalytics.android.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.realm.RealmList
import io.realm.RealmModel
import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException
import net.pokeranalytics.android.model.Stakes
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rows.SessionPropertiesRow
import net.pokeranalytics.android.util.extensions.findById
import java.util.*
class BottomSheetViewModelFactory(var row: RowRepresentable, var delegate: RowRepresentableDelegate): ViewModelProvider.Factory {
@ -84,7 +87,7 @@ class BottomSheetViewModel(var row: RowRepresentable) : ViewModel() {
var alternativeLabels: Boolean = false
/**
* Multiselection
* Multi-selection
*/
val selectedRows: ArrayList<RowRepresentable> = ArrayList()
@ -224,12 +227,31 @@ class BottomSheetViewModel(var row: RowRepresentable) : ViewModel() {
fun isSelected(row: RowRepresentable): Boolean {
return this.selectedRows.contains(row)
// return when(row.bottomSheetType) {
// BottomSheetType.MULTI_SELECTION -> {
// val identifiables = this.selectedRows as List<Identifiable>
// val ids = identifiables.map { it.id }
// val id = (row as Identifiable).id
// ids.contains(id)
// }
// else -> this.selectedRows.contains(row)
// }
}
fun changedValue(): Any? {
return when(row.bottomSheetType) {
BottomSheetType.DOUBLE_EDIT_TEXT -> arrayListOf(this.stringValue, this.secondStringValue)
BottomSheetType.DOUBLE_LIST, BottomSheetType.LIST_GAME -> arrayListOf(this.someValues[0], this.someValues[1])
BottomSheetType.MULTI_SELECTION -> {
this.realmData?.realm?.let { realm ->
val identifiables = this.selectedRows as List<Identifiable>
identifiables.firstOrNull()?.realmObjectClass?.let { clazz ->
val ids = identifiables.map { it.id }
val objects = ids.mapNotNull { realm.findById(clazz, it) }
objects.map { realm.copyFromRealm(it) }
} ?: kotlin.run { this.selectedRows }
} ?: kotlin.run { this.selectedRows }
}
else -> getValue()
}
}
@ -242,9 +264,15 @@ class BottomSheetViewModel(var row: RowRepresentable) : ViewModel() {
}
}
fun rowSelected(position: Int): RowRepresentable? {
fun rowSelected(position: Int): RowRepresentable {
return when(this.row.bottomSheetType) {
BottomSheetType.LIST -> this.realmData?.get(position)
BottomSheetType.LIST -> {
realmData?.realm?.let { realm ->
this.realmData?.get(position)?.let {
realm.copyFromRealm(it as RealmModel) as? RowRepresentable
} ?: throw PAIllegalStateException("item not found at $position")
} ?: throw PAIllegalStateException("realm not found")
}
BottomSheetType.LIST_STATIC -> this.staticRows[position]
else -> throw PAIllegalStateException("row selected for unmanaged bottom sheet type")
}

@ -1,9 +1,21 @@
package net.pokeranalytics.android.ui.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.realm.Realm
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.ui.view.RowRepresentable
open class AddedDataViewModel : ViewModel() {
var dataForAdd = false
lateinit var dataIdentifier: RowRepresentable
var data: MutableLiveData<Identifiable> = MutableLiveData()
}
open class DataManagerViewModel : ViewModel() {

@ -35,9 +35,9 @@ class FakeDataManager {
val locations = realm.where<Location>().findAll()
if (locations.size == 0) {
realm.executeTransaction {
realm.executeTransactionAsync { asyncRealm ->
listOf("Bellagio", "Aria", "Borgata").map {
realm.getOrCreate<Location>(it)
asyncRealm.getOrCreate<Location>(it)
}
}
}

@ -5,3 +5,9 @@ const val RANDOM_PLAYER: String = "☺"
const val FFMPEG_DESCRIPTOR_FILE = "descriptor.txt"
const val BLIND_SEPARATOR: String = "/"
const val UUID_SEPARATOR: String = ","
class Global {
companion object {
const val LAUNCH_ASYNC_LISTENERS = false
}
}

@ -2,15 +2,12 @@ package net.pokeranalytics.android.util
import android.content.Context
import android.content.Intent
import android.graphics.*
import android.graphics.Paint.FILTER_BITMAP_FLAG
import android.media.ExifInterface
import android.net.Uri
import android.os.Environment
import androidx.core.content.ContextCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import timber.log.Timber
@ -271,7 +268,7 @@ object ImageUtils {
*/
fun saveBitmapInFile(context: Context, bitmap: Bitmap, filename: String, action: (filePath: String) -> Unit) {
GlobalScope.launch {
CoroutineScope(Dispatchers.Default).launch {
val outputFile = File(context.filesDir, filename)
@ -291,7 +288,7 @@ object ImageUtils {
}
}
GlobalScope.launch(Dispatchers.Main) {
launch(Dispatchers.Main) {
Timber.d("Save file here: ${outputFile.absolutePath}")
action(outputFile.absolutePath)
}

@ -0,0 +1,12 @@
package net.pokeranalytics.android.util
fun <T, V> List<T>.intersectBy(other: List<V>, by: (T) -> (V)) : Set<T> {
val results = mutableSetOf<T>()
for (item in this) {
val key = by(item)
if (other.contains(key)) {
results.add(item)
}
}
return results
}

@ -61,7 +61,7 @@ abstract class DataCSVDescriptor<T : Identifiable>(source: DataSource, vararg el
if (realm.isInTransaction) {
this.deleteInsertedFromRealm(realm)
} else {
realm.executeTransaction {
realm.executeTransactionAsync {
this.deleteInsertedFromRealm(realm)
}
}

@ -21,15 +21,15 @@ import java.text.SimpleDateFormat
import java.util.*
abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
private var isTournament: Boolean?,
vararg elements: CSVField)
private var isTournament: Boolean?,
vararg elements: CSVField)
: DataCSVDescriptor<T>(source, *elements) {
var noSessionImport: Boolean = false
private var noSessionImport: Boolean = false
init {
val realm = Realm.getDefaultInstance()
this.noSessionImport = realm.count(Session::class.java) == 0L
this.noSessionImport = realm.count(Session::class.java) == 0L
realm.close()
}
@ -39,13 +39,20 @@ abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
private var dateFormat: DateFormat? = null
private fun newSession(isTournament: Boolean): Session {
val session = Session()
session.result = Result()
session.type = if (isTournament) Session.Type.TOURNAMENT.ordinal else Session.Type.CASH_GAME.ordinal
return session
}
/**
* Parses a [record] and return an optional Session
*/
protected fun parseSession(realm: Realm, record: CSVRecord): Session? {
val isTournament = isTournament ?: false
val session = Session.newInstance(realm, isTournament, managed = false)
val session = this.newSession(isTournament) // Session.newInstance(realm, isTournament)
var startDate: Date? = null
var endDate: Date? = null
@ -276,6 +283,7 @@ abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
this.addAdditionallyCreatedIdentifiable(transaction)
}
managedSession.preCompute()
return managedSession
} else {
Timber.d("Session already exists(count=$count): sd=$startDate, ed=$endDate, net=$net")

@ -79,11 +79,11 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el
is SessionField.TournamentPosition -> field.format(data.result?.tournamentFinalPosition)
is SessionField.Comment -> data.comment
is SessionField.NumberCustomField -> {
val entry = data.customFieldEntries.find { it.customField?.id == field.customField.id }
val entry = field.customField.entries.intersect(data.customFieldEntries).firstOrNull()
field.format(entry?.numericValue)
}
is SessionField.ListCustomField -> {
val entry = data.customFieldEntries.find { it.customField?.id == field.customField.id }
val entry = field.customField.entries.intersect(data.customFieldEntries).firstOrNull()
entry?.value
}
else -> null

@ -1,6 +1,8 @@
package net.pokeranalytics.android.util.extensions
import io.realm.*
import io.realm.kotlin.where
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.interfaces.Identifiable
@ -62,7 +64,7 @@ fun <T : RealmModel> Realm.sorted(clazz: Class<T>, editableOnly: Boolean = false
query.notEqualTo("id", it)
}
val items = query.findAll()
val items = query.findAllAsync()
var sortField = arrayOf("name")
var resultSort = arrayOf(Sort.ASCENDING)
@ -96,8 +98,8 @@ inline fun <reified C : RealmModel> Realm.sorted(editableOnly: Boolean = false,
*/
fun <T : RealmModel>Realm.updateUsageCount(clazz: Class<T>) {
val results = this.where(clazz).findAll()
this.executeTransaction {
this.executeTransactionAsync {
val results = it.where(clazz).findAll()
results.forEach { countableUsage ->
val countable = (countableUsage as UsageCountable)
@ -147,3 +149,11 @@ fun Realm.lookupForNameInAllTablesById(id: String): String? {
}
return null
}
fun Realm.computableResult(session: Session): ComputableResult? {
val crs = this.where<ComputableResult>().equalTo("session.id", session.id).findAll()
if (crs.size > 1) {
throw PAIllegalStateException("Session has multiple computable results")
}
return crs.firstOrNull()
}

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

Loading…
Cancel
Save