Merge branch 'dev'

feature/top10
Laurent 7 years ago
commit 3984357077
  1. 33
      app/build.gradle
  2. 11
      app/src/androidTest/java/net/pokeranalytics/android/model/CriteriaTest.kt
  3. 2
      app/src/androidTest/java/net/pokeranalytics/android/performanceTests/PerfsInstrumentedUnitTest.kt
  4. 16
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt
  5. 3
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/DeleteInstrumentedUnitTest.kt
  6. 12
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt
  7. 13
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt
  8. 85
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/CustomFieldFilterInstrumentedUnitTest.kt
  9. 58
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt
  10. 2
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt
  11. 54
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt
  12. 57
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/TransactionFilterInstrumentedUnitTest.kt
  13. 57
      app/src/main/AndroidManifest.xml
  14. 16
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  15. 2
      app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt
  16. 40
      app/src/main/java/net/pokeranalytics/android/calculus/AggregationType.kt
  17. 12
      app/src/main/java/net/pokeranalytics/android/calculus/Aggregator.kt
  18. 190
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  19. 70
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  20. 163
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  21. 19
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt
  22. 152
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt
  23. 3
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  24. 229
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  25. 19
      app/src/main/java/net/pokeranalytics/android/model/Limit.kt
  26. 154
      app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
  27. 13
      app/src/main/java/net/pokeranalytics/android/model/TableSize.kt
  28. 11
      app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt
  29. 7
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  30. 42
      app/src/main/java/net/pokeranalytics/android/model/filter/Query.kt
  31. 615
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  32. 32
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt
  33. 2
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  34. 26
      app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt
  35. 94
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  36. 57
      app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt
  37. 304
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt
  38. 148
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt
  39. 98
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  40. 8
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt
  41. 3
      app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt
  42. 5
      app/src/main/java/net/pokeranalytics/android/model/realm/Location.kt
  43. 91
      app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt
  44. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt
  45. 192
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  46. 15
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  47. 4
      app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt
  48. 5
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt
  49. 5
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentName.kt
  50. 85
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  51. 44
      app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt
  52. 2
      app/src/main/java/net/pokeranalytics/android/model/retrofit/ConvertResult.kt
  53. 7
      app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt
  54. 12
      app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt
  55. 6
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt
  56. 21
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt
  57. 57
      app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt
  58. 48
      app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollDetailsActivity.kt
  59. 31
      app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt
  60. 36
      app/src/main/java/net/pokeranalytics/android/ui/activity/ComparisonReportActivity.kt
  61. 29
      app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt
  62. 12
      app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt
  63. 6
      app/src/main/java/net/pokeranalytics/android/ui/activity/FilterDetailsActivity.kt
  64. 34
      app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt
  65. 70
      app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt
  66. 159
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  67. 86
      app/src/main/java/net/pokeranalytics/android/ui/activity/ImportActivity.kt
  68. 127
      app/src/main/java/net/pokeranalytics/android/ui/activity/NewDataMenuActivity.kt
  69. 63
      app/src/main/java/net/pokeranalytics/android/ui/activity/ProgressReportActivity.kt
  70. 29
      app/src/main/java/net/pokeranalytics/android/ui/activity/ReportCreationActivity.kt
  71. 55
      app/src/main/java/net/pokeranalytics/android/ui/activity/ReportDetailsActivity.kt
  72. 15
      app/src/main/java/net/pokeranalytics/android/ui/activity/SessionActivity.kt
  73. 59
      app/src/main/java/net/pokeranalytics/android/ui/activity/StatisticDetailsActivity.kt
  74. 35
      app/src/main/java/net/pokeranalytics/android/ui/activity/TableReportActivity.kt
  75. 13
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt
  76. 36
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/PokerAnalyticsActivity.kt
  77. 37
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/ReportActivity.kt
  78. 4
      app/src/main/java/net/pokeranalytics/android/ui/adapter/ComparisonChartPagerAdapter.kt
  79. 19
      app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedSessionRowRepresentableAdapter.kt
  80. 163
      app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedTransactionRowRepresentableAdapter.kt
  81. 8
      app/src/main/java/net/pokeranalytics/android/ui/adapter/HomePagerAdapter.kt
  82. 4
      app/src/main/java/net/pokeranalytics/android/ui/adapter/ReportPagerAdapter.kt
  83. 11
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt
  84. 32
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt
  85. 36
      app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
  86. 167
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDetailsFragment.kt
  87. 180
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt
  88. 51
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarDetailsFragment.kt
  89. 46
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt
  90. 18
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ComparisonChartFragment.kt
  91. 14
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CurrenciesFragment.kt
  92. 191
      app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
  93. 222
      app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt
  94. 377
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt
  95. 93
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt
  96. 173
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt
  97. 28
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  98. 152
      app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt
  99. 84
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt
  100. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/MoreFragment.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -4,10 +4,11 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
apply plugin: 'io.fabric'
apply plugin: 'com.google.gms.google-services' // Crashlytics
repositories {
maven { url 'https://maven.fabric.io/public' }
maven { url 'https://jitpack.io' }
maven { url 'https://jitpack.io' } // required for MPAndroidChart
}
android {
@ -28,12 +29,15 @@ android {
applicationId "net.pokeranalytics.android"
minSdkVersion 23
targetSdkVersion 28
versionCode 27
versionName "1.0"
versionCode 30
versionName "2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
ext.enableCrashlytics = false
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@ -56,8 +60,10 @@ android {
}
configurations {
release {
all*.exclude group: 'com.google.guava', module: 'listenablefuture'
}
}
}
@ -72,7 +78,7 @@ dependencies {
// Android
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.1.0-alpha05'
implementation 'androidx.core:core-ktx:1.2.0-alpha01'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
@ -87,11 +93,16 @@ dependencies {
// Places
implementation 'com.google.android.libraries.places:places:1.1.0'
// Billing / Subscriptions
// WARNING FOR 2.0: https://developer.android.com/google/play/billing/billing_library_releases_notes
// Purchases MUST BE ACKNOWLEDGED
implementation 'com.android.billingclient:billing:1.2.2'
// Firebase
implementation 'com.google.firebase:firebase-core:16.0.8'
implementation 'com.google.firebase:firebase-core:16.0.9'
// Crashlytics
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
// Logs
implementation 'com.jakewharton.timber:timber:4.7.1'
@ -99,6 +110,9 @@ dependencies {
// MPAndroidChart
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
// CSV Parser: https://mvnrepository.com/artifact/org.apache.commons/commons-csv
implementation 'org.apache.commons:commons-csv:1.6'
// Instrumented Tests
androidTestImplementation 'androidx.test:core:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1'
@ -107,15 +121,8 @@ dependencies {
// Test
testImplementation 'junit:junit:4.12'
// Optional -- Robolectric environment
//testImplementation 'androidx.test:core:1.1.0'
// Optional -- Mockito framework
testImplementation 'com.android.support.test:runner:1.0.2'
testImplementation 'com.android.support.test:rules:1.0.2'
//testImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}
apply plugin: 'com.google.gms.google-services'

@ -1,14 +1,15 @@
package net.pokeranalytics.android.model
import android.content.Context
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Session
import org.junit.Assert.assertEquals
import org.junit.Test
import java.util.*
import androidx.test.platform.app.InstrumentationRegistry
class CriteriaTest : BaseFilterInstrumentedUnitTest() {
@Test
fun getQueryConditions() {
@ -57,9 +58,12 @@ class CriteriaTest : BaseFilterInstrumentedUnitTest() {
val criterias = listOf(Criteria.MonthsOfYear, Criteria.DaysOfWeek)
val combined = criterias.combined()
val context = InstrumentationRegistry.getInstrumentation().context
combined.forEach {
it.conditions.forEach {qc->
println(qc.getDisplayName())
println(qc.getDisplayName(context))
}
}
}
@ -86,11 +90,12 @@ class CriteriaTest : BaseFilterInstrumentedUnitTest() {
val lastValue = firstValue + 10
realm.commitTransaction()
val context = InstrumentationRegistry.getInstrumentation().context
val allMonths = Criteria.AllMonthsUpToNow.queries
allMonths.forEach {
it.conditions.forEach { qc->
println("<<<<< ${qc.getDisplayName()}")
println("<<<<< ${qc.getDisplayName(context)}")
}
}
}

@ -58,7 +58,7 @@ class PerfsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
val group = ComputableGroup(Query(), stats)
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
Timber.d("*** ended in ${System.currentTimeMillis() - start} milliseconds")

@ -54,12 +54,12 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString())
t1.amount = 100.0
t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm)
br1.transactions.add(t1)
t1.bankroll = br1
val t2 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString())
t2.amount = 500.0
t2.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm)
br2.transactions.add(t2)
t2.bankroll = br2
val s1 = newSessionInstance(realm)
s1.bankroll = br1
@ -73,16 +73,16 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val br1 = realm.where(Bankroll::class.java).equalTo("name", "br1").findFirst()
val brSetup1 = BankrollReportSetup(br1)
val report1 = BankrollCalculator.computeReport(brSetup1)
val report1 = BankrollCalculator.computeReport(realm, brSetup1)
Assert.assertEquals(400.0, report1.total, EPSILON)
val br2 = realm.where(Bankroll::class.java).equalTo("name", "br2").findFirst()
val brSetup2 = BankrollReportSetup(br2)
val report2 = BankrollCalculator.computeReport(brSetup2)
val report2 = BankrollCalculator.computeReport(realm, brSetup2)
Assert.assertEquals(2000.0, report2.total, EPSILON)
val brSetupAll = BankrollReportSetup()
val reportAll = BankrollCalculator.computeReport(brSetupAll)
val reportAll = BankrollCalculator.computeReport(realm, brSetupAll)
Assert.assertEquals(2400.0, reportAll.total, EPSILON)
}
@ -108,7 +108,7 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString())
t1.amount = 100.0
t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm)
br1?.transactions?.add(t1)
t1.bankroll = br1
val s1 = newSessionInstance(realm)
s1.bankroll = br1
@ -117,11 +117,11 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
}
val brSetup1 = BankrollReportSetup(br1)
val report1 = BankrollCalculator.computeReport(brSetup1)
val report1 = BankrollCalculator.computeReport(realm, brSetup1)
Assert.assertEquals(400.0, report1.total, EPSILON)
val brSetupAll = BankrollReportSetup()
val reportAll = BankrollCalculator.computeReport(brSetupAll)
val reportAll = BankrollCalculator.computeReport(realm, brSetupAll)
Assert.assertEquals(4000.0, reportAll.total, EPSILON)
}

@ -3,6 +3,7 @@ package net.pokeranalytics.android.unitTests
import net.pokeranalytics.android.components.RealmInstrumentedUnitTest
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.util.extensions.findById
import org.junit.Assert
import org.junit.Test
@ -40,7 +41,7 @@ class DeleteInstrumentedUnitTest : RealmInstrumentedUnitTest() {
var isValidForDelete = br1.isValidForDelete(realm)
Assert.assertEquals(false, isValidForDelete)
realm.where(Bankroll::class.java).equalTo("id", "1").findFirst()?.let {
realm.findById(Bankroll::class.java, "1")?.let {
isValidForDelete = it.isValidForDelete(realm)
Assert.assertEquals(false, isValidForDelete)

@ -76,7 +76,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query())
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION,
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION,
Stat.LONGEST_STREAKS, Stat.LOCATIONS_PLAYED, Stat.DAYS_PLAYED)
val results: ComputedResults = Calculator.compute(realm, group, options)
@ -255,7 +255,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(), stats)
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01
@ -322,7 +322,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(), stats)
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01
@ -405,7 +405,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(), stats)
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01
@ -728,7 +728,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(QueryCondition.IsCash))
val options = Calculator.Options()
options.displayedStats = listOf(Stat.HOURLY_DURATION)
options.stats = listOf(Stat.HOURLY_DURATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01
@ -773,7 +773,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(QueryCondition.IsCash))
val options = Calculator.Options()
options.displayedStats = listOf(Stat.HOURLY_DURATION)
options.stats = listOf(Stat.HOURLY_DURATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01

@ -47,7 +47,7 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
listOfValues = arrayListOf(s1.blinds!!)
}
blind.filterSectionRow = FilterSectionRow.BLIND
blind.filterSectionRow = FilterSectionRow.Blind
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueBy(filterElement)
@ -97,8 +97,8 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
listOfValues = arrayListOf(s2.blinds!!)
}
blind1.filterSectionRow = FilterSectionRow.BLIND
blind2.filterSectionRow = FilterSectionRow.BLIND
blind1.filterSectionRow = FilterSectionRow.Blind
blind2.filterSectionRow = FilterSectionRow.Blind
val filterElements = FilterCondition(filterElementRows = arrayListOf(blind1, blind2))
filter.updateValueBy(filterElements)
@ -146,11 +146,10 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
listOfValues = arrayListOf(s3.blinds!!)
}
blind.filterSectionRow = FilterSectionRow.BLIND
blind.filterSectionRow = FilterSectionRow.Blind
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueBy(filterElement)
println("<<<< ${filter.listOfValues}")
val sessions = Filter.queryOn<Session>(realm, Query(filter))
Assert.assertEquals(1, sessions.size)
@ -197,8 +196,8 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
listOfValues = arrayListOf(s2.blinds!!)
}
blind1.filterSectionRow = FilterSectionRow.BLIND
blind2.filterSectionRow = FilterSectionRow.BLIND
blind1.filterSectionRow = FilterSectionRow.Blind
blind2.filterSectionRow = FilterSectionRow.Blind
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind1, blind2))
filter.updateValueBy(filterElement)

@ -0,0 +1,85 @@
package net.pokeranalytics.android.unitTests.filter
import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.CustomField
import net.pokeranalytics.android.model.realm.CustomFieldEntry
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.Session
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import java.util.*
@RunWith(AndroidJUnit4::class)
class CustomFieldFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
@Test
fun testCustomFieldListFilter() {
val realm = this.mockRealm
realm.beginTransaction()
val cf1 = CustomField()
cf1.id = "1"
cf1.type = CustomField.Type.LIST.ordinal
val cfe1 = CustomFieldEntry()
val cfe2 = CustomFieldEntry()
cfe1.value = "super"
cfe2.value = "nul"
cf1.entries.add(cfe1)
cf1.entries.add(cfe2)
val s1 = Session.testInstance(100.0, false, Date(), 1)
s1.customFieldEntries.add(cfe1)
val s2 = Session.testInstance(100.0, true, Date(), 1)
s2.customFieldEntries.add(cfe2)
realm.commitTransaction()
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.CustomFieldListQuery(cfe2)))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
Assert.assertEquals(s2.id, (this).id)
}
}
@Test
fun testCustomFieldAmountFilter() {
val realm = this.mockRealm
realm.beginTransaction()
val cf1 = CustomField()
cf1.id = "1234"
cf1.type = CustomField.Type.AMOUNT.ordinal
val cfe1 = CustomFieldEntry()
cfe1.id = "999"
cf1.entries.add(cfe1)
cfe1.numericValue = 30.0
val cfe2 = CustomFieldEntry()
cfe2.id = "888"
cf1.entries.add(cfe2)
cfe2.numericValue = 100.0
val s1 = Session.testInstance(100.0, false, Date(), 1)
s1.customFieldEntries.add(cfe1)
val s2 = Session.testInstance(100.0, true, Date(), 1)
s2.customFieldEntries.add(cfe2)
realm.commitTransaction()
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.CustomFieldNumberQuery(cf1.id, 100.0)))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
Assert.assertEquals(s2.id, (this).id)
}
}
}

@ -8,6 +8,7 @@ import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.FilterCondition
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.util.extensions.hourMinute
import net.pokeranalytics.android.util.extensions.startOfDay
import org.junit.Assert
import org.junit.Test
@ -34,7 +35,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
cal.time = s1.startDate
val filterElementRow = QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(cal.get(Calendar.DAY_OF_WEEK)) }
filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
filterElementRow.filterSectionRow = FilterSectionRow.DynamicDate
val filterElement = FilterCondition(arrayListOf(filterElementRow))
filter.updateValueBy(filterElement)
@ -63,7 +64,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
cal.time = s1.startDate
val filterElementRow = QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(cal.get(Calendar.MONTH)) }
filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
filterElementRow.filterSectionRow = FilterSectionRow.DynamicDate
val filterElement = FilterCondition(arrayListOf(filterElementRow))
filter.updateValueBy(filterElement)
@ -91,7 +92,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyYear()
cal.time = s1.startDate
val filterElementRow = QueryCondition.AnyYear().apply { listOfValues = arrayListOf(cal.get(Calendar.YEAR)) }
filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
filterElementRow.filterSectionRow = FilterSectionRow.DynamicDate
val filterElement = FilterCondition(arrayListOf(filterElementRow))
filter.updateValueBy(filterElement)
@ -379,13 +380,13 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, false, cal.time, 1)
cal.add(Calendar.HOUR_OF_DAY, 2) // adds one hour
cal.add(Calendar.DAY_OF_YEAR, 2) // adds one hour
val s2 = Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction()
val filter = QueryCondition.StartedFromDate()
val filterElementRow = QueryCondition.StartedFromDate().apply { singleValue = s2.startDate!!}
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filterElementRow.filterSectionRow = FilterSectionRow.FixedDate
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -406,14 +407,14 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
cal.time = Date() // sets calendar time/date
val s1 = Session.testInstance(100.0, false, cal.time, 1)
cal.add(Calendar.HOUR_OF_DAY, 2) // adds one hour
cal.add(Calendar.DAY_OF_YEAR, 2) // adds one hour
Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction()
val filter = QueryCondition.StartedToDate()
val filterElementRow = QueryCondition.StartedToDate().apply { singleValue = s1.startDate!! }
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filterElementRow.filterSectionRow = FilterSectionRow.FixedDate
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -435,14 +436,14 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, false, cal.time, 1)
cal.add(Calendar.HOUR_OF_DAY, 2) // adds one hour
cal.add(Calendar.DAY_OF_YEAR, 2) // adds one hour
val s2 = Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction()
val filter = QueryCondition.EndedFromDate()
val filterElementRow = QueryCondition.EndedFromDate().apply { singleValue = s2.endDate() }
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filterElementRow.filterSectionRow = FilterSectionRow.FixedDate
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -463,7 +464,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
cal.time = Date() // sets calendar time/date
val s1 = Session.testInstance(100.0, false, cal.time, 1)
cal.add(Calendar.HOUR_OF_DAY, 2) // adds one hour
cal.add(Calendar.DAY_OF_YEAR, 2) // adds one hour
Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction()
@ -471,7 +472,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.EndedToDate()
val filterElementRow = QueryCondition.EndedToDate().apply { singleValue = s1.endDate() }
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filterElementRow.filterSectionRow = FilterSectionRow.FixedDate
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -481,4 +482,39 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertEquals(s1.id, (this).id)
}
}
@Test
fun testFomTimeToTime() {
val realm = this.mockRealm
realm.beginTransaction()
val cal = Calendar.getInstance() // creates calendar
cal.time = Date() // sets calendar time/date
cal.set(Calendar.HOUR_OF_DAY, 14) // adds one hour
println("<<<<< s1 ${cal.hourMinute()}")
val s1 = Session.testInstance(100.0, false, cal.time, 1)
println("<<<<< s1 ${cal.hourMinute()}")
cal.add(Calendar.HOUR_OF_DAY, 2) // adds one hour
println("<<<<< s2 ${cal.hourMinute()}")
val s2 = Session.testInstance(100.0, true, cal.time, 1)
println("<<<<< s2 ${cal.hourMinute()}")
cal.set(Calendar.HOUR_OF_DAY, 23) // adds one hour
println("<<<<< s3 ${cal.hourMinute()}")
val s3 = Session.testInstance(100.0, true, cal.time, 2)
println("<<<<< s3 ${cal.hourMinute()}")
realm.commitTransaction()
val filter = QueryCondition.StartedFromTime(s2.startDate!!)
val filter2 = QueryCondition.EndedToTime(s2.endDate!!)
val sessions = Filter.queryOn<Session>(realm, Query(filter, filter2))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
Assert.assertEquals(s2.id, (this).id)
}
}
}

@ -24,7 +24,7 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
filter.name = "testSaveLoadCashFilter"
val filterElement = QueryCondition.IsCash
filterElement.filterSectionRow = FilterSectionRow.CASH_TOURNAMENT
filterElement.filterSectionRow = FilterSectionRow.CashOrTournament
filter.createOrUpdateFilterConditions(arrayListOf(filterElement))
val useCount = filter.countBy(FilterCategoryRow.GENERAL)

@ -110,7 +110,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyBankroll()
val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) }
filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL
filterElementRow.filterSectionRow = FilterSectionRow.Bankroll
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -141,10 +141,10 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyBankroll()
val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) }
filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL
filterElementRow.filterSectionRow = FilterSectionRow.Bankroll
val filterElementRow2 = QueryCondition.AnyBankroll().apply { setObject(b2) }
filterElementRow2.filterSectionRow = FilterSectionRow.BANKROLL
filterElementRow2.filterSectionRow = FilterSectionRow.Bankroll
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -197,9 +197,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction()
val filterElementRow = QueryCondition.AnyGame().apply { setObject(g2) }
filterElementRow.filterSectionRow = FilterSectionRow.GAME
filterElementRow.filterSectionRow = FilterSectionRow.Game
val filterElementRow2 = QueryCondition.AnyGame().apply { setObject(g3) }
filterElementRow2.filterSectionRow = FilterSectionRow.GAME
filterElementRow2.filterSectionRow = FilterSectionRow.Game
val filterCondition = FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))
val queryCondition = filterCondition.queryCondition
val sessions = Filter.queryOn<Session>(realm, Query(queryCondition))
@ -225,7 +225,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyLocation()
val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) }
filterElementRow.filterSectionRow = FilterSectionRow.LOCATION
filterElementRow.filterSectionRow = FilterSectionRow.Location
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -257,9 +257,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyLocation()
val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) }
filterElementRow.filterSectionRow = FilterSectionRow.LOCATION
filterElementRow.filterSectionRow = FilterSectionRow.Location
val filterElementRow2 = QueryCondition.AnyLocation().apply { setObject(l3) }
filterElementRow2.filterSectionRow = FilterSectionRow.LOCATION
filterElementRow2.filterSectionRow = FilterSectionRow.Location
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
@ -287,7 +287,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTournamentName()
val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME
filterElementRow.filterSectionRow = FilterSectionRow.TournamentName
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -318,9 +318,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTournamentName()
val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME
filterElementRow.filterSectionRow = FilterSectionRow.TournamentName
val filterElementRow2 = QueryCondition.AnyTournamentName().apply { setObject(t2) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME
filterElementRow.filterSectionRow = FilterSectionRow.TournamentName
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -354,11 +354,11 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AllTournamentFeature()
val filterElementRow = QueryCondition.AllTournamentFeature().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filterElementRow.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow2 = QueryCondition.AllTournamentFeature().apply { setObject(t2) }
filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filterElementRow2.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow3 = QueryCondition.AllTournamentFeature().apply { setObject(t4) }
filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filterElementRow3.filterSectionRow = FilterSectionRow.TournamentFeature
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -389,13 +389,13 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTournamentFeature()
val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filterElementRow.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow2 = QueryCondition.AnyTournamentFeature().apply { setObject(t2) }
filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filterElementRow2.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow3 = QueryCondition.AnyTournamentFeature().apply { setObject(t3) }
filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filterElementRow3.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow4 = QueryCondition.AnyTournamentFeature().apply { setObject(t4) }
filterElementRow4.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filterElementRow4.filterSectionRow = FilterSectionRow.TournamentFeature
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3, filterElementRow4)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -423,7 +423,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTournamentFeature()
val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t2) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filterElementRow.filterSectionRow = FilterSectionRow.TournamentFeature
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -448,9 +448,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTableSize()
val filterElementRow = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(2) }
filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE
filterElementRow.filterSectionRow = FilterSectionRow.TableSize
val filterElementRow2 = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(4) }
filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE
filterElementRow.filterSectionRow = FilterSectionRow.TableSize
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -475,7 +475,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.NetAmountWon()
val filterElementRow = QueryCondition.more<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(204.0) }
filterElementRow.filterSectionRow = FilterSectionRow.VALUE
filterElementRow.filterSectionRow = FilterSectionRow.Value
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filterElementRow))
@ -500,7 +500,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.NetAmountWon()
val filterElementRow = QueryCondition.less<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(540.0) }
filterElementRow.filterSectionRow = FilterSectionRow.VALUE
filterElementRow.filterSectionRow = FilterSectionRow.Value
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -524,13 +524,13 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction()
val filterMore = QueryCondition.NetAmountWon()
val filterElementRow = QueryCondition.more<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(200.0) }
filterElementRow.filterSectionRow = FilterSectionRow.VALUE
val filterElementRow = QueryCondition.more<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(199.0) }
filterElementRow.filterSectionRow = FilterSectionRow.Value
filterMore.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val filterLess = QueryCondition.NetAmountWon()
val filterElementRow2 = QueryCondition.less<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(400.0) }
filterElementRow2.filterSectionRow = FilterSectionRow.VALUE
filterElementRow2.filterSectionRow = FilterSectionRow.Value
filterLess.updateValueBy(FilterCondition(arrayListOf(filterElementRow2)))
val sessions = Filter.queryOn<Session>(realm, Query(filterMore, filterLess))
@ -564,7 +564,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
s4.result!!.buyin = 200.0
realm.commitTransaction()
val filterMore = QueryCondition.NumberOfRebuy(QueryCondition.Operator.MORE, 5.0)
val filterMore = QueryCondition.NumberOfRebuy(QueryCondition.Operator.MORE, 4.0)
val sessions = Filter.queryOn<Session>(realm, Query(filterMore))
Assert.assertEquals(2, sessions.size)

@ -0,0 +1,57 @@
package net.pokeranalytics.android.unitTests.filter
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.realm.RealmList
import io.realm.RealmResults
import net.pokeranalytics.android.R
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import java.util.*
@RunWith(AndroidJUnit4::class)
class TransactionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
@Test
fun testTransactionTypeFilter() {
val context = InstrumentationRegistry.getInstrumentation().context
val realm = this.mockRealm
realm.beginTransaction()
TransactionType.Value.values().forEachIndexed { index, value ->
val type = TransactionType()
val name = "test"
type.name = name
type.additive = value.additive
type.kind = index
type.lock = true
realm.insertOrUpdate(type)
}
val t1: Transaction = realm.createObject(Transaction::class.java, "1")
t1.type = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm)
val t2: Transaction = realm.createObject(Transaction::class.java, "2")
t2.type = TransactionType.getByValue(TransactionType.Value.WITHDRAWAL, realm)
val b1 = realm.createObject(Bankroll::class.java, "1")
t1.bankroll = b1
val b2 = realm.createObject(Bankroll::class.java, "2")
t2.bankroll = b2
realm.commitTransaction()
val transactions = Filter.queryOn<Transaction>(realm, Query(QueryCondition.AnyTransactionType(t1.type!!)))
Assert.assertEquals(1, transactions.size)
transactions[0]?.run {
Assert.assertEquals(t1.type!!.id, (this).type!!.id)
}
}
}

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="net.pokeranalytics.android">
<uses-permission android:name="android.permission.INTERNET" />
@ -21,13 +22,34 @@
<activity
android:name="net.pokeranalytics.android.ui.activity.HomeActivity"
android:launchMode="singleTop"
android:label="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="text/comma-separated-values" />
<data android:mimeType="text/csv" />
</intent-filter>
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.ImportActivity"
android:screenOrientation="portrait"
android:launchMode="singleTop">
</activity>
<activity
@ -36,23 +58,39 @@
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing" />
<activity
android:name="net.pokeranalytics.android.ui.activity.NewDataMenuActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/PokerAnalyticsTheme.MenuDialog" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BankrollActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BankrollDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.SettingsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.StatisticDetailsActivity"
android:name="net.pokeranalytics.android.ui.activity.GraphActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ReportDetailsActivity"
android:name="net.pokeranalytics.android.ui.activity.ProgressReportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ComparisonReportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
@ -96,6 +134,21 @@
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BillingActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ReportCreationActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.TableReportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />

@ -16,26 +16,35 @@ import net.pokeranalytics.android.model.utils.Seed
import net.pokeranalytics.android.util.FakeDataManager
import net.pokeranalytics.android.util.PokerAnalyticsLogs
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.billing.AppGuard
import timber.log.Timber
class PokerAnalyticsApplication : Application() {
override fun onCreate() {
super.onCreate()
UserDefaults.init(this)
// AppGuard / Billing services
AppGuard.load(this.applicationContext)
// Realm
Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(4)
.schemaVersion(6)
.migration(PokerAnalyticsMigration())
.initialData(Seed(this))
.build()
Realm.setDefaultConfiguration(realmConfiguration)
// val realm = Realm.getDefaultInstance()
// realm.executeTransaction {
// realm.where(Session::class.java).findAll().deleteAllFromRealm()
// }
// realm.close()
// Set up Crashlytics, disabled for debug builds
val crashlyticsKit = Crashlytics.Builder()
.core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
@ -44,7 +53,6 @@ class PokerAnalyticsApplication : Application() {
// Initialize Fabric with the debug-disabled crashlytics.
Fabric.with(this, crashlyticsKit)
if (BuildConfig.DEBUG) {
// Logs
Timber.plant(PokerAnalyticsLogs())
@ -55,7 +63,7 @@ class PokerAnalyticsApplication : Application() {
// this.createFakeSessions()
}
Patcher.patchBreaks(applicationContext)
Patcher.patchAll(this.applicationContext)
}
/**

@ -16,7 +16,7 @@ import retrofit2.http.Query
import java.util.concurrent.TimeUnit
/**
* Currency Converter API
* CurrencyCode Converter API
*/
interface CurrencyConverterApi {

@ -0,0 +1,40 @@
package net.pokeranalytics.android.calculus
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.ui.graph.AxisFormatting
enum class AggregationType {
SESSION,
MONTH,
YEAR,
DURATION;
val resId: Int
get() {
return when (this) {
SESSION -> R.string.session
MONTH -> R.string.month
YEAR -> R.string.year
DURATION -> R.string.duration
}
}
val axisFormatting: AxisFormatting
get() {
return when (this) {
DURATION -> AxisFormatting.X_DURATION
else -> AxisFormatting.DEFAULT
}
}
val criterias: List<Criteria>
get() {
return when (this) {
MONTH -> listOf(Criteria.AllMonthsUpToNow)
YEAR -> listOf(Criteria.Years)
else -> listOf()
}
}
}

@ -1,12 +0,0 @@
package net.pokeranalytics.android.calculus
class AggregationParameter<T> {
var values: List<T>? = null
}
class Aggregator {
var parameters: List<AggregationParameter<*>> = listOf()
}

@ -1,6 +1,8 @@
package net.pokeranalytics.android.calculus
import android.content.Context
import io.realm.Realm
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat.*
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined
@ -8,59 +10,114 @@ import net.pokeranalytics.android.model.extensions.hourlyDuration
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.filter
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.ui.activity.ComparisonReportActivity
import net.pokeranalytics.android.ui.activity.ProgressReportActivity
import net.pokeranalytics.android.ui.activity.TableReportActivity
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.util.extensions.startOfDay
import timber.log.Timber
import java.util.*
import kotlin.math.max
import kotlin.math.min
/**
* The class performing stats computation
* The class performing statIds computation
*/
class Calculator {
/**
* The options used for calculations or display
* The options used for calculations and display
*/
class Options(
display: Display = Display.TABLE,
evolutionValues: EvolutionValues = EvolutionValues.NONE,
stats: List<Stat> = listOf(),
aggregationType: AggregationType? = null
var display: Display = Display.TABLE,
progressValues: ProgressValues = ProgressValues.NONE,
var stats: List<Stat> = listOf(),
var criterias: List<Criteria> = listOf(),
var query: Query = Query(),
var filterId: String? = null,
private var aggregationType: AggregationType? = null,
var userGenerated: Boolean = false,
var reportSetupId: String? = null
) {
constructor(display: Display = Display.TABLE,
progressValues: ProgressValues = ProgressValues.NONE,
stats: List<Stat> = listOf(),
criterias: List<Criteria> = listOf(),
filter: Filter? = null,
aggregationType: AggregationType? = null,
userGenerated: Boolean = false,
reportSetupId: String? = null) :
this(display, progressValues, stats, criterias, filter?.query ?: Query(), filter?.id, aggregationType, userGenerated, reportSetupId)
/**
* Specifies whether progress values should be added and their kind
*/
var progressValues: ProgressValues = progressValues
get() {
if (field == ProgressValues.NONE && this.display == Display.PROGRESS) {
return ProgressValues.STANDARD
}
return field
}
init {
this.aggregationType?.let {
this.criterias = it.criterias
}
}
/**
* The way the stats are going to be displayed
* The way the computed stats are going to be displayed
*/
enum class Display {
enum class Display : RowRepresentable {
TABLE,
EVOLUTION,
PROGRESS,
COMPARISON,
MAP,
POLYNOMIAL
POLYNOMIAL;
override val resId: Int?
get() {
return when (this) {
TABLE -> R.string.table
PROGRESS -> R.string.progress
COMPARISON -> R.string.comparison
MAP -> R.string.map
POLYNOMIAL -> null
}
}
val activityClass: Class<*>
get() {
return when (this) {
TABLE -> TableReportActivity::class.java
PROGRESS -> ProgressReportActivity::class.java
COMPARISON -> ComparisonReportActivity::class.java
else -> throw IllegalStateException("undefined activity for report display")
// MAP -> R.string.map
// POLYNOMIAL -> null
}
}
}
/**
* The type of evolution numericValues
*/
enum class EvolutionValues {
enum class ProgressValues {
NONE,
STANDARD,
TIMED
}
var display: Display = display
var evolutionValues: EvolutionValues = evolutionValues
var displayedStats: List<Stat> = stats
var aggregationType: AggregationType? = aggregationType
/**
* This function determines whether the standard deviation should be computed
*/
val computeStandardDeviation: Boolean
get() {
this.displayedStats.forEach {
this.stats.forEach {
if (it == STANDARD_DEVIATION_BB_PER_100_HANDS || it == STANDARD_DEVIATION || it == STANDARD_DEVIATION_HOURLY) {
return true
}
@ -68,29 +125,53 @@ class Calculator {
return false
}
/**
* Whether the longest streaks should be computed
*/
val computeLongestStreak: Boolean
get() {
return this.displayedStats.contains(LONGEST_STREAKS)
return this.stats.contains(LONGEST_STREAKS)
}
/**
* Whether the values should be sorted
*/
val shouldSortValues: Boolean
get() {
return this.evolutionValues != EvolutionValues.NONE || this.computeLongestStreak
return this.progressValues != ProgressValues.NONE || this.computeLongestStreak
}
/**
* Whether the number of locations played should be computed
*/
val computeLocationsPlayed: Boolean
get() {
return this.displayedStats.contains(LOCATIONS_PLAYED)
return this.stats.contains(LOCATIONS_PLAYED)
}
/**
* Whether the number of days played should be computed
*/
val computeDaysPlayed: Boolean
get() {
return this.displayedStats.contains(DAYS_PLAYED)
return this.stats.contains(DAYS_PLAYED)
}
/**
* Whether progress values should be managed at the group level
*/
val shouldManageMultiGroupProgressValues: Boolean
get() {
if (this.aggregationType != null) {
return this.aggregationType == AggregationType.MONTH || this.aggregationType == AggregationType.YEAR
return if (this.aggregationType != null) {
this.aggregationType == AggregationType.MONTH || this.aggregationType == AggregationType.YEAR
} else {
return false
false
}
}
/**
* Returns some default name
*/
fun getName(context: Context): String {
return when (this.stats.size) {
1 -> this.stats.first().localizedTitle(context)
else -> this.query.getName(context)
}
}
@ -106,37 +187,36 @@ class Calculator {
stats: List<Stat>? = null
): Report {
val options = Options(evolutionValues = Options.EvolutionValues.STANDARD, aggregationType = aggregationType)
options.displayedStats = listOf(stat)
val options = Options(
display = Options.Display.PROGRESS,
progressValues = Options.ProgressValues.STANDARD,
stats = listOf(stat),
aggregationType = aggregationType
)
if (aggregationType == AggregationType.DURATION) {
options.evolutionValues = Options.EvolutionValues.TIMED
options.progressValues = Options.ProgressValues.TIMED
}
stats?.let {
options.displayedStats = stats
options.stats = stats
}
return when (aggregationType) {
AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(realm, listOf(group), options)
AggregationType.MONTH, AggregationType.YEAR -> {
this.computeStatsWithCriterias(realm, aggregationType.criterias, group.query, options)
this.computeStats(realm, options)
}
}
}
fun computeStatsWithCriterias(
realm: Realm,
criterias: List<Criteria> = listOf(),
query: Query = Query(),
options: Options = Options()
): Report {
fun computeStats(realm: Realm, options: Options = Options()): Report {
val computableGroups: MutableList<ComputableGroup> = mutableListOf()
criterias.combined().forEach { comparatorQuery ->
options.criterias.combined().forEach { comparatorQuery ->
comparatorQuery.merge(query)
comparatorQuery.merge(options.query)
val group = ComputableGroup(comparatorQuery)
computableGroups.add(group)
@ -144,7 +224,7 @@ class Calculator {
}
if (computableGroups.size == 0) {
val group = ComputableGroup(query)
val group = ComputableGroup(options.query)
computableGroups.add(group)
}
@ -152,18 +232,18 @@ class Calculator {
}
/**
* Computes all stats for list of Session sessionGroup
* Computes all statIds for list of Session sessionGroup
*/
fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options = Options()): Report {
val report = Report(options)
groups.forEach { group ->
val s = Date()
// val s = Date()
// Clean existing computables / sessionSets if group is reused
group.cleanup()
// Computes actual sessionGroup stats
// Computes actual sessionGroup statIds
val results: ComputedResults = this.compute(realm, group, options)
// Computes the compared sessionGroup if existing
@ -181,9 +261,9 @@ class Calculator {
results.finalize() // later treatment, such as evolution numericValues sorting
report.addResults(results)
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> group ${group.name} in $duration seconds")
// val e = Date()
// val duration = (e.time - s.time) / 1000.0
// Timber.d(">>> group ${group.name} in $duration seconds")
}
@ -191,14 +271,14 @@ class Calculator {
}
/**
* Computes stats for a SessionSet
* Computes statIds for a SessionSet
*/
fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options = Options()): ComputedResults {
val results = ComputedResults(computableGroup, options.shouldManageMultiGroupProgressValues)
val computables = computableGroup.computables(realm, options.shouldSortValues)
Timber.d(">>>> Start computing group ${computableGroup.name}, ${computables.size} computables")
// Timber.d(">>>> Start computing group ${computableGroup.name}, ${computables.size} computables")
results.addStat(NUMBER_OF_GAMES, computables.size.toDouble())
val sum = computables.sum(ComputableResult.Field.RATED_NET.identifier).toDouble()
@ -256,7 +336,7 @@ class Calculator {
}
val shouldIterateOverComputables =
(options.evolutionValues == Options.EvolutionValues.STANDARD || options.computeLongestStreak)
(options.progressValues == Options.ProgressValues.STANDARD || options.computeLongestStreak)
// Computable Result
if (shouldIterateOverComputables) {
@ -339,7 +419,7 @@ class Calculator {
var gBBSum: Double? = null
var maxDuration: Double? = null
if (computableGroup.conditions.size == 0) { // SessionSets are fine
if (computableGroup.conditions.isEmpty()) { // SessionSets are fine
gHourlyDuration =
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours)
gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
@ -350,7 +430,7 @@ class Calculator {
}
val shouldIterateOverSets = computableGroup.conditions.isNotEmpty() ||
options.evolutionValues != Options.EvolutionValues.NONE ||
options.progressValues != Options.ProgressValues.NONE ||
options.computeDaysPlayed
// Session Set
@ -381,8 +461,8 @@ class Calculator {
tHourlyRateBB = tBBSum / tHourlyDuration
daysSet.add(sessionSet.startDate.startOfDay())
when (options.evolutionValues) {
Options.EvolutionValues.STANDARD -> {
when (options.progressValues) {
Options.ProgressValues.STANDARD -> {
results.addEvolutionValue(tHourlyRate, stat = HOURLY_RATE, data = sessionSet)
results.addEvolutionValue(tIndex.toDouble(), stat = NUMBER_OF_SETS, data = sessionSet)
results.addEvolutionValue(
@ -403,7 +483,7 @@ class Calculator {
}
}
Options.EvolutionValues.TIMED -> {
Options.ProgressValues.TIMED -> {
results.addEvolutionValue(tRatedNetSum, tHourlyDuration, NET_RESULT, sessionSet)
results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue(

@ -17,6 +17,7 @@ import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.view.DefaultLegendValues
import net.pokeranalytics.android.ui.view.LegendContent
import net.pokeranalytics.android.util.ColorUtils
import net.pokeranalytics.android.util.TextFormat
import kotlin.math.abs
/**
@ -44,7 +45,7 @@ class Report(var options: Calculator.Options) {
*/
fun lineEntries(stat: Stat? = null, context: Context): LineDataSet {
val entries = mutableListOf<Entry>()
val statToUse = stat ?: options.displayedStats.firstOrNull()
val statToUse = stat ?: options.stats.firstOrNull()
val statName = statToUse?.name ?: ""
statToUse?.let {
@ -60,7 +61,7 @@ class Report(var options: Calculator.Options) {
fun barEntries(stat: Stat? = null, context: Context): BarDataSet {
val entries = mutableListOf<BarEntry>()
val statToUse = stat ?: options.displayedStats.firstOrNull()
val statToUse = stat ?: options.stats.firstOrNull()
statToUse?.let {
this._results.forEachIndexed { index, results ->
@ -79,7 +80,7 @@ class Report(var options: Calculator.Options) {
fun multiLineEntries(context: Context): List<LineDataSet> {
val dataSets = mutableListOf<LineDataSet>()
options.displayedStats.forEach { stat ->
options.stats.forEach { stat ->
this._results.forEachIndexed { index, result ->
val ds = result.singleLineEntries(stat, context)
ds.color = ColorUtils.almostRandomColor(index, context)
@ -95,26 +96,22 @@ class Report(var options: Calculator.Options) {
/**
* A sessionGroup of computable items identified by a name
*/
class ComputableGroup(query: Query, stats: List<Stat>? = null) {
class ComputableGroup(var query: Query, var stats: List<Stat>? = null) {
// constructor(query: Query, stats: List<Stat>? = null) : this(query.name, query.conditions)
//
// private constructor(name: String = "", conditions: List<QueryCondition> = listOf(), stats: List<Stat>? = null)
var query: Query = query
/**
* A subgroup used to compute stat variation
*/
var comparedGroup: ComputableGroup? = null
/**
* The display name of the group
* The computed statIds of the comparable sessionGroup
*/
var name: String = ""
get() {
return this.query.name
}
var comparedComputedResults: ComputedResults? = null
/**
* A list of _conditions to get
*/
var conditions: List<QueryCondition> = listOf()
val conditions: List<QueryCondition>
get() {
return this.query.conditions
}
@ -164,21 +161,6 @@ class ComputableGroup(query: Query, stats: List<Stat>? = null) {
return sets
}
/**
* The list of stats to display
*/
var stats: List<Stat>? = stats
/**
* A subgroup used to compute stat variation
*/
var comparedGroup: ComputableGroup? = null
/**
* The computed stats of the comparable sessionGroup
*/
var comparedComputedResults: ComputedResults? = null
fun cleanup() {
this._computables = null
this._sessionSets = null
@ -195,14 +177,14 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
GraphUnderlyingEntry {
/**
* The session group used to computed the stats
* The session group used to computed the statIds
*/
var group: ComputableGroup = group
// The computed stats of the sessionGroup
// The computed statIds of the sessionGroup
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf()
// The map containing all evolution numericValues for all stats
// The map containing all evolution numericValues for all statIds
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf()
private var shouldManageMultiGroupProgressValues = shouldManageMultiGroupProgressValues
@ -221,10 +203,10 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
} else {
Point(value, data = data.objectIdentifier)
}
this._addEvolutionValue(point, stat = stat)
this.addEvolutionValue(point, stat = stat)
}
private fun _addEvolutionValue(point: Point, stat: Stat) {
private fun addEvolutionValue(point: Point, stat: Stat) {
val evolutionValues = this._evolutionValues[stat]
if (evolutionValues != null) {
evolutionValues.add(point)
@ -246,7 +228,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
}
/**
* Adds a [computedStat] to the list of stats
* Adds a [computedStat] to the list of statIds
* Also computes evolution values using the previously computed values
*/
private fun addComputedStat(computedStat: ComputedStat) {
@ -366,9 +348,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
}
fun finalize() {
this.consolidateProgressStats()
}
// MPAndroidChart
@ -388,7 +368,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data))
}
}
return DataSetFactory.lineDataSetInstance(entries, this.group.name, context)
return DataSetFactory.lineDataSetInstance(entries, this.group.query.getName(context), context)
}
fun durationEntries(stat: Stat, context: Context): LineDataSet {
@ -451,7 +431,9 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
// Stat Entry
override val entryTitle: String = this.group.name
override fun entryTitle(context: Context): String {
return this.group.query.getName(context)
}
override fun formattedValue(stat: Stat): TextFormat {
this.computedStat(stat)?.let {
@ -475,12 +457,12 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
DefaultLegendValues(this.entryTitle, totalStatValue)
DefaultLegendValues(this.entryTitle(context), totalStatValue)
}
else -> {
val entryValue = this.formattedValue(stat)
val countValue = this.computedStat(Stat.NUMBER_OF_GAMES)?.format()
DefaultLegendValues(this.entryTitle, entryValue, countValue)
DefaultLegendValues(this.entryTitle(context), entryValue, countValue)
}
}
}
@ -489,12 +471,12 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
DefaultLegendValues(this.entryTitle, totalStatValue)
DefaultLegendValues(this.entryTitle(context), totalStatValue)
}
else -> {
val entryValue = this.formattedValue(stat)
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
DefaultLegendValues(this.entryTitle, entryValue, totalStatValue)
DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue)
}
}

@ -3,12 +3,12 @@ package net.pokeranalytics.android.calculus
import android.content.Context
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FormattingException
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.graph.AxisFormatting
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import net.pokeranalytics.android.util.enumerations.IntSearchable
import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import net.pokeranalytics.android.util.extensions.toCurrency
@ -19,81 +19,57 @@ class StatFormattingException(message: String) : Exception(message) {
}
class ObjectIdentifier(var id: String, var clazz: Class<out Timed>) {
}
/**
* An enum representing all the types of Session statistics
*/
enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable {
NET_RESULT(1),
BB_NET_RESULT(2),
HOURLY_RATE(3),
AVERAGE(4),
NUMBER_OF_SETS(5),
NUMBER_OF_GAMES(6),
HOURLY_DURATION(7),
AVERAGE_HOURLY_DURATION(8),
NET_BB_PER_100_HANDS(9),
HOURLY_RATE_BB(10),
AVERAGE_NET_BB(11),
WIN_RATIO(12),
AVERAGE_BUYIN(13),
ROI(14),
STANDARD_DEVIATION(15),
STANDARD_DEVIATION_HOURLY(16),
STANDARD_DEVIATION_BB_PER_100_HANDS(17),
HANDS_PLAYED(18),
LOCATIONS_PLAYED(19),
LONGEST_STREAKS(20),
MAXIMUM_NETRESULT(21),
MINIMUM_NETRESULT(22),
MAXIMUM_DURATION(23),
DAYS_PLAYED(24),
WINNING_SESSION_COUNT(25),
BB_SESSION_COUNT(26),
TOTAL_BUYIN(27),
RISK_OF_RUIN(28),
;
enum class AggregationType {
SESSION,
MONTH,
YEAR,
DURATION;
companion object : IntSearchable<Stat> {
val resId: Int
get() {
return when (this) {
SESSION -> R.string.session
MONTH -> R.string.month
YEAR -> R.string.year
DURATION -> R.string.duration
}
override fun valuesInternal(): Array<Stat> {
return values()
}
val axisFormatting: AxisFormatting
val userSelectableList: List<Stat>
get() {
return when (this) {
DURATION -> AxisFormatting.X_DURATION
else -> AxisFormatting.DEFAULT
}
return values().filter { it.canBeUserSelected }
}
val criterias: List<Criteria>
val evolutionValuesList: List<Stat>
get() {
return when (this) {
MONTH -> listOf(Criteria.AllMonthsUpToNow)
YEAR -> listOf(Criteria.Years)
else -> listOf()
}
}
return values().filter { it.hasProgressValues }
}
/**
* An enum representing all the types of Session statistics
*/
enum class Stat : RowRepresentable {
NET_RESULT,
BB_NET_RESULT,
HOURLY_RATE,
AVERAGE,
NUMBER_OF_SETS,
NUMBER_OF_GAMES,
HOURLY_DURATION,
AVERAGE_HOURLY_DURATION,
NET_BB_PER_100_HANDS,
HOURLY_RATE_BB,
AVERAGE_NET_BB,
WIN_RATIO,
AVERAGE_BUYIN,
ROI,
STANDARD_DEVIATION,
STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS,
HANDS_PLAYED,
LOCATIONS_PLAYED,
LONGEST_STREAKS,
MAXIMUM_NETRESULT,
MINIMUM_NETRESULT,
MAXIMUM_DURATION,
DAYS_PLAYED,
WINNING_SESSION_COUNT,
BB_SESSION_COUNT,
TOTAL_BUYIN,
;
companion object {
fun returnOnInvestment(netResult: Double, buyin: Double): Double? {
if (buyin == 0.0) {
return null
@ -150,6 +126,7 @@ enum class Stat : RowRepresentable {
MINIMUM_NETRESULT -> R.string.min_net_result
MAXIMUM_DURATION -> R.string.longest_session
DAYS_PLAYED -> R.string.days_played
TOTAL_BUYIN -> R.string.total_buyin
else -> throw IllegalStateException("Stat ${this.name} name required but undefined")
}
}
@ -171,7 +148,7 @@ enum class Stat : RowRepresentable {
return TextFormat(value.toCurrency(currency), color)
}
// Red/green numericValues
HOURLY_RATE_BB, AVERAGE_NET_BB, NET_BB_PER_100_HANDS -> {
HOURLY_RATE_BB, AVERAGE_NET_BB, NET_BB_PER_100_HANDS, BB_NET_RESULT -> {
val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(value.formatted(), color)
}
@ -182,7 +159,7 @@ enum class Stat : RowRepresentable {
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> {
return TextFormat(value.formattedHourlyDuration())
} // red/green percentages
WIN_RATIO, ROI -> {
WIN_RATIO, ROI, RISK_OF_RUIN -> {
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr
@ -197,7 +174,7 @@ enum class Stat : RowRepresentable {
}
}
val threshold: Double
private val threshold: Double
get() {
return when (this) {
WIN_RATIO -> 50.0
@ -206,6 +183,9 @@ enum class Stat : RowRepresentable {
}
/**
* Returns a label used to display the legend right value, typically a total or an average
*/
fun cumulativeLabelResId(context: Context): String {
val resId = when (this) {
AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS,
@ -226,6 +206,9 @@ enum class Stat : RowRepresentable {
}
}
/**
* Returns the different available aggregation type for each statistic
*/
val aggregationTypes: List<AggregationType>
get() {
return when (this) {
@ -240,7 +223,10 @@ enum class Stat : RowRepresentable {
}
}
val hasEvolutionGraph: Boolean
/**
* Returns if the stat has an evolution graph
*/
val hasProgressGraph: Boolean
get() {
return when (this) {
HOURLY_DURATION, AVERAGE_HOURLY_DURATION,
@ -249,7 +235,10 @@ enum class Stat : RowRepresentable {
}
}
val significantIndividualValue: Boolean
/**
* Returns if the stat has a significant value to display in a progress graph
*/
val graphSignificantIndividualValue: Boolean
get() {
return when (this) {
WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false
@ -257,7 +246,10 @@ enum class Stat : RowRepresentable {
}
}
val shouldShowNumberOfSessions: Boolean
/**
* Returns if the stat graph should show the number of sessions
*/
val graphShouldShowNumberOfSessions: Boolean
get() {
return when (this) {
NUMBER_OF_GAMES, NUMBER_OF_SETS -> false
@ -265,7 +257,7 @@ enum class Stat : RowRepresentable {
}
}
val showXAxisZero: Boolean
val graphShowsXAxisZero: Boolean
get() {
return when (this) {
HOURLY_DURATION -> true
@ -273,7 +265,7 @@ enum class Stat : RowRepresentable {
}
}
val showYAxisZero: Boolean
val graphShowsYAxisZero: Boolean
get() {
return when (this) {
HOURLY_DURATION -> true
@ -281,6 +273,25 @@ enum class Stat : RowRepresentable {
}
}
private val canBeUserSelected: Boolean
get() {
return when (this) {
WINNING_SESSION_COUNT, BB_SESSION_COUNT, RISK_OF_RUIN -> false
else -> true
}
}
private val hasProgressValues: Boolean
get() {
return when (this) {
NET_RESULT, NET_BB_PER_100_HANDS, HOURLY_RATE_BB,
AVERAGE_HOURLY_DURATION, HOURLY_DURATION,
NUMBER_OF_SETS, ROI, AVERAGE_BUYIN, WIN_RATIO,
AVERAGE_NET_BB, NUMBER_OF_GAMES, AVERAGE -> true
else -> false
}
}
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
}

@ -11,12 +11,15 @@ class BankrollCalculator {
companion object {
fun computeReport(setup: BankrollReportSetup) : BankrollReport {
fun computeReport(realm: Realm, setup: BankrollReportSetup) : BankrollReport {
val realm = Realm.getDefaultInstance()
//val realm = Realm.getDefaultInstance()
val report = BankrollReport(setup)
val bankrolls: List<Bankroll> = if (setup.bankroll != null) listOf(setup.bankroll) else realm.where(Bankroll::class.java).findAll()
val bankrolls: List<Bankroll> =
if (setup.bankroll != null) listOf(setup.bankroll)
else realm.where(Bankroll::class.java).findAll()
var initialValue = 0.0
var transactionNet = 0.0
@ -25,8 +28,14 @@ class BankrollCalculator {
val rate = if (setup.virtualBankroll) bankroll.rate else 1.0
if (setup.shouldAddInitialValue) {
initialValue += bankroll.initialValue * rate
transactionNet += bankroll.transactions.sumByDouble { it.amount } * rate
}
bankroll.transactions?.let { transactions ->
val sum = transactions.sum("amount")
transactionNet += rate * sum.toDouble()
}
}
report.transactionsNet = transactionNet
@ -72,7 +81,7 @@ class BankrollCalculator {
report.generateGraphPointsIfNecessary()
realm.close()
//realm.close()
return report
}

@ -9,16 +9,48 @@ import net.pokeranalytics.android.model.interfaces.DatedValue
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.graph.DataSetFactory
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import java.util.*
import kotlin.collections.HashMap
class BankrollReport(setup: BankrollReportSetup) {
//object BankrollReportManager {
//
// var mainReport: BankrollReport? = null
// var reports: MutableMap<String, BankrollReport> = mutableMapOf()
//
// fun udpateBankrolls(bankrolls: List<Bankroll>) {
// this.invalidateMainReport()
// bankrolls.forEach {
// this.reports.remove(it.id)
// }
// }
//
// fun deleteBankrolls(bankrolls: List<Bankroll>) {
// this.invalidateMainReport()
// bankrolls.forEach {
// this.reports.remove(it.id)
// }
// }
//
// private fun invalidateMainReport() {
// this.mainReport = null
// }
//
// private fun launchReports(bankrolls: List<Bankroll>) {
//
// this.mainReport = BankrollCalculator.computeReport()
//
//
// }
//
//}
/**
* The setup used to compute the report
* This class holds the results from the BankrollCalculator computations
* It has all the information required for the Bankroll various displays
*/
var setup: BankrollReportSetup = setup
class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable {
/**
* The value of the bankroll
@ -30,6 +62,10 @@ class BankrollReport(setup: BankrollReportSetup) {
* The initial value of the bankroll, or of all bankrolls if virtual is computed
*/
var initial: Double = 0.0
set(value) {
field = value
this.computeBankrollTotal()
}
/**
* The net result from poker computables
@ -49,7 +85,10 @@ class BankrollReport(setup: BankrollReportSetup) {
this.computeBankrollTotal()
}
fun computeBankrollTotal() {
/**
* Computes the bankroll total
*/
private fun computeBankrollTotal() {
this.total = this.initial + this.netResult + this.transactionsNet
}
@ -59,7 +98,7 @@ class BankrollReport(setup: BankrollReportSetup) {
var depositTotal: Double = 0.0
set(value) {
field = value
this.netBanked = this.depositTotal + this.withdrawalTotal
this.computeNetBanked()
}
/**
@ -68,7 +107,14 @@ class BankrollReport(setup: BankrollReportSetup) {
var withdrawalTotal: Double = 0.0
set(value) {
field = value
this.netBanked = this.depositTotal + this.withdrawalTotal
this.computeNetBanked()
}
/**
* Computes the net banked amount
*/
private fun computeNetBanked() {
this.netBanked = -this.withdrawalTotal - this.depositTotal
}
/**
@ -82,20 +128,47 @@ class BankrollReport(setup: BankrollReportSetup) {
*/
var riskOfRuin: Double? = null
/**
* The list of transactions held by the bankroll
*/
var transactions: List<Transaction> = mutableListOf()
private set
/**
* A map containing TransactionBuckets by transaction types
*/
var transactionBuckets: HashMap<String, TransactionBucket> = HashMap()
private set
var evolutionPoints: MutableList<BRGraphPoint> = mutableListOf()
var evolutionItems: MutableList<DatedValue> = mutableListOf()
private set
/**
* The list of bankroll graph points
*/
private var evolutionPoints: MutableList<BRGraphPoint> = mutableListOf()
/**
* The list of dated items used for the graph
*/
private var evolutionItems: MutableList<DatedValue> = mutableListOf()
override val viewType: Int
get() {
return if (setup.bankroll == null) {
RowViewType.LEGEND_DEFAULT.ordinal
} else {
RowViewType.TITLE_VALUE_ARROW.ordinal
}
}
/**
* Adds a list of dated items to the evolution items used to get the bankroll graph
*/
fun addDatedItems(items: Collection<DatedValue>) {
this.evolutionItems.addAll(items)
}
/**
* Adds a transaction to its type bucket
*/
fun addTransaction(transaction: Transaction) {
transaction.type?.let { type ->
@ -111,11 +184,14 @@ class BankrollReport(setup: BankrollReportSetup) {
bucket.addTransaction(transaction)
} ?: run {
throw Exception("Transaction has no type")
throw IllegalStateException("Transaction has no type")
}
}
/**
* Generates the graph points used for the virtual bankroll
*/
fun generateGraphPointsIfNecessary() {
if (!this.setup.virtualBankroll) {
@ -124,18 +200,23 @@ class BankrollReport(setup: BankrollReportSetup) {
this.evolutionItems.sortBy { it.date }
var total = 0.0
this.evolutionItems.forEach {
val point = BRGraphPoint(it.amount, it.date, it)
total += it.amount
val point = BRGraphPoint(total, it.date, it)
this.evolutionPoints.add(point)
}
}
/**
* Returns a data set used for the bankroll graph
*/
fun lineDataSet(context: Context): LineDataSet {
val entries = mutableListOf<Entry>()
this.evolutionPoints.forEach {
val entry = Entry(it.date.time.toFloat(), it.value.toFloat(), it.data)
this.evolutionPoints.forEachIndexed { index, point ->
val entry = Entry(index.toFloat(), point.value.toFloat(), point.data)
entries.add(entry)
}
return DataSetFactory.lineDataSetInstance(entries, "", context)
@ -149,11 +230,18 @@ class BankrollReport(setup: BankrollReportSetup) {
*/
class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null, val to: Date? = null) {
/**
* Returns whether the setup concerns the virtual bankroll,
* i.e. the bankroll summing all concrete bankrolls
*/
val virtualBankroll: Boolean
get() {
return this.bankroll == null
}
/**
* the query used to get bankroll transactions
*/
val query: Query
get() {
val query = Query()
@ -175,20 +263,46 @@ class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null
return query
}
/**
* Returns whether or not the initial value should be added for the bankroll total
*/
val shouldAddInitialValue: Boolean
get() {
return this.from == null
}
}
/**
* A TransactionBucket holds a list of _transactions and computes its amount sum
*/
class TransactionBucket(useRate: Boolean = false) {
var transactions: MutableList<Transaction> = mutableListOf()
private set
/**
* Whether the bankroll rate should be used
*/
private var useRate: Boolean = useRate
/**
* A list of _transactions
*/
private var _transactions: MutableList<Transaction> = mutableListOf()
val transactions: List<Transaction>
get() {
return this._transactions
}
/**
* The sum of all _transactions
*/
var total: Double = 0.0
private set
var useRate: Boolean = useRate
private set
fun addTransaction(transaction: Transaction) {
this.transactions.add(transaction)
this._transactions.add(transaction)
var rate = 1.0
if (this.useRate) {
rate = transaction.bankroll?.currency?.rate ?: 1.0

@ -8,6 +8,9 @@ class RowRepresentableEditDescriptorException(message: String) : Exception(messa
class ConfigurationException(message: String) : Exception(message)
class EnumIdentifierNotFoundException(message: String) : Exception(message)
class MisconfiguredSavableEnumException(message: String) : Exception(message)
sealed class PokerAnalyticsException(message: String) : Exception(message) {
object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition")
object FilterElementUnknownSectionName: PokerAnalyticsException(message = "No filterElement section name was found to identify the queryCondition")

@ -3,6 +3,7 @@ package net.pokeranalytics.android.model
import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.Criteria.Bankrolls.comparison
import net.pokeranalytics.android.model.Criteria.Blinds.comparison
@ -14,10 +15,15 @@ import net.pokeranalytics.android.model.Criteria.TournamentFeatures.comparison
import net.pokeranalytics.android.model.Criteria.TournamentFees.comparison
import net.pokeranalytics.android.model.Criteria.TournamentNames.comparison
import net.pokeranalytics.android.model.Criteria.TournamentTypes.comparison
import net.pokeranalytics.android.model.Criteria.TransactionTypes.comparison
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.realm.*
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
fun List<Criteria>.combined(): List<Query> {
val comparatorList = ArrayList<List<Query>>()
@ -29,7 +35,9 @@ fun List<Criteria>.combined(): List<Query> {
fun getCombinations(queries: List<List<Query>>): List<Query> {
if (queries.size == 0) { return listOf() }
if (queries.isEmpty()) {
return listOf()
}
val mutableQueries = queries.toMutableList()
var combinations = mutableQueries.removeAt(0)
@ -49,64 +57,75 @@ fun getCombinations(queries: List<List<Query>>): List<Query> {
return combinations
}
//fun getCombinations(lists: List<Query>): List<Query> {
// var combinations: LinkedHashSet<Query> = LinkedHashSet()
// var newCombinations: LinkedHashSet<Query>
//
// var index = 0
//
// // extract each of the integers in the first list
// // and add each to ints as a new list
// if (lists.isNotEmpty()) {
// for (i in lists[0]) {
// val newList = ArrayList<T>()
// newList.add(i)
// combinations.add(newList)
// }
// index++
// }
// while (index < lists.size) {
// val nextList = lists[index]
// newCombinations = LinkedHashSet()
// for (first in combinations) {
// for (second in nextList) {
// val newList = ArrayList<T>()
// newList.addAll(first)
// newList.add(second)
// newCombinations.add(newList)
// }
// }
// combinations = newCombinations
//
// index++
// }
//
// return combinations.toList()
//}
sealed class Criteria {
abstract class RealmCriteria : Criteria() {
sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable {
abstract class RealmCriteria(uniqueIdentifier: Int) : Criteria(uniqueIdentifier) {
inline fun <reified T : NameManageable> comparison(): List<Query> {
if (this is ListCustomFields) {
val objects = mutableListOf<QueryCondition.CustomFieldListQuery>()
val realm = Realm.getDefaultInstance()
realm.findById(CustomField::class.java, this.customFieldId)?.entries?.forEach {
objects.add(QueryCondition.CustomFieldListQuery(it))
}
objects.sorted()
realm.close()
return objects.map { Query(it) }
}
return compare<QueryCondition.QueryDataCondition<NameManageable>, T>()
}
}
abstract class SimpleCriteria(private val conditions:List<QueryCondition>): Criteria() {
abstract class SimpleCriteria(private val conditions: List<QueryCondition>, uniqueIdentifier: Int) : Criteria(uniqueIdentifier) {
fun comparison(): List<Query> {
return conditions.map { Query(it) }
}
}
abstract class ListCriteria : Criteria() {
abstract class ListCriteria(uniqueIdentifier: Int) : Criteria(uniqueIdentifier) {
inline fun <reified T : QueryCondition.ListOfValues<S>, reified S : Comparable<S>> comparison(): List<Query> {
if (this is ValueCustomFields) {
val realm = Realm.getDefaultInstance()
val distincts = realm.where<CustomFieldEntry>().equalTo("customFields.id", this.customFieldId).findAll().sort("numericValue", Sort.ASCENDING)
realm.close()
val objects = mutableListOf<QueryCondition.CustomFieldNumberQuery>()
distincts.mapNotNull {
it.numericValue
}.distinct().forEach {value ->
val condition: QueryCondition.CustomFieldNumberQuery = when (this.customFieldType(realm)) {
CustomField.Type.AMOUNT.uniqueIdentifier -> QueryCondition.CustomFieldAmountQuery()
CustomField.Type.NUMBER.uniqueIdentifier -> QueryCondition.CustomFieldNumberQuery()
else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue
}.apply {
this.customFieldId = this@ListCriteria.customFieldId
listOfValues = arrayListOf(value)
}
objects.add(condition)
}
objects.sorted()
return objects.map { Query(it) }
}
QueryCondition.distinct<Session, T, S>()?.let {
val values = it.mapNotNull { session ->
when (this) {
is Limits -> if (session.limit is S) { session.limit as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TournamentTypes -> if (session.tournamentType is S) { session.tournamentType as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TableSizes -> if (session.tableSize is S) { session.tableSize as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TournamentFees -> if (session.tournamentEntryFee is S) { session.tournamentEntryFee as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is Blinds -> if (session.blinds is S) { session.blinds as S } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is Limits -> if (session.limit is S) {
session.limit as S
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TournamentTypes -> if (session.tournamentType is S) {
session.tournamentType as S
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TableSizes -> if (session.tableSize is S) {
session.tableSize as S
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TournamentFees -> if (session.tournamentEntryFee is S) {
session.tournamentEntryFee as S
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is Blinds -> if (session.blinds is S) {
session.blinds as S
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
else -> null
}
}.distinct()
@ -117,25 +136,34 @@ sealed class Criteria {
}
object Bankrolls: RealmCriteria()
object Games: RealmCriteria()
object TournamentNames: RealmCriteria()
object Locations: RealmCriteria()
object TournamentFeatures: RealmCriteria()
object Limits: ListCriteria()
object TableSizes: ListCriteria()
object TournamentTypes: ListCriteria()
object MonthsOfYear: SimpleCriteria(List(12) { index -> QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index)} })
object DaysOfWeek: SimpleCriteria(List(7) { index -> QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) } })
object SessionTypes: SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament))
object BankrollTypes: SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline))
object DayPeriods: SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd))
object Years: ListCriteria()
object AllMonthsUpToNow: ListCriteria()
object Blinds: ListCriteria()
object TournamentFees: ListCriteria()
object Cash: SimpleCriteria(listOf(QueryCondition.IsCash))
object Tournament: SimpleCriteria(listOf(QueryCondition.IsTournament))
object Bankrolls : RealmCriteria(1)
object Games : RealmCriteria(2)
object TournamentNames : RealmCriteria(3)
object Locations : RealmCriteria(4)
object TournamentFeatures : RealmCriteria(5)
object TransactionTypes : RealmCriteria(6)
object Limits : ListCriteria(7)
object TableSizes : ListCriteria(8)
object TournamentTypes : ListCriteria(9)
object MonthsOfYear : SimpleCriteria(List(12) { index ->
QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index) }
}, 10)
object DaysOfWeek : SimpleCriteria(List(7) { index ->
QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) }
}, 11)
object SessionTypes : SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament), 12)
object BankrollTypes : SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline), 13)
object DayPeriods : SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd), 14)
object Years : ListCriteria(15)
object AllMonthsUpToNow : ListCriteria(16)
object Blinds : ListCriteria(17)
object TournamentFees : ListCriteria(18)
object Cash : SimpleCriteria(listOf(QueryCondition.IsCash), 19)
object Tournament : SimpleCriteria(listOf(QueryCondition.IsTournament), 20)
data class ListCustomFields(override var customFieldId: String) : RealmCriteria(21), CustomFieldCriteria
data class ValueCustomFields(override var customFieldId: String) : ListCriteria(22), CustomFieldCriteria
val queries: List<Query>
get() {
@ -165,7 +193,7 @@ sealed class Criteria {
}
val currentMonth = QueryCondition.AnyMonthOfYear(month)
val query = Query(currentYear, currentMonth)
val query = Query(currentMonth, currentYear)
years.add(query)
}
}
@ -185,6 +213,7 @@ sealed class Criteria {
is TournamentFeatures -> comparison<TournamentFeature>()
is TournamentNames -> comparison<TournamentName>()
is Locations -> comparison<Location>()
is TransactionTypes -> comparison<TransactionType>()
is SimpleCriteria -> comparison()
is Limits -> comparison<QueryCondition.AnyLimit, Int>()
is TournamentTypes -> comparison<QueryCondition.AnyTournamentType, Int>()
@ -208,11 +237,49 @@ sealed class Criteria {
years
}
is Blinds -> comparison<QueryCondition.AnyBlind, 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.QueryTypeUnhandled
}
realm.close()
queries
}
else -> throw PokerAnalyticsException.QueryTypeUnhandled
}
}
override val resId: Int?
get() {
return when (this) {
Bankrolls -> R.string.bankroll
Games -> R.string.game
TournamentNames -> R.string.tournament_name
Locations -> R.string.location
TournamentFeatures -> R.string.tournament_feature
Limits -> R.string.limit
TableSizes -> R.string.table_size
TournamentTypes -> R.string.tournament_type
MonthsOfYear -> R.string.month_of_the_year
DaysOfWeek -> R.string.day_of_the_week
SessionTypes -> R.string.cash_or_tournament
BankrollTypes -> R.string.live_or_online
DayPeriods -> R.string.weekdays_or_weekend
Years -> R.string.year
AllMonthsUpToNow -> R.string.month
Blinds -> R.string.blind
TournamentFees -> R.string.entry_fees
// is ListCustomFields -> this.customField.resId
// is ValueCustomFields -> this.customField.resId
else -> null
}
}
companion object : IntSearchable<Criteria> {
companion object {
inline fun <reified S : QueryCondition.QueryDataCondition<NameManageable>, reified T : NameManageable> compare(): List<Query> {
val objects = mutableListOf<S>()
val realm = Realm.getDefaultInstance()
@ -238,5 +305,35 @@ sealed class Criteria {
objects.sorted()
return objects.map { Query(it) }
}
// SavableEnum
override fun valuesInternal(): Array<Criteria> {
return all.toTypedArray()
}
val all: List<Criteria>
get() {
return listOf(
Bankrolls, Games, TournamentNames, Locations,
TournamentFeatures, Limits, TableSizes, TournamentTypes,
MonthsOfYear, DaysOfWeek, SessionTypes,
BankrollTypes, DayPeriods, Years,
AllMonthsUpToNow, Blinds, TournamentFees
)
}
}
}
interface CustomFieldCriteria {
var customFieldId: String
fun customField(realm: Realm) : CustomField {
return realm.findById(this.customFieldId) ?: throw IllegalStateException("Custom field not found")
}
fun customFieldType(realm: Realm): Int {
return this.customField(realm).type
}
}

@ -1,5 +1,6 @@
package net.pokeranalytics.android.model
import android.content.Context
import net.pokeranalytics.android.ui.view.RowRepresentable
enum class Limit : RowRepresentable {
@ -9,6 +10,21 @@ enum class Limit : RowRepresentable {
SPREAD,
MIXED;
companion object {
fun getInstance(value: String) : Limit? {
return when (value) {
"No Limit" -> NO
"Pot Limit" -> POT
"Fixed Limit", "Limit" -> FIXED
"Mixed Limit" -> MIXED
"Spread Limit" -> SPREAD
else -> null
}
}
}
val shortName: String
get() {
return when (this) {
@ -32,7 +48,8 @@ enum class Limit : RowRepresentable {
}
}
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return this.longName
}
}

@ -2,15 +2,11 @@ package net.pokeranalytics.android.model
import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.Sort
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.view.Localizable
import net.pokeranalytics.android.util.extensions.findById
/**
* An enum managing the business objects related to a realm results
@ -22,76 +18,14 @@ enum class LiveData : Localizable {
TOURNAMENT_NAME,
TOURNAMENT_FEATURE,
TRANSACTION,
TRANSACTION_TYPE;
TRANSACTION_TYPE,
FILTER,
CUSTOM_FIELD,
REPORT_SETUP;
fun items(realm: Realm, fieldName: String? = null, sortOrder: Sort? = null): RealmResults<*> {
var results = realm.where(this.relatedEntity).findAll().sort(fieldName ?: this.sortingFieldName, sortOrder ?: this.sorting)
var subType:Int? = null
if (results.size > 0) {
if (results.first() is CountableUsage) {
this.setUseCount(realm, results)
return results.sort("useCount", Sort.DESCENDING)
}
}
return results
}
fun setUseCount(realm: Realm, realmResults: RealmResults<*>) {
realm.executeTransaction {
realmResults.forEach { countableUsage ->
when (this) {
TOURNAMENT_FEATURE -> {
(countableUsage as CountableUsage).useCount = it.where<Session>().contains(
"tournamentFeatures.id",
countableUsage.id
).count().toInt()
}
else -> {
(countableUsage as CountableUsage).useCount = it.where<Session>().equalTo(
"${relatedEntity.simpleName.decapitalize()}.id",
countableUsage.id
).count().toInt()
}
}
}
}
}
/**
* Return a copy of a RealmResults
*/
fun itemsArray(realm: Realm, fieldName: String? = null, sortOrder: Sort? = null): ArrayList<*> {
val results: ArrayList<Any> = ArrayList()
results.addAll(
realm.copyFromRealm(
realm.where(this.relatedEntity).findAll().sort(
fieldName ?: this.sortingFieldName, sortOrder ?: this.sorting
)
)
)
return results
}
private val sortingFieldName: String
get() {
return when (this) {
TRANSACTION -> "date"
else -> "name"
}
}
private val sorting: Sort
get() {
return when (this) {
TRANSACTION -> Sort.DESCENDING
else -> Sort.ASCENDING
}
}
private val relatedEntity: Class<out RealmObject>
val relatedEntity: Class<out Deletable>
get() {
return when (this) {
BANKROLL -> Bankroll::class.java
@ -101,15 +35,14 @@ enum class LiveData : Localizable {
TOURNAMENT_FEATURE -> TournamentFeature::class.java
TRANSACTION -> Transaction::class.java
TRANSACTION_TYPE -> TransactionType::class.java
FILTER -> Filter::class.java
CUSTOM_FIELD -> CustomField::class.java
REPORT_SETUP -> ReportSetup::class.java
}
}
fun deleteData(realm: Realm, data: Deletable) {
realm.where(this.relatedEntity).equalTo("id", data.id).findAll().deleteAllFromRealm()
}
fun updateOrCreate(realm: Realm, primaryKey: String?): RealmObject {
val proxyItem: RealmObject? = this.getData(realm, primaryKey)
fun updateOrCreate(realm: Realm, primaryKey: String?): Deletable {
val proxyItem: Deletable? = this.getData(realm, primaryKey)
proxyItem?.let {
return realm.copyFromRealm(it)
} ?: run {
@ -117,14 +50,14 @@ enum class LiveData : Localizable {
}
}
fun newEntity(): RealmObject {
private fun newEntity(): Deletable {
return this.relatedEntity.newInstance()
}
fun getData(realm: Realm, primaryKey: String?): RealmObject? {
var proxyItem: RealmObject? = null
fun getData(realm: Realm, primaryKey: String?): Deletable? {
var proxyItem: Deletable? = null
primaryKey?.let {
val t = realm.where(this.relatedEntity).equalTo("id", it).findFirst()
val t = realm.findById(this.relatedEntity, it)
t?.let {
proxyItem = t
}
@ -140,16 +73,67 @@ enum class LiveData : Localizable {
LOCATION -> R.string.location
TOURNAMENT_NAME -> R.string.tournament_name
TOURNAMENT_FEATURE -> R.string.tournament_feature
TRANSACTION -> R.string.operation
TRANSACTION_TYPE -> R.string.operation_type
FILTER -> R.string.filter
CUSTOM_FIELD -> R.string.custom_field
REPORT_SETUP -> R.string.custom
}
}
val pluralResId: Int
get() {
return when (this) {
BANKROLL -> R.string.bankrolls
GAME -> R.string.games
LOCATION -> R.string.locations
TOURNAMENT_NAME -> R.string.tournament_names
TOURNAMENT_FEATURE -> R.string.tournament_features
TRANSACTION -> R.string.operations
TRANSACTION_TYPE -> R.string.operation_types
FILTER -> R.string.filters
CUSTOM_FIELD -> R.string.custom_fields
REPORT_SETUP -> R.string.custom
}
}
private val newResId: Int
get() {
return when (this) {
BANKROLL -> R.string.new_bankroll
GAME -> R.string.new_variant
LOCATION -> R.string.new_location
TOURNAMENT_NAME -> R.string.new_tournament_name
TOURNAMENT_FEATURE -> R.string.new_tournament_feature
TRANSACTION -> R.string.new_operation
TRANSACTION_TYPE -> R.string.new_operation_type
FILTER -> R.string.new_filter
CUSTOM_FIELD -> R.string.new_custom_field
REPORT_SETUP -> R.string.new_report
}
}
/**
* Return the new entity title
* Return the new entity titleResId
*/
fun newEntityLocalizedTitle(context: Context): String {
return "${context.getString(R.string.new_str)} ${this.localizedTitle(context)}"
return context.getString(this.newResId)
}
/**
* Return the update entity titleResId
*/
fun updateEntityLocalizedTitle(context: Context): String {
return context.getString(R.string.update_entity, this.localizedTitle(context).toLowerCase())
}
/**
* Return the update entity titleResId
*/
fun pluralLocalizedTitle(context: Context): String {
return context.getString(this.pluralResId, context)
}
}

@ -6,15 +6,26 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITLE_GRID.ordinal) : RowRepresentable {
companion object {
val all: List<TableSize>
get() {
return Array(9, init =
{ index -> TableSize(index + 2) }).toList()
}
fun valueForLabel(label: String) : Int? {
return when (label) {
"Full Ring", "Full-Ring" -> 10
"Short-Handed", "Short Handed" -> 6
"Heads-Up", "Heads Up" -> 2
else -> null
}
}
}
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return if (this.numberOfPlayer == 2) {
return "HU"
} else {

@ -1,5 +1,6 @@
package net.pokeranalytics.android.model
import android.content.Context
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
@ -13,6 +14,14 @@ enum class TournamentType : RowRepresentable {
get() {
return TournamentType.values() as List<TournamentType>
}
fun getValueForLabel(label: String) : TournamentType? {
return when (label) {
"Single-Table" -> SNG
"Multi-Table" -> MTT
else -> null
}
}
}
override val resId: Int?
@ -23,7 +32,7 @@ enum class TournamentType : RowRepresentable {
}
}
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return when (this) {
MTT -> "MTT"
SNG -> "SNG"

@ -64,15 +64,20 @@ class FilterHelper {
Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition)
Result::class.java -> Result.fieldNameForQueryType(queryCondition)
else -> {
throw UnmanagedFilterField("Filterable type fields are not defined for class ${T::class}")
null
// throw UnmanagedFilterField("Filterable type fields are not defined for class ${T::class}")
}
}
return fieldName
/*
fieldName?.let {
return fieldName
} ?: run {
throw PokerAnalyticsException.MissingFieldNameForQueryCondition(queryCondition.name)
}
*/
}

@ -1,6 +1,10 @@
package net.pokeranalytics.android.model.filter
import android.content.Context
import io.realm.RealmQuery
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.util.NULL_TEXT
fun List<Query>.mapFirstCondition() : List<QueryCondition> {
return this.map { it.conditions.first() }
@ -38,15 +42,49 @@ class Query {
this._conditions.addAll(queryConditions)
}
val name: String
val defaultName: String
get() {
return this._conditions.joinToString(" : ") { it.getDisplayName() }
return when (this._conditions.size) {
0 -> NULL_TEXT
else -> this._conditions.joinToString("") { it.id.joinToString("") }
}
}
fun getName(context: Context): String {
return when (this._conditions.size) {
0 -> context.getString(R.string.all_sessions) // @todo should be dependant of the underlying type, ie. Session, Transaction...
else -> this._conditions.joinToString(" : ") { it.getDisplayName(context) }
}
}
inline fun <reified T : Filterable> queryWith(query: RealmQuery<T>): RealmQuery<T> {
var realmQuery = query
val queryFromTime = this.conditions.filter {
it is QueryCondition.StartedFromTime
}.firstOrNull()
val queryToTime = this.conditions.filter {
it is QueryCondition.EndedToTime
}.firstOrNull()
this.conditions.forEach {
if (it is QueryCondition.StartedFromTime) {
realmQuery = it.queryWith(realmQuery, queryToTime)
} else if (it is QueryCondition.EndedToTime) {
realmQuery = it.queryWith(realmQuery, queryFromTime)
} else {
realmQuery = it.queryWith(realmQuery)
}
}
//println("<<<<<< ${realmQuery}")
val queryLast = this.conditions.filter {
it is QueryCondition.Last
}.firstOrNull()
queryLast?.let {qc ->
(qc as QueryCondition.Last).singleValue?.let {
return realmQuery.limit(it.toLong())
}
}
return realmQuery
}

@ -1,10 +1,12 @@
package net.pokeranalytics.android.model.filter
import android.content.Context
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import io.realm.kotlin.where
import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.Limit
@ -19,10 +21,9 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.endOfDay
import net.pokeranalytics.android.util.extensions.startOfDay
import net.pokeranalytics.android.util.extensions.toCurrency
import net.pokeranalytics.android.util.extensions.*
import java.text.DateFormatSymbols
import java.text.NumberFormat
import java.util.*
import kotlin.collections.ArrayList
@ -34,9 +35,17 @@ import kotlin.collections.ArrayList
sealed class QueryCondition : FilterElementRow {
companion object {
inline fun < reified T:QueryCondition> more():T { return T::class.java.newInstance().apply { this.operator = Operator.MORE } }
inline fun < reified T:QueryCondition> less():T { return T::class.java.newInstance().apply { this.operator = Operator.LESS } }
inline fun < reified T:QueryCondition> moreOrLess():ArrayList<T> { return arrayListOf(more(), less()) }
inline fun <reified T : QueryCondition> more(): T {
return T::class.java.newInstance().apply { this.operator = Operator.MORE }
}
inline fun <reified T : QueryCondition> less(): T {
return T::class.java.newInstance().apply { this.operator = Operator.LESS }
}
inline fun <reified T : QueryCondition> moreOrLess(): ArrayList<T> {
return arrayListOf(more(), less())
}
fun <T : QueryCondition> valueOf(name: String): T {
val kClass = Class.forName("${QueryCondition::class.qualifiedName}$$name").kotlin
@ -49,8 +58,9 @@ sealed class QueryCondition : FilterElementRow {
Bankroll::class.java -> AnyBankroll()
Game::class.java -> AnyGame()
Location::class.java -> AnyLocation()
TransactionType::class.java -> AnyTransactionType()
TournamentName::class.java -> AnyTournamentName()
TournamentFeature::class.java -> AnyTournamentFeature()
TournamentFeature::class.java -> AllTournamentFeature()
else -> throw PokerAnalyticsException.QueryTypeUnhandled
}
}
@ -77,15 +87,22 @@ sealed class QueryCondition : FilterElementRow {
MORE,
LESS,
EQUALS,
BETWEEN,
BETWEEN_RIGHT_EXCLUSIVE,
BETWEEN_LEFT_EXCLUSIVE,
TRUE,
;
}
val baseId = this::class.simpleName ?: throw PokerAnalyticsException.FilterElementUnknownName
val id: List<String> get() {
val groupId: String
get() {
when (this.operator) {
Operator.MORE, Operator.LESS -> return "${this.operator.name.toLowerCase().capitalize()}$baseId"
}
return baseId
}
val id: List<String>
get() {
when (this.operator) {
Operator.MORE, Operator.LESS -> return listOf("$baseId+${this.operator.name}")
}
@ -93,65 +110,86 @@ sealed class QueryCondition : FilterElementRow {
return when (this) {
is SingleValue<*> -> listOf(baseId)
is ListOfValues<*> -> {
if (listOfValues.isEmpty()) { return listOf(baseId) }
if (listOfValues.isEmpty()) {
return listOf(baseId)
}
this.listOfValues.map { "$baseId+$it" }
}
else -> listOf(baseId)
}
}
open var operator: Operator = Operator.ANY
abstract var operator: Operator
abstract class ListOfValues<T> : QueryCondition(), Comparable<ListOfValues<T>> where T : Comparable<T> {
abstract var listOfValues: ArrayList<T>
abstract fun labelForValue(value:T): String
abstract fun labelForValue(value: T, context: Context): String
open fun entityName(context: Context): String {
return getDisplayName(context)
}
override fun getDisplayName(context: Context): String {
val prefix = this.resId?.let {
context.getString(it) + " "
} ?: ""
override fun getDisplayName(): String {
return when (listOfValues.size) {
0 -> return NULL_TEXT
1,2 -> listOfValues.map { labelForValue(it) }.joinToString(", ")
else -> "${listOfValues.size} $baseId"
1, 2 -> prefix + listOfValues.map { labelForValue(it, context) }.joinToString(", ")
else -> "${listOfValues.size} $prefix ${entityName(context)}"
}
}
override fun compareTo(other: ListOfValues<T>): Int {
return listOfValues.sorted().first().compareTo(other.listOfValues.sorted().first())
}
fun firstValue(context: Context): String? {
return this.listOfValues.firstOrNull()?.let { this.labelForValue(it, context) }
}
}
abstract class SingleValue<T> : ListOfValues<T>() where T : Comparable<T> {
override var listOfValues = ArrayList<T>()
abstract var singleValue : T
abstract var singleValue: T?
}
abstract class ListOfDouble : ListOfValues<Double>() {
open var sign: Int = 1
override var listOfValues = arrayListOf(0.0)
override var operator: Operator = Operator.ANY
override var listOfValues: ArrayList<Double> = arrayListOf()
override fun updateValueBy(filterCondition: FilterCondition) {
super.updateValueBy(filterCondition)
listOfValues = filterCondition.getValues()
}
override fun labelForValue(value: Double): String {
override fun labelForValue(value: Double, context: Context): String {
return value.toCurrency(UserDefaults.currency)
}
}
abstract class ListOfInt : ListOfValues<Int>() {
override var listOfValues = arrayListOf(0)
override var operator: Operator = Operator.ANY
override var listOfValues: ArrayList<Int> = arrayListOf()
override fun updateValueBy(filterCondition: FilterCondition) {
super.updateValueBy(filterCondition)
listOfValues = filterCondition.getValues()
}
override fun labelForValue(value: Int): String {
override fun labelForValue(value: Int, context: Context): String {
return value.toString()
}
}
abstract class ListOfString : ListOfValues<String>() {
override var operator: Operator = Operator.ANY
override var listOfValues = ArrayList<String>()
override fun labelForValue(value: String): String { return value }
override fun labelForValue(value: String, context: Context): String {
return value
}
override fun updateValueBy(filterCondition: FilterCondition) {
super.updateValueBy(filterCondition)
listOfValues = filterCondition.getValues()
@ -159,13 +197,20 @@ sealed class QueryCondition : FilterElementRow {
}
abstract class SingleDate : SingleValue<Date>() {
override fun labelForValue(value: Date): String {
return value.toString()
override fun labelForValue(value: Date, context: Context): String {
return value.shortDate()
}
override var singleValue: Date
get() { return listOfValues.firstOrNull() ?: Date() }
set(value) { listOfValues.add(value) }
override var listOfValues = ArrayList<Date>()
override var singleValue: Date?
get() {
return listOfValues.firstOrNull()
}
set(value) {
listOfValues.removeAll(this.listOfValues)
value?.let { listOfValues.add(it) }
}
override fun updateValueBy(filterCondition: FilterCondition) {
super.updateValueBy(filterCondition)
@ -174,12 +219,18 @@ sealed class QueryCondition : FilterElementRow {
}
abstract class SingleInt : SingleValue<Int>() {
override fun labelForValue(value: Int): String {
override fun labelForValue(value: Int, context: Context): String {
return value.toString()
}
override var singleValue: Int
get() { return listOfValues.firstOrNull() ?: 0 }
set(value) { listOfValues.add(value) }
override var singleValue: Int?
get() {
return listOfValues.firstOrNull()
}
set(value) {
listOfValues.removeAll(this.listOfValues)
value?.let { listOfValues.add(it) }
}
override fun updateValueBy(filterCondition: FilterCondition) {
super.updateValueBy(filterCondition)
@ -187,9 +238,14 @@ sealed class QueryCondition : FilterElementRow {
}
}
override fun getDisplayName(): String { return baseId }
override fun getDisplayName(context: Context): String {
this.resId?.let {
return context.getString(it)
}
return baseId
}
override var filterSectionRow: FilterSectionRow = FilterSectionRow.CASH_TOURNAMENT
override var filterSectionRow: FilterSectionRow = FilterSectionRow.CashOrTournament
abstract class QueryDataCondition<T : NameManageable> : ListOfString() {
fun setObject(dataObject: T) {
@ -199,19 +255,24 @@ sealed class QueryCondition : FilterElementRow {
abstract val entity: Class<T>
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
val realm = Realm.getDefaultInstance()
val entityName = entityName(realm)
val completeLabel = when (listOfValues.size) {
0 -> return NULL_TEXT
0 -> NULL_TEXT
1, 2 -> {
return listOfValues.map { labelForValue(realm, it) }.joinToString(", ")
listOfValues.map { labelForValue(realm, it) }.joinToString(", ")
}
else -> "${listOfValues.size} $baseId"
else -> "${listOfValues.size} $entityName"
}
realm.close()
return completeLabel
}
open fun entityName(realm: Realm): String {
return baseId
}
private fun labelForValue(realm: Realm, value: String): String {
val query = realm.where(entity)
return query.equalTo("id", value).findFirst()?.name ?: NULL_TEXT
@ -226,36 +287,44 @@ sealed class QueryCondition : FilterElementRow {
abstract class DateQuery : SingleDate(), DateTime {
override val showTime: Boolean = false
override fun labelForValue(value: Date, context: Context): String {
return singleValue?.let {
if (showTime) {
it.shortTime()
} else {
it.shortDate()
}
} ?: NULL_TEXT
}
}
abstract class TimeQuery : DateQuery() {
override val showTime: Boolean = true
}
object IsLive : QueryCondition() {
override fun getDisplayName(): String { return "Live" }
abstract class TrueQueryCondition : QueryCondition() {
override var operator: Operator = Operator.TRUE
}
object IsCash : QueryCondition() {
override fun getDisplayName(): String { return "Cash" }
}
object IsLive : TrueQueryCondition()
object IsOnline : QueryCondition() {
override fun getDisplayName(): String { return "Online" }
}
object IsCash : TrueQueryCondition()
object IsTournament : QueryCondition() {
override fun getDisplayName(): String { return "Tournament" }
}
object IsOnline : TrueQueryCondition()
object IsTournament : TrueQueryCondition()
class AnyBankroll() : QueryDataCondition<Bankroll>() {
override var entity: Class<Bankroll> = Bankroll::class.java
constructor(bankroll: Bankroll) : this() {
this.setObject(bankroll)
}
}
class AnyGame() : QueryDataCondition<Game>() {
override val entity: Class<Game> = Game::class.java
constructor(game: Game) : this() {
this.setObject(game)
}
@ -263,6 +332,7 @@ sealed class QueryCondition : FilterElementRow {
class AnyTournamentName() : QueryDataCondition<TournamentName>() {
override val entity: Class<TournamentName> = TournamentName::class.java
constructor(tournamentName: TournamentName) : this() {
this.setObject(tournamentName)
}
@ -270,6 +340,7 @@ sealed class QueryCondition : FilterElementRow {
class AnyTournamentFeature() : QueryDataCondition<TournamentFeature>() {
override val entity: Class<TournamentFeature> = TournamentFeature::class.java
constructor(tournamentFeature: TournamentFeature) : this() {
this.setObject(tournamentFeature)
}
@ -278,6 +349,7 @@ sealed class QueryCondition : FilterElementRow {
class AllTournamentFeature() : QueryDataCondition<TournamentFeature>() {
override var operator = Operator.ALL
override val entity: Class<TournamentFeature> = TournamentFeature::class.java
constructor(tournamentFeature: TournamentFeature) : this() {
this.setObject(tournamentFeature)
}
@ -285,70 +357,132 @@ sealed class QueryCondition : FilterElementRow {
class AnyLocation() : QueryDataCondition<Location>() {
override val entity: Class<Location> = Location::class.java
constructor(location: Location) : this() {
this.setObject(location)
}
}
class AnyTransactionType() : QueryDataCondition<TransactionType>() {
override val entity: Class<TransactionType> = TransactionType::class.java
constructor(transactionType: TransactionType) : this() {
this.setObject(transactionType)
}
}
class AnyLimit : ListOfInt() {
override fun labelForValue(value: Int): String {
return Limit.values()[value].getDisplayName()
override fun labelForValue(value: Int, context: Context): String {
return Limit.values()[value].getDisplayName(context)
}
}
class AnyTableSize : ListOfInt() {
override fun labelForValue(value: Int): String {
return TableSize(value).getDisplayName()
override fun labelForValue(value: Int, context: Context): String {
return TableSize(value).getDisplayName(context)
}
}
class AnyTournamentType : ListOfInt() {
override fun labelForValue(value: Int): String {
return TournamentType.values()[value].getDisplayName()
override fun labelForValue(value: Int, context: Context): String {
return TournamentType.values()[value].getDisplayName(context)
}
}
class AnyBlind : ListOfString()
class LastGame: SingleInt()
class LastSession: SingleInt()
object Last : SingleInt() {
override var operator = Operator.EQUALS
override fun getDisplayName(context: Context): String {
//TODO update string "last %i"
return "${context.getString(R.string.last_i_records)} $singleValue"
}
}
class NumberOfTable: ListOfInt()
class NumberOfTable : ListOfInt() {
override fun labelForValue(value: Int, context: Context): String {
return value.toString() + " " + context.getString(R.string.tables)
}
override fun entityName(context: Context): String {
return ""
}
}
class NumberOfRebuy() : ListOfDouble() {
constructor(operator: Operator, numberOfRebuy: Double) : this() {
this.operator = operator
this.listOfValues = arrayListOf(numberOfRebuy)
}
override fun labelForValue(value: Double, context: Context): String {
return value.toString()
}
}
open class TournamentFinalPosition(): ListOfInt() {
class TournamentFinalPosition() : ListOfInt() {
constructor(operator: Operator, finalPosition: Int) : this() {
this.operator = operator
this.listOfValues = arrayListOf(finalPosition)
}
override fun labelForValue(value: Int, context: Context): String {
val suffix = when (value%10) {
1 -> context.getString(R.string.ordinal_suffix_first)
2 -> context.getString(R.string.ordinal_suffix_second)
3 -> context.getString(R.string.ordinal_suffix_third)
else -> context.getString(R.string.ordinal_suffix_default)
}
return "$value$suffix "+context.getString(R.string.position)
}
override fun entityName(context: Context): String {
return ""
}
}
open class NetAmount : ListOfDouble()
class NetAmountWon : NetAmount()
class NetAmountLost: NetAmount() { override var sign: Int = -1 }
class NetAmountLost : NetAmount() {
override var sign: Int = -1
}
class TournamentNumberOfPlayer: ListOfInt()
class TournamentNumberOfPlayer : ListOfInt() {
override fun labelForValue(value: Int, context: Context): String {
return value.toString() + " " + context.getString(R.string.number_of_players)
}
override fun entityName(context: Context): String {
return ""
}
}
class StartedFromDate : DateQuery() {
override var operator = Operator.MORE
}
class StartedToDate : DateQuery() {
override var operator = Operator.LESS
}
class StartedFromDate: DateQuery() { override var operator = Operator.MORE }
class StartedToDate: DateQuery() { override var operator = Operator.LESS }
class EndedFromDate: DateQuery() { override var operator = Operator.MORE }
class EndedToDate: DateQuery() { override var operator = Operator.LESS }
class EndedFromDate : DateQuery() {
override var operator = Operator.MORE
}
class EndedToDate : DateQuery() {
override var operator = Operator.LESS
}
class AnyDayOfWeek : ListOfInt() {
override fun labelForValue(value: Int): String {
return DateFormatSymbols.getInstance(Locale.getDefault()).weekdays[value]
override fun labelForValue(value: Int, context: Context): String {
return DateFormatSymbols.getInstance(Locale.getDefault()).weekdays[value].capitalize()
}
}
class AnyMonthOfYear() : ListOfInt() {
override fun labelForValue(value: Int): String {
return DateFormatSymbols.getInstance(Locale.getDefault()).months[value]
override fun labelForValue(value: Int, context: Context): String {
return DateFormatSymbols.getInstance(Locale.getDefault()).months[value].capitalize()
}
constructor(month: Int) : this() {
@ -357,7 +491,7 @@ sealed class QueryCondition : FilterElementRow {
}
class AnyYear() : ListOfInt() {
override fun labelForValue(value: Int): String {
override fun labelForValue(value: Int, context: Context): String {
return "$value"
}
@ -366,45 +500,163 @@ sealed class QueryCondition : FilterElementRow {
}
}
object IsWeekDay: QueryCondition()
object IsWeekEnd: QueryCondition()
object IsToday: QueryCondition()
object WasYesterday: QueryCondition()
object WasTodayAndYesterday: QueryCondition()
object DuringThisWeek: QueryCondition()
object DuringThisMonth: QueryCondition()
object DuringThisYear: QueryCondition()
object IsWeekDay : TrueQueryCondition()
object IsWeekEnd : TrueQueryCondition()
object IsToday : TrueQueryCondition()
object WasYesterday : TrueQueryCondition()
object WasTodayAndYesterday : TrueQueryCondition()
object DuringThisWeek : TrueQueryCondition()
object DuringThisMonth : TrueQueryCondition()
object DuringThisYear : TrueQueryCondition()
class TournamentFee : ListOfDouble() {
override fun labelForValue(value: Double): String {
override fun labelForValue(value: Double, context: Context): String {
return value.toCurrency(UserDefaults.currency)
}
}
class PastDay : SingleInt() {
override var operator = Operator.EQUALS
override val viewType: Int = RowViewType.TITLE_VALUE_CHECK.ordinal
override fun labelForValue(value: Int, context: Context): String {
return value.toString()
}
override fun entityName(context: Context): String {
return this.resId?.let {
" " + context.getString(it)
} ?: ""
}
}
class Duration : SingleInt() {
var minutes:Int
get() { return singleValue }
set(value) { singleValue = value }
override var operator = Operator.EQUALS
var minutes: Int?
get() {
return singleValue
}
set(value) {
singleValue = value
}
val netDuration: Long?
get() {
minutes?.let {
return (it * 60 * 1000).toLong()
}
return null
}
override val viewType: Int = RowViewType.TITLE_VALUE_CHECK.ordinal
override val bottomSheetType: BottomSheetType = BottomSheetType.DOUBLE_EDIT_TEXT
override fun labelForValue(value: Int, context: Context): String {
return value.toMinutes(context)
}
}
class StartedFromTime: TimeQuery() {
class StartedFromTime() : TimeQuery() {
override var operator = Operator.MORE
init {
this.singleValue = Date().startOfDay()
constructor(date: Date) : this() {
singleValue = date
}
}
class EndedToTime: TimeQuery() {
class EndedToTime() : TimeQuery() {
override var operator = Operator.LESS
init {
this.singleValue = Date().endOfDay()
constructor(date: Date) : this() {
singleValue = date
}
}
interface CustomFieldRelated {
var customFieldId: String
fun customFieldName(realm: Realm): String {
val query = realm.where(CustomField::class.java)
val name = query.equalTo("id", customFieldId).findFirst()?.name
return name?.let {
"$it "
} ?: run { "" }
}
}
class CustomFieldQuery() : QueryDataCondition<CustomField>() {
override var entity: Class<CustomField> = CustomField::class.java
constructor(customField: CustomField) : this() {
this.setObject(customField)
}
}
open class CustomFieldNumberQuery() : ListOfDouble(), CustomFieldRelated {
override var customFieldId: String = ""
override var operator: Operator = Operator.EQUALS
constructor(customFieldId: String, value: Double) : this() {
this.listOfValues = arrayListOf(value)
this.customFieldId = customFieldId
}
override fun getDisplayName(context: Context): String {
val realm = Realm.getDefaultInstance()
val name = customFieldName(realm)
val prefix = this.resId?.let {
context.getString(it) + " "
} ?: ""
val completeLabel = when (listOfValues.size) {
0 -> return NULL_TEXT
1, 2 -> {
return name + prefix + listOfValues.map { labelForValue(it, context) }.joinToString(", ")
}
else -> "${listOfValues.size} $prefix $name"
}
realm.close()
return completeLabel
}
override fun labelForValue(value: Double, context: Context): String {
return NumberFormat.getInstance().format(value)
}
override fun updateValueBy(filterCondition: FilterCondition) {
super.updateValueBy(filterCondition)
listOfValues = filterCondition.getValues()
customFieldId = filterCondition.stringValue ?: throw PokerAnalyticsException.QueryValueMapUnexpectedValue
}
}
class CustomFieldAmountQuery : CustomFieldNumberQuery() {
override fun labelForValue(value: Double, context: Context): String {
return value.toCurrency(UserDefaults.currency)
}
}
class CustomFieldListQuery() : QueryDataCondition<CustomFieldEntry>(), CustomFieldRelated {
override var entity: Class<CustomFieldEntry> = CustomFieldEntry::class.java
override var customFieldId: String = ""
constructor(customFieldEntry: CustomFieldEntry) : this() {
this.setObject(customFieldEntry)
this.customFieldId = customFieldEntry.customFields?.firstOrNull()?.id
?: throw PokerAnalyticsException.QueryValueMapUnexpectedValue
}
override fun entityName(realm: Realm): String {
return customFieldName(realm)
}
override fun updateValueBy(filterCondition: FilterCondition) {
super.updateValueBy(filterCondition)
listOfValues = filterCondition.getValues()
customFieldId = filterCondition.stringValue ?: throw PokerAnalyticsException.QueryValueMapUnexpectedValue
}
}
@ -412,69 +664,141 @@ sealed class QueryCondition : FilterElementRow {
* main method of the enum
* providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery]
*/
inline fun <reified T : Filterable> queryWith(realmQuery: RealmQuery<T>): RealmQuery<T> {
inline fun <reified T : Filterable> queryWith(
realmQuery: RealmQuery<T>,
otherQueryCondition: QueryCondition? = null
): RealmQuery<T> {
val fieldName = FilterHelper.fieldNameForQueryType<T>(this::class.java)
if (BuildConfig.DEBUG) {
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
}
fieldName ?: return realmQuery
when (this) {
//is Between -> realmQuery.between(fieldName, leftValue, rightValue)
//is BetweenLeftExclusive -> realmQuery.greaterThan(fieldName, leftValue).and().lessThanOrEqualTo(fieldName, rightValue)
//is BetweenRightExclusive -> realmQuery.greaterThanOrEqualTo(fieldName, leftValue).and().lessThan(fieldName, rightValue)
IsLive, IsOnline -> return realmQuery.equalTo(fieldName, this == IsLive)
IsCash -> return realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal)
IsTournament -> return realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal)
IsWeekEnd, IsWeekDay -> {
is IsLive, is IsOnline -> return realmQuery.equalTo(fieldName, this == IsLive)
is IsCash -> return realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal)
is IsTournament -> return realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal)
is IsWeekEnd, is IsWeekDay -> {
var query = realmQuery
if (this == IsWeekDay) {
query = realmQuery.not()
}
return query.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY))
}
IsToday -> {
is IsToday -> {
val startDate = Date()
return realmQuery.between(fieldName, startDate.startOfDay(), startDate.endOfDay())
return realmQuery.greaterThanOrEqualTo(fieldName, startDate.startOfDay()).and()
.lessThanOrEqualTo(fieldName, startDate.endOfDay())
}
WasTodayAndYesterday-> {
is WasTodayAndYesterday -> {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.add(Calendar.HOUR_OF_DAY, -24)
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
return realmQuery.greaterThanOrEqualTo(fieldName, calendar.time.startOfDay()).and()
.lessThanOrEqualTo(fieldName, startDate.endOfDay())
}
WasYesterday -> {
is WasYesterday -> {
val calendar = Calendar.getInstance()
calendar.time = Date()
calendar.add(Calendar.HOUR_OF_DAY, -24)
return realmQuery.between(fieldName, calendar.time.startOfDay(), calendar.time.endOfDay())
return realmQuery.greaterThanOrEqualTo(fieldName, calendar.time.startOfDay()).and()
.lessThanOrEqualTo(fieldName, calendar.time.endOfDay())
}
DuringThisWeek -> {
is PastDay -> {
singleValue?.let {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.add(Calendar.DAY_OF_YEAR, -it)
return realmQuery.greaterThanOrEqualTo(fieldName, calendar.time.startOfDay()).and()
.lessThanOrEqualTo(fieldName, startDate.endOfDay())
}
return realmQuery
}
is DuringThisWeek -> {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY)
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
return realmQuery.greaterThanOrEqualTo(fieldName, calendar.time.startOfDay()).and()
.lessThanOrEqualTo(fieldName, startDate.endOfDay())
}
DuringThisMonth -> {
is DuringThisMonth -> {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.set(Calendar.DAY_OF_MONTH, 1)
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
return realmQuery.greaterThanOrEqualTo(fieldName, calendar.time.startOfDay()).and()
.lessThanOrEqualTo(fieldName, startDate.endOfDay())
}
DuringThisYear -> {
is DuringThisYear -> {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.set(Calendar.DAY_OF_YEAR, 1)
return realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
return realmQuery.greaterThanOrEqualTo(fieldName, calendar.time.startOfDay()).and()
.lessThanOrEqualTo(fieldName, startDate.endOfDay())
}
is StartedFromTime -> {
val calendar = Calendar.getInstance()
singleValue?.let {
calendar.time = it
realmQuery.greaterThanOrEqualTo(fieldName, calendar.hourMinute())
if (otherQueryCondition is EndedToTime) {
otherQueryCondition.singleValue?.let { endTime ->
calendar.time = endTime
realmQuery.lessThanOrEqualTo(fieldName, calendar.hourMinute())
}
}
}
return realmQuery
}
is EndedToTime -> {
val calendar = Calendar.getInstance()
singleValue?.let {
calendar.time = singleValue
realmQuery.lessThanOrEqualTo(fieldName, calendar.hourMinute())
if (otherQueryCondition is StartedFromTime) {
otherQueryCondition.singleValue?.let { startTime ->
calendar.time = startTime
realmQuery.greaterThanOrEqualTo(fieldName, calendar.hourMinute())
}
}
}
return realmQuery
}
}
if (this is CustomFieldRelated) {
FilterHelper.fieldNameForQueryType<T>(CustomFieldQuery::class.java)?.let {
realmQuery.equalTo(it, customFieldId)
}
}
if (this is ListOfValues<*>) {
if (this.listOfValues.isEmpty()) {
if (BuildConfig.DEBUG) {
throw PokerAnalyticsException.FilterElementExpectedValueMissing
}
return realmQuery
}
}
return when (operator) {
Operator.EQUALS -> {
when (this) {
is SingleDate -> realmQuery.equalTo(fieldName, singleValue)
is SingleInt -> realmQuery.equalTo(fieldName, singleValue)
is SingleDate -> realmQuery.equalTo(
fieldName,
singleValue ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
)
is SingleInt -> realmQuery.equalTo(
fieldName,
singleValue ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
)
is ListOfInt -> realmQuery.equalTo(fieldName, listOfValues.first())
is ListOfDouble -> realmQuery.equalTo(fieldName, listOfValues.first() * sign)
is ListOfString -> realmQuery.equalTo(fieldName, listOfValues.first())
@ -483,19 +807,52 @@ sealed class QueryCondition : FilterElementRow {
}
Operator.MORE -> {
when (this) {
is SingleDate -> realmQuery.greaterThanOrEqualTo(fieldName, singleValue)
is SingleInt -> realmQuery.greaterThanOrEqualTo(fieldName, singleValue)
is ListOfInt -> realmQuery.greaterThanOrEqualTo(fieldName, listOfValues.first())
is ListOfDouble -> realmQuery.greaterThanOrEqualTo(fieldName, listOfValues.first() * sign)
is SingleDate -> realmQuery.greaterThanOrEqualTo(
fieldName,
singleValue?.startOfDay() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
)
is Duration -> realmQuery.greaterThan(
fieldName,
netDuration ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
)
is TournamentFinalPosition -> realmQuery.greaterThanOrEqualTo(fieldName, listOfValues.first())
is TournamentNumberOfPlayer -> realmQuery.greaterThanOrEqualTo(fieldName, listOfValues.first())
is SingleInt -> realmQuery.greaterThan(
fieldName,
singleValue ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
)
is ListOfInt -> realmQuery.greaterThan(fieldName, listOfValues.first())
is NetAmountLost -> realmQuery.lessThan(fieldName, listOfValues.first() * -1)
is ListOfDouble -> realmQuery.greaterThan(fieldName, listOfValues.first() * sign)
else -> realmQuery
}
}
Operator.LESS -> {
when (this) {
is SingleDate -> realmQuery.lessThanOrEqualTo(fieldName, singleValue)
is SingleInt -> realmQuery.lessThanOrEqualTo(fieldName, singleValue)
is ListOfInt -> realmQuery.lessThanOrEqualTo(fieldName, listOfValues.first())
is ListOfDouble -> realmQuery.lessThanOrEqualTo(fieldName, listOfValues.first() * sign)
is SingleDate -> realmQuery.lessThanOrEqualTo(
fieldName,
singleValue?.endOfDay() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
)
is Duration -> realmQuery.lessThan(
fieldName,
netDuration ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
)
is TournamentFinalPosition -> realmQuery.lessThanOrEqualTo(fieldName, listOfValues.first())
is TournamentNumberOfPlayer -> realmQuery.lessThanOrEqualTo(fieldName, listOfValues.first())
is SingleInt -> realmQuery.lessThan(
fieldName,
singleValue ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
)
is ListOfInt -> realmQuery.lessThan(fieldName, listOfValues.first())
is NetAmountLost -> {
realmQuery.greaterThan(fieldName, listOfValues.first() * -1)
realmQuery.lessThan(fieldName, 0.0)
}
is NetAmountWon -> {
realmQuery.lessThan(fieldName, listOfValues.first())
realmQuery.greaterThan(fieldName, 0.0)
}
is ListOfDouble -> realmQuery.lessThan(fieldName, listOfValues.first() * sign)
else -> realmQuery
}
}
@ -538,8 +895,6 @@ sealed class QueryCondition : FilterElementRow {
get() {
return when (this) {
is PastDay -> RowViewType.TITLE_VALUE_CHECK.ordinal
is LastGame -> RowViewType.TITLE_VALUE_CHECK.ordinal
is LastSession -> RowViewType.TITLE_VALUE_CHECK.ordinal
else -> {
when (this.operator) {
Operator.MORE -> RowViewType.TITLE_VALUE_CHECK.ordinal
@ -554,8 +909,6 @@ sealed class QueryCondition : FilterElementRow {
get() {
return when (this) {
is PastDay -> BottomSheetType.EDIT_TEXT
is LastGame -> BottomSheetType.EDIT_TEXT
is LastSession -> BottomSheetType.EDIT_TEXT
else -> {
when (this.operator) {
Operator.MORE -> BottomSheetType.EDIT_TEXT
@ -577,15 +930,22 @@ sealed class QueryCondition : FilterElementRow {
is DuringThisWeek -> R.string.current_week
is DuringThisMonth -> R.string.current_month
is DuringThisYear -> R.string.current_year
is StartedFromTime, is StartedFromDate -> R.string.from
is EndedToDate, is EndedToTime-> R.string.to
is StartedFromDate -> R.string.from
is StartedFromTime -> R.string.from_time
is EndedToDate -> R.string.to
is EndedToTime -> R.string.to_time
is IsLive -> R.string.live
is IsOnline -> R.string.online
is IsWeekDay -> R.string.week_days
is IsWeekEnd -> R.string.weekend
is PastDay -> R.string.period_in_days
is LastGame -> R.string.last_records
is LastSession -> R.string.last_sessions
is TournamentNumberOfPlayer -> {
when (this.operator) {
Operator.MORE -> R.string.minimum
Operator.LESS -> R.string.maximum
else -> null
}
}
is NetAmountWon -> {
when (this.operator) {
Operator.MORE -> R.string.won_amount_more_than
@ -600,6 +960,13 @@ sealed class QueryCondition : FilterElementRow {
else -> null
}
}
is TournamentFinalPosition -> {
when (this.operator) {
Operator.MORE -> R.string.minimum
Operator.LESS -> R.string.maximum
else -> null
}
}
else -> {
when (this.operator) {
Operator.MORE -> R.string.more_than

@ -1,5 +1,6 @@
package net.pokeranalytics.android.model.interfaces
import android.content.Context
import io.realm.Realm
import io.realm.RealmModel
import net.pokeranalytics.android.R
@ -12,8 +13,15 @@ enum class SaveValidityStatus {
DATA_INVALID;
}
enum class DeleteValidityStatus {
VALID,
INVALID,
SESSIONS_LINKED,
TRANSACTIONS_LINKED;
}
/**
* An interface to grouped object which are managed by the database
* An interface to group object which are managed by the database
*/
interface Manageable : Savable, Deletable, Editable
@ -32,19 +40,19 @@ interface NameManageable: Manageable {
throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled")
}
override fun getFailedDeleteMessage(): Int {
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return R.string.relationship_error
}
}
/**
* An interface associate a unique identifier to an object
* An interface associate a unique uniqueIdentifier to an object
*/
interface Identifiable : RealmModel {
/**
* A unique identifier getter
* A unique uniqueIdentifier getter
*/
var id: String
}
@ -101,8 +109,22 @@ interface Deletable : Identifiable {
*/
fun isValidForDelete(realm: Realm): Boolean
fun getDeleteStatus(context: Context, realm: Realm): DeleteValidityStatus {
if (!isValidForDelete(realm)) {
return DeleteValidityStatus.INVALID
}
return DeleteValidityStatus.VALID
}
/**
* A method to get the reason why the object can't be deleted
*/
fun getFailedDeleteMessage(): Int
fun getFailedDeleteMessage(status: DeleteValidityStatus): Int
/**
* A method to override if we need to delete linked objects or other stuff
*/
fun deleteDependencies() {}
}

@ -1,7 +1,7 @@
package net.pokeranalytics.android.model.interfaces
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import java.util.*
interface Timed : GraphUnderlyingEntry, Identifiable {

@ -11,7 +11,18 @@ class Patcher {
companion object {
fun patchBreaks(context: Context) {
fun patchAll(context: Context) {
Preferences.executeOnce(Preferences.Keys.PATCH_BREAK, context) {
patchBreaks()
}
Preferences.executeOnce(Preferences.Keys.PATCH_TRANSACTION_TYPES_NAMES, context) {
patchDefaultTransactionTypes(context)
}
}
private fun patchBreaks() {
val realm = Realm.getDefaultInstance()
val sets = realm.where(SessionSet::class.java).findAll()
@ -44,7 +55,20 @@ class Patcher {
}
private fun patchDefaultTransactionTypes(context: Context) {
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
val tts = realm.where(TransactionType::class.java).findAll()
tts.forEach { tt ->
tt.kind?.let { kind ->
val value = TransactionType.Value.values()[kind]
tt.name = value.localizedTitle(context)
}
}
}
realm.close()
}
}
}

@ -3,10 +3,6 @@ package net.pokeranalytics.android.model.migrations
import io.realm.DynamicRealm
import io.realm.RealmMigration
import timber.log.Timber
import java.util.*
import io.realm.RealmObjectSchema
class PokerAnalyticsMigration : RealmMigration {
@ -23,16 +19,12 @@ class PokerAnalyticsMigration : RealmMigration {
if (currentVersion == 0) {
Timber.d("*** Running migration 1")
schema.get("Filter")?.let {
it.addField("entityType", Int::class.java).setNullable("entityType", true)
}
schema.get("Filter")?.addField("entityType", Int::class.java)?.setNullable("entityType", true)
schema.get("FilterElement")?.let {
it.setNullable("filterName", true)
it.setNullable("sectionName", true)
}
schema.get("FilterElementBlind")?.let {
it.renameField("code", "currencyCode")
}
schema.get("FilterElementBlind")?.renameField("code", "currencyCode")
currentVersion++
}
@ -40,9 +32,9 @@ class PokerAnalyticsMigration : RealmMigration {
if (currentVersion == 1) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.rename("FilterElement", "FilterCondition")
schema.get("Filter")?.let {
it.renameField("filterElements", "filterConditions")
}
schema.get("Filter")?.renameField("filterElements", "filterConditions")
schema.get("SessionSet")?.let {
it.addField("id", String::class.java).setRequired("id", true)
it.addPrimaryKey("id")
@ -50,14 +42,12 @@ class PokerAnalyticsMigration : RealmMigration {
currentVersion++
}
// Migrate to version 2
// Migrate to version 3
if (currentVersion == 2) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.rename("Report", "ReportSetup")
schema.get("Filter")?.let {
it.removeField("entityType")
}
schema.get("Filter")?.removeField("entityType")
schema.get("Session")?.let {
it.addField("blinds", String::class.java).transform {
@ -80,24 +70,78 @@ class PokerAnalyticsMigration : RealmMigration {
it.addField("stringValue", String::class.java)
}
schema.get("ComputableResult")?.let {
it.removeField("sessionSet")
schema.get("ComputableResult")?.removeField("sessionSet")
schema.get("Bankroll")?.addField("initialValue", Double::class.java)
currentVersion++
}
schema.get("Bankroll")?.let {
it.addField("initialValue", Double::class.java)
// Migrate to version 4
if (currentVersion == 3) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.get("Result")?.addField("numberOfRebuy", Double::class.java)?.setNullable("numberOfRebuy", true)
currentVersion++
}
// Migrate to version 5
if (currentVersion == 4) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.get("Bankroll")?.removeField("transactions")
currentVersion++
}
// Migrate to version 3
if (currentVersion == 3) {
// Migrate to version 6
if (currentVersion == 5) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.get("Transaction")?.let {
it.addField("dayOfWeek", Integer::class.java)
it.addField("month", Integer::class.java)
it.addField("year", Integer::class.java)
it.addField("dayOfMonth", Integer::class.java)
}
schema.get("Result")?.let {
it.addField("numberOfRebuy", Double::class.java).setNullable("numberOfRebuy", true)
val cfEntry = schema.create("CustomFieldEntry")?.let {
it.addField("id", String::class.java).setRequired("id", true)
it.addPrimaryKey("id")
it.addField("value", String::class.java).setNullable("value", false)
it.addField("order", Integer::class.java).setNullable("order", false)
// it.addRealmObjectField("customField", it).setNullable("customField", false)
it.addField("numericValue", Double::class.java).setNullable("numericValue", true)
}
cfEntry?.let { customFieldEntrySchema ->
schema.get("CustomField")?.let {
it.addField("type", Integer::class.java).setNullable("type", false)
it.addField("duplicateValue", Boolean::class.java)
it.addField("sortCondition", Integer::class.java).setRequired("sortCondition", true)
it.addRealmListField("entries", customFieldEntrySchema)
}
schema.get("Session")?.let {
it.addField("startDateHourMinuteComponent", Double::class.java)
.setNullable("startDateHourMinuteComponent", true)
it.addField("endDateHourMinuteComponent", Double::class.java)
.setNullable("endDateHourMinuteComponent", true)
it.addRealmListField("customFieldEntries", customFieldEntrySchema)
}
}
schema.get("ReportSetup")?.let {
it.addRealmListField("statIds", Int::class.java).setNullable("statIds", true)
it.addRealmListField("criteriaCustomFieldIds", String::class.java).setNullable("criteriaCustomFieldIds", true)
it.addRealmListField("criteriaIds", Int::class.java).setNullable("criteriaIds", true)
it.removeField("filters")
schema.get("Filter")?.let { filterSchema ->
it.addRealmObjectField("filter", filterSchema)
}
}
schema.get("Filter")?.addField("filterableTypeUniqueIdentifier", Integer::class.java)
schema.get("Filter")?.addField("useCount", Int::class.java)
schema.get("Filter")?.removeField("usageCount")
currentVersion++
}
}

@ -1,11 +1,14 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.view.RowRepresentable
@ -13,7 +16,7 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import java.util.*
open class Bankroll() : RealmObject(), NameManageable, RowRepresentable {
open class Bankroll : RealmObject(), NameManageable, RowRepresentable {
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -23,8 +26,11 @@ open class Bankroll() : RealmObject(), NameManageable, RowRepresentable {
// Indicates whether the bankroll is live or online
var live: Boolean = true
// The list of transactions of the bankroll
var transactions: RealmList<Transaction> = RealmList()
/**
* The list of transactions of the bankroll
*/
@LinkingObjects("bankroll")
val transactions: RealmResults<Transaction>? = null
// The currency of the bankroll
var currency: Currency? = null
@ -37,7 +43,7 @@ open class Bankroll() : RealmObject(), NameManageable, RowRepresentable {
return this.currency?.rate ?: 1.0
}
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return this.name
}
@ -62,10 +68,25 @@ open class Bankroll() : RealmObject(), NameManageable, RowRepresentable {
override fun isValidForDelete(realm: Realm): Boolean {
return realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty()
&& realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty()
}
override fun getFailedDeleteMessage(): Int {
return R.string.bankroll_relationship_error
override fun getDeleteStatus(context: Context, realm: Realm): DeleteValidityStatus {
return if (!realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty()) {
DeleteValidityStatus.SESSIONS_LINKED
} else if (!realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty()) {
DeleteValidityStatus.TRANSACTIONS_LINKED
} else {
DeleteValidityStatus.VALID
}
}
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return when (status) {
DeleteValidityStatus.SESSIONS_LINKED -> R.string.bankroll_relationship_error
DeleteValidityStatus.TRANSACTIONS_LINKED -> R.string.bankroll_relationship_error_transactions
else -> super.getFailedDeleteMessage(status)
}
}
override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
@ -75,4 +96,26 @@ open class Bankroll() : RealmObject(), NameManageable, RowRepresentable {
else -> super.getFailedSaveMessage(status)
}
}
companion object {
fun getOrCreate(realm: Realm, name: String, live: Boolean = true, currencyCode: String? = null, currencyRate: Double? = null) : Bankroll {
val br = realm.where<Bankroll>().equalTo("name", name).findFirst()
return if (br != null) {
br
} else {
val bankroll = Bankroll()
bankroll.name = name
bankroll.live = live
val currency = Currency()
currency.code = currencyCode
currency.rate = currencyRate
bankroll.currency = currency
realm.copyToRealm(bankroll)
}
}
}
}

@ -1,19 +1,313 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import android.text.InputType
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
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.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomFieldRow
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import java.util.*
import kotlin.collections.ArrayList
open class CustomField : RealmObject() {
open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable {
/**
* The custom field type: a list of items, a number or an amont
*/
enum class Type(override var uniqueIdentifier: Int, var resId: Int, var isEnabled: Boolean = true) :
IntIdentifiable {
LIST(0, R.string.enum_custom_field_type),
NUMBER(1, R.string.number),
AMOUNT(2, R.string.amount)
}
/**
* The sorting used for the list, either custom, or alphabetically asc/desc
*/
enum class Sort(override var uniqueIdentifier: Int) : IntIdentifiable {
DEFAULT(0),
ASCENDING(1),
DESCENDING(2)
}
@PrimaryKey
var id = UUID.randomUUID().toString()
override var id = UUID.randomUUID().toString()
/**
* The name of the custom field
*/
override var name: String = ""
// The type of the custom fields, mapped with the CustomField.Type enum
var type: Int = Type.LIST.uniqueIdentifier
set(value) {
if (field == Type.LIST.uniqueIdentifier && value != Type.LIST.uniqueIdentifier) {
this.removeListEntries()
}
field = value
this.updateRowRepresentation()
}
/**
* Indicates whether the custom field value should be copied when a session is duplicated
*/
var duplicateValue: Boolean = false
/**
* The list of entries for the LIST type
*/
var entries: RealmList<CustomFieldEntry> = RealmList()
/**
* The sorting of the entries, mapped with the CustomField.Sort enum
*/
var sortCondition: Int = Sort.DEFAULT.uniqueIdentifier
set(value) {
field = value
sortEntries()
updateRowRepresentation()
}
@Ignore
private var entriesToDelete: ArrayList<CustomFieldEntry> = ArrayList()
@Ignore
override var viewType: Int = RowViewType.TITLE_VALUE_ARROW.ordinal
@Ignore
private var rowRepresentation: List<RowRepresentable> = mutableListOf()
//helper
val isListType: Boolean
get() {
return this.type == Type.LIST.uniqueIdentifier
}
val isAmountType: Boolean
get() {
return this.type == Type.AMOUNT.uniqueIdentifier
}
override fun localizedTitle(context: Context): String {
return this.name
}
override fun getDisplayName(context: Context): String {
return this.name
}
override fun adapterRows(): List<RowRepresentable>? {
return rowRepresentation
}
override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) {
SimpleRow.NAME -> this.name = value as String? ?: ""
CustomFieldRow.TYPE -> this.type = (value as Type?)?.uniqueIdentifier ?: Type.LIST.uniqueIdentifier
CustomFieldRow.COPY_ON_DUPLICATE -> this.duplicateValue = value as Boolean? ?: false
}
}
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
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_cf_error
else -> super.getFailedSaveMessage(status)
}
}
override fun alreadyExists(realm: Realm): Boolean {
return realm.where(this::class.java).equalTo("name", this.name).and().notEqualTo("id", this.id).findAll()
.isNotEmpty()
}
override fun isValidForDelete(realm: Realm): Boolean {
val sessions = realm.where<Session>().contains("customFieldEntries.customField.id", id).findAll()
return sessions.isEmpty()
}
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
//TODO:
return R.string.cf_entry_delete_popup_message
}
// The name of the currency field
var name: String = ""
override val bottomSheetType: BottomSheetType
get() {
return when (type) {
Type.LIST.uniqueIdentifier -> BottomSheetType.LIST_STATIC
else -> BottomSheetType.NUMERIC_TEXT
}
}
// @todo
override fun deleteDependencies() {
if (isValid) {
val entries = realm.where<CustomFieldEntry>().equalTo("customField.id", id).findAll()
entries.deleteAllFromRealm()
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) {
is CustomFieldEntry -> row.editingDescriptors(
mapOf(
"defaultValue" to row.value
)
)
else -> null
}
}
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
return when (type) {
Type.LIST.uniqueIdentifier -> {
val defaultValue: Any? by map
val data: RealmList<CustomFieldEntry>? by map
arrayListOf(
RowRepresentableEditDescriptor(defaultValue, staticData = data)
)
}
else -> {
val defaultValue: Double? by map
arrayListOf(
RowRepresentableEditDescriptor(
defaultValue, inputType = InputType.TYPE_CLASS_NUMBER
or InputType.TYPE_NUMBER_FLAG_DECIMAL
or InputType.TYPE_NUMBER_FLAG_SIGNED
)
)
}
}
}
/**
* Update the row representation
*/
private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> {
val rows = ArrayList<RowRepresentable>()
rows.add(SimpleRow.NAME)
rows.add(CustomFieldRow.TYPE)
if (type == Type.LIST.uniqueIdentifier && entries.size >= 0) {
if (entries.isNotEmpty()) {
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, R.string.items_list))
sortEntries()
entries.forEach { customFieldEntry ->
customFieldEntry.isMovable = sortCondition == Sort.DEFAULT.uniqueIdentifier
}
rows.addAll(entries)
}
}
return rows
}
/**
* Sort the entries element
*/
private fun sortEntries() {
when (sortCondition) {
Sort.ASCENDING.uniqueIdentifier -> entries.sortBy { it.value }
Sort.DESCENDING.uniqueIdentifier -> entries.sortByDescending { it.value }
}
entries.forEachIndexed { index, customFieldEntry ->
customFieldEntry.order = index
}
}
fun updateRowRepresentation() {
this.rowRepresentation = this.updatedRowRepresentationForCurrentState()
}
/**
* Add an entry
*/
fun addEntry(): CustomFieldEntry {
val entry = CustomFieldEntry()
this.entries.add(entry)
sortEntries()
updateRowRepresentation()
return entry
}
/**
* Delete an entry
*/
fun deleteEntry(entry: CustomFieldEntry) {
entries.remove(entry)
entriesToDelete.add(entry)
sortEntries()
updateRowRepresentation()
}
private fun removeListEntries() {
this.entriesToDelete.addAll(entries)
this.entries.clear()
if (realm != null) {
realm.executeTransaction {
this.entriesToDelete.forEach {
if (it.isManaged) {
it.deleteFromRealm()
}
}
}
}
}
/**
* 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
*/
val criteria: Criteria
get() {
return when (this.type) {
CustomField.Type.LIST.uniqueIdentifier -> Criteria.ListCustomFields(this.id)
else -> Criteria.ValueCustomFields(this.id)
}
}
}

@ -0,0 +1,148 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import android.text.InputType
import io.realm.Realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
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.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.toCurrency
import java.text.NumberFormat
import java.util.*
import java.util.Currency
open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable {
@PrimaryKey
override var id = UUID.randomUUID().toString()
/**
* The order in the list
*/
var order: Int = 0
/**
* The inverse relationship with CustomField
*/
@LinkingObjects("entries")
val customFields: RealmResults<CustomField>? = null
val customField: CustomField?
get() {
return this.customFields?.first()
}
/**
* The string value of the entry
*/
var value: String = ""
/**
* The numeric value of the entry
*/
var numericValue: Double? = null
@Ignore
override var name: String = value
get() { return value }
@Ignore
var isMovable: Boolean = false
@Ignore
override val viewType: Int = RowViewType.TITLE_VALUE_ACTION.ordinal
override val imageRes: Int?
get() {
return if (isMovable) R.drawable.ic_reorder else null
}
override val imageTint: Int?
get() {
return R.color.kaki
}
@Ignore
override val bottomSheetType: BottomSheetType = BottomSheetType.EDIT_TEXT
override fun localizedTitle(context: Context): String {
return context.getString(R.string.value)
}
override fun getDisplayName(context: Context): String {
return if (value.isNotEmpty()) value else NULL_TEXT
}
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
val defaultValue: Any? by map
return arrayListOf(
RowRepresentableEditDescriptor(defaultValue, R.string.value, InputType.TYPE_CLASS_TEXT)
)
}
override fun isValidForSave(): Boolean {
return true
}
override fun alreadyExists(realm: Realm): Boolean {
return realm.where(this::class.java).notEqualTo("id", this.id).findAll().isNotEmpty()
}
override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled")
}
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return R.string.cf_entry_delete_popup_message
}
override fun deleteDependencies() {
if (isValid) {
val entries = realm.where<CustomFieldEntry>().equalTo("customField.id", id).findAll()
entries.deleteAllFromRealm()
}
}
override fun updateValue(value: Any?, row: RowRepresentable) {
this.value = value as String? ?: ""
}
override fun isValidForDelete(realm: Realm): Boolean {
if (realm.where<Session>().contains("customFieldEntries.id", id).findAll().isNotEmpty()) {
return false
}
return true
}
/**
* Return the amount
*/
fun getFormattedValue(currency: Currency? = null): String {
return when (customField?.type) {
CustomField.Type.AMOUNT.uniqueIdentifier -> {
numericValue?.toCurrency(currency) ?: run { NULL_TEXT }
}
CustomField.Type.NUMBER.uniqueIdentifier -> {
NumberFormat.getInstance().format(this.numericValue)
}
else -> {
value
}
}
}
}

@ -1,13 +1,21 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.*
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.view.ImageDecorator
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import timber.log.Timber
import java.util.*
/**
@ -15,14 +23,16 @@ import java.util.*
* It contains a list of [FilterCondition] describing the complete query to launch
* The [Filter] is working closely with a [Filterable] interface providing the entity we want the query being launched on
*/
open class Filter : RealmObject() {
open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, CountableUsage, ImageDecorator {
companion object {
// Create a new instance
fun newInstance(realm: Realm): Filter {
fun newInstance(filterableTypeUniqueIdentifier:Int): Filter {
val filter = Filter()
return realm.copyToRealm(filter)
filter.filterableTypeUniqueIdentifier = filterableTypeUniqueIdentifier
return filter
//return realm.copyToRealm(filter)
}
// Get a queryWith by its id
@ -31,43 +41,54 @@ open class Filter : RealmObject() {
}
inline fun <reified T : Filterable> queryOn(realm: Realm, query: Query, sortField: String? = null): RealmResults<T> {
var realmQuery = realm.where<T>()
query.conditions.forEach {
realmQuery = it.queryWith(realmQuery)
}
val realmQuery = realm.where<T>()
sortField?.let {
realmQuery.sort(it)
return query.queryWith(realmQuery).sort(it).findAll()
} ?: run {
return query.queryWith(realmQuery).findAll()
}
Timber.d(">>> Filter query: ${realmQuery.description}")
return realmQuery.findAll()
}
}
@PrimaryKey
var id = UUID.randomUUID().toString()
override var id = UUID.randomUUID().toString()
// the queryWith name
var name: String = ""
get() {
if (field.isEmpty()) {
return this.query.defaultName
}
return field
}
// the number of use of the queryWith,
// for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0)
override var useCount: Int = 0
var filterConditions: RealmList<FilterCondition> = RealmList()
private set
private var filterableTypeUniqueIdentifier: Int? = null
val filterableType: FilterableType
get() {
this.filterableTypeUniqueIdentifier?.let {
return FilterableType.valueByIdentifier(it)
}
return FilterableType.ALL
}
fun createOrUpdateFilterConditions(filterConditionRows: ArrayList<QueryCondition>) {
println("list of querys saving: ${filterConditionRows.map { it.id }}")
println("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}")
filterConditionRows
.map {
it.filterSectionRow
it.groupId
}
.distinct()
.forEach { filterName->
.forEach { groupId->
filterConditionRows
.filter {
it.filterSectionRow == filterName
it.groupId == groupId
}
.apply {
@ -76,7 +97,7 @@ open class Filter : RealmObject() {
casted.addAll(this)
val newFilterCondition = FilterCondition(casted)
val previousCondition = filterConditions.filter {
it.filterName == newFilterCondition.filterName
it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator
}
filterConditions.removeAll(previousCondition)
filterConditions.add(newFilterCondition)
@ -118,15 +139,40 @@ open class Filter : RealmObject() {
}
}
inline fun <reified T : Filterable> results(): RealmResults<T> {
var realmQuery = realm.where<T>()
this.filterConditions.map {
it.queryCondition
}.forEach {
realmQuery = it.queryWith(realmQuery)
inline fun <reified T : Filterable> results(firstField: String? = null, secondField: String? = null): RealmResults<T> {
val realmQuery = realm.where<T>()
if (firstField != null && secondField != null) {
return this.query.queryWith(realmQuery).distinct(firstField, secondField).findAll()
}
return realmQuery.findAll()
if (firstField != null) {
return this.query.queryWith(realmQuery).distinct(firstField).findAll()
}
return this.query.queryWith(realmQuery).findAll()
}
val query: Query
get() {
val query = Query()
this.filterConditions.forEach {
query.add(it.queryCondition)
}
return query
}
override fun getDisplayName(context: Context): String {
if (name.isNotEmpty()) return name
return this.query.getName(context)
}
override fun isValidForDelete(realm: Realm): Boolean {
return true
}
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return R.string.relationship_error
}
}

@ -18,9 +18,12 @@ open class FilterCondition() : RealmObject() {
val row = filterElementRows.first()
this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName
this.operator = row.operator.ordinal
if (row is QueryCondition.CustomFieldRelated) {
this.stringValue = row.customFieldId
}
when (row) {
is QueryCondition.SingleInt -> this.setValue(row.singleValue)
is QueryCondition.SingleDate -> this.setValue(row.singleValue)
is QueryCondition.SingleInt -> this.setValue(row.singleValue?:throw PokerAnalyticsException.FilterElementExpectedValueMissing)
is QueryCondition.SingleDate -> this.setValue(row.singleValue?:throw PokerAnalyticsException.FilterElementExpectedValueMissing)
is QueryCondition.ListOfDouble -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfDouble).listOfValues })
is QueryCondition.ListOfInt -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfInt).listOfValues })
is QueryCondition.ListOfString -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfString).listOfValues })
@ -46,7 +49,6 @@ open class FilterCondition() : RealmObject() {
var operator: Int? = null
inline fun <reified T:Any > getValues(): ArrayList < T > {
println("<<<< r $stringValues")
return when (T::class) {
Int::class -> ArrayList<T>().apply { intValues?.map { add(it as T) } }
Double::class -> ArrayList<T>().apply { doubleValues?.map { add(it as T) } }

@ -1,5 +1,6 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
@ -47,7 +48,7 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
return this.name
}
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return this.name
}

@ -1,13 +1,14 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import com.google.android.libraries.places.api.model.Place
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import java.util.*
@ -30,7 +31,7 @@ open class Location : RealmObject(), NameManageable, RowRepresentable {
// the latitude of the location
var latitude: Double? = null
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return this.name
}

@ -1,33 +1,98 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.extensions.findById
import java.util.*
enum class ReportDisplay {
TABLE,
GRAPH,
MAP
}
open class ReportSetup : RealmObject() {
open class ReportSetup : RealmObject(), RowRepresentable, Deletable {
@PrimaryKey
var id = UUID.randomUUID().toString()
override var id = UUID.randomUUID().toString()
// The name of the report
var name: String = ""
// The type of display of the report
var display: Int = ReportDisplay.TABLE.ordinal
var display: Int = Calculator.Options.Display.TABLE.ordinal
/**
* A list of statIds to compute
* Must contain at least 1
*/
var statIds: RealmList<Int> = RealmList()
/**
* An optional list of criteriaIds to compare statIds
*/
var criteriaIds: RealmList<Int> = RealmList()
/**
* An optional list of custom fields ids to be compared
*/
var criteriaCustomFieldIds: RealmList<String> = RealmList()
/**
* An optional filter to narrow the results
*/
var filter: Filter? = null
// RowRepresentable
override fun getDisplayName(context: Context): String {
return this.name
}
@Ignore
override val viewType: Int = RowViewType.TITLE_ARROW.ordinal
// @todo define the configuration options
/**
* Returns the Options based on the ReportSetup parameters
*/
val options: Calculator.Options
get() {
// var criteria: List<Int> = listOf()
// var stats: List<Int> = listOf()
val realm = Realm.getDefaultInstance()
val stats = this.statIds.map { Stat.valueByIdentifier(it) }
// The filters associated with the report
var filters: RealmList<Filter> = RealmList()
// Comparison criteria
val criteria = this.criteriaIds.map { Criteria.valueByIdentifier(it) }
val customFields = this.criteriaCustomFieldIds.mapNotNull { realm.findById<CustomField>(it) }
val cfCriteria = customFields.map { it.criteria }
val allCriteria = mutableListOf<Criteria>()
allCriteria.addAll(criteria)
allCriteria.addAll(cfCriteria)
return Calculator.Options(
display = Calculator.Options.Display.values()[this.display],
stats = stats,
criterias = allCriteria,
filter = this.filter,
userGenerated = true,
reportSetupId = this.id
)
}
// Deletable
override fun isValidForDelete(realm: Realm): Boolean {
return true
}
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

@ -106,7 +106,7 @@ open class Result : RealmObject(), Filterable {
val transactionsSum = transactions.sumByDouble { it.amount }
val isLive = this.session?.bankroll?.live ?: true
val isLive = this.session?.isLive ?: true
if (isLive) {
val buyin = this.buyin ?: 0.0
val cashOut = this.cashout ?: 0.0

@ -12,10 +12,11 @@ import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.extensions.SessionState
@ -28,11 +29,13 @@ import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import net.pokeranalytics.android.ui.view.*
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.*
import java.text.DateFormat
@ -47,7 +50,20 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
enum class Type {
CASH_GAME,
TOURNAMENT
TOURNAMENT;
companion object {
fun getValueFromString(string: String): Type? {
return when (string) {
"Cash", "Cash Game" -> CASH_GAME
"Tournament" -> TOURNAMENT
else -> null
}
}
}
}
companion object {
@ -76,18 +92,23 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
AnyTableSize::class.java -> "tableSize"
AnyTournamentType::class.java -> "tournamentType"
AnyBlind::class.java -> "blinds"
NumberOfTable::class.java -> "numberOfTable"
NumberOfTable::class.java -> "numberOfTables"
NetAmountWon::class.java, NetAmountLost::class.java -> "computableResults.ratedNet"
NumberOfRebuy::class.java -> "result.numberOfRebuy"
TournamentNumberOfPlayer::class.java -> "result.tournamentNumberOfPlayers"
TournamentNumberOfPlayer::class.java -> "tournamentNumberOfPlayers"
TournamentFinalPosition::class.java -> "result.tournamentFinalPosition"
TournamentFee::class.java -> "tournamentEntryFee"
StartedFromDate::class.java, StartedToDate::class.java -> "startDate"
EndedFromDate::class.java, EndedToDate::class.java -> "endDate"
StartedFromDate::class.java, StartedToDate::class.java, EndedFromDate::class.java, EndedToDate::class.java -> "startDate"
AnyDayOfWeek::class.java, IsWeekEnd::class.java, IsWeekDay::class.java -> "dayOfWeek"
AnyMonthOfYear::class.java -> "month"
AnyYear::class.java -> "year"
IsToday::class.java, WasYesterday::class.java, WasTodayAndYesterday::class.java, DuringThisYear::class.java, DuringThisMonth::class.java, DuringThisWeek::class.java -> "startDate"
PastDay::class.java, IsToday::class.java, WasYesterday::class.java, WasTodayAndYesterday::class.java, DuringThisYear::class.java, DuringThisMonth::class.java, DuringThisWeek::class.java -> "startDate"
StartedFromTime::class.java -> "startDateHourMinuteComponent"
EndedToTime::class.java -> "endDateHourMinuteComponent"
Duration::class.java -> "netDuration"
CustomFieldListQuery::class.java -> "customFieldEntries.id"
CustomFieldAmountQuery::class.java, CustomFieldNumberQuery::class.java -> "customFieldEntries.numericValue"
CustomFieldQuery::class.java -> "customFieldEntries.customFields.id"
else -> null
}
}
@ -118,12 +139,39 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
override var year: Int? = null
override var dayOfMonth: Int? = null
private var startDateHourMinuteComponent: Double? = null
get() {
if (field == null && startDate != null) {
val cal = Calendar.getInstance()
cal.time = startDate
field = cal.hourMinute()
}
return field
}
private var endDateHourMinuteComponent: Double? = null
get() {
if (field == null && endDate != null) {
val cal = Calendar.getInstance()
cal.time = endDate
field = cal.hourMinute()
}
return field
}
/**
* The start date of the session
*/
var startDate: Date? = null
set(value) {
field = value
if (field == null) {
startDateHourMinuteComponent = null
} else {
val cal = Calendar.getInstance()
cal.time = field
startDateHourMinuteComponent = cal.hourMinute()
}
this.updateTimeParameter(field)
this.computeNetDuration()
@ -142,6 +190,14 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
var endDate: Date? = null
set(value) {
field = value
if (field == null) {
endDateHourMinuteComponent = null
} else {
val cal = Calendar.getInstance()
cal.time = field
endDateHourMinuteComponent = cal.hourMinute()
}
this.computeNetDuration()
this.dateChanged()
this.defineDefaultTournamentBuyinIfNecessary()
@ -251,6 +307,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The features of the tournament, like Knockout, Shootout, Turbo...
var tournamentFeatures: RealmList<TournamentFeature> = RealmList()
// The custom fields values
var customFieldEntries: RealmList<CustomFieldEntry> = RealmList()
fun bankrollHasBeenUpdated() {
formatBlinds()
}
@ -344,7 +403,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
/**
* Pre-compute various stats
* Pre-compute various statIds
*/
fun computeStats() {
@ -373,8 +432,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
val numberOfHandsPerHour: Double
get() {
val tableSize = this.tableSize ?: 9 // 9 is the default table size if null
val isLive = this.bankroll?.live ?: true
val playerHandsPerHour = if (isLive) LIVE_PLAYER_HANDS_PER_HOUR else ONLINE_PLAYER_HANDS_PER_HOUR
val playerHandsPerHour = if (this.isLive) LIVE_PLAYER_HANDS_PER_HOUR else ONLINE_PLAYER_HANDS_PER_HOUR
return playerHandsPerHour / tableSize.toDouble()
}
@ -391,6 +449,24 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return this.bbNet / this.hourlyDuration
}
val isLive: Boolean
get() {
this.bankroll?.let {
return it.live
}
return true // default should be true
}
val hasBuyin: Boolean
get() {
return this.result?.buyin != null
}
val hasNetResult: Boolean
get() {
return this.result?.netResult != null
}
// Manageable
override fun isValidForSave(): Boolean {
@ -512,7 +588,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
/**
* Return the game title
* Return the game titleResId
* Example: NL Holdem
*/
fun getFormattedGame(): String {
@ -545,14 +621,15 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
/**
* Delete the object from realm
* @TODO: Cascade delete?
*/
fun delete() {
if (isValid) {
realm.executeTransaction {
cleanup()
deleteFromRealm()
}
}
}
/**
* This method is called whenever a session is about to be deleted
@ -572,7 +649,17 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
@Ignore
override val viewType: Int = RowViewType.ROW_SESSION.ordinal
override fun getDisplayName(): String {
// Override to surcharge custom field viewType
override fun viewTypeForPosition(position: Int): Int {
rowRepresentationForCurrentState[position].let {
if (it is CustomField) {
return RowViewType.TITLE_VALUE.ordinal
}
}
return super.viewTypeForPosition(position)
}
override fun getDisplayName(context: Context): String {
return "Session ${this.creationDate}"
}
@ -637,6 +724,13 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// Rows
rows.addAll(SessionRow.getRows(this))
// Add custom fields
realm?.let {
rows.add(SeparatorRow())
rows.addAll(it.sorted<CustomField>())
}
return rows
}
@ -670,11 +764,13 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
SessionRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT
SessionRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: NULL_TEXT
SessionRow.TIPS -> result?.tips?.toCurrency(currency) ?: NULL_TEXT
SessionRow.TOURNAMENT_TYPE -> this.tournamentType?.let {
SessionRow.TOURNAMENT_TYPE -> {
this.tournamentType?.let {
TournamentType.values()[it].localizedTitle(context)
} ?: run {
NULL_TEXT
}
}
SessionRow.TOURNAMENT_FEATURE -> {
if (tournamentFeatures.size > 2) {
"${tournamentFeatures.subList(0, 2).joinToString {
@ -689,7 +785,13 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
}
SessionRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT
else -> throw UnmanagedRowRepresentableException("Unmanaged row = ${row.getDisplayName()}")
is CustomField -> {
customFieldEntries.find { it.customField?.id == row.id }?.let { customFieldEntry ->
return customFieldEntry.getFormattedValue(currency)
}
return NULL_TEXT
}
else -> throw UnmanagedRowRepresentableException("Unmanaged row = ${row}")
}
}
@ -707,32 +809,32 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
SessionRow.BANKROLL -> row.editingDescriptors(
mapOf(
"defaultValue" to this.bankroll,
"data" to LiveData.BANKROLL.items(realm)
"data" to realm.sorted<Bankroll>() // LiveData.Bankroll.items(realm)
)
)
SessionRow.GAME -> row.editingDescriptors(
mapOf(
"limit" to this.limit,
"defaultValue" to this.game,
"data" to LiveData.GAME.items(realm)
"data" to realm.sorted<Game>() //LiveData.Game.items(realm)
)
)
SessionRow.LOCATION -> row.editingDescriptors(
mapOf(
"defaultValue" to this.location,
"data" to LiveData.LOCATION.items(realm)
"data" to realm.sorted<Location>() // LiveData.Location.items(realm)
)
)
SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentFeatures,
"data" to LiveData.TOURNAMENT_FEATURE.items(realm)
"data" to realm.sorted<TournamentFeature>() //LiveData.TournamentFeature.items(realm)
)
)
SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentName,
"data" to LiveData.TOURNAMENT_NAME.items(realm)
"data" to realm.sorted<TournamentName>() //LiveData.TournamentName.items(realm)
)
)
SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors(
@ -796,6 +898,19 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
"tips" to result?.tips
)
)
is CustomField -> {
row.editingDescriptors(
when (row.type) {
CustomField.Type.LIST.uniqueIdentifier -> mapOf(
"defaultValue" to customFieldEntries.find { it.customField?.id == row.id }?.value,
"data" to row.entries
)
else -> mapOf(
"defaultValue" to customFieldEntries.find { it.customField?.id == row.id }?.numericValue
)
}
)
}
else -> null
}
}
@ -905,7 +1020,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
result = localResult
}
SessionRow.TOURNAMENT_NAME -> tournamentName = value as TournamentName?
SessionRow.TOURNAMENT_TYPE -> tournamentType = value as Int?
SessionRow.TOURNAMENT_TYPE -> tournamentType = (value as TournamentType?)?.ordinal
SessionRow.TOURNAMENT_FEATURE -> {
value?.let {
@ -915,6 +1030,28 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
tournamentFeatures.removeAll(this.tournamentFeatures)
}
}
is CustomField -> {
customFieldEntries.filter { it.customField?.id == row.id }.let {
customFieldEntries.removeAll(it)
}
when (row.type) {
CustomField.Type.AMOUNT.uniqueIdentifier,
CustomField.Type.NUMBER.uniqueIdentifier -> {
if (value != null) {
val customFieldEntry = CustomFieldEntry()
customFieldEntry.numericValue = value as Double?
customFieldEntries.add(customFieldEntry)
row.entries.add(customFieldEntry)
}
}
CustomField.Type.LIST.uniqueIdentifier -> {
if (value != null && value is CustomFieldEntry) {
customFieldEntries.add(value)
}
}
}
}
}
}
@ -922,8 +1059,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// Stat Entry
override val entryTitle: String
get() {
override fun entryTitle(context: Context): String {
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate)
}
@ -962,7 +1098,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
} ?: run {
throw java.lang.IllegalStateException("Asking for stats on Session without Result")
throw java.lang.IllegalStateException("Asking for statIds on Session without Result")
}
}
@ -980,9 +1116,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
val secondTitle = stat.localizedTitle(context)
val entryValue = this.formattedValue(stat)
val dateValue = TextFormat(this.entryTitle)
val dateValue = TextFormat(this.entryTitle(context))
return MultilineLegendValues(groupName, secondTitle, entryValue, dateValue)
}
@ -1001,7 +1135,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
}
DefaultLegendValues(this.entryTitle, left, right)
DefaultLegendValues(this.entryTitle(context), left, right)
}
else -> {
super.legendValues(stat, entry, style, groupName, context)

@ -1,18 +1,18 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import net.pokeranalytics.android.util.NULL_TEXT
import java.text.DateFormat
import java.util.*
@ -74,8 +74,10 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
var ratedNet: Double = 0.0
@Ignore
val hourlyRate: Double = this.ratedNet / this.hourlyDuration
val hourlyRate: Double
get() {
return this.ratedNet / this.hourlyDuration
}
var estimatedHands: Double = 0.0
@ -113,8 +115,7 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
// Stat Base
override val entryTitle: String
get() {
override fun entryTitle(context: Context): String {
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate)
}

@ -174,7 +174,7 @@
// }
//
// fun createSessionSet(owner: Session) {
// val set: SessionSet = SessionSet.newInstance(this.realm)
// val set: SessionSet = SessionSet.newInstanceForResult(this.realm)
// set.timeFrame?.let {
// it.startDate = this.startDate
// it.endDate = this.endDate
@ -269,7 +269,7 @@
// sessionSets.deleteAllFromRealm()
//
// // Create a new sets
// val set: SessionSet = SessionSet.newInstance(this.realm)
// val set: SessionSet = SessionSet.newInstanceForResult(this.realm)
// set.timeFrame?.let {
// it.setDate(startDate, endDate)
// } ?: run {

@ -1,13 +1,14 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
@ -38,7 +39,7 @@ open class TournamentFeature : RealmObject(), NameManageable, StaticRowRepresent
// CountableUsage
override var useCount: Int = 0
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return this.name
}

@ -1,12 +1,13 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
@ -33,7 +34,7 @@ open class TournamentName : RealmObject(), NameManageable, StaticRowRepresentabl
// The name of the tournament
override var name: String = ""
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return this.name
}

@ -1,26 +1,36 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import com.github.mikephil.charting.data.Entry
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.DatedValue
import net.pokeranalytics.android.model.interfaces.Manageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.view.DefaultLegendValues
import net.pokeranalytics.android.ui.view.LegendContent
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionRow
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.util.extensions.findById
import java.text.DateFormat
import java.util.*
import kotlin.collections.ArrayList
open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, Filterable, DatedValue {
open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, TimeFilterable, Filterable, DatedValue,
GraphUnderlyingEntry {
companion object {
val rowRepresentation: List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.addAll(TransactionRow.values())
@ -29,13 +39,23 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
fun fieldNameForQueryType(queryCondition: Class<out QueryCondition>): String? {
return when (queryCondition) {
QueryCondition.AnyBankroll::class.java -> "bankroll.id"
QueryCondition.StartedFromDate::class.java, QueryCondition.StartedToDate::class.java -> "date"
QueryCondition.AnyTransactionType::class.java -> "type.id"
QueryCondition.StartedFromDate::class.java, QueryCondition.StartedToDate::class.java, QueryCondition.EndedFromDate::class.java, QueryCondition.EndedToDate::class.java -> "date"
QueryCondition.AnyDayOfWeek::class.java, QueryCondition.IsWeekEnd::class.java, QueryCondition.IsWeekDay::class.java -> "dayOfWeek"
QueryCondition.AnyMonthOfYear::class.java -> "month"
QueryCondition.AnyYear::class.java -> "year"
QueryCondition.PastDay::class.java, QueryCondition.IsToday::class.java, QueryCondition.WasYesterday::class.java, QueryCondition.WasTodayAndYesterday::class.java, QueryCondition.DuringThisYear::class.java, QueryCondition.DuringThisMonth::class.java, QueryCondition.DuringThisWeek::class.java -> "date"
else -> null
}
}
}
init {
this.updateTimeParameter(Date())
}
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -47,6 +67,10 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
// The date of the transaction
override var date: Date = Date()
set(value) {
field = value
this.updateTimeParameter(field)
}
// The type of the transaction
var type: TransactionType? = null
@ -54,6 +78,12 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
// A user comment
var comment: String = ""
// Timed interface
override var dayOfWeek: Int? = null
override var month: Int? = null
override var year: Int? = null
override var dayOfMonth: Int? = null
@Ignore
override val viewType: Int = RowViewType.ROW_TRANSACTION.ordinal
@ -75,10 +105,6 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
return bankroll != null && type != null && amount != 0.0
}
override fun alreadyExists(realm: Realm): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
return if (bankroll == null) {
R.string.no_br_popup_message
@ -89,12 +115,47 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
}
}
override fun alreadyExists(realm: Realm): Boolean {
return realm.findById<Transaction>(id) != null
}
override fun isValidForDelete(realm: Realm): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
return true
}
override fun getFailedDeleteMessage(): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return R.string.relationship_error
}
override fun getSaveValidityStatus(realm: Realm): SaveValidityStatus {
if (bankroll == null || type == null || amount == 0.0) {
return SaveValidityStatus.DATA_INVALID
}
return SaveValidityStatus.VALID
}
// GraphUnderlyingEntry
override fun entryTitle(context: Context): String {
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.date)
}
override fun formattedValue(stat: Stat): TextFormat {
return stat.format(this.amount)
}
override fun legendValues(
stat: Stat,
entry: Entry,
style: GraphFragment.Style,
groupName: String,
context: Context
): LegendContent {
val entryValue = this.formattedValue(stat)
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
val leftName = context.getString(R.string.amount)
return DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName)
}
}

@ -1,10 +1,14 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.Localizable
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
@ -15,10 +19,19 @@ import kotlin.collections.ArrayList
open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable {
enum class Value(val additive: Boolean) {
enum class Value(val additive: Boolean) : Localizable {
WITHDRAWAL(false),
DEPOSIT(true),
BONUS(true)
BONUS(true);
override val resId: Int?
get() {
return when (this) {
WITHDRAWAL -> R.string.withdrawal
DEPOSIT -> R.string.deposit
BONUS -> R.string.bonus
}
}
}
companion object {
@ -42,10 +55,15 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
@PrimaryKey
override var id = UUID.randomUUID().toString()
// The name of the transaction type
/**
* The name of the transaction type
*/
override var name: String = ""
// Whether or not the amount is added, or subtracted to the bankroll total
/**
* Whether or not the amount is added, or subtracted to the bankroll total
* The value can only be changed if no transaction is linked to this type
*/
var additive: Boolean = false
// Whether or not the type can be deleted by the user
@ -54,7 +72,7 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
// The predefined kind, if necessary, like: Withdrawal, deposit, or tips
var kind: Int? = null
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return this.name
}
@ -69,6 +87,13 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
}
}
override fun boolForRow(row: RowRepresentable): Boolean {
return when (row) {
TransactionTypeRow.TRANSACTION_ADDITIVE -> this.additive
else -> super.boolForRow(row)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf("defaultValue" to this.name))
}
@ -76,15 +101,18 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) {
SimpleRow.NAME -> this.name = value as String? ?: ""
TransactionTypeRow.TRANSACTION_ADDITIVE -> this.additive = value as Boolean? ?: false
}
}
override fun isValidForDelete(realm: Realm): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
return realm.where(Transaction::class.java)
.equalTo("type.id", this.id).findAll().isEmpty()
}
override fun getFailedDeleteMessage(): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return R.string.transaction_relationship_error
}
}

@ -3,7 +3,7 @@ package net.pokeranalytics.android.model.retrofit
import com.google.gson.annotations.SerializedName
/**
* Currency Converter mapping class
* CurrencyCode Converter mapping class
*/
class CurrencyConverterValue {
@SerializedName("val")

@ -11,7 +11,7 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
* Returns all significant parameters concatenated in a String
* Not suitable for display
*/
fun Session.parameterRepresentation(context: Context): String {
private fun Session.parameterRepresentation(context: Context): String {
var representation = ""
this.significantFields().forEach {
@ -56,9 +56,8 @@ class FavoriteSessionFinder {
/**
* A counter convenience class
*/
class Counter(session: Session) {
class Counter(var session: Session) {
var session: Session = session
var counter: Int = 1
fun increment() {
@ -77,7 +76,7 @@ class FavoriteSessionFinder {
fun copyParametersFromFavoriteSession(newSession: Session, location: Location?, context: Context) {
val favoriteSession =
FavoriteSessionFinder.favoriteSession(newSession.type, location, newSession.realm, context)
favoriteSession(newSession.type, location, newSession.realm, context)
favoriteSession?.let { fav ->

@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.utils
import android.content.Context
import io.realm.Realm
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.util.UserDefaults
@ -19,7 +20,7 @@ class Seed(var context:Context) : Realm.Transaction {
}
private fun createDefaultTournamentFeatures(realm: Realm) {
context.resources.getStringArray(net.pokeranalytics.android.R.array.seed_tournament_features).forEach {
context.resources.getStringArray(R.array.seed_tournament_features).forEach {
val tournamentFeature = TournamentFeature()
tournamentFeature.id = UUID.randomUUID().toString()
tournamentFeature.name = it
@ -29,7 +30,7 @@ class Seed(var context:Context) : Realm.Transaction {
private fun createDefaultCurrencyAndBankroll(realm: Realm) {
// Currency
// CurrencyCode
val localeCurrency = UserDefaults.getLocaleCurrency()
val defaultCurrency = Currency()
defaultCurrency.code = localeCurrency.currencyCode
@ -37,15 +38,15 @@ class Seed(var context:Context) : Realm.Transaction {
// Bankroll
val bankroll = Bankroll()
bankroll.name = context.resources.getString(net.pokeranalytics.android.R.string.live)
bankroll.name = context.resources.getString(R.string.live)
bankroll.live = true
bankroll.currency = realm.where<Currency>().equalTo("code", localeCurrency.currencyCode).findFirst()
realm.insertOrUpdate(bankroll)
}
private fun createDefaultGames(realm: Realm) {
val gamesName = context.resources.getStringArray(net.pokeranalytics.android.R.array.seed_games)
val gamesShortName = context.resources.getStringArray(net.pokeranalytics.android.R.array.seed_games_short_name)
val gamesName = context.resources.getStringArray(R.array.seed_games)
val gamesShortName = context.resources.getStringArray(R.array.seed_games_short_name)
for ((index, name) in gamesName.withIndex()) {
val game = Game()
game.id = UUID.randomUUID().toString()
@ -58,6 +59,7 @@ class Seed(var context:Context) : Realm.Transaction {
private fun createDefaultTransactionTypes(realm: Realm) {
TransactionType.Value.values().forEachIndexed { index, value ->
val type = TransactionType()
type.name = value.localizedTitle(context)
type.additive = value.additive
type.kind = index
type.lock = true

@ -7,9 +7,7 @@ import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import kotlin.math.max
class CorruptSessionSetException(message: String) : Exception(message) {
}
class CorruptSessionSetException(message: String) : Exception(message)
/**
* The manager is in charge of updating the abstract concept of timeline,
@ -169,7 +167,7 @@ class SessionSetManager {
sessionSet.deleteFromRealm()
sessions.forEach {
SessionSetManager.updateTimeline(it)
updateTimeline(it)
}
}
}

@ -0,0 +1,21 @@
package net.pokeranalytics.android.model.utils
import io.realm.Realm
import net.pokeranalytics.android.model.realm.Session
import java.util.*
class SessionUtils {
companion object {
/**
* Returns true if the provided parameters doesn't correspond to an existing session
*/
fun unicityCheck(realm: Realm, startDate: Date, endDate: Date, net: Double) : Boolean {
val sessions = realm.where(Session::class.java).equalTo("startDate", startDate).equalTo("endDate", endDate).equalTo("result.net", net).findAll()
return sessions.isEmpty()
}
}
}

@ -4,11 +4,19 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import io.realm.RealmResults
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
class BankrollActivity : PokerAnalyticsActivity() {
private lateinit var computableResults: RealmResults<ComputableResult>
private lateinit var bankrolls: RealmResults<Bankroll>
private lateinit var transactions: RealmResults<Transaction>
companion object {
fun newInstance(context: Context) {
val intent = Intent(context, BankrollActivity::class.java)
@ -28,6 +36,55 @@ class BankrollActivity : PokerAnalyticsActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bankroll)
// this.computableResults = getRealm().where(ComputableResult::class.java).findAll() // ComputableResult are existing only if sessions are ended
// this.computableResults.addChangeListener { t, changeSet ->
//
// val bankrolls = mutableSetOf<Bankroll>()
// val indexes = mutableSetOf<Int>()
// indexes.addAll(changeSet.changes.toList())
// indexes.addAll(changeSet.insertions.toList())
// indexes.addAll(changeSet.deletions.toList())
// indexes.forEach { index ->
// t[index]?.session?.bankroll?.let { br ->
// bankrolls.add(br)
// }
// }
// this.computeBankrollReports(bankrolls)
// }
// this.bankrolls = getRealm().where(Bankroll::class.java).findAll() // ComputableResult are existing only if sessions are ended
// this.bankrolls.addChangeListener { _, changeSet ->
//
//
//
//
//
// }
// this.transactions = getRealm().where(Transaction::class.java).findAll() // ComputableResult are existing only if sessions are ended
// this.transactions.addChangeListener { t, changeSet ->
//
// val bankrolls = mutableSetOf<Bankroll>()
// val indexes = mutableSetOf<Int>()
// indexes.addAll(changeSet.changes.toList())
// indexes.addAll(changeSet.insertions.toList())
// indexes.addAll(changeSet.deletions.toList())
// indexes.forEach { index ->
// if (t.isNotEmpty()) {
// t[index]?.bankroll?.let { br ->
// bankrolls.add(br)
// }
// }
// }
// this.computeBankrollReports(bankrolls)
// }
}
fun computeBankrollReports(bankrolls: Collection<Bankroll>) {
}
}

@ -0,0 +1,48 @@
package net.pokeranalytics.android.ui.activity
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.bankroll.BankrollReport
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.BankrollDetailsFragment
class BankrollDetailsActivity : PokerAnalyticsActivity() {
companion object {
private var bankrollReport: BankrollReport? = null
/**
* Default constructor
*/
fun newInstanceForResult(fragment: Fragment, bankrollReport: BankrollReport, requestCode: Int) {
this.bankrollReport = bankrollReport
val intent = Intent(fragment.requireContext(), BankrollDetailsActivity::class.java)
fragment.startActivityForResult(intent, requestCode)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bankroll_details)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
bankrollReport?.let {
val fragmentTransaction = supportFragmentManager.beginTransaction()
val reportDetailsFragment = BankrollDetailsFragment.newInstance(it)
fragmentTransaction.add(R.id.container, reportDetailsFragment)
fragmentTransaction.commit()
bankrollReport = null
}
}
}

@ -0,0 +1,31 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.util.billing.AppGuard
class BillingActivity : PokerAnalyticsActivity() {
companion object {
fun newInstance(context: Context) {
val intent = Intent(context, BillingActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_billing)
}
override fun onResume() {
super.onResume()
}
}

@ -0,0 +1,36 @@
package net.pokeranalytics.android.ui.activity
import android.os.Bundle
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.ReportActivity
import net.pokeranalytics.android.ui.fragment.report.ComparisonReportFragment
class ComparisonReportActivity : ReportActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_report_details)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
parameters?.let {
val report = it.report
val title = it.title
val fragmentTransaction = supportFragmentManager.beginTransaction()
val reportDetailsFragment = ComparisonReportFragment.newInstance(report, title)
fragmentTransaction.add(R.id.reportDetailsContainer, reportDetailsFragment)
fragmentTransaction.commit()
}
parameters = null
}
}

@ -3,23 +3,37 @@ package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_data_list.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.DataListFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
class DataListActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"),
ITEM_DELETED("ITEM_DELETED")
LIVE_DATA_TYPE("LIVE_DATA_TYPE"),
ITEM_DELETED("ITEM_DELETED"),
SHOW_ADD_BUTTON("SHOW_ADD_BUTTON"),
}
companion object {
fun newInstance(context: Context, dataType: Int) {
context.startActivity(getIntent(context, dataType))
}
fun newSelectInstance(fragment: Fragment, dataType: Int, showAddButton: Boolean = true) {
val context = fragment.requireContext()
fragment.startActivityForResult(getIntent(context, dataType, showAddButton), FilterActivityRequestCode.SELECT_FILTER.ordinal)
}
private fun getIntent(context: Context, dataType: Int, showAddButton: Boolean = true): Intent {
val intent = Intent(context, DataListActivity::class.java)
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType)
context.startActivity(intent)
intent.putExtra(IntentKey.SHOW_ADD_BUTTON.keyName, showAddButton)
return intent
}
}
@ -34,18 +48,11 @@ class DataListActivity : PokerAnalyticsActivity() {
* Init UI
*/
private fun initUI() {
val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0)
val showAddButton = intent.getBooleanExtra(IntentKey.SHOW_ADD_BUTTON.keyName, true)
val fragment = dataListFragment as DataListFragment
fragment.setData(dataType)
}
/**
* Init data
*/
private fun initData() {
fragment.updateUI(showAddButton)
}
}

@ -7,12 +7,10 @@ import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.BankrollDataFragment
import net.pokeranalytics.android.ui.fragment.EditableDataFragment
import net.pokeranalytics.android.ui.fragment.LocationDataFragment
import net.pokeranalytics.android.ui.fragment.TransactionDataFragment
import net.pokeranalytics.android.ui.fragment.data.*
class EditableDataActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"),
PRIMARY_KEY("PRIMARY_KEY");
@ -34,9 +32,9 @@ class EditableDataActivity : PokerAnalyticsActivity() {
/**
* Create a new instance for result
*/
fun newInstanceForResult(fragment: Fragment, dataType: Int, primaryKey: String? = null, requestCode: Int) {
fun newInstanceForResult(fragment: Fragment, dataType: LiveData, primaryKey: String? = null, requestCode: Int) {
val intent = Intent(fragment.requireContext(), EditableDataActivity::class.java)
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType)
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType.ordinal)
primaryKey?.let {
intent.putExtra(IntentKey.PRIMARY_KEY.keyName, it)
}
@ -65,6 +63,8 @@ class EditableDataActivity : PokerAnalyticsActivity() {
LiveData.BANKROLL.ordinal -> BankrollDataFragment()
LiveData.LOCATION.ordinal -> LocationDataFragment()
LiveData.TRANSACTION.ordinal -> TransactionDataFragment()
LiveData.CUSTOM_FIELD.ordinal -> CustomFieldDataFragment()
LiveData.TRANSACTION_TYPE.ordinal -> TransactionTypeDataFragment()
else -> EditableDataFragment()
}

@ -5,6 +5,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.FilterDetailsFragment
@ -16,6 +17,7 @@ class FilterDetailsActivity : PokerAnalyticsActivity() {
}
companion object {
/**
* Default constructor
*/
@ -29,7 +31,8 @@ class FilterDetailsActivity : PokerAnalyticsActivity() {
/**
* Create a new instance for result
*/
fun newInstanceForResult(fragment: Fragment, filterId: String, filterCategoryOrdinal: Int, requestCode: Int) {
fun newInstanceForResult(fragment: Fragment, filterId: String, filterCategoryOrdinal: Int, requestCode: Int, filter: Filter? = null) {
val intent = Intent(fragment.requireContext(), FilterDetailsActivity::class.java)
intent.putExtra(IntentKey.FILTER_ID.keyName, filterId)
intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal)
@ -63,7 +66,6 @@ class FilterDetailsActivity : PokerAnalyticsActivity() {
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
fragment.setData(filterId, filterCategoryOrdinal)
}
}

@ -7,33 +7,37 @@ import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.FiltersFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType
class FiltersActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) {
FILTER_ID("FILTER_ID");
FILTER_ID("FILTER_ID"),
FILTERABLE_TYPE("FILTERABLE_TYPE"),
HIDE_MOST_USED_FILTERS("HIDE_MOST_USED_FILTERS"),
;
}
private lateinit var fragment: FiltersFragment
companion object {
/**
* Default constructor
* Create a new instance for result
*/
fun newInstance(context: Context, filterId: String? = null) {
fun newInstanceForResult(fragment: Fragment, filterId: String? = null, currentFilterable: FilterableType, hideMostUsedFilters: Boolean = false) {
val intent = getIntent(fragment.requireContext(), filterId, currentFilterable, hideMostUsedFilters)
fragment.startActivityForResult(intent, FilterActivityRequestCode.CREATE_FILTER.ordinal)
}
private fun getIntent(context: Context, filterId: String?, currentFilterable: FilterableType, hideMostUsedFilters: Boolean = false): Intent {
val intent = Intent(context, FiltersActivity::class.java)
intent.putExtra(IntentKey.FILTERABLE_TYPE.keyName, currentFilterable.uniqueIdentifier)
intent.putExtra(IntentKey.HIDE_MOST_USED_FILTERS.keyName, hideMostUsedFilters)
filterId?.let {
intent.putExtra(IntentKey.FILTER_ID.keyName, it)
}
context.startActivity(intent)
}
/**
* Create a new instance for result
*/
fun newInstanceForResult(fragment: Fragment, requestCode: Int) {
val intent = Intent(fragment.requireContext(), FiltersActivity::class.java)
fragment.startActivityForResult(intent, requestCode)
return intent
}
}
@ -55,11 +59,15 @@ class FiltersActivity : PokerAnalyticsActivity() {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val filterId = intent.getStringExtra(IntentKey.FILTER_ID.keyName)
val uniqueIdentifier= intent.getIntExtra(IntentKey.FILTERABLE_TYPE.keyName, 0)
val hideMostUsedFilters = intent.getBooleanExtra(IntentKey.HIDE_MOST_USED_FILTERS.keyName, false)
val filterableType = FilterableType.valueByIdentifier(uniqueIdentifier)
fragment = FiltersFragment()
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
fragment.setData(filterId)
fragment.setData(filterId, filterableType)
fragment.updateMostUsedFiltersVisibility(!hideMostUsedFilters)
}

@ -0,0 +1,70 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.LineDataSet
import kotlinx.android.synthetic.main.activity_graph.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.GraphFragment
class GraphActivity : PokerAnalyticsActivity() {
companion object {
private var lineDataSets: List<LineDataSet>? = null
private var barDataSets: List<BarDataSet>? = null
private var style: GraphFragment.Style? = GraphFragment.Style.LINE
private var activityTitle: String? = null
/**
* Default constructor
*/
fun newInstance(
context: Context, lineDataSets: List<LineDataSet>? = null, barDataSets: List<BarDataSet>? = null,
style: GraphFragment.Style = GraphFragment.Style.LINE, title: String? = null
) {
this.lineDataSets = lineDataSets
this.barDataSets = barDataSets
this.style = style
this.activityTitle = title
val intent = Intent(context, GraphActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_graph)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
activityTitle?.let {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
title = activityTitle
activityTitle = null
}
style?.let {
val fragmentTransaction = supportFragmentManager.beginTransaction()
val graphFragment = GraphFragment.newInstance(lineDataSets, barDataSets, it)
fragmentTransaction.add(R.id.container, graphFragment)
fragmentTransaction.commit()
}
lineDataSets = null
barDataSets = null
style = null
}
}

@ -3,20 +3,21 @@ package net.pokeranalytics.android.ui.activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import com.google.android.material.bottomnavigation.BottomNavigationView
import io.realm.RealmResults
import kotlinx.android.synthetic.main.activity_home.*
import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.activity.components.ResultCode
import net.pokeranalytics.android.ui.adapter.HomePagerAdapter
import net.pokeranalytics.android.ui.extensions.showAlertDialog
import net.pokeranalytics.android.util.billing.AppGuard
import timber.log.Timber
@ -30,24 +31,11 @@ class HomeActivity : PokerAnalyticsActivity() {
}
}
private var homeMenu: Menu? = null
private lateinit var currencies: RealmResults<Currency>
private var homePagerAdapter: HomePagerAdapter? = null
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
//CLEAN
/*
R.id.navigation_history -> {
displayFragment(0)§
}
R.id.navigation_stats -> {
displayFragment(1)
}
R.id.navigation_settings -> {
displayFragment(2)
}
*/
R.id.navigation_history -> {
displayFragment(0)
}
@ -70,6 +58,11 @@ class HomeActivity : PokerAnalyticsActivity() {
return@OnNavigationItemSelectedListener true
}
override fun onResume() {
super.onResume()
AppGuard.requestPurchasesUpdate()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -87,22 +80,41 @@ class HomeActivity : PokerAnalyticsActivity() {
observeRealmObjects()
initUI()
checkFirstLaunch()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menu?.clear()
menuInflater.inflate(R.menu.toolbar_home, menu)
this.homeMenu = menu
//TODO: Change queryWith button visibility
homeMenu?.findItem(R.id.filter)?.isVisible = false
return super.onCreateOptionsMenu(menu)
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let {
when (intent.action) {
"android.intent.action.VIEW" -> { // import
val data = it.data
if (data != null) {
this.requestImportConfirmation(data)
} else {
throw IllegalStateException("URI null on import")
}
}
else -> {
Timber.d("Intent ${intent.action} unmanaged")
}
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.filter -> manageFilters()
}
return super.onOptionsItemSelected(item)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
RequestCode.IMPORT.value -> {
if (resultCode == ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) {
showAlertDialog(context = this, message = R.string.unknown_import_format_popup_message)
}
}
}
}
private fun observeRealmObjects() {
@ -112,14 +124,12 @@ class HomeActivity : PokerAnalyticsActivity() {
// observe currency changes
this.currencies = realm.where(Currency::class.java).findAll()
this.currencies.addChangeListener { t, _ ->
realm.beginTransaction()
realm.executeTransaction {
t.forEach {
it.refreshRelatedRatedValues()
}
realm.commitTransaction()
}
}
}
/**
@ -127,17 +137,13 @@ class HomeActivity : PokerAnalyticsActivity() {
*/
private fun initUI() {
setSupportActionBar(toolbar)
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
navigation.selectedItemId = R.id.navigation_history
val homePagerAdapter = HomePagerAdapter(supportFragmentManager)
homePagerAdapter = HomePagerAdapter(supportFragmentManager)
viewPager.offscreenPageLimit = 5
viewPager.enablePaging = false
viewPager.adapter = homePagerAdapter
updateToolbar(0)
}
/**
@ -159,83 +165,16 @@ class HomeActivity : PokerAnalyticsActivity() {
*/
private fun displayFragment(index: Int) {
viewPager.setCurrentItem(index, false)
updateToolbar(index)
}
/**
* Update toolbar
*/
private fun updateToolbar(index: Int) {
when (index) {
//CLEAN
/*
0 -> {
toolbar.title = getString(R.string.feed)
homeMenu?.findItem(R.id.queryWith)?.isVisible = false
}
1 -> {
toolbar.title = getString(R.string.stats)
homeMenu?.findItem(R.id.queryWith)?.isVisible = false
}
2 -> {
toolbar.title = getString(R.string.services)
homeMenu?.findItem(R.id.queryWith)?.isVisible = false
}
*/
// Import
0 -> {
toolbar.title = getString(R.string.feed)
homeMenu?.findItem(R.id.filter)?.isVisible = false
}
1 -> {
toolbar.title = getString(R.string.stats)
homeMenu?.findItem(R.id.filter)?.isVisible = false
}
2 -> {
toolbar.title = getString(R.string.calendar)
homeMenu?.findItem(R.id.filter)?.isVisible = false
}
3 -> {
toolbar.title = getString(R.string.reports)
homeMenu?.findItem(R.id.filter)?.isVisible = false
}
4 -> {
toolbar.title = getString(R.string.services) //getString(R.string.more)
homeMenu?.findItem(R.id.filter)?.isVisible = false
}
}
}
/**
* Manage filters
*/
private fun manageFilters() {
val filterSelected = false
private fun requestImportConfirmation(uri: Uri) {
val choices = ArrayList<CharSequence>()
choices.add(getString(R.string.new_str))
if (filterSelected) {
choices.add(getString(R.string.modify_current_filter))
choices.add(getString(R.string.load_from_db))
choices.add(getString(R.string.remove_filter))
}
val builder = AlertDialog.Builder(this)
builder.setTitle(R.string.filter_selection)
.setCancelable(true)
.setItems(choices.toTypedArray()) { _, which ->
Timber.d("Click on $which")
when (which) {
0 -> FiltersActivity.newInstance(this@HomeActivity)
}
}
.setNegativeButton(R.string.cancel) { _, _ ->
Timber.d("Click on cancel")
}
showAlertDialog(context = this, title = R.string.import_confirmation, showCancelButton = true, positiveAction = {
ImportActivity.newInstanceForResult(this, uri)
})
builder.show()
}
}

@ -0,0 +1,86 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import io.realm.Realm
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.fragment.ImportFragment
import timber.log.Timber
class ImportActivity : PokerAnalyticsActivity() {
private lateinit var fileURI: Uri
enum class IntentKey(val keyName: String) {
URI("uri")
}
companion object {
/**
* Create a new instance for result
*/
fun newInstanceForResult(context: FragmentActivity, uri: Uri) {
context.startActivityForResult(getIntent(context, uri), RequestCode.IMPORT.value)
}
private fun getIntent(context: Context, uri: Uri): Intent {
val intent = Intent(context, ImportActivity::class.java)
intent.putExtra(ImportActivity.IntentKey.URI.keyName, uri)
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.fileURI = intent.getParcelableExtra(ImportActivity.IntentKey.URI.keyName)
setContentView(R.layout.activity_import)
initUI()
}
override fun onStop() {
super.onStop()
// Updates the main thread instance with newly inserted data
val realm = Realm.getDefaultInstance()
realm.refresh()
realm.close()
}
private fun initUI() {
val fragmentTransaction = supportFragmentManager.beginTransaction()
val fragment = ImportFragment()
val fis = contentResolver.openInputStream(fileURI)
Timber.d("Load fragment data with: $fis")
fis?.let {
fragment.setData(it)
}
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
}
// private fun requestPermission() {
// if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
// ActivityCompat.requestPermissions(
// this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PERMISSION_REQUEST_ACCESS_FINE_LOCATION
// )
// }
// }
//
// override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// }
}

@ -0,0 +1,127 @@
package net.pokeranalytics.android.ui.activity
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewAnimationUtils
import kotlinx.android.synthetic.main.activity_new_data.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.extensions.px
class NewDataMenuActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) {
CHOICE("CHOICE"),
}
companion object {
fun newInstance(context: Context) {
val intent = Intent(context, NewDataMenuActivity::class.java)
context.startActivity(intent)
}
}
private val fabSize = 48.px
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(net.pokeranalytics.android.R.layout.activity_new_data)
initUI()
}
override fun onBackPressed() {
hideMenu()
}
override fun onPause() {
super.onPause()
overridePendingTransition(0, 0)
}
/**
* Init UI
*/
private fun initUI() {
overridePendingTransition(0, 0)
container.viewTreeObserver.addOnGlobalLayoutListener {
showMenu()
}
newCashGame.setOnClickListener {
finishWithResult(0)
}
newTournament.setOnClickListener {
finishWithResult(1)
}
newTransaction.setOnClickListener {
finishWithResult(2)
}
container.setOnClickListener {
hideMenu()
}
}
/**
* Set the result and hide menu
*/
private fun finishWithResult(choice: Int) {
val intent = Intent()
intent.putExtra(IntentKey.CHOICE.keyName, choice)
setResult(RESULT_OK, intent)
GlobalScope.launch(Dispatchers.Main) {
delay(200)
hideMenu(true)
}
}
/**
* Show menu
*/
private fun showMenu() {
val cx = menuContainer.measuredWidth - fabSize / 2
val cy = menuContainer.measuredHeight - fabSize / 2
val finalRadius = Math.max(menuContainer.width, menuContainer.height)
val anim = ViewAnimationUtils.createCircularReveal(menuContainer, cx, cy, 0f, finalRadius.toFloat())
anim.duration = 150
menuContainer.visibility = View.VISIBLE
anim.start()
}
/**
* Hide menu
*/
private fun hideMenu(hideQuickly: Boolean = false) {
val cx = menuContainer.measuredWidth - fabSize / 2
val cy = menuContainer.measuredHeight - fabSize / 2
val initialRadius = menuContainer.width
val anim = ViewAnimationUtils.createCircularReveal(menuContainer, cx, cy, initialRadius.toFloat(), 0f)
anim.duration = 150
anim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
menuContainer.visibility = View.INVISIBLE
finish()
}
})
anim.start()
}
}

@ -0,0 +1,63 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.activity.components.ReportActivity
import net.pokeranalytics.android.ui.activity.components.ReportParameters
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.fragment.report.ProgressReportFragment
class ProgressReportActivity : ReportActivity() {
companion object {
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects
/**
* Default constructor
*/
fun newInstance(context: Context, report: Report, title: String, stat: Stat? = null, displayAggregationChoices: Boolean = true) {
parameters = ReportParameters(report, title, stat, showAggregationChoices = displayAggregationChoices)
val intent = Intent(context, ProgressReportActivity::class.java)
context.startActivity(intent)
}
fun newInstanceForResult(fragment: Fragment, report: Report, title: String, stat: Stat? = null, displayAggregationChoices: Boolean = true) {
parameters = ReportParameters(report, title, stat, showAggregationChoices = displayAggregationChoices)
val intent = Intent(fragment.context, ProgressReportActivity::class.java)
fragment.startActivityForResult(intent, RequestCode.DEFAULT.value)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_progress_report)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
val fragmentTransaction = supportFragmentManager.beginTransaction()
val statisticDetailsFragment = ProgressReportFragment()
fragmentTransaction.add(R.id.statisticDetailsContainer, statisticDetailsFragment)
fragmentTransaction.commit()
parameters?.let {
val report = it.report
val stat = it.stat ?: report.options.stats.first()
statisticDetailsFragment.setData(report, stat, it.showAggregationChoices, it.title)
parameters = null
}
}
}

@ -0,0 +1,29 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
class ReportCreationActivity : PokerAnalyticsActivity() {
companion object {
var options: Calculator.Options? = null
fun newInstanceForResult(fragment: Fragment, context: Context) {
val intent = Intent(context, ReportCreationActivity::class.java)
fragment.startActivityForResult(intent, RequestCode.NEW_REPORT.value)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_report_creation)
}
}

@ -1,55 +0,0 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.ReportDetailsFragment
class ReportDetailsActivity : PokerAnalyticsActivity() {
companion object {
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects
private var report: Report? = null
private var reportTitle: String = ""
/**
* Default constructor
*/
fun newInstance(context: Context, report: Report, reportTitle: String) {
//parameters = GraphParameters(stat, group, report)
this.report = report
this.reportTitle = reportTitle
val intent = Intent(context, ReportDetailsActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_report_details)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
report?.let {
val fragmentTransaction = supportFragmentManager.beginTransaction()
val reportDetailsFragment = ReportDetailsFragment.newInstance(it, reportTitle)
fragmentTransaction.add(R.id.reportDetailsContainer, reportDetailsFragment)
fragmentTransaction.commit()
report = null
}
}
}

@ -1,8 +1,10 @@
package net.pokeranalytics.android.ui.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_session.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
@ -16,6 +18,7 @@ class SessionActivity: PokerAnalyticsActivity() {
}
companion object {
fun newInstance(context: Context, isTournament: Boolean? = false, sessionId: String? = "") {
val intent = Intent(context, SessionActivity::class.java)
isTournament?.let {
@ -27,6 +30,17 @@ class SessionActivity: PokerAnalyticsActivity() {
context.startActivity(intent)
}
fun newInstanceforResult(fragment: Fragment, isTournament: Boolean? = false, sessionId: String? = "", requestCode: Int) {
val intent = Intent(fragment.requireContext(), SessionActivity::class.java)
isTournament?.let {
intent.putExtra(IntentKey.IS_TOURNAMENT.keyName, isTournament)
}
sessionId?.let {
intent.putExtra(IntentKey.SESSION_ID.keyName, sessionId)
}
fragment.startActivityForResult(intent, requestCode)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -37,6 +51,7 @@ class SessionActivity: PokerAnalyticsActivity() {
}
override fun onBackPressed() {
setResult(Activity.RESULT_OK)
super.onBackPressed()
(sessionFragment as SessionFragment).onBackPressed()
}

@ -1,59 +0,0 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.StatisticDetailsFragment
class StatisticsDetailsParameters(var stat: Stat, var computableGroup: ComputableGroup, var report: Report, var title: String? = null)
class StatisticDetailsActivity : PokerAnalyticsActivity() {
companion object {
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects
private var parameters: StatisticsDetailsParameters? = null
private var displayAggregationChoices: Boolean = true
/**
* Default constructor
*/
fun newInstance(context: Context, stat: Stat, group: ComputableGroup, report: Report, displayAggregationChoices: Boolean = true, title: String? = null) {
parameters = StatisticsDetailsParameters(stat, group, report, title)
this.displayAggregationChoices = displayAggregationChoices
val intent = Intent(context, StatisticDetailsActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_statistic_details)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
val fragmentTransaction = supportFragmentManager.beginTransaction()
val statisticDetailsFragment = StatisticDetailsFragment()
fragmentTransaction.add(R.id.statisticDetailsContainer, statisticDetailsFragment)
fragmentTransaction.commit()
parameters?.let {
statisticDetailsFragment.setData(it.stat, it.computableGroup, it.report, displayAggregationChoices, it.title)
parameters = null
}
}
}

@ -0,0 +1,35 @@
package net.pokeranalytics.android.ui.activity
import android.os.Bundle
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.ReportActivity
import net.pokeranalytics.android.ui.fragment.report.TableReportFragment
class TableReportActivity : ReportActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_table_report)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
parameters?.let {
val report = it.report
val title = it.title
val fragmentTransaction = supportFragmentManager.beginTransaction()
val fragment = TableReportFragment.newInstance(report, title)
fragmentTransaction.add(R.id.reportDetailsContainer, fragment)
fragmentTransaction.commit()
}
parameters = null
}
}

@ -0,0 +1,13 @@
package net.pokeranalytics.android.ui.activity.components
enum class RequestCode(var value: Int) {
DEFAULT(1),
NEW_SESSION(800),
NEW_TRANSACTION(801),
NEW_REPORT(802),
IMPORT(900)
}
enum class ResultCode(var value: Int) {
IMPORT_UNRECOGNIZED_FORMAT(901)
}

@ -1,12 +1,14 @@
package net.pokeranalytics.android.ui.activity.components
import android.Manifest
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.PersistableBundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.crashlytics.android.Crashlytics
import com.google.android.libraries.places.api.model.PlaceLikelihood
import io.realm.Realm
import net.pokeranalytics.android.model.realm.Location
@ -18,18 +20,43 @@ open class PokerAnalyticsActivity : AppCompatActivity() {
companion object {
const val PERMISSION_REQUEST_ACCESS_FINE_LOCATION = 1000
const val PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 1001
const val PLAY_SERVICES_RESOLUTION_REQUEST = 2000
}
private var realm: Realm? = null
private var permissionCallback: ((granted: Boolean) -> Unit)? = null
// Lifecycle
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
Crashlytics.log("$this.localClassName onCreate")
}
override fun onResume() {
super.onResume()
Crashlytics.log("$this.localClassName onResume")
}
override fun onPause() {
super.onPause()
Crashlytics.log("$this.localClassName onPause")
}
override fun onDestroy() {
super.onDestroy()
Crashlytics.log("$this.localClassName onDestroy")
this.realm?.close()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
PERMISSION_REQUEST_ACCESS_FINE_LOCATION -> {
if (permissions.isNotEmpty() && permissions[0] == Manifest.permission.ACCESS_FINE_LOCATION
if (permissions.isNotEmpty() && permissions[0] == ACCESS_FINE_LOCATION
&& grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
permissionCallback?.invoke(true)
@ -53,11 +80,6 @@ open class PokerAnalyticsActivity : AppCompatActivity() {
return super.onOptionsItemSelected(item)
}
override fun onDestroy() {
super.onDestroy()
this.realm?.close()
}
/**
* Return the realm instance
*/

@ -0,0 +1,37 @@
package net.pokeranalytics.android.ui.activity.components
import android.content.Context
import android.content.Intent
import androidx.fragment.app.Fragment
import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.calculus.Stat
class ReportParameters(var report: Report, var title: String, var stat: Stat? = null, var showAggregationChoices: Boolean = true)
abstract class ReportActivity : PokerAnalyticsActivity() {
companion object {
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects
var parameters: ReportParameters? = null
/**
* Default constructor
*/
fun newInstance(context: Context, report: Report, reportTitle: String, stat: Stat? = null) {
val options = report.options
this.parameters = ReportParameters(report, reportTitle, stat)
val intent = Intent(context, options.display.activityClass)
context.startActivity(intent)
}
fun newInstanceForResult(fragment: Fragment, report: Report, reportTitle: String, stat: Stat? = null) {
val options = report.options
this.parameters = ReportParameters(report, reportTitle, stat)
val intent = Intent(fragment.requireContext(), options.display.activityClass)
fragment.startActivityForResult(intent, RequestCode.DEFAULT.value)
}
}
}

@ -8,7 +8,7 @@ import androidx.fragment.app.FragmentStatePagerAdapter
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.fragment.CalendarFragment
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.fragment.HistoryFragment
import net.pokeranalytics.android.ui.fragment.FeedFragment
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import java.lang.ref.WeakReference
@ -24,7 +24,7 @@ class ComparisonChartPagerAdapter(val context: Context, fragmentManager: Fragmen
0 -> GraphFragment()
1 -> GraphFragment()
2 -> CalendarFragment.newInstance()
else -> HistoryFragment.newInstance()
else -> FeedFragment.newInstance()
}
}

@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import io.realm.RealmResults
import kotlinx.android.synthetic.main.row_history_session.view.*
import kotlinx.android.synthetic.main.row_feed_session.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.view.BindableHolder
@ -24,7 +24,7 @@ import kotlin.collections.HashMap
* @param dataSource the datasource providing rows
* @param delegate the delegate, notified of UI actions
*/
class HistorySessionRowRepresentableAdapter(
class FeedSessionRowRepresentableAdapter(
var delegate: RowRepresentableDelegate? = null,
var realmResults: RealmResults<Session>,
var pendingRealmResults: RealmResults<Session>,
@ -43,7 +43,7 @@ class HistorySessionRowRepresentableAdapter(
* Display a session view
*/
inner class RowSessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, row: Session?, adapter: HistorySessionRowRepresentableAdapter) {
fun bind(position: Int, row: Session?, adapter: FeedSessionRowRepresentableAdapter) {
itemView.sessionRow.setData(row as Session)
val listener = View.OnClickListener {
@ -57,7 +57,7 @@ class HistorySessionRowRepresentableAdapter(
* Display a session view
*/
inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, title: String, adapter: HistorySessionRowRepresentableAdapter) {
fun bind(title: String) {
// Title
itemView.findViewById<AppCompatTextView>(R.id.title)?.let {
it.text = title
@ -66,17 +66,16 @@ class HistorySessionRowRepresentableAdapter(
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == RowViewType.ROW_SESSION.ordinal) {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_history_session, parent, false)
return RowSessionViewHolder(layout)
return if (viewType == RowViewType.ROW_SESSION.ordinal) {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_feed_session, parent, false)
RowSessionViewHolder(layout)
} else {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_header_title, parent, false)
return HeaderTitleViewHolder(layout)
HeaderTitleViewHolder(layout)
}
}
override fun getItemViewType(position: Int): Int {
if (sortedHeaders.containsKey(position)) {
return RowViewType.HEADER_TITLE.ordinal
@ -93,7 +92,7 @@ class HistorySessionRowRepresentableAdapter(
if (holder is RowSessionViewHolder) {
holder.bind(position, getSessionForPosition(position), this)
} else if (holder is HeaderTitleViewHolder) {
holder.bind(position, getHeaderForPosition(holder.itemView.context, position), this)
holder.bind(getHeaderForPosition(holder.itemView.context, position))
}
}

@ -0,0 +1,163 @@
package net.pokeranalytics.android.ui.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import io.realm.RealmResults
import kotlinx.android.synthetic.main.row_transaction.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.view.BindableHolder
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.getMonthAndYear
import java.util.*
import kotlin.collections.HashMap
/**
* An adapter capable of displaying a list of RowRepresentables
* @param dataSource the datasource providing rows
* @param delegate the delegate, notified of UI actions
*/
class FeedTransactionRowRepresentableAdapter(
var delegate: RowRepresentableDelegate? = null,
var realmTransactions: RealmResults<Transaction>,
var distinctTransactionsHeaders: RealmResults<Transaction>
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var headersPositions = HashMap<Int, Date?>()
private lateinit var sortedHeaders: SortedMap<Int, Date?>
init {
refreshData()
}
/**
* Display a transaction view
*/
inner class RowTransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, row: Transaction?, adapter: FeedTransactionRowRepresentableAdapter) {
itemView.transactionRow.setData(row as Transaction)
val listener = View.OnClickListener {
adapter.delegate?.onRowSelected(position, row)
}
itemView.transactionRow.setOnClickListener(listener)
}
}
/**
* Display a header
*/
inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(title: String) {
// Title
itemView.findViewById<AppCompatTextView>(R.id.title)?.let {
it.text = title
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == RowViewType.ROW_TRANSACTION.ordinal) {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_transaction, parent, false)
RowTransactionViewHolder(layout)
} else {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_header_title, parent, false)
HeaderTitleViewHolder(layout)
}
}
override fun getItemViewType(position: Int): Int {
if (sortedHeaders.containsKey(position)) {
return RowViewType.HEADER_TITLE.ordinal
} else {
return RowViewType.ROW_TRANSACTION.ordinal
}
}
override fun getItemCount(): Int {
return realmTransactions.size + distinctTransactionsHeaders.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is RowTransactionViewHolder) {
holder.bind(position, getTransactionForPosition(position), this)
} else if (holder is HeaderTitleViewHolder) {
holder.bind(getHeaderForPosition(position))
}
}
/**
* Return the header
*/
private fun getHeaderForPosition(position: Int): String {
if (sortedHeaders.containsKey(position)) {
val realmHeaderPosition = sortedHeaders.keys.indexOf(position)
return distinctTransactionsHeaders[realmHeaderPosition]?.date?.getMonthAndYear() ?: ""
}
return NULL_TEXT
}
/**
* Get real index
*/
private fun getTransactionForPosition(position: Int): Transaction? {
// Row position
var headersBefore = 0
for (key in sortedHeaders.keys) {
if (position > key) {
headersBefore++
} else {
break
}
}
return realmTransactions[position - headersBefore]
}
/**
* Refresh headers positions
*/
fun refreshData() {
headersPositions.clear()
val start = System.currentTimeMillis()
var previousYear = Int.MAX_VALUE
var previousMonth = Int.MAX_VALUE
val calendar = Calendar.getInstance()
// Add headers if the date doesn't exist yet
for ((index, transaction) in realmTransactions.withIndex()) {
calendar.time = transaction.date
if (checkHeaderCondition(calendar, previousYear, previousMonth)) {
headersPositions[index + headersPositions.size] = transaction.date
previousYear = calendar.get(Calendar.YEAR)
previousMonth = calendar.get(Calendar.MONTH)
}
}
sortedHeaders = headersPositions.toSortedMap()
}
/**
* Check if we need to add a header
* Can be change to manage different condition
*/
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)
}
}

@ -17,12 +17,12 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
override fun getItem(position: Int): PokerAnalyticsFragment {
return when (position) {
0 -> HistoryFragment.newInstance()
0 -> FeedFragment.newInstance()
1 -> StatisticsFragment.newInstance()
2 -> CalendarFragment.newInstance()
3 -> ReportsFragment.newInstance()
4 -> SettingsFragment.newInstance() // MoreFragment.newInstance()
else -> HistoryFragment.newInstance()
4 -> MoreFragment.newInstance()
else -> FeedFragment.newInstance()
}
}
@ -43,7 +43,7 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
override fun getItemPosition(obj: Any): Int {
return when (obj) {
HistoryFragment::class.java -> 0
FeedFragment::class.java -> 0
StatisticsFragment::class.java -> 1
CalendarFragment::class.java -> 2
ReportsFragment::class.java -> 3

@ -9,7 +9,7 @@ import androidx.viewpager.widget.PagerAdapter
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.fragment.TableReportFragment
import net.pokeranalytics.android.ui.fragment.report.ComposableTableReportFragment
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import java.lang.ref.WeakReference
@ -31,7 +31,7 @@ class ReportPagerAdapter(val context: Context, val fragmentManager: FragmentMana
GraphFragment.newInstance(lineDataSets = dataSetList, style = GraphFragment.Style.MULTILINE)
}
2 -> {
TableReportFragment.newInstance(report)
ComposableTableReportFragment.newInstance(report)
}
else -> PokerAnalyticsFragment()
}

@ -10,6 +10,7 @@ import net.pokeranalytics.android.ui.view.RowViewType
interface RowRepresentableDelegate {
fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean = false) {}
fun onRowValueChanged(value: Any?, row: RowRepresentable) {}
fun onRowDeleted(row: RowRepresentable) {}
}
/**
@ -47,16 +48,20 @@ class RowRepresentableAdapter(
*/
fun refreshRow(row: RowRepresentable) {
if (row.viewType == RowViewType.TITLE_SWITCH.ordinal) {
if (row.viewType == RowViewType.TITLE_SWITCH.ordinal ||
row.viewType == RowViewType.LIST.ordinal) {
// Avoid to refresh the view because it will refresh itself
// Caution if we want to update the title for example
// Caution if we want to update the titleResId for example
return
}
val index = this.dataSource.indexForRow(row)
val rows = this.dataSource.adapterRows()
val index = rows?.indexOf(row) ?: -1
if (index >= 0) {
notifyItemChanged(index)
}
//notifyDataSetChanged()
}
/**

@ -1,9 +1,9 @@
package net.pokeranalytics.android.ui.adapter
import android.content.Context
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.util.TextFormat
/**
* Base Interface to provide the RowRepresentable to the adapter
@ -30,8 +30,6 @@ interface RowRepresentableDataSource: EditableDataSource, DisplayableDataSource,
*/
fun viewTypeForPosition(position:Int): Int
//TODO should be removed
fun indexForRow(row: RowRepresentable): Int
}
/**
@ -61,12 +59,6 @@ interface StaticRowRepresentableDataSource: RowRepresentableDataSource {
throw IllegalStateException("Need to implement Data Source")
}
override fun indexForRow(row: RowRepresentable): Int {
this.adapterRows()?.let {
return it.indexOf(row)
}
throw IllegalStateException("Need to implement Data Source")
}
}
@ -118,6 +110,13 @@ interface DisplayableDataSource {
return false
}
/**
* Returns a int for a specific row
*/
fun intForRow(row: RowRepresentable): Int {
return -1
}
/**
* Returns a localized string for a specific row
*/
@ -140,12 +139,25 @@ interface DisplayableDataSource {
}
/**
* Returns an action icon identifier for a specific row
* Returns an action icon uniqueIdentifier for a specific row
*/
fun actionIconForRow(row: RowRepresentable): Int? {
return 0
}
/**
* Returns whether the row, or its component, shoudl be enabled
*/
fun isEnabled(row: RowRepresentable): Boolean {
return true
}
/**
* Returns whether the row, or its component, shoudl be enabled
*/
fun isSelectable(row: RowRepresentable): Boolean {
return true
}
}
/**

@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.net.Uri
import android.util.TypedValue
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
@ -15,18 +16,15 @@ import androidx.core.content.FileProvider
import androidx.core.view.isVisible
import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.util.DeviceUtils
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.billing.AppGuard
import java.io.File
// Sizes
val Int.dp: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
@ -70,7 +68,8 @@ fun PokerAnalyticsActivity.openPlayStorePage() {
// Open email for "Contact us"
fun PokerAnalyticsActivity.openContactMail(subjectStringRes: Int, filePath: String? = null) {
val info = "v${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE}), Android ${android.os.Build.VERSION.SDK_INT}, ${DeviceUtils.getDeviceName()}"
val info =
"v${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE}) - ${AppGuard.isProUser}, Android ${android.os.Build.VERSION.SDK_INT}, ${DeviceUtils.getDeviceName()}"
val emailIntent = Intent(Intent.ACTION_SEND)
@ -104,6 +103,7 @@ fun PokerAnalyticsActivity.openUrl(url: String) {
fun PokerAnalyticsActivity.showAlertDialog(title: Int? = null, message: Int? = null) {
showAlertDialog(this, title, message)
}
fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = null) {
context?.let {
showAlertDialog(it, title, message)
@ -113,7 +113,10 @@ fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = n
/**
* Create and show an alert dialog
*/
fun showAlertDialog(context: Context, title: Int? = null, message: Int? = null) {
fun showAlertDialog(
context: Context, title: Int? = null, message: Int? = null, cancelButtonTitle: Int? = null, showCancelButton: Boolean = false,
positiveAction: (() -> Unit)? = null, negativeAction: (() -> Unit)? = null
) {
val builder = AlertDialog.Builder(context)
title?.let {
builder.setTitle(title)
@ -121,7 +124,19 @@ fun showAlertDialog(context: Context, title: Int? = null, message: Int? = null)
message?.let {
builder.setMessage(message)
}
builder.setPositiveButton(net.pokeranalytics.android.R.string.ok, null)
builder.setPositiveButton(net.pokeranalytics.android.R.string.ok) { _, _ ->
positiveAction?.invoke()
}
if (cancelButtonTitle != null) {
builder.setNegativeButton(cancelButtonTitle) { _, _ ->
negativeAction?.invoke()
}
} else if (showCancelButton) {
builder.setNegativeButton(R.string.cancel) { _, _ ->
negativeAction?.invoke()
}
}
builder.show()
}
@ -141,3 +156,8 @@ fun View.showWithAnimation() {
animate().cancel()
animate().alpha(1f).start()
}
fun View.addCircleRipple() = with(TypedValue()) {
context.theme.resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, this, true)
setBackgroundResource(resourceId)
}

@ -0,0 +1,167 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.*
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_bankroll.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.bankroll.BankrollReport
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
class BankrollDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_EDIT = 1000
/**
* Create new instance
*/
fun newInstance(bankrollReport: BankrollReport): BankrollDetailsFragment {
val fragment = BankrollDetailsFragment()
fragment.bankrollReport = bankrollReport
return fragment
}
}
private lateinit var bankrollAdapter: RowRepresentableAdapter
private lateinit var bankrollReport: BankrollReport
private var bankrollDetailsMenu: Menu? = null
private var rows: ArrayList<RowRepresentable> = ArrayList()
// Life Cycle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_bankroll_details, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
initData()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_EDIT && resultCode == RESULT_OK) {
if (data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) != null) {
activity?.setResult(RESULT_OK, data)
activity?.finish()
} else {
updateMenuUI()
}
}
}
override fun adapterRows(): List<RowRepresentable>? {
return rows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
menu?.clear()
inflater?.inflate(R.menu.toolbar_comparison_chart, menu)
this.bankrollDetailsMenu = menu
updateMenuUI()
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
R.id.settings -> editBankroll()
}
return true
}
// Business
/**
* Init data
*/
private fun initData() {
rows.clear()
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.global))
val totalComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total)
val netComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netResult)
val netBankedComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netBanked)
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, resId = R.string.bankroll, computedStat = totalComputedStat))
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, resId = R.string.net_result, computedStat = netComputedStat))
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, resId = R.string.net_banked, computedStat = netBankedComputedStat))
if (bankrollReport.transactionBuckets.isNotEmpty()) {
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.operations))
bankrollReport.transactionBuckets.keys.forEach { key ->
bankrollReport.transactionBuckets[key]?.let { transactionBucket ->
val typeName = transactionBucket.transactions.firstOrNull()?.type?.getDisplayName(requireContext())
val computedStat = ComputedStat(Stat.NET_RESULT, transactionBucket.total)
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, title = typeName, computedStat = computedStat))
}
}
}
}
/**
* Init UI
*/
private fun initUI() {
setDisplayHomeAsUpEnabled(true)
updateMenuUI()
bankrollAdapter = RowRepresentableAdapter(this, this)
val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = bankrollAdapter
}
}
/**
* Update menu UI
*/
private fun updateMenuUI() {
if (bankrollReport.setup.virtualBankroll) {
setToolbarTitle(getString(R.string.total))
bankrollDetailsMenu?.findItem(R.id.settings)?.isVisible = false
} else {
setToolbarTitle(bankrollReport.setup.bankroll?.name)
bankrollDetailsMenu?.findItem(R.id.settings)?.isVisible = true
}
}
/**
* Open Bankroll edit activity
*/
private fun editBankroll() {
EditableDataActivity.newInstanceForResult(this, LiveData.BANKROLL, bankrollReport.setup.bankroll?.id, REQUEST_CODE_EDIT)
}
}

@ -1,20 +1,54 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.mikephil.charting.data.LineDataSet
import io.realm.RealmObject
import io.realm.RealmResults
import kotlinx.android.synthetic.main.fragment_bankroll.*
import kotlinx.android.synthetic.main.fragment_stats.recyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
class BankrollFragment : PokerAnalyticsFragment() {
import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator
import net.pokeranalytics.android.calculus.bankroll.BankrollReport
import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.GraphActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow
import net.pokeranalytics.android.util.extensions.sorted
import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList
class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_DETAILS = 100
const val REQUEST_CODE_CREATE = 101
/**
* Create new instance
*/
@ -26,20 +60,71 @@ class BankrollFragment : PokerAnalyticsFragment() {
}
}
private lateinit var parentActivity: PokerAnalyticsActivity
private var rows: ArrayList<RowRepresentable> = ArrayList()
private var bankrollReportForRow: HashMap<RowRepresentable, BankrollReport> = HashMap()
private var lastItemClickedPosition: Int = 0
private var lastItemClickedId: String = ""
private var deletedRow: RowRepresentable? = null
private lateinit var bankrolls: RealmResults<Bankroll>
override fun deletableItems() : List<Deletable> {
return this.bankrolls
}
// Life Cycle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_bankroll, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initData()
initUI()
initData()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_DETAILS && resultCode == Activity.RESULT_OK) {
val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName)
itemToDeleteId?.let { id ->
GlobalScope.launch(Dispatchers.Main) {
delay(300)
deleteItem(dataListAdapter, bankrolls, id)
}
}
} else if (requestCode == REQUEST_CODE_CREATE && resultCode == Activity.RESULT_OK) {
//TODO: Refresh bankrolls
initData()
}
}
override fun adapterRows(): List<RowRepresentable>? {
return rows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
is GraphRow -> {
val lineDataSet = row.dataSet as LineDataSet
GraphActivity.newInstance(requireContext(), listOf(lineDataSet), title = getString(R.string.bankroll))
}
else -> {
if (bankrollReportForRow.containsKey(row)) {
bankrollReportForRow[row]?.let { bankrollReport ->
lastItemClickedPosition = position
lastItemClickedId = (row as? Identifiable)?.id ?: ""
BankrollDetailsActivity.newInstanceForResult(this, bankrollReport, REQUEST_CODE_DETAILS)
}
}
}
}
}
// Business
@ -48,6 +133,53 @@ class BankrollFragment : PokerAnalyticsFragment() {
*/
private fun initData() {
val realm = getRealm()
this.bankrolls = realm.sorted()
rows.clear()
bankrollReportForRow.clear()
GlobalScope.launch {
launch(Dispatchers.Main) {
// TODO: Improve that
// We are in the main thread...
val startDate = Date()
// Graph
val globalBankrollReportSetup = BankrollReportSetup()
val globalBankrollReport = BankrollCalculator.computeReport(getRealm(), globalBankrollReportSetup)
rows.add(0, GraphRow(dataSet = globalBankrollReport.lineDataSet(requireContext())))
rows.add(globalBankrollReport)
bankrollReportForRow[globalBankrollReport] = globalBankrollReport
// Bankrolls
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.bankrolls))
Timber.d("initData: ${System.currentTimeMillis() - startDate.time}ms")
// val bankrolls = LiveData.Bankroll.items(getRealm()) as RealmResults<Bankroll>
bankrolls.forEach { bankroll ->
val bankrollReportSetup = BankrollReportSetup(bankroll)
val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup)
val computedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total)
val row =
CustomizableRowRepresentable(RowViewType.TITLE_VALUE_ARROW, title = bankroll.name, computedStat = computedStat, isSelectable = true)
row.id = bankroll.id
rows.add(row)
bankrollReportForRow[row] = bankrollReport
}
if (!isDetached) {
dataListAdapter.notifyDataSetChanged()
}
}
}
}
/**
@ -55,18 +187,42 @@ class BankrollFragment : PokerAnalyticsFragment() {
*/
private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
setDisplayHomeAsUpEnabled(true)
parentActivity.setSupportActionBar(toolbar)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
dataListAdapter = RowRepresentableAdapter(this, this)
val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
//adapter = statsAdapter
adapter = dataListAdapter
}
addButton.setOnClickListener {
EditableDataActivity.newInstanceForResult(this@BankrollFragment, dataType = LiveData.BANKROLL, primaryKey = null, requestCode = REQUEST_CODE_CREATE)
}
}
override fun updateUIAfterDeletion(itemPosition: Int) {
lastItemClickedPosition = rows.indexOfFirst { if (it is Identifiable) it.id == lastItemClickedId else false }
deletedRow = rows.find { if (it is Identifiable) it.id == lastItemClickedId else false }
rows.removeAt(lastItemClickedPosition)
dataListAdapter.notifyItemRemoved(lastItemClickedPosition)
}
override fun updateUIAfterUndoDeletion(newItem: RealmObject) {
// TODO: Improve that
// We are recreating a Bankroll report because the last one is invalid => the bankroll of the setup has been deleted
deletedRow?.let { row ->
val bankrollReportSetup = BankrollReportSetup(newItem as Bankroll)
val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup)
bankrollReportForRow[row] = bankrollReport
rows.add(lastItemClickedPosition, row)
dataListAdapter.notifyItemInserted(lastItemClickedPosition)
}
}

@ -20,8 +20,7 @@ import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.ui.activity.StatisticDetailsActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.activity.ProgressReportActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -44,7 +43,6 @@ class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
}
}
private lateinit var parentActivity: PokerAnalyticsActivity
private lateinit var statsAdapter: RowRepresentableAdapter
private var title: String? = ""
@ -80,14 +78,7 @@ class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
*/
private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
// Avoid a bug during setting the title
toolbar.title = ""
parentActivity.setSupportActionBar(toolbar)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
setDisplayHomeAsUpEnabled(true)
var tabIndexToSelect = 0
sessionTypeCondition?.let {
@ -134,11 +125,7 @@ class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
* Display data
*/
private fun displayData() {
title?.let {
toolbar.title = it
}
setToolbarTitle(title)
}
// StaticRowRepresentableDataSource
@ -150,8 +137,12 @@ class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
is GraphRow -> {
row.report.results.firstOrNull()?.group?.let { computableGroup ->
StatisticDetailsActivity.newInstance(requireContext(), row.stat, computableGroup, row.report, false, row.title)
val report = row.report
val stat = row.stat
if (report != null && stat != null) {
val title = row.title ?: stat.localizedTitle(requireContext())
ProgressReportActivity.newInstance(requireContext(), report, title, stat, false)
}
}
}
@ -196,25 +187,39 @@ class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
// }
val requiredStats: List<Stat> = listOf(Stat.LOCATIONS_PLAYED, Stat.LONGEST_STREAKS, Stat.DAYS_PLAYED, Stat.STANDARD_DEVIATION_HOURLY)
val options = Calculator.Options(evolutionValues = Calculator.Options.EvolutionValues.STANDARD, stats = requiredStats)
val report = Calculator.computeStatsWithCriterias(realm, listOf(), query, options)
val options = Calculator.Options(
progressValues = Calculator.Options.ProgressValues.STANDARD,
stats = requiredStats,
query = query
)
val report = Calculator.computeStats(realm, options)
Timber.d("Report take: ${System.currentTimeMillis() - startDate.time}ms")
report.results.firstOrNull()?.let {
// Create rows
val dataSet1 = report.results.firstOrNull()?.defaultStatEntries(Stat.NET_RESULT, requireContext())
val dataSet2 = report.results.firstOrNull()?.defaultStatEntries(Stat.STANDARD_DEVIATION, requireContext())
val dataSet3 = report.results.firstOrNull()?.defaultStatEntries(Stat.HOURLY_DURATION, requireContext())
rowRepresentables.clear()
rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.net_result))
rowRepresentables.add(GraphRow(report, Stat.NET_RESULT))
rowRepresentables.add(GraphRow(dataSet1, report = report, stat = Stat.NET_RESULT))
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.NET_RESULT), it.computedStat(Stat.HOURLY_RATE)))
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.LOCATIONS_PLAYED), it.computedStat(Stat.LONGEST_STREAKS)))
rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.distribution))
rowRepresentables.add(GraphRow(report, Stat.STANDARD_DEVIATION, requireContext().getString(R.string.distribution)))
rowRepresentables.add(
GraphRow(
dataSet2,
requireContext().getString(R.string.distribution),
report = report,
stat = Stat.STANDARD_DEVIATION
)
)
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.WIN_RATIO), it.computedStat(Stat.MAXIMUM_NETRESULT)))
rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.volume))
rowRepresentables.add(GraphRow(report, Stat.HOURLY_DURATION))
rowRepresentables.add(GraphRow(dataSet3, report = report, stat = Stat.HOURLY_DURATION))
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.HOURLY_DURATION), it.computedStat(Stat.AVERAGE_HOURLY_DURATION)))
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.DAYS_PLAYED), it.computedStat(Stat.MAXIMUM_DURATION)))
}

@ -7,25 +7,27 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.tabs.TabLayout
import io.realm.Realm
import io.realm.RealmModel
import kotlinx.android.synthetic.main.fragment_calendar.*
import kotlinx.android.synthetic.main.fragment_stats.recyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.ui.activity.CalendarDetailsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.extensions.hideWithAnimation
import net.pokeranalytics.android.ui.extensions.showWithAnimation
import net.pokeranalytics.android.ui.fragment.components.SessionObserverFragment
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.view.CalendarTabs
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
@ -36,7 +38,8 @@ import java.util.*
import kotlin.coroutines.CoroutineContext
class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRepresentableDataSource, RowRepresentableDelegate {
class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentableDataSource,
RowRepresentableDelegate {
enum class TimeFilter {
MONTH, YEAR
@ -73,7 +76,8 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
// Life Cycle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(net.pokeranalytics.android.R.layout.fragment_calendar, container, false)
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_calendar, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -94,7 +98,12 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
TimeFilter.MONTH -> {
val date = datesForRows[row]
sortedMonthlyReports[datesForRows[row]]?.let {
CalendarDetailsActivity.newInstance(requireContext(), it, sessionTypeCondition, date?.getMonthAndYear())
CalendarDetailsActivity.newInstance(
requireContext(),
it,
sessionTypeCondition,
date?.getMonthAndYear()
)
}
}
TimeFilter.YEAR -> {
@ -106,7 +115,9 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
}
}
override fun sessionsChanged() {
override val observedEntities: List<Class<out RealmModel>> = listOf(ComputableResult::class.java)
override fun entitiesChanged(clazz: Class<out RealmModel>) {
launchStatComputation()
}
@ -235,8 +246,8 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
val monthlyReports: HashMap<Date, ComputedResults> = HashMap()
val yearlyReports: HashMap<Date, ComputedResults> = HashMap()
val requiredStats: List<Stat> = listOf(Stat.LOCATIONS_PLAYED, Stat.LONGEST_STREAKS, Stat.DAYS_PLAYED, Stat.STANDARD_DEVIATION_HOURLY )
val options = Calculator.Options(evolutionValues = Calculator.Options.EvolutionValues.STANDARD, stats = requiredStats)
val requiredStats: List<Stat> =
listOf(Stat.LOCATIONS_PLAYED, Stat.LONGEST_STREAKS, Stat.DAYS_PLAYED, Stat.STANDARD_DEVIATION_HOURLY)
// Compute data per AnyYear and AnyMonthOfYear
@ -249,14 +260,22 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
}
monthlyQueries.forEach { query ->
val report = Calculator.computeStatsWithCriterias(realm, query = query, options = options)
val options = Calculator.Options(
progressValues = Calculator.Options.ProgressValues.STANDARD,
stats = requiredStats,
query = query
)
val report = Calculator.computeStats(realm, options = options)
report.results.forEach { computedResults ->
if (!computedResults.isEmpty) {
// Set date data
query.conditions.forEach { condition ->
when (condition) {
is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first())
is QueryCondition.AnyMonthOfYear -> calendar.set(Calendar.MONTH, condition.listOfValues.first())
is QueryCondition.AnyMonthOfYear -> calendar.set(
Calendar.MONTH,
condition.listOfValues.first()
)
}
}
@ -275,7 +294,12 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
}
yearConditions.forEach { query ->
val report = Calculator.computeStatsWithCriterias(realm, query = query, options = options)
val options = Calculator.Options(
progressValues = Calculator.Options.ProgressValues.STANDARD,
stats = requiredStats,
query = query
)
val report = Calculator.computeStats(realm, options = options)
report.results.forEach { computedResults ->
if (!computedResults.isEmpty) {
// Set date data

@ -6,7 +6,6 @@ import kotlinx.android.synthetic.main.fragment_comparison_chart.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.BankrollActivity
import net.pokeranalytics.android.ui.activity.SettingsActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.ComparisonChartPagerAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -37,7 +36,6 @@ class ComparisonChartFragment : PokerAnalyticsFragment(), StaticRowRepresentable
}
private lateinit var parentActivity: PokerAnalyticsActivity
private lateinit var viewPagerAdapter: ComparisonChartPagerAdapter
private var comparisonChartMenu: Menu? = null
@ -94,21 +92,15 @@ class ComparisonChartFragment : PokerAnalyticsFragment(), StaticRowRepresentable
*/
private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
setDisplayHomeAsUpEnabled(true)
setToolbarTitle(getString(R.string.comparison_chart))
toolbar.title = ""
parentActivity.setSupportActionBar(toolbar)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
toolbar.title = "Comparison chart"
viewPagerAdapter = ComparisonChartPagerAdapter(requireContext(), parentActivity.supportFragmentManager)
parentActivity?.let {
viewPagerAdapter = ComparisonChartPagerAdapter(requireContext(), it.supportFragmentManager)
viewPager.adapter = viewPagerAdapter
viewPager.offscreenPageLimit = 2
tabs.setupWithViewPager(viewPager)
}
}

@ -1,6 +1,7 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
@ -9,7 +10,6 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_data_list.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -63,7 +63,7 @@ class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataS
private class CurrencyRow(var currency: Currency) : RowRepresentable {
override fun getDisplayName(): String {
override fun getDisplayName(context: Context): String {
return currency.getDisplayName(Locale.getDefault()).capitalize()
}
@ -113,14 +113,8 @@ class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataS
*/
private fun initUI() {
val activity = activity as PokerAnalyticsActivity
// Avoid a bug during setting the title
toolbar.title = this.getString( R.string.currency)
activity.setSupportActionBar(toolbar)
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
setDisplayHomeAsUpEnabled(true)
setToolbarTitle(getString(R.string.currency))
val viewManager = LinearLayoutManager(requireContext())
val dataListAdapter = RowRepresentableAdapter(this, this)

@ -6,109 +6,70 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import io.realm.RealmObject
import io.realm.Realm
import io.realm.RealmResults
import io.realm.kotlin.isValid
import kotlinx.android.synthetic.main.fragment_data_list.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow
import net.pokeranalytics.android.util.extensions.sorted
class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_DETAILS = 1000
}
private lateinit var dataType: SettingRow
private lateinit var items: RealmResults<*>
private lateinit var dataListAdapter: RowRepresentableAdapter
private lateinit var identifiableClass: Class<out Deletable>
private var deletedItem: RealmObject? = null
private var lastDeletedItemPosition: Int = 0
private var lastItemClickedPosition: Int = 0
private lateinit var dataType: LiveData
private lateinit var items: RealmResults<out Deletable>
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_data_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_DETAILS && resultCode == Activity.RESULT_OK) {
val needToDeleteItem = data?.getBooleanExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, false) ?: false
if (needToDeleteItem) {
GlobalScope.launch(Dispatchers.Main) {
delay(300)
deleteItem(lastItemClickedPosition)
}
}
}
}
override fun onResume() {
super.onResume()
this.recyclerView?.adapter?.notifyDataSetChanged()
}
/**
* Set fragment data
*/
fun setData(dataType: Int) {
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return this.items[position] as RowRepresentable
}
this.dataType = LiveData.values()[dataType]
this.identifiableClass = this.dataType.relatedEntity
setToolbarTitle(this.dataType.pluralLocalizedTitle(requireContext()))
override fun numberOfRows(): Int {
return this.items.size
this.items = this.retrieveItems(getRealm())
}
override fun viewTypeForPosition(position: Int): Int {
val viewType = (this.items[position] as RowRepresentable).viewType
return if (viewType != -1) viewType else RowViewType.DATA.ordinal
open fun retrieveItems(realm: Realm): RealmResults<out Deletable> {
return realm.sorted(this.identifiableClass, editableOnly = true, filterableTypeUniqueIdentifier = dataType.subType)
}
override fun indexForRow(row: RowRepresentable): Int {
return this.items.indexOf(row)
override fun deletableItems() : List<Deletable> {
return this.items
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
if (row is Identifiable && !row.isValid()) {
Toast.makeText(requireContext(), R.string.invalid_object, Toast.LENGTH_LONG)
return
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_data_list, container, false)
}
this.dataType.relatedResultsRepresentable?.let {
lastItemClickedPosition = position
EditableDataActivity.newInstanceForResult(
this,
it.ordinal,
(row as Identifiable).id,
REQUEST_CODE_DETAILS
)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
}
/**
@ -116,20 +77,19 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
*/
private fun initUI() {
val activity = activity as PokerAnalyticsActivity
// Avoid a bug during setting the title
toolbar.title = ""
activity.setSupportActionBar(toolbar)
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
setDisplayHomeAsUpEnabled(true)
val viewManager = LinearLayoutManager(requireContext())
dataListAdapter = RowRepresentableAdapter(this, this)
val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position ->
deleteItem(position)
val item = this.items[position]
if (item != null) {
val itemId = item.id
deleteItem(dataListAdapter, items, itemId)
} else {
throw IllegalStateException("Item with position $position not found")
}
}
val itemTouchHelper = ItemTouchHelper(swipeToDelete)
@ -142,74 +102,53 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
}
this.addButton.setOnClickListener {
this.dataType.relatedResultsRepresentable?.let {
EditableDataActivity.newInstance(
requireContext(),
dataType = it.ordinal,
dataType = this.dataType.ordinal,
primaryKey = null
)
}
}
override fun onResume() {
super.onResume()
this.recyclerView?.adapter?.notifyDataSetChanged()
}
/**
* Delete item
*/
private fun deleteItem(position: Int) {
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return this.items[position] as RowRepresentable
}
if (isDetached || activity == null) {
return
override fun numberOfRows(): Int {
return this.items.size
}
// Save the delete position & create a copy of the object
val mRecentlyDeletedItem = rowRepresentableForPosition(position)
lastDeletedItemPosition = position
override fun viewTypeForPosition(position: Int): Int {
val viewType = (this.items[position] as RowRepresentable).viewType
return if (viewType != -1) viewType else RowViewType.DATA.ordinal
}
if (mRecentlyDeletedItem is RealmObject) {
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
// Check if the object is valid for the deletion
if ((mRecentlyDeletedItem as Deletable).isValidForDelete(this.getRealm())) {
deletedItem = getRealm().copyFromRealm(mRecentlyDeletedItem)
getRealm().executeTransaction {
mRecentlyDeletedItem.deleteFromRealm()
when (this.dataType) {
LiveData.FILTER -> {
val intent = Intent()
intent.putExtra(FiltersActivity.IntentKey.FILTER_ID.keyName, (row as Filter).id)
activity?.setResult(Activity.RESULT_OK, intent)
activity?.finish()
}
dataListAdapter.notifyItemRemoved(position)
showUndoSnackBar()
} else {
dataListAdapter.notifyItemChanged(position)
val builder = AlertDialog.Builder(requireContext())
.setMessage((mRecentlyDeletedItem as Deletable).getFailedDeleteMessage())
.setNegativeButton(R.string.ok, null)
builder.show()
else -> {
val identifier = (row as Identifiable).id
EditableDataActivity.newInstanceForResult(this, this.dataType, identifier, REQUEST_CODE_DETAILS)
}
}
}
/**
* Show undo snack bar
* Update UI
*/
private fun showUndoSnackBar() {
val message = String.format(getString(R.string.data_deleted), this.dataType.localizedTitle(requireContext()))
val snackBar = Snackbar.make(constraintLayout, message, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction(R.string.cancel) {
getRealm().executeTransaction { realm ->
deletedItem?.let {
realm.copyToRealmOrUpdate(it)
dataListAdapter.notifyItemInserted(lastDeletedItemPosition)
}
}
}
snackBar.show()
fun updateUI(showAddButton: Boolean) {
this.addButton.isVisible = showAddButton
}
/**
* Set fragment data
*/
fun setData(dataType: Int) {
this.dataType = SettingRow.values()[dataType]
this.toolbar.title = this.dataType.localizedTitle(requireContext())
this.dataType.relatedResultsRepresentable?.let {
this.items = it.items(getRealm())
}
}
}

@ -1,222 +0,0 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.*
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.RealmObject
import kotlinx.android.synthetic.main.fragment_editable_data.*
import kotlinx.android.synthetic.main.fragment_editable_data.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.ConfigurationException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Editable
import net.pokeranalytics.android.model.interfaces.Savable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
lateinit var parentActivity: PokerAnalyticsActivity
lateinit var item: RealmObject
lateinit var liveDataType: LiveData
lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private var editableMenu: Menu? = null
private var dataType: Int? = null
private var primaryKey: String? = null
var isUpdating = false
var shouldOpenKeyboard = true
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_editable_data, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
initData()
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
menu?.clear()
inflater?.inflate(R.menu.toolbar_editable_data, menu)
this.editableMenu = menu
updateMenuUI()
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
R.id.save -> saveData()
R.id.delete -> deleteData()
}
return true
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
BottomSheetFragment.create(fragmentManager, row, this, getDataSource().editDescriptors(row))
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
this.getRealm().executeTransaction {
(this.item as Editable).updateValue(value, row)
}
rowRepresentableAdapter.refreshRow(row)
}
/**
* Init UI
*/
private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
parentActivity.setSupportActionBar(toolbar)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
}
}
/**
* Return the data source
*/
open fun getDataSource(): RowRepresentableDataSource {
return this.item as RowRepresentableDataSource
}
/**
* Init data
*/
private fun initData() {
if (this.dataType != null) {
val proxyItem: RealmObject? = this.liveDataType.getData(this.getRealm(), primaryKey)
proxyItem?.let {
//TODO: Localize
this.appBar.toolbar.title = "Update ${this.liveDataType.localizedTitle(this.parentActivity).toLowerCase().capitalize()}"
isUpdating = true
} ?: run {
//TODO: Localize
this.appBar.toolbar.title = this.liveDataType.newEntityLocalizedTitle(requireContext())
}
this.item = this.liveDataType.updateOrCreate(this.getRealm(), primaryKey)
val dataSource = getDataSource()
this.rowRepresentableAdapter = RowRepresentableAdapter(getDataSource(), this)
this.recyclerView.adapter = rowRepresentableAdapter
// When creating an object, open automatically the keyboard for the first row
if (!isUpdating && shouldOpenKeyboard) {
val row = dataSource.adapterRows()?.firstOrNull()
row?.let {
onRowSelected(0, it)
}
}
}
}
/**
* Update menu UI
*/
private fun updateMenuUI() {
editableMenu?.findItem(R.id.delete)?.isVisible = isUpdating
editableMenu?.findItem(R.id.save)?.isVisible = true
}
/**
* Save data
*/
fun saveData() {
val savable = this.item
when (savable) {
is Savable -> {
val status = savable.getSaveValidityStatus(realm = this.getRealm())
when (status) {
SaveValidityStatus.VALID -> {
this.getRealm().executeTransaction {
val managedItem = it.copyToRealmOrUpdate(this.item)
if (managedItem is Savable) {
val uniqueIdentifier = (managedItem as Savable).id
finishActivityWithResult(uniqueIdentifier)
}
}
}
else -> {
val message = savable.getFailedSaveMessage(status)
val builder = AlertDialog.Builder(requireContext())
.setMessage(message)
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
} else -> {
throw ConfigurationException("Save action called on un-Savable object")
}
}
}
/**
* Delete data
*/
private fun deleteData() {
val deletable = this.item as Deletable
val realm = this.getRealm()
if (deletable.isValidForDelete(realm)) {
val intent = Intent()
intent.putExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, true)
activity?.setResult(RESULT_OK, intent)
activity?.finish()
} else {
val message = deletable.getFailedDeleteMessage()
val builder = AlertDialog.Builder(requireContext())
.setMessage(message)
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
/**
* Finish the activity with a result
*/
private fun finishActivityWithResult(uniqueIdentifier: String) {
val intent = Intent()
intent.putExtra(EditableDataActivity.IntentKey.DATA_TYPE.keyName, dataType)
intent.putExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName, uniqueIdentifier)
activity?.setResult(RESULT_OK, intent)
activity?.finish()
}
/**
* Set fragment data
*/
fun setData(dataType: Int, primaryKey: String?) {
this.dataType = dataType
this.liveDataType = LiveData.values()[dataType]
this.primaryKey = primaryKey
}
}

@ -0,0 +1,377 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.app.ActivityOptionsCompat
import androidx.core.view.isVisible
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.google.android.material.tabs.TabLayout
import io.realm.RealmModel
import io.realm.RealmResults
import io.realm.Sort
import io.realm.kotlin.where
import kotlinx.android.synthetic.main.fragment_feed.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Editable
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.ui.activity.*
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.FeedSessionRowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.FeedTransactionRowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.FilterableFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.util.Preferences
import java.text.SimpleDateFormat
import java.util.*
class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
private enum class Tab {
SESSIONS,
TRANSACTIONS
}
companion object {
const val REQUEST_CODE_MENU = 100
const val REQUEST_CODE_TRANSACTION_DETAILS = 101
fun newInstance(): FeedFragment {
val fragment = FeedFragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
}
}
private lateinit var feedSessionAdapter: FeedSessionRowRepresentableAdapter
private lateinit var feedTransactionAdapter: FeedTransactionRowRepresentableAdapter
private lateinit var realmSessions: RealmResults<Session>
private lateinit var realmTransactions: RealmResults<Transaction>
private lateinit var betaLimitDate: Date
private var newSessionCreated: Boolean = false
private var adapterHasBeenSet: Boolean = false
private var selectedTransaction: Transaction? = null
private var selectedTransactionPosition: Int = -1
override val observedEntities: List<Class<out RealmModel>> = listOf(Session::class.java, Transaction::class.java)
override fun entitiesChanged(clazz: Class<out RealmModel>) {
super.entitiesChanged(clazz)
when (clazz.kotlin) {
Session::class -> {
this.feedSessionAdapter.refreshData()
this.feedSessionAdapter.notifyDataSetChanged()
}
Transaction::class -> {
this.feedTransactionAdapter.refreshData()
this.feedTransactionAdapter.notifyDataSetChanged()
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_feed, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
initData()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_MENU && resultCode == RESULT_OK && data != null) {
when (data.getIntExtra(NewDataMenuActivity.IntentKey.CHOICE.keyName, -1)) {
0 -> createNewSession(false)
1 -> createNewSession(true)
2 -> createNewTransaction()
}
} else if (requestCode == REQUEST_CODE_TRANSACTION_DETAILS && resultCode == RESULT_OK && data != null) {
if (data.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) != null) {
deleteSelectedTransaction()
}
} else if (requestCode == FilterActivityRequestCode.CREATE_FILTER.ordinal && resultCode == RESULT_OK) {
data?.let {
this.saveFilter(this.requireContext(), it.getStringExtra(FiltersActivity.IntentKey.FILTER_ID.keyName))
}
} else if (requestCode == RequestCode.NEW_TRANSACTION.value && resultCode == RESULT_OK) {
this.selectTab(Tab.TRANSACTIONS)
} else if (requestCode == RequestCode.NEW_SESSION.value && resultCode == RESULT_OK) {
this.selectTab(Tab.SESSIONS)
}
}
override fun onDestroyView() {
super.onDestroyView()
realmSessions.removeAllChangeListeners()
realmTransactions.removeAllChangeListeners()
}
/*
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser && view != null) {
if (FilterHandler.filterWasUpdated) {
applyFilter()
FilterHandler.filterWasUpdated = false
}
}
}
*/
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
is Session -> SessionActivity.newInstance(requireContext(), sessionId = (row as Editable).id)
is Transaction -> {
selectedTransaction = row
selectedTransactionPosition = position
EditableDataActivity.newInstanceForResult(
this,
LiveData.TRANSACTION,
row.id,
REQUEST_CODE_TRANSACTION_DETAILS
)
}
}
}
/**
* Init UI
*/
private fun initUI() {
disclaimerContainer.isVisible = Preferences.shouldShowDisclaimer(requireContext())
disclaimerDismiss.setOnClickListener {
Preferences.setStopShowingDisclaimer(requireContext())
disclaimerContainer.animate().translationY(disclaimerContainer.height.toFloat())
.setInterpolator(FastOutSlowInInterpolator())
.withEndAction { disclaimerContainer?.isVisible = false }
.start()
}
addButton.setOnClickListener {
activity?.let {
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(it)
val intent = Intent(requireContext(), NewDataMenuActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_MENU, options.toBundle())
}
}
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
when (tab.position) {
0 -> {
currentFilterable = FilterableType.SESSION
recyclerView.adapter = feedSessionAdapter
}
1 -> {
currentFilterable = FilterableType.TRANSACTION
recyclerView.adapter = feedTransactionAdapter
}
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
}
/**
* Init data
*/
private fun initData() {
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm", Locale.getDefault())
betaLimitDate = sdf.parse("17/7/2019 10:00")
this.currentFilterable = FilterableType.SESSION
val viewManager = SmoothScrollLinearLayoutManager(requireContext())
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
}
applyFilter()
}
private fun loadSessions(filter: Filter? = null) {
val sessionFilter: Filter? = filter?.let {
if (it.filterableType == FilterableType.SESSION) {
it
} else {
null
}
}
// Sessions
this.realmSessions =
sessionFilter?.results() ?: run { getRealm().where<Session>().isNotNull("startDate").findAll() }
this.realmSessions = this.realmSessions.sort("startDate", Sort.DESCENDING)
val pendingSessions = sessionFilter?.let {
getRealm().where<Session>().alwaysFalse().findAll()
} ?: run {
getRealm().where<Session>().isNull("year").isNull("month").findAll().sort("startDate", Sort.DESCENDING)
}
var distinctDateSessions = sessionFilter?.results("year", "month") ?: run {
getRealm().where<Session>().distinct("year", "month").findAll()
}
distinctDateSessions = distinctDateSessions.sort("startDate", Sort.DESCENDING)
this.feedSessionAdapter =
FeedSessionRowRepresentableAdapter(this, realmSessions, pendingSessions, distinctDateSessions)
}
private fun loadTransactions(filter: Filter? = null) {
val transactionFilter: Filter? = filter?.let {
if (it.filterableType == FilterableType.TRANSACTION) {
it
} else {
null
}
}
// Transactions
this.realmTransactions = transactionFilter?.results() ?: run { getRealm().where<Transaction>().findAll() }
this.realmTransactions = this.realmTransactions.sort("date", Sort.DESCENDING)
var distinctDateTransactions = transactionFilter?.results("year", "month") ?: run {
getRealm().where<Transaction>().distinct("year", "month").findAll()
}
distinctDateTransactions = distinctDateTransactions.sort("date", Sort.DESCENDING)
this.feedTransactionAdapter =
FeedTransactionRowRepresentableAdapter(this, realmTransactions, distinctDateTransactions)
}
/**
* Create a new cash game
*/
private fun createNewSession(isTournament: Boolean) {
// val sessionCount = this.feedSessionAdapter.realmResults.size
// if (!AppGuard.isProUser && sessionCount >= AppGuard.MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION) { // && !BuildConfig.DEBUG
// Toast.makeText(context, "Please subscribe!", Toast.LENGTH_LONG).show()
// BillingActivity.newInstanceForResult(requireContext())
// return
// }
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return
}
SessionActivity.newInstanceforResult(this, isTournament, requestCode = RequestCode.NEW_SESSION.value)
newSessionCreated = true
}
/**
* Create a new transaction
*/
private fun createNewTransaction() {
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return
}
EditableDataActivity.newInstanceForResult(this, LiveData.TRANSACTION, null, RequestCode.NEW_TRANSACTION.value)
// EditableDataActivity.newInstance(requireContext(), LiveData.TRANSACTION.ordinal)
}
/**
* Delete selected transaction
*/
private fun deleteSelectedTransaction() {
val realm = getRealm()
realm.beginTransaction()
selectedTransaction?.deleteFromRealm()
realm.commitTransaction()
selectedTransactionPosition = -1
}
/**
* Show end of beta message
*/
private fun showEndOfBetaMessage() {
Toast.makeText(
context,
"Beta has ended. Thanks a lot for your participation! Please update with the Google Play version to continue using the app",
Toast.LENGTH_LONG
).show()
}
// Filter Handler
override fun applyFilter() {
super.applyFilter()
val filter: Filter? = this.currentFilter(this.requireContext(), getRealm())
this.loadSessions(filter)
this.loadTransactions(filter)
filter?.let {
when (it.filterableType) {
FilterableType.SESSION -> {
recyclerView.adapter = feedSessionAdapter
this.selectTab(Tab.SESSIONS)
}
FilterableType.TRANSACTION -> {
recyclerView.adapter = feedTransactionAdapter
this.selectTab(Tab.TRANSACTIONS)
}
else -> {
}
}
adapterHasBeenSet = true
}
if (!adapterHasBeenSet) {
adapterHasBeenSet = true
recyclerView.adapter = feedSessionAdapter
}
}
override fun removeFilter() {
super.removeFilter()
this.loadSessions()
this.loadTransactions()
if (currentFilterable == FilterableType.SESSION) {
recyclerView.adapter = feedSessionAdapter
} else {
recyclerView.adapter = feedTransactionAdapter
}
}
private fun selectTab(tab: Tab) {
this.tabs.getTabAt(tab.ordinal)?.select()
}
}

@ -1,10 +1,10 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
@ -14,11 +14,10 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.FilterDetailsActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.helpers.DateTimePickerManager
import net.pokeranalytics.android.ui.view.RowRepresentable
@ -27,16 +26,12 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.shortDate
import net.pokeranalytics.android.util.extensions.shortTime
import net.pokeranalytics.android.util.extensions.toMinutes
import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList
open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
open class FilterDetailsFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
lateinit var parentActivity: PokerAnalyticsActivity
lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private lateinit var primaryKey: String
private lateinit var filterCategoryRow: FilterCategoryRow
@ -45,12 +40,10 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
private var rows: ArrayList<RowRepresentable> = ArrayList()
private var rowsForFilterSubcategoryRow: HashMap<FilterSectionRow, ArrayList<RowRepresentable>> = HashMap()
private var filterMenu: Menu? = null
private val selectedRows = ArrayList<QueryCondition>()
private var isUpdating = false
private var shouldOpenKeyboard = true
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_filter_details, container, false)
}
@ -68,37 +61,46 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
super.onRowSelected(position, row, fromAction)
Timber.d("Row: $row")
if (row.viewType == RowViewType.TITLE_CHECK.ordinal) {
updateRowsSelection(row)
return
}
when (row) {
is QueryCondition.DateQuery -> DateTimePickerManager.create(requireContext(), row, this, row.singleValue, onlyDate = !row.showTime, onlyTime = row.showTime)
is QueryCondition.DateQuery -> DateTimePickerManager.create(
requireContext(),
row,
this,
row.singleValue,
onlyDate = !row.showTime,
onlyTime = row.showTime
)
is QueryCondition.Duration -> {
val hours = if (row.minutes / 60 > 0) (row.minutes / 60).toString() else ""
val minutes = if (row.minutes % 60 > 0) (row.minutes % 60).toString() else ""
var hours: String? = null
var minutes: String? = null
row.minutes?.let {
hours = if (it / 60 > 0) (it / 60).toString() else null
minutes = if (it % 60 > 0) (it % 60).toString() else null
}
val data = row.editingDescriptors(mapOf("hours" to hours, "minutes" to minutes))
BottomSheetFragment.create(fragmentManager, row, this, data, true)
}
is QueryCondition.ListOfValues<*> -> {
val valueAsString = row.listOfValues.firstOrNull()?.toString() ?: ""
var valueAsString: String? = null
row.listOfValues.firstOrNull()?.let {
valueAsString = row.listOfValues.firstOrNull()?.toString()
}
val data = row.editingDescriptors(mapOf("valueAsString" to valueAsString))
BottomSheetFragment.create(fragmentManager, row, this, data, true)
}
}
}
override fun stringForRow(row: RowRepresentable): String {
override fun stringForRow(row: RowRepresentable, context: Context): String {
return when (row) {
is QueryCondition.DateQuery -> if (row.showTime) row.singleValue.shortTime() else row.singleValue.shortDate()
is QueryCondition.Duration -> row.minutes.toMinutes(requireContext())
is QueryCondition.ListOfValues<*> -> row.listOfValues.firstOrNull()?.toString() ?: NULL_TEXT
is QueryCondition.ListOfValues<*> -> row.firstValue(context)
else -> super.stringForRow(row)
}
} ?: NULL_TEXT
}
override fun isSelected(row: RowRepresentable): Boolean {
@ -110,28 +112,40 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
Timber.d("onRowValueChanged: $row $value")
when (row) {
is QueryCondition.DateQuery -> row.singleValue = if (value != null && value is Date) value else Date()
is QueryCondition.DateQuery -> row.singleValue = if (value != null && value is Date) value else null
is QueryCondition.Duration -> {
if (value is ArrayList<*>) {
val hours = try {
(value[0] as String? ?: "0").toInt()
val hours: Int? = try {
(value[0] as String?)?.toInt()
} catch (e: Exception) {
0
null
}
val minutes = try {
(value[1] as String? ?: "0").toInt()
(value[1] as String?)?.toInt()
} catch (e: Exception) {
0
null
}
if (hours != null && minutes != null) {
row.minutes = hours * 60 + minutes
} else if (hours != null) {
row.minutes = hours * 60
} else if (minutes != null) {
row.minutes = minutes
}
} else {
row.minutes = 0
row.minutes = null
}
}
is QueryCondition.SingleInt -> row.singleValue = if (value != null && value is String) value.toInt() else 0
is QueryCondition.ListOfDouble-> row.listOfValues = arrayListOf(if (value != null && value is String) value.toDouble() else 0.0)
is QueryCondition.ListOfInt-> row.listOfValues = arrayListOf(if (value != null && value is String) value.toInt() else 0)
is QueryCondition.SingleInt -> row.singleValue = if (value != null && value is String) value.toInt() else null
is QueryCondition.ListOfDouble -> row.listOfValues = arrayListOf<Double>().apply {
if (value != null && value is String) this.add(value.toDouble())
}
is QueryCondition.ListOfInt -> row.listOfValues = arrayListOf<Int>().apply {
if (value != null && value is String) this.add(value.toInt())
}
is QueryCondition.ListOfString -> row.listOfValues = arrayListOf<String>().apply {
if (value != null && value is String) this.add(value)
}
}
// Remove the row before updating the selected rows list
@ -153,10 +167,8 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
* Init UI
*/
private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
parentActivity.setSupportActionBar(toolbar)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
setDisplayHomeAsUpEnabled(true)
this.appBar.toolbar.title = getString(R.string.filter)
@ -173,8 +185,8 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
*/
private fun initData() {
currentFilter = Filter.getFilterBydId(getRealm(), primaryKey)
//currentFilter = Filter.getFilterBydId(getRealm(), primaryKey)
currentFilter = FiltersFragment.currentFilter
this.appBar.toolbar.title = filterCategoryRow.localizedTitle(requireContext())
@ -241,7 +253,6 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
Timber.d("Condition: $it")
}
finishActivityWithResult(currentFilter?.id)
}

@ -1,47 +1,61 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.*
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.kotlin.where
import kotlinx.android.synthetic.main.fragment_editable_data.*
import kotlinx.android.synthetic.main.fragment_filters.view.*
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.fragment_editable_data.appBar
import kotlinx.android.synthetic.main.fragment_editable_data.recyclerView
import kotlinx.android.synthetic.main.fragment_filters.*
import kotlinx.android.synthetic.main.fragment_filters.view.toolbar
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.FilterDetailsActivity
import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.extensions.px
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.extensions.sorted
import timber.log.Timber
open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_FILTER_DETAILS = 100
const val MOST_USED_FILTERS_DISPLAYED = 6
var currentFilter: Filter? = null
}
private lateinit var parentActivity: PokerAnalyticsActivity
private lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private var currentFilter: Filter? = null
private var filterCopy: Filter? = null
private var rows: ArrayList<RowRepresentable> = ArrayList()
private var filterMenu: Menu? = null
private var primaryKey: String? = null
private lateinit var filterableType: FilterableType
private var selectedRow: RowRepresentable? = null
private var isUpdating = false
private var showMostUsedFiltersLayout = true
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_filters, container, false)
}
@ -49,24 +63,34 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
super.onViewCreated(view, savedInstanceState)
initUI()
initData()
updateMostUsedFilters()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_FILTER_DETAILS && resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_FILTER_DETAILS && resultCode == RESULT_OK) {
// Update object
/*
Timber.d("onActivityResult: $requestCode")
if (data != null && data.hasExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)) {
val filterId = data.getStringExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)
Timber.d("Updated queryWith: ${filterId}")
currentFilter?.id?.let { currentFilterId ->
Filter.getFilterBydId(getRealm(), currentFilterId)?.let { filter ->
currentFilter = filter
}
}
*/
selectedRow?.let {
rowRepresentableAdapter.refreshRow(it)
}
} else if (requestCode == FilterActivityRequestCode.SELECT_FILTER.ordinal) {
updateMostUsedFilters()
if (resultCode == RESULT_OK && data != null && data.hasExtra(FiltersActivity.IntentKey.FILTER_ID.keyName)) {
val filterId = data.getStringExtra(FiltersActivity.IntentKey.FILTER_ID.keyName)
finishActivityWithResult(filterId)
}
}
}
@ -82,14 +106,13 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
if (isUpdating) {
cancelUpdates()
} else {
deleteFilter()
activity?.finish()
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
R.id.save -> validUpdates()
R.id.delete -> deleteFilter()
R.id.save -> validateUpdates()
}
return true
}
@ -123,19 +146,23 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
* Init UI
*/
private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
parentActivity.setSupportActionBar(toolbar)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
setDisplayHomeAsUpEnabled(true)
this.appBar.toolbar.title = getString(R.string.filter)
val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
}
moreFilters.setOnClickListener {
LiveData.FILTER.subType = filterableType.uniqueIdentifier
DataListActivity.newSelectInstance(this, LiveData.FILTER.ordinal, false)
}
mostUsedFiltersLayout.isVisible = showMostUsedFiltersLayout
}
/**
@ -145,45 +172,93 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
val realm = getRealm()
//TODO: Remove that
val filters = realm.where<Filter>().findAll()
Timber.d("Filters: ${filters.size}")
primaryKey?.let {
currentFilter = Filter.getFilterBydId(realm, it)
currentFilter = realm.copyFromRealm(Filter.getFilterBydId(realm, it))
isUpdating = true
} ?: run {
realm.beginTransaction()
currentFilter = Filter.newInstance(realm)
realm.commitTransaction()
currentFilter = Filter.newInstance(this.filterableType.uniqueIdentifier) //realm.copyFromRealm(Filter.newInstanceForResult(realm, this.filterableType.ordinal))
}
// Create a copy if the user cancels the updates
currentFilter?.let {
if (it.isValid && it.isManaged) {
filterCopy = getRealm().copyFromRealm(it)
}
}
rows.clear()
rows.addAll(FilterCategoryRow.values())
rows.addAll(FilterCategoryRow.values(this.filterableType))
this.rowRepresentableAdapter = RowRepresentableAdapter(this, this)
this.recyclerView.adapter = rowRepresentableAdapter
}
/**
* Update the most used filters chips
*/
private fun updateMostUsedFilters() {
var nbChips = 0
val filters = getRealm().sorted(Filter::class.java, editableOnly = false, filterableTypeUniqueIdentifier = this.filterableType.uniqueIdentifier)
val currentFilterId = Preferences.getActiveFilterId(requireContext())
if (isUpdating || filters.isEmpty() || (filters.size == 1 && filters.first()?.id == currentFilterId)) {
mostUsedFiltersLayout.visibility = View.GONE
return
}
mostUsedFilters.removeAllViews()
filters.forEach { filter ->
if (nbChips < MOST_USED_FILTERS_DISPLAYED) {
if (filter.id != currentFilterId) {
val chip = Chip(requireContext())
chip.id = View.generateViewId()
chip.tag = filter.id
chip.text = filter.getDisplayName(requireContext())
chip.chipStartPadding = 8f.px
chip.chipEndPadding = 8f.px
chip.isChecked = filter.id == currentFilterId
chip.setOnCloseIconClickListener {
chip.isChecked = false
}
chip.setOnClickListener {
if (chip.isChecked) {
finishActivityWithResult(filter.id)
} else {
finishActivityWithResult("")
}
}
mostUsedFilters.addView(chip)
nbChips++
}
}
}
}
/**
* Update menu UI
*/
private fun updateMenuUI() {
filterMenu?.findItem(R.id.delete)?.isVisible = isUpdating
filterMenu?.findItem(R.id.save)?.isVisible = true
filterMenu?.findItem(R.id.delete)?.isVisible = false
}
/**
* Valid the updates of the queryWith
* Validate the updates of the queryWith
*/
private fun validUpdates() {
Timber.d("Valid queryWith updates")
private fun validateUpdates() {
val realm = getRealm()
realm.beginTransaction()
currentFilter?.let {
it.name = it.query.getName(requireContext())
realm.copyToRealmOrUpdate(it)
}
realm.commitTransaction()
val filterId = currentFilter?.id ?: ""
finishActivityWithResult(filterId)
}
@ -192,10 +267,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
* Cancel the latest updates of the queryWith
*/
private fun cancelUpdates() {
Timber.d("Cancel queryWith updates")
val filterId = filterCopy?.id ?: ""
val realm = getRealm()
realm.beginTransaction()
filterCopy?.let {
@ -205,19 +277,6 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
finishActivityWithResult(filterId)
}
/**
* Delete data
*/
private fun deleteFilter() {
Timber.d("Delete queryWith")
val realm = getRealm()
realm.beginTransaction()
currentFilter?.deleteFromRealm()
realm.commitTransaction()
finishActivityWithResult("")
}
/**
* Finish the activity with a result
*/
@ -231,8 +290,18 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
/**
* Set fragment data
*/
fun setData(primaryKey: String?) {
fun setData(primaryKey: String?, filterableType: FilterableType) {
this.primaryKey = primaryKey
this.filterableType = filterableType
}
/**
* Update the most used filters visibility
*/
fun updateMostUsedFiltersVisibility(visible: Boolean) {
Timber.d("updateMostUsedFiltersVisibility: $visible")
showMostUsedFiltersLayout = visible
mostUsedFiltersLayout?.isVisible = visible
}
}

@ -13,18 +13,17 @@ import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBub
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import kotlinx.android.synthetic.main.fragment_graph.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.graph.AxisFormatting
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import net.pokeranalytics.android.ui.graph.setStyle
import net.pokeranalytics.android.ui.view.LegendView
import net.pokeranalytics.android.ui.view.MultiLineLegendView
class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
class GraphFragment : RealmFragment(), OnChartValueSelectedListener {
enum class Style {
LINE,
@ -49,8 +48,6 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
}
private lateinit var parentActivity: PokerAnalyticsActivity
private var style: Style = Style.LINE
private lateinit var legendView: LegendView
@ -63,6 +60,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
private var axisFormatting: AxisFormatting = AxisFormatting.DEFAULT
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_graph, container, false)
}
@ -100,9 +98,6 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
*/
private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
parentActivity.title = stat.localizedTitle(requireContext())
this.legendView = when (this.style) {
Style.MULTILINE -> MultiLineLegendView(requireContext())
else -> LegendView(requireContext())
@ -129,15 +124,12 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
this.chartView = lineChart
dataSets.firstOrNull()?.let { dataSet ->
this.legendView.prepareWithStat(this.stat, dataSet.entryCount, this.style)
if (dataSet.entryCount > 0) {
val entry = dataSet.getEntryForIndex(dataSet.entryCount - 1)
this.selectValue(entry, dataSet)
}
}
}
this.barDataSetList?.let { dataSets ->
@ -146,10 +138,10 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
val barChart = BarChart(context)
barChart.setOnChartValueSelectedListener(this)
if (stat.showXAxisZero) {
if (stat.graphShowsXAxisZero) {
barChart.xAxis.axisMinimum = 0.0f
}
if (stat.showYAxisZero) {
if (stat.graphShowsYAxisZero) {
barChart.axisLeft.axisMinimum = 0.0f
}
this.chartView = barChart
@ -178,15 +170,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
override fun onValueSelected(e: Entry?, h: Highlight?) {
var groupName = ""
h?.let { highlight ->
this.chartView?.data?.getDataSetByIndex(highlight.dataSetIndex)?.let {
groupName = it.label
}
}
val dataSet = this.chartView?.data?.getDataSetForEntry(e)
e?.let { entry ->
this.selectValue(entry, dataSet)
}

@ -1,152 +0,0 @@
package net.pokeranalytics.android.ui.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import io.realm.RealmResults
import io.realm.Sort
import io.realm.kotlin.where
import kotlinx.android.synthetic.main.fragment_history.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.Editable
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.activity.SessionActivity
import net.pokeranalytics.android.ui.adapter.HistorySessionRowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.util.Preferences
import java.text.SimpleDateFormat
import java.util.*
class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
fun newInstance(): HistoryFragment {
val fragment = HistoryFragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
}
}
private lateinit var historyAdapter: HistorySessionRowRepresentableAdapter
private lateinit var realmSessions: RealmResults<Session>
private val rows: ArrayList<RowRepresentable> = ArrayList()
private var newSessionCreated: Boolean = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_history, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
initData()
}
override fun onDestroyView() {
super.onDestroyView()
realmSessions.removeAllChangeListeners()
}
/**
* Init UI
*/
private fun initUI() {
disclaimerContainer.isVisible = Preferences.shouldShowDisclaimer(requireContext())
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val betaLimitDate = sdf.parse("17/7/2019 10:00")
newCashGame.setOnClickListener {
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return@setOnClickListener
}
SessionActivity.newInstance(requireContext(), false)
newSessionCreated = true
}
newTournament.setOnClickListener {
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return@setOnClickListener
}
SessionActivity.newInstance(requireContext(), true)
newSessionCreated = true
}
disclaimerDismiss.setOnClickListener {
Preferences.setStopShowingDisclaimer(requireContext())
disclaimerContainer.animate().translationY(disclaimerContainer.height.toFloat())
.setInterpolator(FastOutSlowInInterpolator())
.withEndAction { disclaimerContainer?.isVisible = false }
.start()
}
}
private fun showEndOfBetaMessage() {
Toast.makeText(context, "Beta has ended. Please update with the Google Play version", Toast.LENGTH_LONG).show()
}
/**
* Init data
*/
private fun initData() {
this.realmSessions = getRealm().where<Session>().findAll().sort("startDate", Sort.DESCENDING)
this.realmSessions.addChangeListener { _, _ ->
this.historyAdapter.refreshData()
this.historyAdapter.notifyDataSetChanged()
}
val startedSessions = getRealm().where<Session>().isNotNull("year").isNotNull("month").findAll().sort("startDate", Sort.DESCENDING)
val pendingSessions = getRealm().where<Session>().isNull("year").isNull("month").findAll().sort("startDate", Sort.DESCENDING)
val distinctDateSessions = getRealm().where<Session>().distinct("year", "month").findAll().sort("startDate", Sort.DESCENDING)
this.historyAdapter = HistorySessionRowRepresentableAdapter(this, startedSessions, pendingSessions, distinctDateSessions)
val viewManager = SmoothScrollLinearLayoutManager(requireContext())
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = historyAdapter
}
}
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return this.rows[position]
}
override fun numberOfRows(): Int {
return this.rows.size
}
override fun viewTypeForPosition(position: Int): Int {
return rows[position].viewType
}
override fun indexForRow(row: RowRepresentable): Int {
return this.rows.indexOf(row)
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
SessionActivity.newInstance(requireContext(), sessionId = (row as Editable).id)
}
}

@ -0,0 +1,84 @@
package net.pokeranalytics.android.ui.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.ResultCode
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.util.csv.CSVImporter
import net.pokeranalytics.android.util.csv.ImportException
import timber.log.Timber
import java.io.InputStream
import java.util.*
import kotlin.coroutines.CoroutineContext
class ImportFragment : RealmFragment() {
val coroutineContext: CoroutineContext
get() = Dispatchers.Main
private lateinit var filePath: String
private lateinit var inputStream: InputStream
fun setData(path: String) {
this.filePath = path
}
fun setData(inputStream: InputStream) {
this.inputStream = inputStream
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_import, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
this.startImport()
}
fun startImport() {
var shouldDismissActivity = false
GlobalScope.launch(coroutineContext) {
val test = GlobalScope.async {
val s = Date()
Timber.d(">>> Start Import...")
try {
val csv = CSVImporter(inputStream)
csv.start()
} catch (e: ImportException) {
shouldDismissActivity = true
}
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> Import ended in $duration seconds")
}
test.await()
if (shouldDismissActivity) {
activity?.let {
it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value)
it.finish()
}
}
}
}
}

@ -5,7 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_stats.*
import kotlinx.android.synthetic.main.fragment_more.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.BankrollActivity
import net.pokeranalytics.android.ui.activity.SettingsActivity

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

Loading…
Cancel
Save