Compare commits

...

33 Commits

Author SHA1 Message Date
Laurent c08396b638 more work in progress 3 years ago
Laurent dc90960bcb Fixes duration calculation 3 years ago
Laurent 2aeb2bf48f Fix warning 3 years ago
Laurent 1ec2a438dc Fixes in duration calculus 3 years ago
Laurent 929365fc4c RealmWriteService + FlatTimeInterval for better performance 3 years ago
Laurent 526e50f8e4 Replace foreach by for loop 3 years ago
Laurent 897098499f Update 3 years ago
Laurent 7c64408749 Block stuff 3 years ago
Laurent 47b30f1788 enable listeners 3 years ago
Laurent d9638ce516 changes and improvements 3 years ago
Laurent 887dea91da Avoid notification for hourly rate when there is not much data 3 years ago
Laurent 9c99ef9c35 version bump to 6.0.4 3 years ago
Laurent 7fe851d072 cleanup 3 years ago
Laurent 54776dc6e7 Revert previous commit 3 years ago
Laurent af3c5ffe26 Removes useles primary key from Result 3 years ago
Laurent c95ab684d8 Fixes issue in CSV import 3 years ago
Laurent bd2c17909b Fixes filters 3 years ago
Laurent 6a1249a78c Fixes custom field entries on async writes 3 years ago
Laurent 3eeb8379bd cleanup 3 years ago
Laurent 1bd5876845 Add comment 3 years ago
Laurent 323a3e3bbc Fix crash 3 years ago
Laurent 35ef3047b7 Fix crash when adding transaction 3 years ago
Laurent 676bd0c94e cleanup 3 years ago
Laurent 3d8991baab Fixes various issues with async writes 3 years ago
Laurent 99cf2d465a Fixes missing ComputableResults in sessions 3 years ago
Laurent 0aac37b288 Fixes issue when creating subdata 3 years ago
Laurent e0279fcc85 Fixes crashes 3 years ago
Laurent e94a677cbb Fixes crash 3 years ago
Laurent 621357610b Put async transaction everywhere 3 years ago
Laurent 4ab1f561d8 fix 3 years ago
Laurent bc4bc3ca2e Improvement and cleanup 3 years ago
Laurent 56019357b4 Refactor executeTransaction into executeTransactionAsync 3 years ago
Laurent c99ef285af coroutines changes 3 years ago
  1. 25
      app/build.gradle
  2. 3
      app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt
  3. 6
      app/src/androidTest/java/net/pokeranalytics/android/model/CriteriaTest.kt
  4. 3
      app/src/androidTest/java/net/pokeranalytics/android/performanceTests/PerfsInstrumentedUnitTest.kt
  5. 5
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt
  6. 24
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt
  7. 1
      app/src/main/AndroidManifest.xml
  8. 78
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  9. 56
      app/src/main/java/net/pokeranalytics/android/RealmWriteService.kt
  10. 2
      app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt
  11. 1238
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  12. 7
      app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt
  13. 82
      app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt
  14. 14
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  15. 10
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt
  16. 21
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt
  17. 12
      app/src/main/java/net/pokeranalytics/android/calculus/optimalduration/CashGameOptimalDurationCalculator.kt
  18. 1
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  19. 22
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  20. 27
      app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
  21. 2
      app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt
  22. 7
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  23. 10
      app/src/main/java/net/pokeranalytics/android/model/filter/Query.kt
  24. 2
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  25. 59
      app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt
  26. 22
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  27. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Comment.kt
  28. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  29. 26
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt
  30. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt
  31. 6
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  32. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt
  33. 61
      app/src/main/java/net/pokeranalytics/android/model/realm/FlatTimeInterval.kt
  34. 9
      app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt
  35. 4
      app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt
  36. 91
      app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt
  37. 278
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  38. 27
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  39. 288
      app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt
  40. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt
  41. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentName.kt
  42. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  43. 15
      app/src/main/java/net/pokeranalytics/android/model/realm/UserConfig.kt
  44. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/HandHistory.kt
  45. 14
      app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt
  46. 209
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt
  47. 534
      app/src/main/java/net/pokeranalytics/android/model/utils/TimeManager.kt
  48. 10
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  49. 28
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/BaseActivity.kt
  50. 13
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt
  51. 36
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt
  52. 5
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportCreationFragment.kt
  53. 33
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt
  54. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  55. 46
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt
  56. 5
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BaseFragment.kt
  57. 24
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt
  58. 77
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt
  59. 12
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetDataListFragment.kt
  60. 21
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetDataMultiSelectionFragment.kt
  61. 53
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt
  62. 9
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt
  63. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetStaticListFragment.kt
  64. 18
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt
  65. 27
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt
  66. 15
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt
  67. 2
      app/src/main/java/net/pokeranalytics/android/ui/helpers/AppReviewManager.kt
  68. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollFragment.kt
  69. 3
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt
  70. 185
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt
  71. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/CustomFieldDataFragment.kt
  72. 55
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/DataManagerFragment.kt
  73. 21
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataFragment.kt
  74. 24
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/TransactionDataFragment.kt
  75. 6
      app/src/main/java/net/pokeranalytics/android/ui/modules/datalist/DataListFragment.kt
  76. 100
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedFragment.kt
  77. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedHandHistoryRowRepresentableAdapter.kt
  78. 12
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedSessionRowRepresentableAdapter.kt
  79. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedTransactionRowRepresentableAdapter.kt
  80. 7
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt
  81. 44
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsFragment.kt
  82. 11
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterDetailsViewModel.kt
  83. 4
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterHandler.kt
  84. 30
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FilterViewModel.kt
  85. 28
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersFragment.kt
  86. 6
      app/src/main/java/net/pokeranalytics/android/ui/modules/filter/FiltersListFragment.kt
  87. 16
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/editor/EditorFragment.kt
  88. 87
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/EditorViewModel.kt
  89. 571
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt
  90. 196
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt
  91. 10
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionViewModel.kt
  92. 26
      app/src/main/java/net/pokeranalytics/android/ui/modules/settings/DealtHandsPerHourFragment.kt
  93. 14
      app/src/main/java/net/pokeranalytics/android/ui/modules/settings/TransactionFilterFragment.kt
  94. 7
      app/src/main/java/net/pokeranalytics/android/ui/view/rows/FilterCategoryRow.kt
  95. 43
      app/src/main/java/net/pokeranalytics/android/ui/view/rows/FilterSectionRow.kt
  96. 8
      app/src/main/java/net/pokeranalytics/android/ui/view/rows/StaticReport.kt
  97. 34
      app/src/main/java/net/pokeranalytics/android/ui/viewmodel/BottomSheetViewModel.kt
  98. 12
      app/src/main/java/net/pokeranalytics/android/ui/viewmodel/DataManagerViewModel.kt
  99. 4
      app/src/main/java/net/pokeranalytics/android/util/FakeDataManager.kt
  100. 6
      app/src/main/java/net/pokeranalytics/android/util/Global.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -12,7 +12,7 @@ apply plugin: "kotlinx-serialization"
repositories {
maven { url 'https://jitpack.io' } // required for MPAndroidChart
jcenter() // for kotlin serialization
mavenCentral() // for kotlin serialization
}
android {
@ -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"
}
@ -98,8 +98,8 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Kotlin
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0-native-mt"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
// implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0") // JVM dependency
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
@ -141,16 +141,19 @@ dependencies {
// ffmpeg for encoding video (HH export)
implementation 'com.arthenica:ffmpeg-kit-min-gpl:4.4.LTS'
// https://mvnrepository.com/artifact/com.android.volley/volley
implementation 'com.android.volley:volley:1.2.1'
// Instrumented Tests
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
// Test
testImplementation 'junit:junit:4.12'
testImplementation 'com.android.support.test:runner:1.0.2'
testImplementation 'com.android.support.test:rules:1.0.2'
testImplementation 'androidx.test.ext:junit:1.1.5'
testImplementation 'androidx.test:rules:1.5.0'
// gross, somehow needed to make the stop notif work
implementation 'com.google.guava:guava:27.0.1-android'

@ -20,8 +20,9 @@ open class RealmInstrumentedUnitTest {
fun newSessionInstance(realm: Realm, isCashGame: Boolean = true) : Session {
val session = realm.createObject(Session::class.java, UUID.randomUUID().toString())
session.startDate = Date()
session.endDate = Date()
session.type = if (isCashGame) Session.Type.CASH_GAME.ordinal else Session.Type.TOURNAMENT.ordinal
session.result = realm.createObject(Result::class.java)
session.result = realm.createObject(Result::class.java, UUID.randomUUID().toString())
return session
}

@ -34,7 +34,7 @@ class CriteriaTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction()
val yearQueries = Criteria.Years.queryConditions
val yearQueries = Criteria.Years.queryConditions(realm)
assertEquals(16, yearQueries.size)
@ -57,7 +57,7 @@ class CriteriaTest : BaseFilterInstrumentedUnitTest() {
fun combined() {
val criterias = listOf(Criteria.MonthsOfYear, Criteria.DaysOfWeek)
val combined = criterias.combined()
val combined = criterias.combined(this.mockRealm)
val context = InstrumentationRegistry.getInstrumentation().context
combined.forEach {
@ -92,7 +92,7 @@ class CriteriaTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction()
val context = InstrumentationRegistry.getInstrumentation().context
val allMonths = Criteria.AllMonthsUpToNow.queries
val allMonths = Criteria.AllMonthsUpToNow.queries(realm)
allMonths.forEach {
it.conditions.forEach { qc->
println("<<<<< ${qc.getDisplayName(context)}")

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

@ -16,7 +16,7 @@ import java.util.*
class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
private fun createDefaultTransactionTypes(realm: Realm) {
TransactionType.Value.values().forEachIndexed { index, value ->
TransactionType.Value.values().forEachIndexed { _, value ->
val type = TransactionType()
type.additive = value.additive
type.kind = value.uniqueIdentifier
@ -64,10 +64,12 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val s1 = newSessionInstance(realm)
s1.bankroll = br1
s1.result?.cashout = 200.0
s1.preCompute()
val s2 = newSessionInstance(realm)
s2.bankroll = br2
s2.result?.cashout = 500.0
s2.preCompute()
}
@ -113,6 +115,7 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val s1 = newSessionInstance(realm)
s1.bankroll = br1
s1.result?.cashout = 200.0
s1.endDate = Date()
}

@ -65,6 +65,9 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
s1.location = l1
s2.location = l1
s1.preCompute()
s2.preCompute()
realm.commitTransaction()
assertEquals(2, computableResults.size)
@ -186,13 +189,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")
@ -249,6 +252,9 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
// netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
// netDuration = 4h, hourly = 100, bb100 = 150 / 75 * 100 = +200
s1.preCompute()
s2.preCompute()
}
val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE)
@ -316,6 +322,10 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
realm.copyToRealmOrUpdate(s2)
realm.copyToRealmOrUpdate(s3)
s1.preCompute()
s2.preCompute()
s3.preCompute()
realm.commitTransaction()
val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE)
@ -454,6 +464,9 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
s1.endDate = ed1
realm.copyToRealmOrUpdate(s1)
s1.preCompute()
realm.commitTransaction()
val sets = realm.where(SessionSet::class.java).findAll()
@ -498,6 +511,9 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
realm.copyToRealmOrUpdate(s1)
realm.copyToRealmOrUpdate(s2)
s1.preCompute()
s2.preCompute()
realm.commitTransaction()
val sets = realm.where(SessionSet::class.java).findAll()
@ -731,6 +747,10 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
s2.startDate = sd2
s2.endDate = ed2
s1.preCompute()
s2.preCompute()
}
val group = ComputableGroup(Query(), listOf())

@ -188,6 +188,7 @@
android:launchMode="singleTop"/>
<service android:name="net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayExportService" android:exported="false"/>
<service android:name="net.pokeranalytics.android.RealmWriteService" android:exported="false"/>
<meta-data
android:name="preloaded_fonts"

@ -1,19 +1,27 @@
package net.pokeranalytics.android
import android.app.Application
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Build
import android.os.IBinder
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.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.migrations.Patcher
import net.pokeranalytics.android.model.migrations.PokerAnalyticsMigration
import net.pokeranalytics.android.model.realm.FlatTimeInterval
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.utils.Seed
import net.pokeranalytics.android.model.utils.TimeManager
import net.pokeranalytics.android.util.CrashLogging
import net.pokeranalytics.android.util.FakeDataManager
import net.pokeranalytics.android.util.PokerAnalyticsLogs
@ -22,11 +30,16 @@ import net.pokeranalytics.android.util.billing.AppGuard
import timber.log.Timber
import java.util.*
object AppState {
var isImporting = false
}
class PokerAnalyticsApplication : Application() {
var reportWhistleBlower: ReportWhistleBlower? = null
private var realmWriteService: RealmWriteService? = null
companion object {
fun timeSinceInstall(context: Context): Long {
@ -52,13 +65,14 @@ 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()
Realm.setDefaultConfiguration(realmConfiguration)
initRealmWriteService()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val locales = resources.configuration.locales
CrashLogging.log("App onCreate. Locales = $locales")
@ -80,17 +94,65 @@ class PokerAnalyticsApplication : Application() {
// Patch
Patcher.patchAll(this)
// Report
// Reports
this.reportWhistleBlower = ReportWhistleBlower(this.applicationContext)
TimeManager.configure()
// Infos
val locale = Locale.getDefault()
CrashLogging.log("Country: ${locale.country}, language: ${locale.language}")
// Realm.getDefaultInstance().executeTransaction {
// it.delete(Performance::class.java)
// }
// Debugging
val realm = Realm.getDefaultInstance()
val emptyFTI = realm.where(FlatTimeInterval::class.java).isEmpty("sessions").findAll()
if (emptyFTI.isNotEmpty()) {
Timber.w(">>> WARNING: There are ${emptyFTI.size} FTIs without sessions")
// Timber.w(">>> DELETING THE EMPTY FTIs")
// realm.executeTransactionAsync {
// it.where(FlatTimeInterval::class.java).isEmpty("sessions").findAll().deleteAllFromRealm()
// }
}
val ftis = realm.where(FlatTimeInterval::class.java).sort("startDate").findAll()
Timber.d(">>> Total FTIs count = ${ftis.size}")
ftis.forEach {
Timber.d("fti > ${it.startDate} / ${it.endDate}")
}
Timber.d("================")
val sessions = realm.where<Session>().findAll()
sessions.forEach {
Timber.d("Session FTI count = ${it.flatTimeIntervals.size}")
}
}
/** Defines callbacks for service binding, passed to bindService() */
private val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
realmWriteService = null
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as RealmWriteService.LocalBinder
realmWriteService = binder.getService()
}
}
private fun initRealmWriteService() {
val intent = Intent(this, RealmWriteService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
override fun onTerminate() {
super.onTerminate()
unbindService(connection)
}
fun executeRealmAsyncTransaction(handler: (Realm) -> (Unit)) {
this.realmWriteService?.executeRealmAsyncTransaction(handler) ?: throw PAIllegalStateException("no realmWriteService")
}
/**
@ -103,7 +165,7 @@ class PokerAnalyticsApplication : Application() {
realm.close()
if (sessionsCount < 10) {
GlobalScope.launch {
CoroutineScope(Dispatchers.Default).launch {
FakeDataManager.createFakeSessions(500)
}
}

@ -0,0 +1,56 @@
package net.pokeranalytics.android
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import io.realm.Realm
import timber.log.Timber
class RealmWriteService : Service() {
private lateinit var realm: Realm
private val binder = LocalBinder()
override fun onBind(intent: Intent?): IBinder {
return binder
}
inner class LocalBinder : Binder() {
fun getService(): RealmWriteService = this@RealmWriteService
}
override fun onCreate() {
super.onCreate()
this.realm = Realm.getDefaultInstance()
}
override fun onDestroy() {
super.onDestroy()
Timber.d(">>>> Service destroyed : realm close")
this.realm.close()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
fun executeRealmAsyncTransaction(handler: (Realm) -> (Unit)) {
Timber.d(">>>> Launch async transaction...")
this.realm.executeTransactionAsync({ asyncRealm ->
handler(asyncRealm)
Timber.d(">> transaction handler done")
}, {
Timber.d(">> onSuccess, refreshing...")
this.realm.refresh()
}, {
Timber.d(">> transaction failed: $it")
})
}
}

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

@ -53,6 +53,8 @@ class ComputableGroup(val query: Query, var displayedStats: List<Stat>? = null)
}
}
// Timber.d("QUERY = ${query.defaultName}")
val sortedField = if (sorted) "session.startDate" else null
val computables = Filter.queryOn<ComputableResult>(realm, this.query, sortedField)
@ -62,6 +64,11 @@ class ComputableGroup(val query: Query, var displayedStats: List<Stat>? = null)
return computables
}
fun timeIntervals(realm: Realm): RealmResults<FlatTimeInterval> {
return Filter.queryOn(realm, this.query)
}
/**
* The list of sets to compute
*/

@ -1,21 +1,26 @@
package net.pokeranalytics.android.calculus
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.CountDownTimer
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.RealmResults
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.pokeranalytics.android.AppState
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.fragment.ImportBroadcast
import net.pokeranalytics.android.ui.view.rows.StaticReport
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import timber.log.Timber
import kotlin.coroutines.CoroutineContext
interface NewPerformanceListener {
fun newBestPerformanceHandler()
@ -25,6 +30,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,25 +38,42 @@ class ReportWhistleBlower(var context: Context) {
private val listeners: MutableList<NewPerformanceListener> = mutableListOf()
private var paused: Boolean = false
private var timer: CountDownTimer? = null
private val startImportReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
pause()
}
}
private val endImportReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
resume()
}
}
init {
val realm = Realm.getDefaultInstance()
this.sessions = realm.where(Session::class.java).findAll()
this.sessions?.addChangeListener { _ ->
sessions = realm.where(Session::class.java).findAll()
sessions?.addChangeListener { _ ->
requestReportLaunch()
}
this.results = realm.where(Result::class.java).findAll()
this.results?.addChangeListener { _ ->
results = realm.where(Result::class.java).findAll()
results?.addChangeListener { _ ->
requestReportLaunch()
}
sessionSets = realm.where(SessionSet::class.java).findAll()
sessionSets?.addChangeListener { _ ->
requestReportLaunch()
}
realm.close()
LocalBroadcastManager.getInstance(context).registerReceiver(startImportReceiver, IntentFilter(ImportBroadcast.START.identifier))
LocalBroadcastManager.getInstance(context).registerReceiver(endImportReceiver, IntentFilter(ImportBroadcast.END.identifier))
}
fun addListener(newPerformanceListener: NewPerformanceListener) {
@ -62,9 +85,8 @@ class ReportWhistleBlower(var context: Context) {
}
fun requestReportLaunch() {
Timber.d(">>> Launch report")
if (paused) {
if (AppState.isImporting) {
return
}
@ -98,14 +120,12 @@ class ReportWhistleBlower(var context: Context) {
/**
* Pauses the whistleblower, for example when importing data
*/
fun pause() {
this.paused = true
private fun pause() {
this.currentTask?.cancel()
this.currentTask = null
}
fun resume() {
this.paused = false
private fun resume() {
this.requestReportLaunch()
}
@ -131,19 +151,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 +178,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 +193,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 +222,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 +243,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 +265,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 +283,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()
}
}

@ -55,7 +55,8 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
RISK_OF_RUIN(28),
STANDARD_DEVIATION_BB(29),
TOURNAMENT_ITM_RATIO(30),
TOTAL_TIPS(31)
TOTAL_TIPS(31),
FTI_COUNT(32)
;
companion object : IntSearchable<Stat> {
@ -136,6 +137,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
TOTAL_BUYIN -> R.string.total_buyin
TOURNAMENT_ITM_RATIO -> R.string.itm_ratio
TOTAL_TIPS -> R.string.total_tips
FTI_COUNT -> R.string.players_count
else -> throw PAIllegalStateException("Stat ${this.name} name required but undefined")
}
}
@ -162,7 +164,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
return TextFormat(value.formatted, color)
}
// white integers
NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED -> {
NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED, FTI_COUNT -> {
return TextFormat("${value.toInt()}")
} // white durations
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> {
@ -319,6 +321,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) {

@ -31,7 +31,7 @@ class BankrollCalculator {
var initialValue = 0.0
var transactionNet = 0.0
bankrolls.forEach { bankroll ->
for (bankroll in bankrolls) {
val rate = if (setup.virtualBankroll) bankroll.rate else 1.0
@ -70,8 +70,8 @@ class BankrollCalculator {
val transactions = Filter.queryOn<Transaction>(realm, baseQuery)
report.addDatedItems(transactions)
transactions.forEach {
report.addTransaction(it)
for (transaction in transactions) {
report.addTransaction(transaction)
}
val sessionQuery = Query(QueryCondition.DateNotNull).merge(baseQuery)
@ -83,8 +83,8 @@ class BankrollCalculator {
val options = Calculator.Options(stats = listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY))
val group = ComputableGroup(baseQuery)
val result = Calculator.compute(realm, group, options)
result.computedStat(Stat.NET_RESULT)?.let {
report.netResult = it.value
result.computedStat(Stat.NET_RESULT)?.let { computedStat ->
report.netResult = computedStat.value
}
this.computeRiskOfRuin(report, result)

@ -2,22 +2,17 @@ package net.pokeranalytics.android.calculus.bankroll
import io.realm.Realm
import io.realm.RealmResults
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Transaction
import timber.log.Timber
import java.util.*
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 +63,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 +77,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")

@ -25,11 +25,12 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import net.pokeranalytics.android.util.enumerations.IntSearchable
import net.pokeranalytics.android.util.extensions.findById
import timber.log.Timber
fun List<Criteria>.combined(): List<Query> {
fun List<Criteria>.combined(realm: Realm): List<Query> {
val comparatorList = ArrayList<List<Query>>()
this.forEach { criteria ->
comparatorList.add(criteria.queries)
comparatorList.add(criteria.queries(realm))
}
return getCombinations(comparatorList)
}
@ -64,11 +65,10 @@ sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, Row
inline fun <reified T : NameManageable> comparison(): List<Query> {
if (this is ListCustomFields) {
val objects = mutableListOf<QueryCondition.CustomFieldListQuery>()
val realm = Realm.getDefaultInstance()
val realm = Realm.getDefaultInstance()
realm.findById(CustomField::class.java, this.customFieldId)?.entries?.forEach {
objects.add(QueryCondition.CustomFieldListQuery(it))
}
objects.sort()
realm.close()
return objects.map { Query(it) }
}
@ -166,14 +166,11 @@ sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, Row
data class ValueCustomFields(override var customFieldId: String) : ListCriteria(22), CustomFieldCriteria
object Duration : ListCriteria(23)
val queries: List<Query>
get() {
fun queries(realm: Realm): List<Query> {
return when (this) {
is AllMonthsUpToNow -> {
val realm = Realm.getDefaultInstance()
val firstSession = realm.where<Session>().isNotNull("startDate").sort("startDate", Sort.ASCENDING).findFirst()
val lastSession = realm.where<Session>().isNotNull("startDate").sort("startDate", Sort.DESCENDING).findFirst()
realm.close()
val years: ArrayList<Query> = arrayListOf()
@ -201,13 +198,12 @@ sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, Row
years
}
else -> {
return this.queryConditions
return this.queryConditions(realm)
}
}
}
val queryConditions: List<Query>
get() {
fun queryConditions(realm: Realm): List<Query> {
return when (this) {
is Bankrolls -> comparison<Bankroll>()
is Games -> comparison<Game>()
@ -222,7 +218,6 @@ sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, Row
is TournamentFees -> comparison<QueryCondition.TournamentFee, Double>()
is Years -> {
val years = arrayListOf<Query>()
val realm = Realm.getDefaultInstance()
val lastSession = realm.where<Session>().isNotNull("startDate").sort("startDate", Sort.DESCENDING).findFirst()
val yearNow = lastSession?.year ?: return years
@ -234,19 +229,16 @@ sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, Row
years.add(Query(yearCondition))
}
}
realm.close()
years
}
is Stakes -> comparison<QueryCondition.AnyStake, String>()
is ListCustomFields -> comparison<CustomFieldEntry>()
is ValueCustomFields -> {
val realm = Realm.getDefaultInstance()
val queries = when (this.customFieldType(realm)) {
CustomField.Type.AMOUNT.uniqueIdentifier -> comparison<QueryCondition.CustomFieldAmountQuery, Double >()
CustomField.Type.NUMBER.uniqueIdentifier -> comparison<QueryCondition.CustomFieldNumberQuery, Double >()
else -> throw PokerAnalyticsException.ComparisonCriteriaUnhandled(this)
}
realm.close()
queries
}
is Duration -> {

@ -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()
}
}
}

@ -133,7 +133,7 @@ val AbstractList<Session>.hourlyDuration: Double
return intervals.sumOf { it.hourlyDuration }
}
class TimeInterval(var start: Date, var end: Date, var breakDuration: Long) {
class TimeInterval(var start: Date, var end: Date, var breakDuration: Long = 0L) {
val hourlyDuration: Double
get() {

@ -32,9 +32,9 @@ import net.pokeranalytics.android.util.CrashLogging
*
*/
class UnmanagedFilterField(message: String) : Exception(message) {
}
//class UnmanagedFilterField(message: String) : Exception(message) {
//
//}
/**
* Companion-level Interface to indicate an RealmObject class can be filtered and to provide all the fieldNames (eg: parameter's path) needed to be query on.
@ -64,6 +64,7 @@ class FilterHelper {
SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition)
Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition)
Result::class.java -> Result.fieldNameForQueryType(queryCondition)
FlatTimeInterval::class.java -> FlatTimeInterval.fieldNameForQueryType(queryCondition)
else -> {
CrashLogging.logException(PAIllegalStateException("Filterable type fields are not defined for condition ${queryCondition::class}, class ${T::class}"))
null

@ -73,16 +73,16 @@ class Query {
it is QueryCondition.EndedToTime
}
this.conditions.forEach {
realmQuery = when (it) {
for (condition in this.conditions) {
realmQuery = when (condition) {
is QueryCondition.StartedFromTime -> {
it.queryWith(realmQuery, queryToTime)
condition.queryWith(realmQuery, queryToTime)
}
is QueryCondition.EndedToTime -> {
it.queryWith(realmQuery, queryFromTime)
condition.queryWith(realmQuery, queryFromTime)
}
else -> {
it.queryWith(realmQuery)
condition.queryWith(realmQuery)
}
}
}

@ -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
}

@ -9,7 +9,7 @@ import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.model.utils.Seed
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.model.utils.TimeManager
import net.pokeranalytics.android.util.BLIND_SEPARATOR
import net.pokeranalytics.android.util.Preferences
import java.text.NumberFormat
@ -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,11 +169,11 @@ 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)
TimeManager.updateTimeline(session)
}
}
realm.close()
@ -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()
}

@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.migrations
import io.realm.DynamicRealm
import io.realm.RealmMigration
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.FlatTimeInterval
import timber.log.Timber
import java.util.*
@ -335,6 +336,27 @@ 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")
}
schema.create("FlatTimeInterval")?.let { fs ->
fs.addField("id", String::class.java).setRequired("id", true)
fs.addPrimaryKey("id")
fs.addField("startDate", Date::class.java).setRequired("startDate", true)
fs.addField("endDate", Date::class.java).setRequired("endDate", true)
fs.addField("duration", Long::class.java)
schema.get("Session")?.let { ss ->
ss.addRealmSetField("flatTimeIntervals", fs)
}
}
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

@ -0,0 +1,61 @@
package net.pokeranalytics.android.model.realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import java.util.*
@RealmClass
open class FlatTimeInterval : RealmObject(), Filterable {
@PrimaryKey
var id = UUID.randomUUID().toString()
/**
* The start date of the session
*/
var startDate: Date = Date()
set(value) {
field = value
this.computeDuration()
}
/**
* The start date of the session
*/
var endDate: Date = Date()
set(value) {
field = value
this.computeDuration()
}
/**
* the net duration of the session, automatically calculated
*/
var duration: Long = 0L
@LinkingObjects("flatTimeIntervals")
val sessions: RealmResults<Session>? = null
private fun computeDuration() {
duration = endDate.time - startDate.time
}
companion object {
fun fieldNameForQueryType(queryCondition: Class <out QueryCondition>): String? {
Session.fieldNameForQueryType(queryCondition)?.let {
return "sessions.$it"
}
return null
}
}
}

@ -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?

@ -27,16 +27,13 @@ import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.filter.QueryCondition.*
import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.model.utils.TimeManager
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.graph.Graph
import net.pokeranalytics.android.ui.view.*
import net.pokeranalytics.android.ui.view.rows.SessionPropertiesRow
import net.pokeranalytics.android.util.*
import net.pokeranalytics.android.util.extensions.hourMinute
import net.pokeranalytics.android.util.extensions.shortDateTime
import net.pokeranalytics.android.util.extensions.toCurrency
import net.pokeranalytics.android.util.extensions.toMinutes
import net.pokeranalytics.android.util.extensions.*
import java.text.DateFormat
import java.text.NumberFormat
import java.text.ParseException
@ -67,24 +64,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 +162,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
@Ignore
val computableResult: ComputableResult? = this.computableResults?.firstOrNull()
// Timed interface
override var dayOfWeek: Int? = null
@ -193,6 +199,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
*/
var startDate: Date? = null
set(value) {
val previous = this.startDate
field = value
if (value == null) {
startDateHourMinuteComponent = null
@ -204,12 +211,14 @@ 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()
TimeManager.startChanged(this, min(previous, value))
// this.computeStats()
}
/**
@ -218,6 +227,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
@Index
var endDate: Date? = null
set(value) {
val previous = this.endDate
field = value
if (value == null) {
endDateHourMinuteComponent = null
@ -228,9 +238,9 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
}
this.computeNetDuration()
this.dateChanged()
TimeManager.endChanged(this, max(previous, value))
this.defineDefaultTournamentBuyinIfNecessary()
this.computeStats()
// this.computeStats()
}
/**
@ -240,7 +250,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
set(value) {
field = value
this.computeNetDuration()
this.computeStats()
// this.computeStats()
}
/**
@ -252,10 +262,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 +274,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
set(value) {
field = value
this.generateStakes()
this.computeStats()
// this.computeStats()
// this.updateRowRepresentation()
}
@ -296,7 +302,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
set(value) {
if (value > 0) {
field = value
this.computeStats()
// this.computeStats()
}
}
@ -314,16 +320,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 +337,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 +346,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 +358,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
@ -375,29 +374,29 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
// The custom fields values
var customFieldEntries: RealmList<CustomFieldEntry> = RealmList()
// The list of opponents who participated to the session
var flatTimeIntervals: RealmList<FlatTimeInterval> = RealmList()
// 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()
}
// /**
// * 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,8 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
CrashLogging.log("Deletes session. Id = ${this.id}")
if (isValid) {
realm.executeTransaction {
cleanup()
deleteFromRealm()
}
cleanup()
deleteFromRealm()
} else {
CrashLogging.log("Attempt to delete an invalid session")
}
@ -737,11 +711,15 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
// Updates the timeline
this.sessionSet?.let {
SessionSetManager.removeFromTimeline(this)
TimeManager.removeFromTimeline(this)
}
TimeManager.sessionDateChanged(this)
// cleanup unnecessary related objects
this.flatTimeIntervals.deleteAllFromRealm()
this.result?.deleteFromRealm()
this.computableResults?.deleteAllFromRealm()
this.computableResult?.deleteFromRealm()
}
@ -776,32 +754,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 +770,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 +839,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 +880,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 +995,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 +1030,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 +1055,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()

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

@ -0,0 +1,534 @@
package net.pokeranalytics.android.model.utils
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmQuery
import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.realm.FlatTimeInterval
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.max
import net.pokeranalytics.android.util.extensions.min
import timber.log.Timber
import java.util.*
class CorruptSessionSetException(message: String) : Exception(message)
/**
* The TimeManager pre-computes time related data:
* - SessionSet: All overlapping sessions are grouped into a SessionSet,
* used to calculate the number of sessions and break durations
* - FlatTimeInterval: Sessions time intervals are breaked down into smaller intervals
* when overlapping occurs to get faster duration calculations
*/
object TimeManager {
var sessions: RealmResults<Session>? = null
private val sessionIdsToProcess = mutableSetOf<String>()
private var start: Date? = null
private var end: Date? = null
fun configure() {} // launch init
fun startChanged(session: Session, date: Date?) {
this.start = min(this.start, date)
this.end = max(this.end, session.endDate)
this.sessionIdsToProcess.add(session.id)
}
fun endChanged(session: Session, date: Date?) {
this.end = max(this.end, date)
this.start = min(this.start, session.startDate)
this.sessionIdsToProcess.add(session.id)
}
fun sessionDateChanged(session: Session) {
this.start = min(this.start, session.startDate)
this.end = max(this.end, session.endDate)
this.sessionIdsToProcess.add(session.id)
}
init {
val realm = Realm.getDefaultInstance()
sessions = realm.where(Session::class.java).findAllAsync()
sessions?.addChangeListener { _, _ ->
if (sessionIdsToProcess.isNotEmpty()) {
realm.executeTransactionAsync({ asyncRealm ->
val sessions = sessionIdsToProcess.mapNotNull { asyncRealm.findById<Session>(it) }
sessionIdsToProcess.clear()
for (session in sessions) {
Timber.d("Session id = ${session.id}")
Timber.d("Session time intervals count = ${session.flatTimeIntervals.size}")
session.flatTimeIntervals.deleteAllFromRealm()
val fti = FlatTimeInterval()
session.flatTimeIntervals.add(fti)
asyncRealm.insertOrUpdate(session)
}
}, {
Timber.d("executeTransactionAsync onSuccess listener...")
val timeIntervals = realm.where(FlatTimeInterval::class.java).findAll()
Timber.d("Total timeIntervals count = ${timeIntervals.size}")
timeIntervals.forEach {
Timber.d(">>> Time interval session count = ${it.sessions?.size}, session id = ${it.sessions?.firstOrNull()?.id}")
}
}, {})
}
}
// sessions?.addChangeListener { _, _ ->
//
// Timber.d("...sessions change at ${Date().time}")
//
// val start = this.start
// val end = this.end
// if (start != null && end != null) {
//
// Timber.d("...process date changes from $start to $end")
//
// this.start = null
// this.end = null
//
// realm.executeTransactionAsync ({ asyncRealm ->
// processSessions(asyncRealm, start, end)
// cleanUp()
// }, {
// Timber.d(">>>>> ON SUCCESS")
//
// realm.where(FlatTimeInterval::class.java).findAll().forEach {
// Timber.d("######## sessions count = ${it.sessions?.size}")
// }
//
// }, {
// Timber.d("Transaction failed : $it")
// })
// }
// }
realm.close()
}
private fun cleanUp() {
this.start = null
this.end = null
this.sessionIdsToProcess.clear()
}
private fun processSessions(realm: Realm, start: Date, end: Date) {
Timber.d("***** processSessions, process count = ${sessionIdsToProcess.size}")
// val start = this.start
// val end = this.end
val sessions = sessionIdsToProcess.mapNotNull { realm.findById<Session>(it) }
for (session in sessions) {
// Session Sets
val startDate = session.startDate
val endDate = session.endDate
if (startDate != null && endDate != null) {
updateTimeline(session)
} else if (session.sessionSet != null) {
removeFromTimeline(session)
}
}
// FlatTimeIntervals
processFlatTimeInterval(realm, sessions.toSet(), start, end)
val ftis = realm.where(FlatTimeInterval::class.java).findAll()
Timber.d("*** FTIs count = ${ftis.size}")
}
/**
* 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 start = session.startDate!!
val end = session.endDate!!
val sessionSets = this.matchingData<SessionSet>(session.realm, start, end)
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()
// }
private inline fun <reified T : RealmModel> matchingData(realm: Realm, startDate: Date, endDate: Date): RealmResults<T> {
val query: RealmQuery<T> = realm.where(T::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 = matchingData<SessionSet>(impactedSession.realm, impactedSession.startDate!!, impactedSession.endDate!!)
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.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) {
// if (!session.realm.isInTransaction) {
// throw PAIllegalStateException("realm should be in transaction at this point")
// }
val sessionSet = session.sessionSet
if (sessionSet != null) {
val sessions = mutableSetOf<Session>()
sessionSet.sessions?.asIterable()?.let { sessions.addAll(it) }
sessions.remove(session)
sessionSet.deleteFromRealm()
sessions.forEach {
updateTimeline(it)
}
}
}
private fun processFlatTimeInterval(realm: Realm, changedSessions: Set<Session>, start: Date, end: Date) {
// Timber.d("***************************************************")
// Timber.d("*** processFlatTimeInterval, from: $start, to $end")
// Timber.d("***************************************************")
val sessions = matchingData<Session>(realm, start, end)
val intervalsStore = IntervalsStore(sessions.toSet())
intervalsStore.processSessions(changedSessions)
Timber.d("*** sessions count = ${intervalsStore.sessions.size}")
Timber.d("*** ftis to delete: ${intervalsStore.intervals.size}")
for (fti in intervalsStore.intervals) {
fti.deleteFromRealm()
}
// intervalsStore.intervals.forEach { it.deleteFromRealm() }
val intervals = SessionInterval.intervalMap(intervalsStore.sessions)
for (interval in intervals) {
val sortedDates = interval.dates.sorted()
for (i in (0 until sortedDates.size - 1)) {
val s = sortedDates[i]
val e = sortedDates[i + 1]
val matchingSessions = interval.sessions.filter {
val sd = it.startDate
val ed = it.endDate
(sd != null && ed != null && sd <= s && ed >= e)
}
if (matchingSessions.isNotEmpty()) {
// Timber.d("**** Create FTI: $s - $e")
val fti = FlatTimeInterval()
fti.startDate = s
fti.endDate = e
for (session in matchingSessions) {
session.flatTimeIntervals.add(fti)
realm.insertOrUpdate(session)
}
realm.insertOrUpdate(fti)
} else {
Timber.w("The FTI has no sessions")
}
}
}
sessions.forEach {
Timber.d("ending process...session FTI count = ${it.flatTimeIntervals.size}")
}
}
}
class IntervalsStore(sessionSet: Set<Session>) {
var start: Date = Date()
var end: Date = Date(0L)
val intervals = mutableSetOf<FlatTimeInterval>()
val sessions = mutableSetOf<Session>()
private val sessionIds: MutableSet<String> = mutableSetOf()
init {
processSessions(sessionSet)
}
fun processSessions(sessions: Set<Session>) {
this.sessions.addAll(sessions)
for (session in sessions) {
// Timber.d("PROCESS > s = ${session.startDate} / e = ${session.endDate} ")
loadIntervals(session)
}
}
private fun loadIntervals(session: Session) {
if (sessionIds.contains(session.id)) {
return
}
session.startDate?.let { this.start = min(this.start, it) }
session.endDate?.let { this.end = max(this.end, it) }
this.sessionIds.add(session.id)
Timber.d("session FTI count = ${session.flatTimeIntervals.size}")
for (fti in session.flatTimeIntervals) {
this.intervals.add(fti)
fti.sessions?.let { sessions ->
processSessions(sessions.toSet())
}
}
}
}
class SessionInterval(session: Session) {
var start: Date
var end: Date?
var sessions: MutableSet<Session> = mutableSetOf()
val dates: MutableSet<Date> = mutableSetOf()
val duration: Long
get() {
val endDate = end ?: Date()
return endDate.time - start.time
}
init {
this.start = session.startDate!!
this.end = session.endDate
// Timber.d("INTERVAL init: s = $start, e = $end")
this.addSession(session)
}
private fun addSession(session: Session) {
this.sessions.add(session)
session.startDate?.let { this.dates.add(it) }
session.endDate?.let { endDate ->
this.dates.add(endDate)
if (endDate > end) {
end = endDate
}
}
}
companion object {
fun intervalMap(sessions: Set<Session>): List<SessionInterval> {
val sorted = sessions.sortedBy { it.startDate }
val intervals = mutableListOf<SessionInterval>()
sorted.firstOrNull()?.let { firstSession ->
var currentInterval = SessionInterval(firstSession)
intervals.add(currentInterval)
val remainingSessions = sorted.drop(1)
for (session in remainingSessions) {
val start = session.startDate!!
val currentEnd = currentInterval.end
if (currentEnd != null && start > currentEnd) {
val interval = SessionInterval(session)
currentInterval = interval
intervals.add(interval)
} else {
currentInterval.addSession(session)
}
}
}
// intervals.forEach {
// Timber.d("s = ${it.start}, e = ${it.end}")
// }
return intervals
}
}
}

@ -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 { _, _ ->
getRealm().executeTransactionAsync { asyncRealm ->
asyncRealm.where(Currency::class.java).findAll().forEach { currency ->
currency.refreshRelatedRatedValues()
}
}
}

@ -34,6 +34,7 @@ abstract class BaseActivity : AppCompatActivity() {
}
private var realm: Realm? = null
private var permissionCallback: ((granted: Boolean) -> Unit)? = null
private var permissionRequest: PermissionRequest? = null
@ -86,7 +87,7 @@ abstract class BaseActivity : AppCompatActivity() {
override fun onBackPressed() {
super.onBackPressed()
AppReviewManager.showReviewManager(this)
AppReviewManager.showReviewManagerIfNecessary(this)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
@ -129,11 +130,19 @@ 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
*/
fun getRealm(): Realm {
// return this.realm
this.realm?.let {
return it
} ?: run {
@ -143,6 +152,23 @@ abstract class BaseActivity : AppCompatActivity() {
}
}
fun executeRealmAsyncTransaction(handler: (Realm) -> (Unit)) {
this.paApplication.executeRealmAsyncTransaction(handler)
// Timber.d(">>>> Launch async transaction")
//
// this.realm.executeTransactionAsync({ asyncRealm ->
// handler(asyncRealm)
// }, {
// Timber.d("YEAAAAAAAAAAAH !!!")
// this.realm.refresh()
// }, {
// Timber.d("NOOOOO error = $it")
// })
}
/**
* Return if the location permission has been granted by the user
*/

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

@ -1,15 +1,15 @@
package net.pokeranalytics.android.ui.fragment
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.localbroadcastmanager.content.LocalBroadcastManager
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.AppState
import net.pokeranalytics.android.R
import net.pokeranalytics.android.databinding.FragmentImportBinding
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
@ -20,6 +20,11 @@ import java.io.InputStream
import java.text.NumberFormat
import java.util.*
enum class ImportBroadcast(var identifier: String) {
START("start-import"),
END("end-import")
}
class ImportFragment : RealmFragment(), ImportDelegate {
private lateinit var filePath: String
@ -84,14 +89,17 @@ class ImportFragment : RealmFragment(), ImportDelegate {
private fun startImport() {
this.parentActivity?.paApplication?.reportWhistleBlower?.pause()
AppState.isImporting = true
LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(Intent(ImportBroadcast.START.identifier))
// this.parentActivity?.paApplication?.reportWhistleBlower?.pause()
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...")
@ -116,15 +124,6 @@ class ImportFragment : RealmFragment(), ImportDelegate {
snackBar.show()
}
// if (shouldDismissActivity) {
//
// activity?.let {
// it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value)
// it.finish()
// }
//
// } else {
// }
importDidFinish()
}
@ -140,7 +139,10 @@ class ImportFragment : RealmFragment(), ImportDelegate {
}
private fun end() {
this.parentActivity?.paApplication?.reportWhistleBlower?.resume()
AppState.isImporting = false
LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(Intent(ImportBroadcast.END.identifier))
// this.parentActivity?.paApplication?.reportWhistleBlower?.resume()
activity?.finish()
}

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

@ -41,7 +41,6 @@ import net.pokeranalytics.android.ui.view.rows.StaticReport
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.Preferences
import timber.log.Timber
import java.util.*
data class ReportSection(val report: StaticReport, var performances: MutableList<PerformanceRow>) {
@ -60,10 +59,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 +67,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 +128,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,15 +149,21 @@ 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).sort("name").findAllAsync()
this.performances = getRealm().where(Performance::class.java).findAllAsync()
this.reportSetups.addChangeListener { _, _ ->
this.updateRows()
if (isAdded) {
this.updateRows()
}
}
this.performances = getRealm().where(Performance::class.java).findAll()
this.performances.addChangeListener { _, _ ->
this.updateRows()
if (isAdded) {
this.updateRows()
}
}
}
/**
@ -291,7 +294,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
*/
private fun launchComputation(criteriaList: List<Criteria>, reportName: String, stat: Stat) {
if (criteriaList.combined().size < 2) {
if (criteriaList.combined(getRealm()).size < 2) {
Toast.makeText(context, R.string.less_then_2_values_for_display, Toast.LENGTH_LONG).show()
return
}
@ -311,19 +314,17 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
*/
private fun launchReportWithOptions(options: Calculator.Options, reportDisplay: ReportDisplay, reportName: String) {
Timber.d("launchReportWithOptions")
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 ->
executeRealmAsyncTransaction { 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
@ -73,9 +74,10 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
this.currentFilterable = FilterableType.SESSION
applyFilter()
addRealmChangeListener(this, UserConfig::class.java)
addRealmChangeListener(this, ComputableResult::class.java)
addRealmChangeListener(this, Transaction::class.java)
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 +101,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 +149,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 +163,11 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
*/
private fun launchStatComputation() {
CoroutineScope(coroutineContext).launch {
CoroutineScope(Dispatchers.Default).launch {
val async = GlobalScope.async {
val async = async {
val s = Date()
Timber.d(">>> start...")
// Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
realm.refresh()
@ -174,13 +179,15 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds")
// Timber.d(">>> computations took $duration seconds")
}
async.await()
if (isAdded && !isDetached) {
tableReportFragment.showResults()
launch(Dispatchers.Main) {
if (isAdded && !isDetached) {
tableReportFragment.showResults()
}
}
}
}
@ -190,6 +197,8 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
*/
private fun createSessionGroupsAndStartCompute(realm: Realm): Report {
// Timber.d(">>> Launch statistics computations")
val filter: Filter? = this.currentFilter(this.requireContext(), realm)?.let {
if (it.filterableType == currentFilterable) { it } else { null }
}
@ -201,6 +210,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
Stat.NUMBER_OF_SETS,
Stat.AVERAGE_HOURLY_DURATION,
Stat.HOURLY_DURATION,
Stat.FTI_COUNT,
Stat.HANDS_PLAYED
)
@ -238,8 +248,6 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
val tSessionGroup = ComputableGroup(Query(QueryCondition.IsTournament).merge(query), tStats)
Timber.d(">>>>> Start computations...")
val options = Calculator.Options()
val computedStats = mutableListOf<Stat>()
computedStats.addAll(allStats)
@ -247,7 +255,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()
executeRealmAsyncTransaction { 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 ->
executeRealmAsyncTransaction { realm ->
deletedItem?.let {
val item = realm.copyToRealmOrUpdate(it)
updateUIAfterUndoDeletion(item)

@ -1,37 +1,45 @@
package net.pokeranalytics.android.ui.fragment.components
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmResults
import kotlinx.coroutines.Dispatchers
import net.pokeranalytics.android.AppState
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import timber.log.Timber
import kotlin.coroutines.CoroutineContext
import net.pokeranalytics.android.ui.fragment.ImportBroadcast
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
*/
private lateinit var realm: Realm
// private lateinit var realm: Realm
/***
* A listener to async updates
*/
private var changeListener: RealmAsyncListener? = null
private var realmResults: RealmResults<out RealmModel>? = null
private val endImportReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
sendRealmChange()
}
}
// private var realmResults: RealmResults<out RealmModel>? = null
/**
* A List of observed RealmResults
@ -39,18 +47,8 @@ open class RealmFragment : BaseFragment() {
private var observedRealmResults: MutableList<RealmResults<*>> = mutableListOf()
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)
}
// realm = Realm.getDefaultInstance()
LocalBroadcastManager.getInstance(requireContext()).registerReceiver(endImportReceiver, IntentFilter(ImportBroadcast.END.identifier))
return super.onCreateView(inflater, container, savedInstanceState)
}
@ -58,7 +56,11 @@ open class RealmFragment : BaseFragment() {
* Get the realm instance
*/
fun getRealm(): Realm {
return this.realm
return this.parentActivity?.getRealm() ?: throw PAIllegalStateException("parent activity missing")
}
fun executeRealmAsyncTransaction(handler: (Realm) -> (Unit)) {
this.parentActivity?.executeRealmAsyncTransaction(handler) ?: throw PAIllegalStateException("parent activity missing")
}
fun addRealmChangeListener(listener: RealmAsyncListener, clazz: Class<out RealmModel>) {
@ -68,33 +70,48 @@ 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)
val results = getRealm().where(clazz).findAllAsync()
results.addChangeListener { res, _ ->
// Timber.d("Realm changes: ${realmResults?.size}, $this")
if (!AppState.isImporting) {
this.changeListener?.asyncListenedEntityChange(res.realm, clazz)
} else {
this.changedClasses.add(clazz)
}
}
this.observedRealmResults.add(results)
}
private var changedClasses: MutableSet<Class<out RealmModel>> = mutableSetOf()
fun sendRealmChange() {
if (!AppState.isImporting) {
for (clazz in this.changedClasses) {
this.changeListener?.asyncListenedEntityChange(getRealm(), clazz)
}
this.changedClasses.clear()
}
}
override fun onDestroyView() {
super.onDestroyView()
LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(endImportReceiver)
this.observedRealmResults.forEach {
it.removeAllChangeListeners()
}
this.realm.close()
this.realmResults?.removeAllChangeListeners()
}
/**
* 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)
executeRealmAsyncTransaction { 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
@ -31,14 +28,10 @@ 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 +204,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 +220,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
@ -140,7 +141,7 @@ class ProgressReportFragment : AbstractReportFragment() {
when (aggregationType) {
AggregationType.MONTH, AggregationType.YEAR -> {
if (aggregationType.criterias.combined().size < 2) {
if (aggregationType.criterias.combined(getRealm()).size < 2) {
Toast.makeText(context, R.string.less_then_2_values_for_display, Toast.LENGTH_LONG).show()
return
}
@ -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)

@ -14,7 +14,7 @@ object AppReviewManager {
this.reviewRequested = true
}
fun showReviewManager(activity: Activity) {
fun showReviewManagerIfNecessary(activity: Activity) {
if (this.reviewRequested && this.shouldAskForReview(activity.baseContext)) {
this.reviewRequested = false

@ -35,8 +35,6 @@ import net.pokeranalytics.android.ui.view.rows.BankrollTotalRow
import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.util.extensions.sorted
import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList
interface BankrollRowRepresentable : RowRepresentable {
var bankrollId: String?

@ -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,11 +11,13 @@ import io.realm.Realm
import io.realm.RealmModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.AppState
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.databinding.FragmentCalendarBinding
import net.pokeranalytics.android.exceptions.PAIllegalStateException
@ -23,9 +25,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
@ -43,8 +43,7 @@ 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 {
@ -87,6 +86,13 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
private var _binding: FragmentCalendarBinding? = null
private val binding get() = _binding!!
private val requiredStats: List<Stat > = listOf(
Stat.LOCATIONS_PLAYED,
Stat.LONGEST_STREAKS,
Stat.DAYS_PLAYED,
Stat.STANDARD_DEVIATION_HOURLY
)
// Life Cycle
override fun onCreateView(
@ -101,13 +107,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 +141,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))
}
}
}
@ -161,12 +168,44 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
}
private fun showDetails(computedResults: ComputedResults, title: String?) {
CalendarDetailsActivity.newInstance(
requireContext(),
computedResults,
sessionTypeCondition,
title
)
// start calculation with progress values
CoroutineScope(Dispatchers.Default).launch {
var report: Report? = null
val coroutine = async {
val realm = Realm.getDefaultInstance()
val options = Calculator.Options(
progressValues = Calculator.Options.ProgressValues.STANDARD,
stats = requiredStats,
query = computedResults.group.query,
includedTransactions = transactionTypes(realm)
)
report = Calculator.computeStats(realm, options = options)
realm.close()
}
coroutine.await()
launch(Dispatchers.Main) {
report?.results?.firstOrNull()?.let { cr ->
CalendarDetailsActivity.newInstance(
requireContext(),
cr,
sessionTypeCondition,
title
)
}
}
}
}
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
@ -211,9 +250,6 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
}
}
override val observedEntities: List<Class<out RealmModel>> = listOf(ComputableResult::class.java)
// Business
/**
@ -348,41 +384,71 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
binding.progressBar.showWithAnimation()
binding.recyclerView.hideWithAnimation()
GlobalScope.launch {
CoroutineScope(Dispatchers.Default).launch {
val realm = Realm.getDefaultInstance()
realm.refresh()
val async = async {
val s = Date()
// Timber.d(">>> start...")
launchStatComputation(realm)
val realm = Realm.getDefaultInstance()
realm.refresh()
realm.close()
launchStatComputation(realm)
GlobalScope.launch(Dispatchers.Main) {
displayData()
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
// Timber.d(">>> computations took $duration seconds")
}
async.await()
launch(Dispatchers.Main) {
if (isAdded && !isDetached) {
displayData()
}
}
}
// CoroutineScope(Dispatchers.Default).launch {
//
// val realm = Realm.getDefaultInstance()
// realm.refresh()
//
// launchStatComputation(realm)
//
// realm.close()
//
// launch(Dispatchers.Main) {
// displayData()
// }
// }
}
private fun transactionTypes(realm: Realm): List<TransactionType> {
var transactionTypes = listOf<TransactionType>()
UserConfig.getConfiguration(realm) { userConfig ->
transactionTypes = userConfig.transactionTypes(realm)
}
return transactionTypes
}
private fun launchStatComputation(realm: Realm) {
return
Timber.d(">>> Launch calendar computations")
val calendar = Calendar.getInstance()
calendar.time = Date().startOfMonth()
val startDate = Date()
val requiredStats: List<Stat> =
listOf(
Stat.LOCATIONS_PLAYED,
Stat.LONGEST_STREAKS,
Stat.DAYS_PLAYED,
Stat.STANDARD_DEVIATION_HOURLY
)
val transactionTypes = UserConfig.getConfiguration(realm).transactionTypes(realm)
val transactionTypes = this.transactionTypes(realm)
// All
val allOptions = Calculator.Options(
progressValues = Calculator.Options.ProgressValues.STANDARD,
stats = requiredStats,
query = Query(this.sessionTypeCondition),
includedTransactions = transactionTypes
@ -392,7 +458,6 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
// Sliding Month [sm]
val smOptions = Calculator.Options(
progressValues = Calculator.Options.ProgressValues.STANDARD,
stats = requiredStats,
query = Query(this.slidingMonthQueryCondition, this.sessionTypeCondition),
includedTransactions = transactionTypes
@ -405,23 +470,24 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
val monthlyReports: HashMap<Date, ComputedResults> = HashMap()
val monthlyQueries = when (sessionTypeCondition) {
QueryCondition.IsCash -> listOf(Criteria.AllMonthsUpToNow, Criteria.Cash).combined()
QueryCondition.IsTournament -> listOf(Criteria.AllMonthsUpToNow, Criteria.Tournament).combined()
else -> listOf(Criteria.Years, Criteria.MonthsOfYear).combined()
QueryCondition.IsCash -> listOf(Criteria.AllMonthsUpToNow, Criteria.Cash).combined(realm)
QueryCondition.IsTournament -> listOf(Criteria.AllMonthsUpToNow, Criteria.Tournament).combined(realm)
else -> listOf(Criteria.Years, Criteria.MonthsOfYear).combined(realm)
}
monthlyQueries.forEach { query ->
for (monthlyQuery in monthlyQueries) {
val options = Calculator.Options(
progressValues = Calculator.Options.ProgressValues.STANDARD,
stats = requiredStats,
query = query,
query = monthlyQuery,
includedTransactions = transactionTypes
)
val report = Calculator.computeStats(realm, options = options)
report.results.forEach { computedResults ->
for (computedResults in report.results) {
if (!computedResults.isEmpty) {
// Set date data
query.conditions.forEach { condition ->
for (condition in monthlyQuery.conditions) {
when (condition) {
is QueryCondition.AnyYear -> calendar.set(
Calendar.YEAR,
@ -438,11 +504,11 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
monthlyReports[calendar.time] = computedResults
}
}
}
// Sliding Year [sm]
val syOptions = Calculator.Options(
progressValues = Calculator.Options.ProgressValues.STANDARD,
stats = requiredStats,
query = Query(this.slidingYearQueryCondition, this.sessionTypeCondition),
includedTransactions = transactionTypes
@ -457,14 +523,13 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
val yearlyReports: HashMap<Date, ComputedResults> = HashMap()
val yearConditions = when (sessionTypeCondition) {
QueryCondition.IsCash -> listOf(Criteria.Years, Criteria.Cash).combined()
QueryCondition.IsTournament -> listOf(Criteria.Years, Criteria.Tournament).combined()
else -> listOf(Criteria.Years).combined()
QueryCondition.IsCash -> listOf(Criteria.Years, Criteria.Cash).combined(realm)
QueryCondition.IsTournament -> listOf(Criteria.Years, Criteria.Tournament).combined(realm)
else -> listOf(Criteria.Years).combined(realm)
}
yearConditions.forEach { query ->
val options = Calculator.Options(
progressValues = Calculator.Options.ProgressValues.STANDARD,
stats = requiredStats,
query = query,
includedTransactions = transactionTypes
@ -498,7 +563,10 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
* Display data
*/
private fun displayData() {
Timber.d("displayData")
// Timber.d("displayData")
this.binding.progressBar.hideWithAnimation()
this.binding.recyclerView.showWithAnimation()
if (context == null) { return } // required because of launchAsyncStatComputation
@ -594,21 +662,20 @@ 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()
this.binding.progressBar.hideWithAnimation()
this.binding.recyclerView.showWithAnimation()
}
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)
}
executeRealmAsyncTransaction { 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,22 @@ 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) {
executeRealmAsyncTransaction { 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
@ -21,15 +22,14 @@ import net.pokeranalytics.android.api.BlogPostApi
import net.pokeranalytics.android.databinding.FragmentFeedBinding
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.model.utils.TimeManager
import net.pokeranalytics.android.ui.activity.BillingActivity
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
@ -40,13 +40,13 @@ import net.pokeranalytics.android.ui.modules.session.SessionActivity
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.billing.AppGuard
import net.pokeranalytics.android.util.billing.PurchaseListener
import net.pokeranalytics.android.util.extensions.count
import net.pokeranalytics.android.util.extensions.findById
import java.util.*
class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseListener {
class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseListener, RealmAsyncListener {
private enum class Tab {
SESSIONS,
@ -75,6 +75,7 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
private lateinit var realmTransactions: RealmResults<Transaction>
private lateinit var realmHandHistories: RealmResults<HandHistory>
// private lateinit var realmSessions: RealmResults<Session>
private var newSessionCreated: Boolean = false
private var adapterHasBeenSet: Boolean = false
@ -83,17 +84,12 @@ 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>) {
// Timber.d("asyncListenedEntityChange for $clazz")
when (clazz.kotlin) {
Session::class -> {
// Timber.d("WOWOWOOWOOWOWOWOWOWOWOWOWO")
this.sessionAdapter.refreshData()
this.sessionAdapter.notifyDataSetChanged()
}
@ -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,12 +148,30 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
AppGuard.registerListener(this)
// this.realmSessions = getRealm().where<Session>().findAllAsync()
// val listener = RealmChangeListener<RealmResults<Session>> {
// Timber.d("WOWOWOOWOOWOWOWOWOWOWOWOWO")
// sessionAdapter.refreshData()
// sessionAdapter.notifyDataSetChanged()
// }
// this.realmSessions.addChangeListener(listener)
// this.realmSessions.addChangeListener { t, changeSet ->
// Timber.d("WOWOWOOWOOWOWOWOWOWOWOWOWO")
// this.sessionAdapter.refreshData()
// this.sessionAdapter.notifyDataSetChanged()
// }
initUI()
initData()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
getRealm().refresh()
if (requestCode == RequestCode.FEED_MENU.value && resultCode == RESULT_OK && data != null) {
when (data.getIntExtra(NewDataMenuActivity.IntentKey.CHOICE.keyName, -1)) {
0 -> createNewSession(false)
@ -276,9 +285,20 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
displayBlogPostButton()
binding.postButton.setOnClickListener {
Preferences.setBlogTipsTapped(requireContext())
parentActivity?.openUrl(URL.BLOG_TIPS.value)
displayBlogPostButton()
getRealm().executeTransactionAsync { realm ->
realm.where<Session>().findAll().deleteAllFromRealm()
realm.where<SessionSet>().findAll().deleteAllFromRealm()
realm.where<FlatTimeInterval>().findAll().deleteAllFromRealm()
realm.where<Result>().findAll().deleteAllFromRealm()
realm.where<ComputableResult>().findAll().deleteAllFromRealm()
}
// Preferences.setBlogTipsTapped(requireContext())
// parentActivity?.openUrl(URL.BLOG_TIPS.value)
// displayBlogPostButton()
}
binding.postButton.viewTreeObserver.addOnGlobalLayoutListener {
@ -345,6 +365,10 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
*/
private fun initData() {
addRealmChangeListener(this, Session::class.java)
addRealmChangeListener(this, Transaction::class.java)
addRealmChangeListener(this, HandHistory::class.java)
this.currentFilterable = FilterableType.SESSION
applyFilter()
}
@ -452,6 +476,18 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
*/
private fun createNewTransaction() {
val sessions = getRealm().where(Session::class.java).findAll().map { it.id }
getRealm().executeTransactionAsync { r ->
for (id in sessions) {
r.findById<Session>(id)?.let { s ->
s.tableSize = 6
TimeManager.sessionDateChanged(s)
}
}
}
return
AppGuard.endOfUse?.let { endDate ->
if (Date().after(endDate)) {
this.showEndOfUseMessage()
@ -481,9 +517,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 +528,16 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
* Delete selected transaction
*/
private fun deleteSelectedTransaction() {
getRealm().executeTransaction {
selectedTransaction?.deleteFromRealm()
selectedTransaction?.id?.let { id ->
executeRealmAsyncTransaction { asyncRealm ->
asyncRealm.findById<Transaction>(id)?.deleteFromRealm()
}
}
// getRealm().executeTransaction {
// selectedTransaction?.deleteFromRealm()
// }
selectedTransactionPosition = -1
}
@ -612,7 +654,7 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
show = true
this.badgeDrawable?.number = newCount
}
this.binding.postButton.isVisible = show
this.binding.postButton.isVisible = true
this.badgeDrawable?.isVisible = show
}

@ -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?>()
@ -210,7 +210,7 @@ class FeedSessionRowRepresentableAdapter(
}
sortedHeaders = headersPositions.toSortedMap()
Timber.d("Create viewTypesPositions in: ${System.currentTimeMillis() - start}ms")
// Timber.d("Create viewTypesPositions in: ${System.currentTimeMillis() - start}ms")
}
/**

@ -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,17 +90,17 @@ 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")
val factory = FilterDetailsViewModelFactory(filter, categoryRow)
val factory = FilterDetailsViewModelFactory(getRealm(), filter, categoryRow)
this.model = ViewModelProvider(this, factory).get(FilterDetailsViewModel::class.java)
} ?: 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)
}

@ -3,6 +3,7 @@ package net.pokeranalytics.android.ui.modules.filter
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.realm.Realm
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -13,25 +14,25 @@ import net.pokeranalytics.android.ui.view.rows.FilterItemRow
import net.pokeranalytics.android.util.NULL_TEXT
import timber.log.Timber
class FilterDetailsViewModelFactory(var filter: Filter, private var categoryRow: FilterCategoryRow): ViewModelProvider.Factory {
class FilterDetailsViewModelFactory(var realm: Realm, var filter: Filter, private var categoryRow: FilterCategoryRow): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FilterDetailsViewModel(categoryRow, filter) as T
return FilterDetailsViewModel(realm, categoryRow, filter) as T
}
}
class FilterDetailsViewModel(categoryRow: FilterCategoryRow, var filter: Filter) : ViewModel(), StaticRowRepresentableDataSource {
class FilterDetailsViewModel(var realm: Realm, categoryRow: FilterCategoryRow, var filter: Filter) : ViewModel(), StaticRowRepresentableDataSource {
private var rows: ArrayList<RowRepresentable> = ArrayList()
val selectedRows = ArrayList<FilterItemRow>()
init {
this.rows.addAll(categoryRow.filterElements)
this.rows.addAll(categoryRow.filterElements(realm))
this.defineSelectedItems()
}
override fun adapterRows(): List<RowRepresentable>? {
override fun adapterRows(): List<RowRepresentable> {
return this.rows
}

@ -9,6 +9,7 @@ import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import net.pokeranalytics.android.util.enumerations.IntSearchable
import timber.log.Timber
enum class FilterActivityRequestCode {
SELECT_FILTER,
@ -45,9 +46,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)
}
}
executeRealmAsyncTransaction { 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 ->
executeRealmAsyncTransaction { 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)
executeRealmAsyncTransaction { 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
executeRealmAsyncTransaction { 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
@ -58,7 +62,6 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
private var sessionMenu: Menu? = null
private val oldRows: ArrayList<RowRepresentable> = ArrayList()
private var sessionHasBeenUserCustomized = false
private val handler: Handler = Handler()
private val timer: Runnable = object : Runnable {
@ -104,13 +107,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
*/
@ -134,7 +145,6 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
binding.floatingActionButton.setOnClickListener {
if (this.currentSession.isValidForSave()) {
sessionHasBeenUserCustomized = true
startPauseResumeSession()
} else {
val builder = AlertDialog.Builder(requireContext())
@ -178,40 +188,41 @@ 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
sessionHasBeenUserCustomized = true
currentSession = copy
}
} 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
}
}
@ -221,6 +232,8 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
return
}
this.addedDataViewModel.dataIdentifier = row
val session = this.currentSession
val data = this.editDescriptors(row)
@ -253,15 +266,24 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
this.sessionHasBeenUserCustomized = true
try {
getRealm().executeTransaction {
this.currentSession.updateValue(value, row)
}
} catch (e: PAIllegalStateException) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
return
Timber.d("value changed: $value, row: $row")
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 +297,25 @@ 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
}
executeRealmAsyncTransaction { 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 +429,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 +455,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 +469,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 +486,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 +507,9 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
* Restart timer
*/
private fun restartTimer() {
currentSession.restart()
updateSessionThenSaveAsynchronously { session ->
session.restart()
}
updateSessionUI()
}
@ -478,22 +533,30 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepr
val bankrollId = this.currentSession.bankroll?.id
this.currentSession.delete()
val sessionId = this.currentSession.id
executeRealmAsyncTransaction { 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 +704,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 +723,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 ->
executeRealmAsyncTransaction { 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)
executeRealmAsyncTransaction { asyncRealm ->
UserConfig.getConfiguration(asyncRealm) { userConfig ->
userConfig.setTransactionTypeIds(this.model.selectedTransactionTypes)
asyncRealm.insertOrUpdate(userConfig)
}
}
this.activity?.finish()
}

@ -60,14 +60,13 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In
}
}
val filterElements: List<RowRepresentable>
get() {
fun filterElements(realm: Realm): List<RowRepresentable> {
return filterSectionRows.flatMap {
val items = it.filterItems
val items = it.filterItems(realm)
val list = mutableListOf<RowRepresentable>()
if (items.isNotEmpty()) {
list.add(it)
list.addAll(it.filterItems)
list.addAll(it.filterItems(realm))
}
list
}

@ -1,6 +1,7 @@
package net.pokeranalytics.android.ui.view.rows
import android.content.Context
import io.realm.Realm
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.Criteria
@ -71,9 +72,8 @@ sealed class FilterSectionRow(override val resId: Int?) : RowRepresentable {
val allowMultiSelection: Boolean
get() = (this.selectionType == SelectionType.MULTIPLE)
val filterItems: List<FilterItemRow>
get() {
return this.queryConditions.map {
fun filterItems(realm: Realm): List<FilterItemRow> {
return this.queryConditions(realm).map {
rowWrapper(it, this@FilterSectionRow)
// it.toRowWrapper(this@FilterSectionRow)
}
@ -101,15 +101,14 @@ sealed class FilterSectionRow(override val resId: Int?) : RowRepresentable {
}
}
private val queryConditions: List<QueryCondition>
get() {
private fun queryConditions(realm: Realm): List<QueryCondition> {
return when (this@FilterSectionRow) {
// General
CashOrTournament -> Criteria.SessionTypes.queryConditions.mapFirstCondition()
LiveOrOnline -> Criteria.BankrollTypes.queryConditions.mapFirstCondition()
Game -> Criteria.Games.queryConditions.mapFirstCondition()
LimitType -> Criteria.Limits.queryConditions.mapFirstCondition()
TableSize -> Criteria.TableSizes.queryConditions.mapFirstCondition()
CashOrTournament -> Criteria.SessionTypes.queryConditions(realm).mapFirstCondition()
LiveOrOnline -> Criteria.BankrollTypes.queryConditions(realm).mapFirstCondition()
Game -> Criteria.Games.queryConditions(realm).mapFirstCondition()
LimitType -> Criteria.Limits.queryConditions(realm).mapFirstCondition()
TableSize -> Criteria.TableSizes.queryConditions(realm).mapFirstCondition()
// Date
DynamicDate -> arrayListOf(
QueryCondition.IsToday,
@ -120,9 +119,9 @@ sealed class FilterSectionRow(override val resId: Int?) : RowRepresentable {
QueryCondition.DuringThisYear
)
WeekdayOrWeekend -> arrayListOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd)
Year -> Criteria.Years.queryConditions.mapFirstCondition()
DayOfWeek -> Criteria.DaysOfWeek.queryConditions.mapFirstCondition()
MonthOfYear -> Criteria.MonthsOfYear.queryConditions.mapFirstCondition()
Year -> Criteria.Years.queryConditions(realm).mapFirstCondition()
DayOfWeek -> Criteria.DaysOfWeek.queryConditions(realm).mapFirstCondition()
MonthOfYear -> Criteria.MonthsOfYear.queryConditions(realm).mapFirstCondition()
// Duration
SessionDuration -> QueryCondition.moreEqualOrLessEqual<QueryCondition.Duration>()
@ -135,20 +134,20 @@ sealed class FilterSectionRow(override val resId: Int?) : RowRepresentable {
//Sessions -> arrayListOf(QueryCondition.LastGame(), QueryCondition.LastSession())
// Cash
Stakes -> Criteria.Stakes.queryConditions.mapFirstCondition()
Stakes -> Criteria.Stakes.queryConditions(realm).mapFirstCondition()
// CashRebuyCount -> QueryCondition.moreOrLess<QueryCondition.Rebuy>()
// Tournament
TournamentType -> Criteria.TournamentTypes.queryConditions.mapFirstCondition()
TournamentType -> Criteria.TournamentTypes.queryConditions(realm).mapFirstCondition()
// CompletionPercentage -> arrayListOf()
TournamentFinalPosition -> QueryCondition.moreEqualOrLessEqual<QueryCondition.TournamentFinalPosition>()
TournamentNumberOfPlayer -> QueryCondition.moreEqualOrLessEqual<QueryCondition.TournamentNumberOfPlayer>()
TournamentEntryFee -> Criteria.TournamentFees.queryConditions.mapFirstCondition()
TournamentName -> Criteria.TournamentNames.queryConditions.mapFirstCondition()
TournamentFeature -> Criteria.TournamentFeatures.queryConditions.mapFirstCondition()
Location -> Criteria.Locations.queryConditions.mapFirstCondition()
Bankroll -> Criteria.Bankrolls.queryConditions.mapFirstCondition()
TournamentEntryFee -> Criteria.TournamentFees.queryConditions(realm).mapFirstCondition()
TournamentName -> Criteria.TournamentNames.queryConditions(realm).mapFirstCondition()
TournamentFeature -> Criteria.TournamentFeatures.queryConditions(realm).mapFirstCondition()
Location -> Criteria.Locations.queryConditions(realm).mapFirstCondition()
Bankroll -> Criteria.Bankrolls.queryConditions(realm).mapFirstCondition()
MultiTabling -> QueryCondition.moreEqualOrLessEqual<QueryCondition.NumberOfTable>()
//NumberOfPlayers -> QueryCondition.moreOrLess<QueryCondition.TournamentNumberOfPlayer>()
NumberOfRebuy -> QueryCondition.moreEqualOrLessEqual<QueryCondition.NumberOfRebuy>()
@ -158,12 +157,12 @@ sealed class FilterSectionRow(override val resId: Int?) : RowRepresentable {
addAll(QueryCondition.moreEqualOrLessEqual<QueryCondition.NetAmountWon>())
addAll(QueryCondition.moreEqualOrLessEqual<QueryCondition.NetAmountLost>())
}
TransactionType -> Criteria.TransactionTypes.queryConditions.mapFirstCondition()
TransactionType -> Criteria.TransactionTypes.queryConditions(realm).mapFirstCondition()
is CustomField -> {
val cf = this@FilterSectionRow.customField
when {
cf.isListType -> {
Criteria.ListCustomFields(cf.id).queryConditions.mapFirstCondition()
Criteria.ListCustomFields(cf.id).queryConditions(realm).mapFirstCondition()
}
cf.isAmountType -> {
QueryCondition.moreEqualOrLessEqual<QueryCondition.CustomFieldAmountQuery>().apply {

@ -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
// }
//}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save