Merge branch 'dev'

feature/top10
Laurent 7 years ago
commit 3984357077
  1. 35
      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. 275
      app/src/main/AndroidManifest.xml
  14. 18
      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. 194
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  19. 70
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  20. 167
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  21. 21
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt
  22. 156
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt
  23. 3
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  24. 503
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  25. 19
      app/src/main/java/net/pokeranalytics/android/model/Limit.kt
  26. 156
      app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
  27. 35
      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. 50
      app/src/main/java/net/pokeranalytics/android/model/filter/Query.kt
  31. 1523
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  32. 44
      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. 28
      app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt
  35. 164
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  36. 163
      app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt
  37. 306
      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. 106
      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. 224
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  46. 19
      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. 87
      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. 36
      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. 163
      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. 42
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt
  85. 38
      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. 228
      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. 58
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt
  90. 24
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ComparisonChartFragment.kt
  91. 216
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CurrenciesFragment.kt
  92. 205
      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. 141
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt
  96. 175
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt
  97. 30
      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: 'kotlin-kapt'
apply plugin: 'realm-android' apply plugin: 'realm-android'
apply plugin: 'io.fabric' apply plugin: 'io.fabric'
apply plugin: 'com.google.gms.google-services' // Crashlytics
repositories { repositories {
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' } // required for MPAndroidChart
} }
android { android {
@ -28,12 +29,15 @@ android {
applicationId "net.pokeranalytics.android" applicationId "net.pokeranalytics.android"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 28 targetSdkVersion 28
versionCode 27 versionCode 30
versionName "1.0" versionName "2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
debug {
ext.enableCrashlytics = false
}
release { release {
minifyEnabled true minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@ -56,7 +60,9 @@ android {
} }
configurations { configurations {
all*.exclude group: 'com.google.guava', module: 'listenablefuture' release {
all*.exclude group: 'com.google.guava', module: 'listenablefuture'
}
} }
} }
@ -72,7 +78,7 @@ dependencies {
// Android // Android
implementation 'androidx.appcompat:appcompat:1.0.2' 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 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
@ -87,11 +93,16 @@ dependencies {
// Places // Places
implementation 'com.google.android.libraries.places:places:1.1.0' 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 // Firebase
implementation 'com.google.firebase:firebase-core:16.0.8' implementation 'com.google.firebase:firebase-core:16.0.9'
// Crashlytics // Crashlytics
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
// Logs // Logs
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
@ -99,6 +110,9 @@ dependencies {
// MPAndroidChart // MPAndroidChart
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' 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 // Instrumented Tests
androidTestImplementation 'androidx.test:core:1.1.0' androidTestImplementation 'androidx.test:core:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:runner:1.1.1'
@ -107,15 +121,8 @@ dependencies {
// Test // Test
testImplementation 'junit:junit:4.12' 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:runner:1.0.2'
testImplementation 'com.android.support.test:rules: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 package net.pokeranalytics.android.model
import android.content.Context
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import java.util.* import java.util.*
import androidx.test.platform.app.InstrumentationRegistry
class CriteriaTest : BaseFilterInstrumentedUnitTest() { class CriteriaTest : BaseFilterInstrumentedUnitTest() {
@Test @Test
fun getQueryConditions() { fun getQueryConditions() {
@ -57,9 +58,12 @@ class CriteriaTest : BaseFilterInstrumentedUnitTest() {
val criterias = listOf(Criteria.MonthsOfYear, Criteria.DaysOfWeek) val criterias = listOf(Criteria.MonthsOfYear, Criteria.DaysOfWeek)
val combined = criterias.combined() val combined = criterias.combined()
val context = InstrumentationRegistry.getInstrumentation().context
combined.forEach { combined.forEach {
it.conditions.forEach {qc-> it.conditions.forEach {qc->
println(qc.getDisplayName())
println(qc.getDisplayName(context))
} }
} }
} }
@ -86,11 +90,12 @@ class CriteriaTest : BaseFilterInstrumentedUnitTest() {
val lastValue = firstValue + 10 val lastValue = firstValue + 10
realm.commitTransaction() realm.commitTransaction()
val context = InstrumentationRegistry.getInstrumentation().context
val allMonths = Criteria.AllMonthsUpToNow.queries val allMonths = Criteria.AllMonthsUpToNow.queries
allMonths.forEach { allMonths.forEach {
it.conditions.forEach { qc-> it.conditions.forEach { qc->
println("<<<<< ${qc.getDisplayName()}") println("<<<<< ${qc.getDisplayName(context)}")
} }
} }
} }

@ -58,7 +58,7 @@ class PerfsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
val group = ComputableGroup(Query(), stats) val group = ComputableGroup(Query(), stats)
val options = Calculator.Options() 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 results: ComputedResults = Calculator.compute(realm, group, options)
Timber.d("*** ended in ${System.currentTimeMillis() - start} milliseconds") 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()) val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString())
t1.amount = 100.0 t1.amount = 100.0
t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) 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()) val t2 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString())
t2.amount = 500.0 t2.amount = 500.0
t2.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) t2.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm)
br2.transactions.add(t2) t2.bankroll = br2
val s1 = newSessionInstance(realm) val s1 = newSessionInstance(realm)
s1.bankroll = br1 s1.bankroll = br1
@ -73,16 +73,16 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val br1 = realm.where(Bankroll::class.java).equalTo("name", "br1").findFirst() val br1 = realm.where(Bankroll::class.java).equalTo("name", "br1").findFirst()
val brSetup1 = BankrollReportSetup(br1) val brSetup1 = BankrollReportSetup(br1)
val report1 = BankrollCalculator.computeReport(brSetup1) val report1 = BankrollCalculator.computeReport(realm, brSetup1)
Assert.assertEquals(400.0, report1.total, EPSILON) Assert.assertEquals(400.0, report1.total, EPSILON)
val br2 = realm.where(Bankroll::class.java).equalTo("name", "br2").findFirst() val br2 = realm.where(Bankroll::class.java).equalTo("name", "br2").findFirst()
val brSetup2 = BankrollReportSetup(br2) val brSetup2 = BankrollReportSetup(br2)
val report2 = BankrollCalculator.computeReport(brSetup2) val report2 = BankrollCalculator.computeReport(realm, brSetup2)
Assert.assertEquals(2000.0, report2.total, EPSILON) Assert.assertEquals(2000.0, report2.total, EPSILON)
val brSetupAll = BankrollReportSetup() val brSetupAll = BankrollReportSetup()
val reportAll = BankrollCalculator.computeReport(brSetupAll) val reportAll = BankrollCalculator.computeReport(realm, brSetupAll)
Assert.assertEquals(2400.0, reportAll.total, EPSILON) Assert.assertEquals(2400.0, reportAll.total, EPSILON)
} }
@ -108,7 +108,7 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString())
t1.amount = 100.0 t1.amount = 100.0
t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm)
br1?.transactions?.add(t1) t1.bankroll = br1
val s1 = newSessionInstance(realm) val s1 = newSessionInstance(realm)
s1.bankroll = br1 s1.bankroll = br1
@ -117,11 +117,11 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
} }
val brSetup1 = BankrollReportSetup(br1) val brSetup1 = BankrollReportSetup(br1)
val report1 = BankrollCalculator.computeReport(brSetup1) val report1 = BankrollCalculator.computeReport(realm, brSetup1)
Assert.assertEquals(400.0, report1.total, EPSILON) Assert.assertEquals(400.0, report1.total, EPSILON)
val brSetupAll = BankrollReportSetup() val brSetupAll = BankrollReportSetup()
val reportAll = BankrollCalculator.computeReport(brSetupAll) val reportAll = BankrollCalculator.computeReport(realm, brSetupAll)
Assert.assertEquals(4000.0, reportAll.total, EPSILON) 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.components.RealmInstrumentedUnitTest
import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Currency import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.util.extensions.findById
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
@ -40,7 +41,7 @@ class DeleteInstrumentedUnitTest : RealmInstrumentedUnitTest() {
var isValidForDelete = br1.isValidForDelete(realm) var isValidForDelete = br1.isValidForDelete(realm)
Assert.assertEquals(false, isValidForDelete) 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) isValidForDelete = it.isValidForDelete(realm)
Assert.assertEquals(false, isValidForDelete) Assert.assertEquals(false, isValidForDelete)

@ -76,7 +76,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query()) val group = ComputableGroup(Query())
val options = Calculator.Options() 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) Stat.LONGEST_STREAKS, Stat.LOCATIONS_PLAYED, Stat.DAYS_PLAYED)
val results: ComputedResults = Calculator.compute(realm, group, options) val results: ComputedResults = Calculator.compute(realm, group, options)
@ -255,7 +255,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(), stats) val group = ComputableGroup(Query(), stats)
val options = Calculator.Options() 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 results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01 val delta = 0.01
@ -322,7 +322,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(), stats) val group = ComputableGroup(Query(), stats)
val options = Calculator.Options() 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 results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01 val delta = 0.01
@ -405,7 +405,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(), stats) val group = ComputableGroup(Query(), stats)
val options = Calculator.Options() 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 results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01 val delta = 0.01
@ -728,7 +728,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(QueryCondition.IsCash)) val group = ComputableGroup(Query(QueryCondition.IsCash))
val options = Calculator.Options() 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 results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01 val delta = 0.01
@ -773,7 +773,7 @@ class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
val group = ComputableGroup(Query(QueryCondition.IsCash)) val group = ComputableGroup(Query(QueryCondition.IsCash))
val options = Calculator.Options() 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 results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01 val delta = 0.01

@ -47,7 +47,7 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
listOfValues = arrayListOf(s1.blinds!!) listOfValues = arrayListOf(s1.blinds!!)
} }
blind.filterSectionRow = FilterSectionRow.BLIND blind.filterSectionRow = FilterSectionRow.Blind
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind)) val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueBy(filterElement) filter.updateValueBy(filterElement)
@ -97,8 +97,8 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
listOfValues = arrayListOf(s2.blinds!!) listOfValues = arrayListOf(s2.blinds!!)
} }
blind1.filterSectionRow = FilterSectionRow.BLIND blind1.filterSectionRow = FilterSectionRow.Blind
blind2.filterSectionRow = FilterSectionRow.BLIND blind2.filterSectionRow = FilterSectionRow.Blind
val filterElements = FilterCondition(filterElementRows = arrayListOf(blind1, blind2)) val filterElements = FilterCondition(filterElementRows = arrayListOf(blind1, blind2))
filter.updateValueBy(filterElements) filter.updateValueBy(filterElements)
@ -146,11 +146,10 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
listOfValues = arrayListOf(s3.blinds!!) listOfValues = arrayListOf(s3.blinds!!)
} }
blind.filterSectionRow = FilterSectionRow.BLIND blind.filterSectionRow = FilterSectionRow.Blind
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind)) val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueBy(filterElement) filter.updateValueBy(filterElement)
println("<<<< ${filter.listOfValues}")
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
@ -197,8 +196,8 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
listOfValues = arrayListOf(s2.blinds!!) listOfValues = arrayListOf(s2.blinds!!)
} }
blind1.filterSectionRow = FilterSectionRow.BLIND blind1.filterSectionRow = FilterSectionRow.Blind
blind2.filterSectionRow = FilterSectionRow.BLIND blind2.filterSectionRow = FilterSectionRow.Blind
val filterElement = FilterCondition(filterElementRows = arrayListOf(blind1, blind2)) val filterElement = FilterCondition(filterElementRows = arrayListOf(blind1, blind2))
filter.updateValueBy(filterElement) 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.FilterCondition
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.util.extensions.hourMinute
import net.pokeranalytics.android.util.extensions.startOfDay import net.pokeranalytics.android.util.extensions.startOfDay
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
@ -34,7 +35,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
cal.time = s1.startDate cal.time = s1.startDate
val filterElementRow = QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(cal.get(Calendar.DAY_OF_WEEK)) } 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)) val filterElement = FilterCondition(arrayListOf(filterElementRow))
filter.updateValueBy(filterElement) filter.updateValueBy(filterElement)
@ -63,7 +64,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
cal.time = s1.startDate cal.time = s1.startDate
val filterElementRow = QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(cal.get(Calendar.MONTH)) } 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)) val filterElement = FilterCondition(arrayListOf(filterElementRow))
filter.updateValueBy(filterElement) filter.updateValueBy(filterElement)
@ -91,7 +92,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyYear() val filter = QueryCondition.AnyYear()
cal.time = s1.startDate cal.time = s1.startDate
val filterElementRow = QueryCondition.AnyYear().apply { listOfValues = arrayListOf(cal.get(Calendar.YEAR)) } 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)) val filterElement = FilterCondition(arrayListOf(filterElementRow))
filter.updateValueBy(filterElement) filter.updateValueBy(filterElement)
@ -379,13 +380,13 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, false, cal.time, 1) 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) val s2 = Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.StartedFromDate() val filter = QueryCondition.StartedFromDate()
val filterElementRow = QueryCondition.StartedFromDate().apply { singleValue = s2.startDate!!} val filterElementRow = QueryCondition.StartedFromDate().apply { singleValue = s2.startDate!!}
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FixedDate
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -406,14 +407,14 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
cal.time = Date() // sets calendar time/date cal.time = Date() // sets calendar time/date
val s1 = Session.testInstance(100.0, false, cal.time, 1) 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) Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.StartedToDate() val filter = QueryCondition.StartedToDate()
val filterElementRow = QueryCondition.StartedToDate().apply { singleValue = s1.startDate!! } val filterElementRow = QueryCondition.StartedToDate().apply { singleValue = s1.startDate!! }
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FixedDate
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -435,14 +436,14 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, false, cal.time, 1) 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) val s2 = Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryCondition.EndedFromDate() val filter = QueryCondition.EndedFromDate()
val filterElementRow = QueryCondition.EndedFromDate().apply { singleValue = s2.endDate() } val filterElementRow = QueryCondition.EndedFromDate().apply { singleValue = s2.endDate() }
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FixedDate
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -463,7 +464,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
cal.time = Date() // sets calendar time/date cal.time = Date() // sets calendar time/date
val s1 = Session.testInstance(100.0, false, cal.time, 1) 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) Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction() realm.commitTransaction()
@ -471,7 +472,7 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.EndedToDate() val filter = QueryCondition.EndedToDate()
val filterElementRow = QueryCondition.EndedToDate().apply { singleValue = s1.endDate() } val filterElementRow = QueryCondition.EndedToDate().apply { singleValue = s1.endDate() }
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FixedDate
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -481,4 +482,39 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertEquals(s1.id, (this).id) 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" filter.name = "testSaveLoadCashFilter"
val filterElement = QueryCondition.IsCash val filterElement = QueryCondition.IsCash
filterElement.filterSectionRow = FilterSectionRow.CASH_TOURNAMENT filterElement.filterSectionRow = FilterSectionRow.CashOrTournament
filter.createOrUpdateFilterConditions(arrayListOf(filterElement)) filter.createOrUpdateFilterConditions(arrayListOf(filterElement))
val useCount = filter.countBy(FilterCategoryRow.GENERAL) val useCount = filter.countBy(FilterCategoryRow.GENERAL)

@ -110,7 +110,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyBankroll() val filter = QueryCondition.AnyBankroll()
val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) } val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) }
filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL filterElementRow.filterSectionRow = FilterSectionRow.Bankroll
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -141,10 +141,10 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyBankroll() val filter = QueryCondition.AnyBankroll()
val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) } val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) }
filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL filterElementRow.filterSectionRow = FilterSectionRow.Bankroll
val filterElementRow2 = QueryCondition.AnyBankroll().apply { setObject(b2) } val filterElementRow2 = QueryCondition.AnyBankroll().apply { setObject(b2) }
filterElementRow2.filterSectionRow = FilterSectionRow.BANKROLL filterElementRow2.filterSectionRow = FilterSectionRow.Bankroll
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -197,9 +197,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filterElementRow = QueryCondition.AnyGame().apply { setObject(g2) } val filterElementRow = QueryCondition.AnyGame().apply { setObject(g2) }
filterElementRow.filterSectionRow = FilterSectionRow.GAME filterElementRow.filterSectionRow = FilterSectionRow.Game
val filterElementRow2 = QueryCondition.AnyGame().apply { setObject(g3) } val filterElementRow2 = QueryCondition.AnyGame().apply { setObject(g3) }
filterElementRow2.filterSectionRow = FilterSectionRow.GAME filterElementRow2.filterSectionRow = FilterSectionRow.Game
val filterCondition = FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)) val filterCondition = FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))
val queryCondition = filterCondition.queryCondition val queryCondition = filterCondition.queryCondition
val sessions = Filter.queryOn<Session>(realm, Query(queryCondition)) val sessions = Filter.queryOn<Session>(realm, Query(queryCondition))
@ -225,7 +225,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyLocation() val filter = QueryCondition.AnyLocation()
val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) } val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) }
filterElementRow.filterSectionRow = FilterSectionRow.LOCATION filterElementRow.filterSectionRow = FilterSectionRow.Location
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -257,9 +257,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyLocation() val filter = QueryCondition.AnyLocation()
val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) } val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) }
filterElementRow.filterSectionRow = FilterSectionRow.LOCATION filterElementRow.filterSectionRow = FilterSectionRow.Location
val filterElementRow2 = QueryCondition.AnyLocation().apply { setObject(l3) } val filterElementRow2 = QueryCondition.AnyLocation().apply { setObject(l3) }
filterElementRow2.filterSectionRow = FilterSectionRow.LOCATION filterElementRow2.filterSectionRow = FilterSectionRow.Location
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
@ -287,7 +287,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTournamentName() val filter = QueryCondition.AnyTournamentName()
val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) } val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME filterElementRow.filterSectionRow = FilterSectionRow.TournamentName
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -318,9 +318,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTournamentName() val filter = QueryCondition.AnyTournamentName()
val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) } val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME filterElementRow.filterSectionRow = FilterSectionRow.TournamentName
val filterElementRow2 = QueryCondition.AnyTournamentName().apply { setObject(t2) } val filterElementRow2 = QueryCondition.AnyTournamentName().apply { setObject(t2) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME filterElementRow.filterSectionRow = FilterSectionRow.TournamentName
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -354,11 +354,11 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AllTournamentFeature() val filter = QueryCondition.AllTournamentFeature()
val filterElementRow = QueryCondition.AllTournamentFeature().apply { setObject(t1) } val filterElementRow = QueryCondition.AllTournamentFeature().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow2 = QueryCondition.AllTournamentFeature().apply { setObject(t2) } val filterElementRow2 = QueryCondition.AllTournamentFeature().apply { setObject(t2) }
filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow2.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow3 = QueryCondition.AllTournamentFeature().apply { setObject(t4) } val filterElementRow3 = QueryCondition.AllTournamentFeature().apply { setObject(t4) }
filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow3.filterSectionRow = FilterSectionRow.TournamentFeature
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -389,13 +389,13 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTournamentFeature() val filter = QueryCondition.AnyTournamentFeature()
val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t1) } val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t1) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow2 = QueryCondition.AnyTournamentFeature().apply { setObject(t2) } val filterElementRow2 = QueryCondition.AnyTournamentFeature().apply { setObject(t2) }
filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow2.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow3 = QueryCondition.AnyTournamentFeature().apply { setObject(t3) } val filterElementRow3 = QueryCondition.AnyTournamentFeature().apply { setObject(t3) }
filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow3.filterSectionRow = FilterSectionRow.TournamentFeature
val filterElementRow4 = QueryCondition.AnyTournamentFeature().apply { setObject(t4) } 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))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3, filterElementRow4)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -423,7 +423,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTournamentFeature() val filter = QueryCondition.AnyTournamentFeature()
val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t2) } val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t2) }
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow.filterSectionRow = FilterSectionRow.TournamentFeature
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -448,9 +448,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.AnyTableSize() val filter = QueryCondition.AnyTableSize()
val filterElementRow = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(2) } 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) } val filterElementRow2 = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(4) }
filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE filterElementRow.filterSectionRow = FilterSectionRow.TableSize
filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueBy(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -475,7 +475,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.NetAmountWon() val filter = QueryCondition.NetAmountWon()
val filterElementRow = QueryCondition.more<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(204.0) } val filterElementRow = QueryCondition.more<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(204.0) }
filterElementRow.filterSectionRow = FilterSectionRow.VALUE filterElementRow.filterSectionRow = FilterSectionRow.Value
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filterElementRow)) val sessions = Filter.queryOn<Session>(realm, Query(filterElementRow))
@ -500,7 +500,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filter = QueryCondition.NetAmountWon() val filter = QueryCondition.NetAmountWon()
val filterElementRow = QueryCondition.less<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(540.0) } val filterElementRow = QueryCondition.less<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(540.0) }
filterElementRow.filterSectionRow = FilterSectionRow.VALUE filterElementRow.filterSectionRow = FilterSectionRow.Value
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow))) filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val sessions = Filter.queryOn<Session>(realm, Query(filter)) val sessions = Filter.queryOn<Session>(realm, Query(filter))
@ -524,13 +524,13 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filterMore = QueryCondition.NetAmountWon() val filterMore = QueryCondition.NetAmountWon()
val filterElementRow = QueryCondition.more<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(200.0) } val filterElementRow = QueryCondition.more<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(199.0) }
filterElementRow.filterSectionRow = FilterSectionRow.VALUE filterElementRow.filterSectionRow = FilterSectionRow.Value
filterMore.updateValueBy(FilterCondition(arrayListOf(filterElementRow))) filterMore.updateValueBy(FilterCondition(arrayListOf(filterElementRow)))
val filterLess = QueryCondition.NetAmountWon() val filterLess = QueryCondition.NetAmountWon()
val filterElementRow2 = QueryCondition.less<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(400.0) } val filterElementRow2 = QueryCondition.less<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(400.0) }
filterElementRow2.filterSectionRow = FilterSectionRow.VALUE filterElementRow2.filterSectionRow = FilterSectionRow.Value
filterLess.updateValueBy(FilterCondition(arrayListOf(filterElementRow2))) filterLess.updateValueBy(FilterCondition(arrayListOf(filterElementRow2)))
val sessions = Filter.queryOn<Session>(realm, Query(filterMore, filterLess)) val sessions = Filter.queryOn<Session>(realm, Query(filterMore, filterLess))
@ -564,7 +564,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
s4.result!!.buyin = 200.0 s4.result!!.buyin = 200.0
realm.commitTransaction() 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)) val sessions = Filter.queryOn<Session>(realm, Query(filterMore))
Assert.assertEquals(2, sessions.size) 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,115 +1,168 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.pokeranalytics.android"> xmlns:tools="http://schemas.android.com/tools"
package="net.pokeranalytics.android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="com.android.vending.BILLING" />
<application
android:name=".PokerAnalyticsApplication" <application
android:allowBackup="true" android:name=".PokerAnalyticsApplication"
android:icon="@mipmap/ic_launcher" android:allowBackup="true"
android:label="@string/app_name" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:label="@string/app_name"
android:supportsRtl="true" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/PokerAnalyticsTheme"> android:supportsRtl="true"
android:theme="@style/PokerAnalyticsTheme">
<meta-data
android:name="firebase_crashlytics_collection_enabled" <meta-data
android:value="false" /> android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<activity
android:name="net.pokeranalytics.android.ui.activity.HomeActivity" <activity
android:label="@string/app_name" android:name="net.pokeranalytics.android.ui.activity.HomeActivity"
android:screenOrientation="portrait"> android:launchMode="singleTop"
<intent-filter> android:label="@string/app_name"
<action android:name="android.intent.action.MAIN" /> android:screenOrientation="portrait">
<action android:name="android.intent.action.VIEW" /> <intent-filter>
<category android:name="android.intent.category.LAUNCHER" /> <action android:name="android.intent.action.MAIN" />
</intent-filter> <action android:name="android.intent.action.VIEW" />
</activity>
<category android:name="android.intent.category.LAUNCHER" />
<activity </intent-filter>
android:name="net.pokeranalytics.android.ui.activity.SessionActivity"
android:launchMode="singleTop" <intent-filter tools:ignore="AppLinkUrlError">
android:screenOrientation="portrait" <action android:name="android.intent.action.VIEW" />
android:windowSoftInputMode="adjustNothing" /> <category android:name="android.intent.category.DEFAULT" />
<activity <data android:scheme="file" />
android:name="net.pokeranalytics.android.ui.activity.BankrollActivity" <data android:scheme="content" />
android:launchMode="singleTop" <data android:mimeType="text/comma-separated-values" />
android:screenOrientation="portrait" /> <data android:mimeType="text/csv" />
<activity </intent-filter>
android:name="net.pokeranalytics.android.ui.activity.SettingsActivity"
android:launchMode="singleTop" </activity>
android:screenOrientation="portrait" />
<activity
<activity android:name="net.pokeranalytics.android.ui.activity.ImportActivity"
android:name="net.pokeranalytics.android.ui.activity.StatisticDetailsActivity" android:screenOrientation="portrait"
android:launchMode="singleTop" android:launchMode="singleTop">
android:screenOrientation="portrait" />
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.ReportDetailsActivity" <activity
android:launchMode="singleTop" android:name="net.pokeranalytics.android.ui.activity.SessionActivity"
android:screenOrientation="portrait" /> android:launchMode="singleTop"
android:screenOrientation="portrait"
<activity android:windowSoftInputMode="adjustNothing" />
android:name="net.pokeranalytics.android.ui.activity.CalendarDetailsActivity"
android:launchMode="singleTop" <activity
android:screenOrientation="portrait" /> android:name="net.pokeranalytics.android.ui.activity.NewDataMenuActivity"
android:launchMode="singleTop"
<activity android:screenOrientation="portrait"
android:name="net.pokeranalytics.android.ui.activity.ComparisonChartActivity" android:theme="@style/PokerAnalyticsTheme.MenuDialog" />
android:launchMode="singleTop"
android:screenOrientation="portrait" /> <activity
android:name="net.pokeranalytics.android.ui.activity.BankrollActivity"
<activity android:launchMode="singleTop"
android:name="net.pokeranalytics.android.ui.activity.DataListActivity" android:screenOrientation="portrait" />
android:launchMode="singleTop"
android:screenOrientation="portrait" /> <activity
android:name="net.pokeranalytics.android.ui.activity.BankrollDetailsActivity"
<activity android:launchMode="singleTop"
android:name="net.pokeranalytics.android.ui.activity.EditableDataActivity" android:screenOrientation="portrait" />
android:launchMode="singleTop"
android:screenOrientation="portrait" /> <activity
android:name="net.pokeranalytics.android.ui.activity.SettingsActivity"
<activity android:launchMode="singleTop"
android:name="net.pokeranalytics.android.ui.activity.CurrenciesActivity" android:screenOrientation="portrait" />
android:launchMode="singleTop"
android:screenOrientation="portrait" /> <activity
android:name="net.pokeranalytics.android.ui.activity.GraphActivity"
<activity android:launchMode="singleTop"
android:name="net.pokeranalytics.android.ui.activity.FiltersActivity" android:screenOrientation="portrait" />
android:launchMode="singleTop"
android:screenOrientation="portrait" /> <activity
android:name="net.pokeranalytics.android.ui.activity.ProgressReportActivity"
<activity android:launchMode="singleTop"
android:name="net.pokeranalytics.android.ui.activity.FilterDetailsActivity" android:screenOrientation="portrait" />
android:launchMode="singleTop"
android:screenOrientation="portrait" /> <activity
android:name="net.pokeranalytics.android.ui.activity.ComparisonReportActivity"
<activity android:launchMode="singleTop"
android:name="net.pokeranalytics.android.ui.activity.GDPRActivity" android:screenOrientation="portrait" />
android:launchMode="singleTop"
android:screenOrientation="portrait" /> <activity
android:name="net.pokeranalytics.android.ui.activity.CalendarDetailsActivity"
<meta-data android:launchMode="singleTop"
android:name="preloaded_fonts" android:screenOrientation="portrait" />
android:resource="@array/preloaded_fonts" />
<activity
<provider android:name="net.pokeranalytics.android.ui.activity.ComparisonChartActivity"
android:name="androidx.core.content.FileProvider" android:launchMode="singleTop"
android:authorities="${applicationId}.fileprovider" android:screenOrientation="portrait" />
android:exported="false"
android:grantUriPermissions="true"> <activity
<meta-data android:name="net.pokeranalytics.android.ui.activity.DataListActivity"
android:name="android.support.FILE_PROVIDER_PATHS" android:launchMode="singleTop"
android:resource="@xml/provider_paths" /> android:screenOrientation="portrait" />
</provider>
<activity
</application> android:name="net.pokeranalytics.android.ui.activity.EditableDataActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.CurrenciesActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FiltersActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FilterDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.GDPRActivity"
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" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest> </manifest>

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

@ -16,7 +16,7 @@ import retrofit2.http.Query
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
* Currency Converter API * CurrencyCode Converter API
*/ */
interface CurrencyConverterApi { 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 package net.pokeranalytics.android.calculus
import android.content.Context
import io.realm.Realm import io.realm.Realm
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat.* import net.pokeranalytics.android.calculus.Stat.*
import net.pokeranalytics.android.model.Criteria import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined 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.Query
import net.pokeranalytics.android.model.filter.filter import net.pokeranalytics.android.model.filter.filter
import net.pokeranalytics.android.model.realm.ComputableResult 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.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 net.pokeranalytics.android.util.extensions.startOfDay
import timber.log.Timber
import java.util.* import java.util.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/** /**
* The class performing stats computation * The class performing statIds computation
*/ */
class Calculator { class Calculator {
/** /**
* The options used for calculations or display * The options used for calculations and display
*/ */
class Options( class Options(
display: Display = Display.TABLE, var display: Display = Display.TABLE,
evolutionValues: EvolutionValues = EvolutionValues.NONE, progressValues: ProgressValues = ProgressValues.NONE,
stats: List<Stat> = listOf(), var stats: List<Stat> = listOf(),
aggregationType: AggregationType? = null 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)
/** /**
* The way the stats are going to be displayed * Specifies whether progress values should be added and their kind
*/ */
enum class Display { 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 computed stats are going to be displayed
*/
enum class Display : RowRepresentable {
TABLE, TABLE,
EVOLUTION, PROGRESS,
COMPARISON, COMPARISON,
MAP, 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 * The type of evolution numericValues
*/ */
enum class EvolutionValues { enum class ProgressValues {
NONE, NONE,
STANDARD, STANDARD,
TIMED 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 * This function determines whether the standard deviation should be computed
*/ */
val computeStandardDeviation: Boolean val computeStandardDeviation: Boolean
get() { get() {
this.displayedStats.forEach { this.stats.forEach {
if (it == STANDARD_DEVIATION_BB_PER_100_HANDS || it == STANDARD_DEVIATION || it == STANDARD_DEVIATION_HOURLY) { if (it == STANDARD_DEVIATION_BB_PER_100_HANDS || it == STANDARD_DEVIATION || it == STANDARD_DEVIATION_HOURLY) {
return true return true
} }
@ -68,32 +125,56 @@ class Calculator {
return false return false
} }
/**
* Whether the longest streaks should be computed
*/
val computeLongestStreak: Boolean val computeLongestStreak: Boolean
get() { get() {
return this.displayedStats.contains(LONGEST_STREAKS) return this.stats.contains(LONGEST_STREAKS)
} }
/**
* Whether the values should be sorted
*/
val shouldSortValues: Boolean val shouldSortValues: Boolean
get() { 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 val computeLocationsPlayed: Boolean
get() { 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 val computeDaysPlayed: Boolean
get() { 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 val shouldManageMultiGroupProgressValues: Boolean
get() { get() {
if (this.aggregationType != null) { return if (this.aggregationType != null) {
return this.aggregationType == AggregationType.MONTH || this.aggregationType == AggregationType.YEAR this.aggregationType == AggregationType.MONTH || this.aggregationType == AggregationType.YEAR
} else { } 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)
}
}
} }
companion object { companion object {
@ -106,37 +187,36 @@ class Calculator {
stats: List<Stat>? = null stats: List<Stat>? = null
): Report { ): Report {
val options = Options(evolutionValues = Options.EvolutionValues.STANDARD, aggregationType = aggregationType) val options = Options(
options.displayedStats = listOf(stat) display = Options.Display.PROGRESS,
progressValues = Options.ProgressValues.STANDARD,
stats = listOf(stat),
aggregationType = aggregationType
)
if (aggregationType == AggregationType.DURATION) { if (aggregationType == AggregationType.DURATION) {
options.evolutionValues = Options.EvolutionValues.TIMED options.progressValues = Options.ProgressValues.TIMED
} }
stats?.let { stats?.let {
options.displayedStats = stats options.stats = stats
} }
return when (aggregationType) { return when (aggregationType) {
AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(realm, listOf(group), options) AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(realm, listOf(group), options)
AggregationType.MONTH, AggregationType.YEAR -> { AggregationType.MONTH, AggregationType.YEAR -> {
this.computeStatsWithCriterias(realm, aggregationType.criterias, group.query, options) this.computeStats(realm, options)
} }
} }
} }
fun computeStats(realm: Realm, options: Options = Options()): Report {
fun computeStatsWithCriterias(
realm: Realm,
criterias: List<Criteria> = listOf(),
query: Query = Query(),
options: Options = Options()
): Report {
val computableGroups: MutableList<ComputableGroup> = mutableListOf() 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) val group = ComputableGroup(comparatorQuery)
computableGroups.add(group) computableGroups.add(group)
@ -144,7 +224,7 @@ class Calculator {
} }
if (computableGroups.size == 0) { if (computableGroups.size == 0) {
val group = ComputableGroup(query) val group = ComputableGroup(options.query)
computableGroups.add(group) 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 { fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options = Options()): Report {
val report = Report(options) val report = Report(options)
groups.forEach { group -> groups.forEach { group ->
val s = Date() // val s = Date()
// Clean existing computables / sessionSets if group is reused // Clean existing computables / sessionSets if group is reused
group.cleanup() group.cleanup()
// Computes actual sessionGroup stats // Computes actual sessionGroup statIds
val results: ComputedResults = this.compute(realm, group, options) val results: ComputedResults = this.compute(realm, group, options)
// Computes the compared sessionGroup if existing // Computes the compared sessionGroup if existing
@ -181,9 +261,9 @@ class Calculator {
results.finalize() // later treatment, such as evolution numericValues sorting results.finalize() // later treatment, such as evolution numericValues sorting
report.addResults(results) report.addResults(results)
val e = Date() // val e = Date()
val duration = (e.time - s.time) / 1000.0 // val duration = (e.time - s.time) / 1000.0
Timber.d(">>> group ${group.name} in $duration seconds") // 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 { fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options = Options()): ComputedResults {
val results = ComputedResults(computableGroup, options.shouldManageMultiGroupProgressValues) val results = ComputedResults(computableGroup, options.shouldManageMultiGroupProgressValues)
val computables = computableGroup.computables(realm, options.shouldSortValues) 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()) results.addStat(NUMBER_OF_GAMES, computables.size.toDouble())
val sum = computables.sum(ComputableResult.Field.RATED_NET.identifier).toDouble() val sum = computables.sum(ComputableResult.Field.RATED_NET.identifier).toDouble()
@ -256,7 +336,7 @@ class Calculator {
} }
val shouldIterateOverComputables = val shouldIterateOverComputables =
(options.evolutionValues == Options.EvolutionValues.STANDARD || options.computeLongestStreak) (options.progressValues == Options.ProgressValues.STANDARD || options.computeLongestStreak)
// Computable Result // Computable Result
if (shouldIterateOverComputables) { if (shouldIterateOverComputables) {
@ -339,7 +419,7 @@ class Calculator {
var gBBSum: Double? = null var gBBSum: Double? = null
var maxDuration: Double? = null var maxDuration: Double? = null
if (computableGroup.conditions.size == 0) { // SessionSets are fine if (computableGroup.conditions.isEmpty()) { // SessionSets are fine
gHourlyDuration = gHourlyDuration =
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours) sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours)
gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble() gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
@ -350,7 +430,7 @@ class Calculator {
} }
val shouldIterateOverSets = computableGroup.conditions.isNotEmpty() || val shouldIterateOverSets = computableGroup.conditions.isNotEmpty() ||
options.evolutionValues != Options.EvolutionValues.NONE || options.progressValues != Options.ProgressValues.NONE ||
options.computeDaysPlayed options.computeDaysPlayed
// Session Set // Session Set
@ -381,8 +461,8 @@ class Calculator {
tHourlyRateBB = tBBSum / tHourlyDuration tHourlyRateBB = tBBSum / tHourlyDuration
daysSet.add(sessionSet.startDate.startOfDay()) daysSet.add(sessionSet.startDate.startOfDay())
when (options.evolutionValues) { when (options.progressValues) {
Options.EvolutionValues.STANDARD -> { Options.ProgressValues.STANDARD -> {
results.addEvolutionValue(tHourlyRate, stat = HOURLY_RATE, data = sessionSet) results.addEvolutionValue(tHourlyRate, stat = HOURLY_RATE, data = sessionSet)
results.addEvolutionValue(tIndex.toDouble(), stat = NUMBER_OF_SETS, data = sessionSet) results.addEvolutionValue(tIndex.toDouble(), stat = NUMBER_OF_SETS, data = sessionSet)
results.addEvolutionValue( results.addEvolutionValue(
@ -403,7 +483,7 @@ class Calculator {
} }
} }
Options.EvolutionValues.TIMED -> { Options.ProgressValues.TIMED -> {
results.addEvolutionValue(tRatedNetSum, tHourlyDuration, NET_RESULT, sessionSet) results.addEvolutionValue(tRatedNetSum, tHourlyDuration, NET_RESULT, sessionSet)
results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet) results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue( 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.DefaultLegendValues
import net.pokeranalytics.android.ui.view.LegendContent import net.pokeranalytics.android.ui.view.LegendContent
import net.pokeranalytics.android.util.ColorUtils import net.pokeranalytics.android.util.ColorUtils
import net.pokeranalytics.android.util.TextFormat
import kotlin.math.abs import kotlin.math.abs
/** /**
@ -44,7 +45,7 @@ class Report(var options: Calculator.Options) {
*/ */
fun lineEntries(stat: Stat? = null, context: Context): LineDataSet { fun lineEntries(stat: Stat? = null, context: Context): LineDataSet {
val entries = mutableListOf<Entry>() val entries = mutableListOf<Entry>()
val statToUse = stat ?: options.displayedStats.firstOrNull() val statToUse = stat ?: options.stats.firstOrNull()
val statName = statToUse?.name ?: "" val statName = statToUse?.name ?: ""
statToUse?.let { statToUse?.let {
@ -60,7 +61,7 @@ class Report(var options: Calculator.Options) {
fun barEntries(stat: Stat? = null, context: Context): BarDataSet { fun barEntries(stat: Stat? = null, context: Context): BarDataSet {
val entries = mutableListOf<BarEntry>() val entries = mutableListOf<BarEntry>()
val statToUse = stat ?: options.displayedStats.firstOrNull() val statToUse = stat ?: options.stats.firstOrNull()
statToUse?.let { statToUse?.let {
this._results.forEachIndexed { index, results -> this._results.forEachIndexed { index, results ->
@ -79,7 +80,7 @@ class Report(var options: Calculator.Options) {
fun multiLineEntries(context: Context): List<LineDataSet> { fun multiLineEntries(context: Context): List<LineDataSet> {
val dataSets = mutableListOf<LineDataSet>() val dataSets = mutableListOf<LineDataSet>()
options.displayedStats.forEach { stat -> options.stats.forEach { stat ->
this._results.forEachIndexed { index, result -> this._results.forEachIndexed { index, result ->
val ds = result.singleLineEntries(stat, context) val ds = result.singleLineEntries(stat, context)
ds.color = ColorUtils.almostRandomColor(index, 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 * 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) /**
// * A subgroup used to compute stat variation
// private constructor(name: String = "", conditions: List<QueryCondition> = listOf(), stats: List<Stat>? = null) */
var comparedGroup: ComputableGroup? = null
var query: Query = query
/** /**
* The display name of the group * The computed statIds of the comparable sessionGroup
*/ */
var name: String = "" var comparedComputedResults: ComputedResults? = null
get() {
return this.query.name
}
/** /**
* A list of _conditions to get * A list of _conditions to get
*/ */
var conditions: List<QueryCondition> = listOf() val conditions: List<QueryCondition>
get() { get() {
return this.query.conditions return this.query.conditions
} }
@ -164,21 +161,6 @@ class ComputableGroup(query: Query, stats: List<Stat>? = null) {
return sets 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() { fun cleanup() {
this._computables = null this._computables = null
this._sessionSets = null this._sessionSets = null
@ -195,14 +177,14 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
GraphUnderlyingEntry { GraphUnderlyingEntry {
/** /**
* The session group used to computed the stats * The session group used to computed the statIds
*/ */
var group: ComputableGroup = group var group: ComputableGroup = group
// The computed stats of the sessionGroup // The computed statIds of the sessionGroup
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() 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 _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf()
private var shouldManageMultiGroupProgressValues = shouldManageMultiGroupProgressValues private var shouldManageMultiGroupProgressValues = shouldManageMultiGroupProgressValues
@ -221,10 +203,10 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
} else { } else {
Point(value, data = data.objectIdentifier) 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] val evolutionValues = this._evolutionValues[stat]
if (evolutionValues != null) { if (evolutionValues != null) {
evolutionValues.add(point) 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 * Also computes evolution values using the previously computed values
*/ */
private fun addComputedStat(computedStat: ComputedStat) { private fun addComputedStat(computedStat: ComputedStat) {
@ -366,9 +348,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
} }
fun finalize() { fun finalize() {
this.consolidateProgressStats() this.consolidateProgressStats()
} }
// MPAndroidChart // MPAndroidChart
@ -388,7 +368,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) 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 { fun durationEntries(stat: Stat, context: Context): LineDataSet {
@ -451,7 +431,9 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
// Stat Entry // 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 { override fun formattedValue(stat: Stat): TextFormat {
this.computedStat(stat)?.let { this.computedStat(stat)?.let {
@ -475,12 +457,12 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
return when (stat) { return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null) val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
DefaultLegendValues(this.entryTitle, totalStatValue) DefaultLegendValues(this.entryTitle(context), totalStatValue)
} }
else -> { else -> {
val entryValue = this.formattedValue(stat) val entryValue = this.formattedValue(stat)
val countValue = this.computedStat(Stat.NUMBER_OF_GAMES)?.format() 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) { return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null) val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
DefaultLegendValues(this.entryTitle, totalStatValue) DefaultLegendValues(this.entryTitle(context), totalStatValue)
} }
else -> { else -> {
val entryValue = this.formattedValue(stat) val entryValue = this.formattedValue(stat)
val totalStatValue = stat.format(entry.y.toDouble(), currency = null) 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 android.content.Context
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FormattingException 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.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import net.pokeranalytics.android.util.enumerations.IntSearchable
import net.pokeranalytics.android.util.extensions.formatted import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import net.pokeranalytics.android.util.extensions.toCurrency import net.pokeranalytics.android.util.extensions.toCurrency
@ -19,80 +19,56 @@ 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 { companion object : IntSearchable<Stat> {
SESSION,
MONTH,
YEAR,
DURATION;
val resId: Int override fun valuesInternal(): Array<Stat> {
get() { return values()
return when (this) {
SESSION -> R.string.session
MONTH -> R.string.month
YEAR -> R.string.year
DURATION -> R.string.duration
}
} }
val axisFormatting: AxisFormatting val userSelectableList: List<Stat>
get() { get() {
return when (this) { return values().filter { it.canBeUserSelected }
DURATION -> AxisFormatting.X_DURATION
else -> AxisFormatting.DEFAULT
} }
}
val criterias: List<Criteria> val evolutionValuesList: List<Stat>
get() { get() {
return when (this) { return values().filter { it.hasProgressValues }
MONTH -> listOf(Criteria.AllMonthsUpToNow)
YEAR -> listOf(Criteria.Years)
else -> listOf()
} }
}
}
/**
* 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? { fun returnOnInvestment(netResult: Double, buyin: Double): Double? {
if (buyin == 0.0) { if (buyin == 0.0) {
@ -150,6 +126,7 @@ enum class Stat : RowRepresentable {
MINIMUM_NETRESULT -> R.string.min_net_result MINIMUM_NETRESULT -> R.string.min_net_result
MAXIMUM_DURATION -> R.string.longest_session MAXIMUM_DURATION -> R.string.longest_session
DAYS_PLAYED -> R.string.days_played DAYS_PLAYED -> R.string.days_played
TOTAL_BUYIN -> R.string.total_buyin
else -> throw IllegalStateException("Stat ${this.name} name required but undefined") else -> throw IllegalStateException("Stat ${this.name} name required but undefined")
} }
} }
@ -171,7 +148,7 @@ enum class Stat : RowRepresentable {
return TextFormat(value.toCurrency(currency), color) return TextFormat(value.toCurrency(currency), color)
} }
// Red/green numericValues // 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 val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(value.formatted(), color) return TextFormat(value.formatted(), color)
} }
@ -182,7 +159,7 @@ enum class Stat : RowRepresentable {
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> { HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> {
return TextFormat(value.formattedHourlyDuration()) return TextFormat(value.formattedHourlyDuration())
} // red/green percentages } // 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 val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color) return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr } // white amountsr
@ -197,7 +174,7 @@ enum class Stat : RowRepresentable {
} }
} }
val threshold: Double private val threshold: Double
get() { get() {
return when (this) { return when (this) {
WIN_RATIO -> 50.0 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 { fun cumulativeLabelResId(context: Context): String {
val resId = when (this) { val resId = when (this) {
AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS, 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> val aggregationTypes: List<AggregationType>
get() { get() {
return when (this) { 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() { get() {
return when (this) { return when (this) {
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, 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() { get() {
return when (this) { return when (this) {
WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false 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() { get() {
return when (this) { return when (this) {
NUMBER_OF_GAMES, NUMBER_OF_SETS -> false NUMBER_OF_GAMES, NUMBER_OF_SETS -> false
@ -265,7 +257,7 @@ enum class Stat : RowRepresentable {
} }
} }
val showXAxisZero: Boolean val graphShowsXAxisZero: Boolean
get() { get() {
return when (this) { return when (this) {
HOURLY_DURATION -> true HOURLY_DURATION -> true
@ -273,7 +265,7 @@ enum class Stat : RowRepresentable {
} }
} }
val showYAxisZero: Boolean val graphShowsYAxisZero: Boolean
get() { get() {
return when (this) { return when (this) {
HOURLY_DURATION -> true 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 override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
} }

@ -11,12 +11,15 @@ class BankrollCalculator {
companion object { 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 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 initialValue = 0.0
var transactionNet = 0.0 var transactionNet = 0.0
@ -25,8 +28,14 @@ class BankrollCalculator {
val rate = if (setup.virtualBankroll) bankroll.rate else 1.0 val rate = if (setup.virtualBankroll) bankroll.rate else 1.0
initialValue += bankroll.initialValue * rate if (setup.shouldAddInitialValue) {
transactionNet += bankroll.transactions.sumByDouble { it.amount } * rate initialValue += bankroll.initialValue * rate
}
bankroll.transactions?.let { transactions ->
val sum = transactions.sum("amount")
transactionNet += rate * sum.toDouble()
}
} }
report.transactionsNet = transactionNet report.transactionsNet = transactionNet
@ -72,7 +81,7 @@ class BankrollCalculator {
report.generateGraphPointsIfNecessary() report.generateGraphPointsIfNecessary()
realm.close() //realm.close()
return report 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.Bankroll
import net.pokeranalytics.android.model.realm.Transaction import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.graph.DataSetFactory 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 java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
//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()
//
//
// }
//
//}
class BankrollReport(setup: BankrollReportSetup) { /**
* This class holds the results from the BankrollCalculator computations
/** * It has all the information required for the Bankroll various displays
* The setup used to compute the report */
*/ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable {
var setup: BankrollReportSetup = setup
/** /**
* The value of the bankroll * 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 * The initial value of the bankroll, or of all bankrolls if virtual is computed
*/ */
var initial: Double = 0.0 var initial: Double = 0.0
set(value) {
field = value
this.computeBankrollTotal()
}
/** /**
* The net result from poker computables * The net result from poker computables
@ -49,7 +85,10 @@ class BankrollReport(setup: BankrollReportSetup) {
this.computeBankrollTotal() this.computeBankrollTotal()
} }
fun computeBankrollTotal() { /**
* Computes the bankroll total
*/
private fun computeBankrollTotal() {
this.total = this.initial + this.netResult + this.transactionsNet this.total = this.initial + this.netResult + this.transactionsNet
} }
@ -59,7 +98,7 @@ class BankrollReport(setup: BankrollReportSetup) {
var depositTotal: Double = 0.0 var depositTotal: Double = 0.0
set(value) { set(value) {
field = value field = value
this.netBanked = this.depositTotal + this.withdrawalTotal this.computeNetBanked()
} }
/** /**
@ -68,9 +107,16 @@ class BankrollReport(setup: BankrollReportSetup) {
var withdrawalTotal: Double = 0.0 var withdrawalTotal: Double = 0.0
set(value) { set(value) {
field = 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
}
/** /**
* The difference between withdrawals and deposits * The difference between withdrawals and deposits
*/ */
@ -82,20 +128,47 @@ class BankrollReport(setup: BankrollReportSetup) {
*/ */
var riskOfRuin: Double? = null var riskOfRuin: Double? = null
/**
* The list of transactions held by the bankroll
*/
var transactions: List<Transaction> = mutableListOf() var transactions: List<Transaction> = mutableListOf()
private set private set
/**
* A map containing TransactionBuckets by transaction types
*/
var transactionBuckets: HashMap<String, TransactionBucket> = HashMap() var transactionBuckets: HashMap<String, TransactionBucket> = HashMap()
private set private set
var evolutionPoints: MutableList<BRGraphPoint> = mutableListOf() /**
var evolutionItems: MutableList<DatedValue> = mutableListOf() * The list of bankroll graph points
private set */
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>) { fun addDatedItems(items: Collection<DatedValue>) {
this.evolutionItems.addAll(items) this.evolutionItems.addAll(items)
} }
/**
* Adds a transaction to its type bucket
*/
fun addTransaction(transaction: Transaction) { fun addTransaction(transaction: Transaction) {
transaction.type?.let { type -> transaction.type?.let { type ->
@ -111,11 +184,14 @@ class BankrollReport(setup: BankrollReportSetup) {
bucket.addTransaction(transaction) bucket.addTransaction(transaction)
} ?: run { } ?: run {
throw Exception("Transaction has no type") throw IllegalStateException("Transaction has no type")
} }
} }
/**
* Generates the graph points used for the virtual bankroll
*/
fun generateGraphPointsIfNecessary() { fun generateGraphPointsIfNecessary() {
if (!this.setup.virtualBankroll) { if (!this.setup.virtualBankroll) {
@ -124,18 +200,23 @@ class BankrollReport(setup: BankrollReportSetup) {
this.evolutionItems.sortBy { it.date } this.evolutionItems.sortBy { it.date }
var total = 0.0
this.evolutionItems.forEach { 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) this.evolutionPoints.add(point)
} }
} }
/**
* Returns a data set used for the bankroll graph
*/
fun lineDataSet(context: Context): LineDataSet { fun lineDataSet(context: Context): LineDataSet {
val entries = mutableListOf<Entry>() val entries = mutableListOf<Entry>()
this.evolutionPoints.forEach { this.evolutionPoints.forEachIndexed { index, point ->
val entry = Entry(it.date.time.toFloat(), it.value.toFloat(), it.data) val entry = Entry(index.toFloat(), point.value.toFloat(), point.data)
entries.add(entry) entries.add(entry)
} }
return DataSetFactory.lineDataSetInstance(entries, "", context) 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) { 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 val virtualBankroll: Boolean
get() { get() {
return this.bankroll == null return this.bankroll == null
} }
/**
* the query used to get bankroll transactions
*/
val query: Query val query: Query
get() { get() {
val query = Query() val query = Query()
@ -175,20 +263,46 @@ class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null
return query 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) { 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 var total: Double = 0.0
private set private set
var useRate: Boolean = useRate
private set
fun addTransaction(transaction: Transaction) { fun addTransaction(transaction: Transaction) {
this.transactions.add(transaction) this._transactions.add(transaction)
var rate = 1.0 var rate = 1.0
if (this.useRate) { if (this.useRate) {
rate = transaction.bankroll?.currency?.rate ?: 1.0 rate = transaction.bankroll?.currency?.rate ?: 1.0

@ -8,6 +8,9 @@ class RowRepresentableEditDescriptorException(message: String) : Exception(messa
class ConfigurationException(message: String) : Exception(message) 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) { sealed class PokerAnalyticsException(message: String) : Exception(message) {
object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition") 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") 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.Realm
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.Criteria.Bankrolls.comparison import net.pokeranalytics.android.model.Criteria.Bankrolls.comparison
import net.pokeranalytics.android.model.Criteria.Blinds.comparison import net.pokeranalytics.android.model.Criteria.Blinds.comparison
@ -14,229 +15,325 @@ import net.pokeranalytics.android.model.Criteria.TournamentFeatures.comparison
import net.pokeranalytics.android.model.Criteria.TournamentFees.comparison import net.pokeranalytics.android.model.Criteria.TournamentFees.comparison
import net.pokeranalytics.android.model.Criteria.TournamentNames.comparison import net.pokeranalytics.android.model.Criteria.TournamentNames.comparison
import net.pokeranalytics.android.model.Criteria.TournamentTypes.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.Query
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.realm.* 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> { fun List<Criteria>.combined(): List<Query> {
val comparatorList = ArrayList<List<Query>>() val comparatorList = ArrayList<List<Query>>()
this.forEach { criteria -> this.forEach { criteria ->
comparatorList.add(criteria.queries) comparatorList.add(criteria.queries)
} }
return getCombinations(comparatorList) return getCombinations(comparatorList)
} }
fun getCombinations(queries: List<List<Query>>): 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() val mutableQueries = queries.toMutableList()
var combinations = mutableQueries.removeAt(0) var combinations = mutableQueries.removeAt(0)
for (queryList in mutableQueries) { for (queryList in mutableQueries) {
val newCombinations = mutableListOf<Query>() val newCombinations = mutableListOf<Query>()
combinations.forEach { combinedQuery -> combinations.forEach { combinedQuery ->
queryList.forEach { queryToAdd -> queryList.forEach { queryToAdd ->
val nq = Query().merge(combinedQuery).merge(queryToAdd) val nq = Query().merge(combinedQuery).merge(queryToAdd)
newCombinations.add(nq) newCombinations.add(nq)
} }
} }
combinations = newCombinations combinations = newCombinations
} }
return combinations return combinations
} }
//fun getCombinations(lists: List<Query>): List<Query> { sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable {
// var combinations: LinkedHashSet<Query> = LinkedHashSet()
// var newCombinations: LinkedHashSet<Query> abstract class RealmCriteria(uniqueIdentifier: Int) : Criteria(uniqueIdentifier) {
// inline fun <reified T : NameManageable> comparison(): List<Query> {
// var index = 0 if (this is ListCustomFields) {
// val objects = mutableListOf<QueryCondition.CustomFieldListQuery>()
// // extract each of the integers in the first list val realm = Realm.getDefaultInstance()
// // and add each to ints as a new list realm.findById(CustomField::class.java, this.customFieldId)?.entries?.forEach {
// if (lists.isNotEmpty()) { objects.add(QueryCondition.CustomFieldListQuery(it))
// for (i in lists[0]) { }
// val newList = ArrayList<T>() objects.sorted()
// newList.add(i) realm.close()
// combinations.add(newList) return objects.map { Query(it) }
// }
// 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() {
inline fun <reified T: NameManageable> comparison(): List<Query> {
return compare<QueryCondition.QueryDataCondition<NameManageable>, T>()
}
}
abstract class SimpleCriteria(private val conditions:List<QueryCondition>): Criteria() {
fun comparison(): List<Query> {
return conditions.map { Query(it) }
}
}
abstract class ListCriteria : Criteria() {
inline fun <reified T : QueryCondition.ListOfValues<S>, reified S : Comparable<S>> comparison(): List<Query> {
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
else -> null
}
}.distinct()
return compareList<T, S>(values = values)
} }
return listOf() return compare<QueryCondition.QueryDataCondition<NameManageable>, T>()
} }
} }
abstract class SimpleCriteria(private val conditions: List<QueryCondition>, uniqueIdentifier: Int) : Criteria(uniqueIdentifier) {
object Bankrolls: RealmCriteria() fun comparison(): List<Query> {
object Games: RealmCriteria() return conditions.map { Query(it) }
object TournamentNames: RealmCriteria() }
object Locations: RealmCriteria() }
object TournamentFeatures: RealmCriteria()
object Limits: ListCriteria() abstract class ListCriteria(uniqueIdentifier: Int) : Criteria(uniqueIdentifier) {
object TableSizes: ListCriteria() inline fun <reified T : QueryCondition.ListOfValues<S>, reified S : Comparable<S>> comparison(): List<Query> {
object TournamentTypes: ListCriteria()
object MonthsOfYear: SimpleCriteria(List(12) { index -> QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index)} }) if (this is ValueCustomFields) {
object DaysOfWeek: SimpleCriteria(List(7) { index -> QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) } }) val realm = Realm.getDefaultInstance()
object SessionTypes: SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament)) val distincts = realm.where<CustomFieldEntry>().equalTo("customFields.id", this.customFieldId).findAll().sort("numericValue", Sort.ASCENDING)
object BankrollTypes: SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline)) realm.close()
object DayPeriods: SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd))
object Years: ListCriteria() val objects = mutableListOf<QueryCondition.CustomFieldNumberQuery>()
object AllMonthsUpToNow: ListCriteria() distincts.mapNotNull {
object Blinds: ListCriteria() it.numericValue
object TournamentFees: ListCriteria() }.distinct().forEach {value ->
object Cash: SimpleCriteria(listOf(QueryCondition.IsCash)) val condition: QueryCondition.CustomFieldNumberQuery = when (this.customFieldType(realm)) {
object Tournament: SimpleCriteria(listOf(QueryCondition.IsTournament)) CustomField.Type.AMOUNT.uniqueIdentifier -> QueryCondition.CustomFieldAmountQuery()
CustomField.Type.NUMBER.uniqueIdentifier -> QueryCondition.CustomFieldNumberQuery()
val queries: List<Query> else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue
get() { }.apply {
return when (this) { this.customFieldId = this@ListCriteria.customFieldId
is AllMonthsUpToNow -> { listOfValues = arrayListOf(value)
val realm = Realm.getDefaultInstance()
val firstSession= realm.where<Session>().sort("startDate", Sort.ASCENDING).findFirst()
val lastSession = realm.where<Session>().sort("startDate", Sort.DESCENDING).findFirst()
realm.close()
val years: ArrayList<Query> = arrayListOf()
val firstYear = firstSession?.year ?: return years
val firstMonth = firstSession.month ?: return years
val lastYear = lastSession?.year ?: return years
val lastMonth = lastSession.month ?: return years
for (year in firstYear..lastYear) {
val currentYear = QueryCondition.AnyYear(year)
for (month in 0..11) {
if (year == firstYear && month < firstMonth) {
continue
}
if (year == lastYear && month > lastMonth) {
continue
}
val currentMonth = QueryCondition.AnyMonthOfYear(month)
val query = Query(currentYear, currentMonth)
years.add(query)
}
} }
years objects.add(condition)
}
else -> {
return this.queryConditions
} }
objects.sorted()
return objects.map { Query(it) }
} }
}
val queryConditions: List<Query> QueryCondition.distinct<Session, T, S>()?.let {
get() { val values = it.mapNotNull { session ->
return when (this) { when (this) {
is Bankrolls -> comparison<Bankroll>() is Limits -> if (session.limit is S) {
is Games -> comparison<Game>() session.limit as S
is TournamentFeatures -> comparison<TournamentFeature>() } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TournamentNames -> comparison<TournamentName>() is TournamentTypes -> if (session.tournamentType is S) {
is Locations -> comparison<Location>() session.tournamentType as S
is SimpleCriteria -> comparison() } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is Limits -> comparison<QueryCondition.AnyLimit, Int>() is TableSizes -> if (session.tableSize is S) {
is TournamentTypes -> comparison<QueryCondition.AnyTournamentType, Int>() session.tableSize as S
is TableSizes -> comparison<QueryCondition.AnyTableSize, Int>() } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
is TournamentFees -> comparison<QueryCondition.TournamentFee, Double >() is TournamentFees -> if (session.tournamentEntryFee is S) {
is Years -> { session.tournamentEntryFee as S
val years = arrayListOf<Query>() } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
val realm = Realm.getDefaultInstance() is Blinds -> if (session.blinds is S) {
val lastSession = realm.where<Session>().sort("startDate", Sort.DESCENDING).findFirst() session.blinds as S
val yearNow = lastSession?.year ?: return years } else throw PokerAnalyticsException.QueryValueMapUnexpectedValue
else -> null
realm.where<Session>().sort("year", Sort.ASCENDING).findFirst()?.year?.let { }
for (index in 0..(yearNow - it)) { }.distinct()
val yearCondition = QueryCondition.AnyYear().apply { return compareList<T, S>(values = values)
listOfValues = arrayListOf(it + index) }
} return listOf()
years.add(Query(yearCondition)) }
} }
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() {
return when (this) {
is AllMonthsUpToNow -> {
val realm = Realm.getDefaultInstance()
val firstSession = realm.where<Session>().sort("startDate", Sort.ASCENDING).findFirst()
val lastSession = realm.where<Session>().sort("startDate", Sort.DESCENDING).findFirst()
realm.close()
val years: ArrayList<Query> = arrayListOf()
val firstYear = firstSession?.year ?: return years
val firstMonth = firstSession.month ?: return years
val lastYear = lastSession?.year ?: return years
val lastMonth = lastSession.month ?: return years
for (year in firstYear..lastYear) {
val currentYear = QueryCondition.AnyYear(year)
for (month in 0..11) {
if (year == firstYear && month < firstMonth) {
continue
}
if (year == lastYear && month > lastMonth) {
continue
}
val currentMonth = QueryCondition.AnyMonthOfYear(month)
val query = Query(currentMonth, currentYear)
years.add(query)
}
}
years
}
else -> {
return this.queryConditions
}
}
}
val queryConditions: List<Query>
get() {
return when (this) {
is Bankrolls -> comparison<Bankroll>()
is Games -> comparison<Game>()
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>()
is TableSizes -> comparison<QueryCondition.AnyTableSize, Int>()
is TournamentFees -> comparison<QueryCondition.TournamentFee, Double>()
is Years -> {
val years = arrayListOf<Query>()
val realm = Realm.getDefaultInstance()
val lastSession = realm.where<Session>().sort("startDate", Sort.DESCENDING).findFirst()
val yearNow = lastSession?.year ?: return years
realm.where<Session>().sort("year", Sort.ASCENDING).findFirst()?.year?.let {
for (index in 0..(yearNow - it)) {
val yearCondition = QueryCondition.AnyYear().apply {
listOfValues = arrayListOf(it + index)
}
years.add(Query(yearCondition))
}
}
realm.close()
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() realm.close()
years queries
}
is Blinds -> comparison<QueryCondition.AnyBlind, String >()
else -> throw PokerAnalyticsException.QueryTypeUnhandled
}
}
companion object {
inline fun <reified S : QueryCondition.QueryDataCondition<NameManageable >, reified T : NameManageable> compare(): List<Query> {
val objects = mutableListOf<S>()
val realm = Realm.getDefaultInstance()
realm.where<T>().findAll().forEach {
val condition = (QueryCondition.getInstance<T>() as S).apply {
setObject(it)
}
objects.add(condition)
}
objects.sorted()
realm.close()
return objects.map { Query(it) }
}
inline fun < reified S : QueryCondition.ListOfValues<T>, T:Any > compareList(values:List<T>): List<Query> {
val objects = mutableListOf<S>()
values.forEach {
val condition =(S::class.java.newInstance()).apply {
listOfValues = arrayListOf(it)
} }
objects.add(condition) else -> throw PokerAnalyticsException.QueryTypeUnhandled
} }
objects.sorted() }
return objects.map { Query(it) }
} 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> {
inline fun <reified S : QueryCondition.QueryDataCondition<NameManageable>, reified T : NameManageable> compare(): List<Query> {
val objects = mutableListOf<S>()
val realm = Realm.getDefaultInstance()
realm.where<T>().findAll().forEach {
val condition = (QueryCondition.getInstance<T>() as S).apply {
setObject(it)
}
objects.add(condition)
}
objects.sorted()
realm.close()
return objects.map { Query(it) }
}
inline fun <reified S : QueryCondition.ListOfValues<T>, T : Any> compareList(values: List<T>): List<Query> {
val objects = mutableListOf<S>()
values.forEach {
val condition = (S::class.java.newInstance()).apply {
listOfValues = arrayListOf(it)
}
objects.add(condition)
}
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 package net.pokeranalytics.android.model
import android.content.Context
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
enum class Limit : RowRepresentable { enum class Limit : RowRepresentable {
@ -9,6 +10,21 @@ enum class Limit : RowRepresentable {
SPREAD, SPREAD,
MIXED; 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 val shortName: String
get() { get() {
return when (this) { return when (this) {
@ -32,7 +48,8 @@ enum class Limit : RowRepresentable {
} }
} }
override fun getDisplayName(): String { override fun getDisplayName(context: Context): String {
return this.longName return this.longName
} }
} }

@ -2,15 +2,11 @@ package net.pokeranalytics.android.model
import android.content.Context import android.content.Context
import io.realm.Realm 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.R
import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.view.Localizable 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 * An enum managing the business objects related to a realm results
@ -22,76 +18,14 @@ enum class LiveData : Localizable {
TOURNAMENT_NAME, TOURNAMENT_NAME,
TOURNAMENT_FEATURE, TOURNAMENT_FEATURE,
TRANSACTION, TRANSACTION,
TRANSACTION_TYPE; TRANSACTION_TYPE,
FILTER,
CUSTOM_FIELD,
REPORT_SETUP;
fun items(realm: Realm, fieldName: String? = null, sortOrder: Sort? = null): RealmResults<*> { var subType:Int? = null
var results = realm.where(this.relatedEntity).findAll().sort(fieldName ?: this.sortingFieldName, sortOrder ?: this.sorting)
if (results.size > 0) { val relatedEntity: Class<out Deletable>
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>
get() { get() {
return when (this) { return when (this) {
BANKROLL -> Bankroll::class.java BANKROLL -> Bankroll::class.java
@ -101,15 +35,14 @@ enum class LiveData : Localizable {
TOURNAMENT_FEATURE -> TournamentFeature::class.java TOURNAMENT_FEATURE -> TournamentFeature::class.java
TRANSACTION -> Transaction::class.java TRANSACTION -> Transaction::class.java
TRANSACTION_TYPE -> TransactionType::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) { fun updateOrCreate(realm: Realm, primaryKey: String?): Deletable {
realm.where(this.relatedEntity).equalTo("id", data.id).findAll().deleteAllFromRealm() val proxyItem: Deletable? = this.getData(realm, primaryKey)
}
fun updateOrCreate(realm: Realm, primaryKey: String?): RealmObject {
val proxyItem: RealmObject? = this.getData(realm, primaryKey)
proxyItem?.let { proxyItem?.let {
return realm.copyFromRealm(it) return realm.copyFromRealm(it)
} ?: run { } ?: run {
@ -117,14 +50,14 @@ enum class LiveData : Localizable {
} }
} }
fun newEntity(): RealmObject { private fun newEntity(): Deletable {
return this.relatedEntity.newInstance() return this.relatedEntity.newInstance()
} }
fun getData(realm: Realm, primaryKey: String?): RealmObject? { fun getData(realm: Realm, primaryKey: String?): Deletable? {
var proxyItem: RealmObject? = null var proxyItem: Deletable? = null
primaryKey?.let { primaryKey?.let {
val t = realm.where(this.relatedEntity).equalTo("id", it).findFirst() val t = realm.findById(this.relatedEntity, it)
t?.let { t?.let {
proxyItem = t proxyItem = t
} }
@ -140,16 +73,67 @@ enum class LiveData : Localizable {
LOCATION -> R.string.location LOCATION -> R.string.location
TOURNAMENT_NAME -> R.string.tournament_name TOURNAMENT_NAME -> R.string.tournament_name
TOURNAMENT_FEATURE -> R.string.tournament_feature 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 -> R.string.operations
TRANSACTION_TYPE -> R.string.operation_types 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
* Return the new entity title 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 titleResId
*/ */
fun newEntityLocalizedTitle(context: Context): String { 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,21 +6,32 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITLE_GRID.ordinal) : RowRepresentable { class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITLE_GRID.ordinal) : RowRepresentable {
companion object { companion object {
val all : List<TableSize>
get() { val all: List<TableSize>
return Array(9, init = get() {
{ index -> TableSize(index + 2) }).toList() 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 if (this.numberOfPlayer == 2) {
return "HU" return "HU"
} else { } else {
"${this.numberOfPlayer}-max" "${this.numberOfPlayer}-max"
} }
} }
override val resId: Int? override val resId: Int?
get() { get() {

@ -1,5 +1,6 @@
package net.pokeranalytics.android.model package net.pokeranalytics.android.model
import android.content.Context
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
@ -13,6 +14,14 @@ enum class TournamentType : RowRepresentable {
get() { get() {
return TournamentType.values() as List<TournamentType> 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? 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) { return when (this) {
MTT -> "MTT" MTT -> "MTT"
SNG -> "SNG" SNG -> "SNG"

@ -64,15 +64,20 @@ class FilterHelper {
Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition) Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition)
Result::class.java -> Result.fieldNameForQueryType(queryCondition) Result::class.java -> Result.fieldNameForQueryType(queryCondition)
else -> { else -> {
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 { fieldName?.let {
return fieldName return fieldName
} ?: run { } ?: run {
throw PokerAnalyticsException.MissingFieldNameForQueryCondition(queryCondition.name) throw PokerAnalyticsException.MissingFieldNameForQueryCondition(queryCondition.name)
} }
*/
} }

@ -1,6 +1,10 @@
package net.pokeranalytics.android.model.filter package net.pokeranalytics.android.model.filter
import android.content.Context
import io.realm.RealmQuery 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> { fun List<Query>.mapFirstCondition() : List<QueryCondition> {
return this.map { it.conditions.first() } return this.map { it.conditions.first() }
@ -38,17 +42,51 @@ class Query {
this._conditions.addAll(queryConditions) this._conditions.addAll(queryConditions)
} }
val name: String val defaultName: String
get() { 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> { inline fun <reified T : Filterable> queryWith(query: RealmQuery<T>): RealmQuery<T> {
var realmQuery = query var realmQuery = query
this.conditions.forEach {
realmQuery = it.queryWith(realmQuery) 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)
}
} }
return 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
} }
fun merge(query: Query) : Query { fun merge(query: Query) : Query {

@ -1,5 +1,6 @@
package net.pokeranalytics.android.model.interfaces package net.pokeranalytics.android.model.interfaces
import android.content.Context
import io.realm.Realm import io.realm.Realm
import io.realm.RealmModel import io.realm.RealmModel
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
@ -12,12 +13,19 @@ enum class SaveValidityStatus {
DATA_INVALID; 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 interface Manageable : Savable, Deletable, Editable
interface NameManageable: Manageable { interface NameManageable : Manageable {
var name: String var name: String
override fun isValidForSave(): Boolean { override fun isValidForSave(): Boolean {
@ -25,26 +33,26 @@ interface NameManageable: Manageable {
} }
override fun alreadyExists(realm: Realm): Boolean { override fun alreadyExists(realm: Realm): Boolean {
return realm.where(this::class.java).equalTo("name", this.name).and().notEqualTo("id", this.id).findAll().isNotEmpty() return realm.where(this::class.java).equalTo("name", this.name).and().notEqualTo("id", this.id).findAll().isNotEmpty()
} }
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled") 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 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 { interface Identifiable : RealmModel {
/** /**
* A unique identifier getter * A unique uniqueIdentifier getter
*/ */
var id: String var id: String
} }
@ -87,7 +95,7 @@ interface Savable : Identifiable {
/** /**
* A method to get the reason why the object can't be saved * A method to get the reason why the object can't be saved
*/ */
fun getFailedSaveMessage(status:SaveValidityStatus): Int fun getFailedSaveMessage(status: SaveValidityStatus): Int
} }
@ -101,8 +109,22 @@ interface Deletable : Identifiable {
*/ */
fun isValidForDelete(realm: Realm): Boolean 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 * 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 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.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import java.util.* import java.util.*
interface Timed : GraphUnderlyingEntry, Identifiable { interface Timed : GraphUnderlyingEntry, Identifiable {

@ -11,7 +11,18 @@ class Patcher {
companion object { 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 realm = Realm.getDefaultInstance()
val sets = realm.where(SessionSet::class.java).findAll() 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,61 +3,51 @@ package net.pokeranalytics.android.model.migrations
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import timber.log.Timber import timber.log.Timber
import java.util.*
import io.realm.RealmObjectSchema
class PokerAnalyticsMigration : RealmMigration { class PokerAnalyticsMigration : RealmMigration {
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
// DynamicRealm exposes an editable schema // DynamicRealm exposes an editable schema
val schema = realm.schema val schema = realm.schema
var currentVersion = oldVersion.toInt() var currentVersion = oldVersion.toInt()
Timber.d("*** migrate from $oldVersion to $newVersion") Timber.d("*** migrate from $oldVersion to $newVersion")
// Migrate to version 1 // Migrate to version 1
if (currentVersion == 0) { if (currentVersion == 0) {
Timber.d("*** Running migration 1") Timber.d("*** Running migration 1")
schema.get("Filter")?.let { schema.get("Filter")?.addField("entityType", Int::class.java)?.setNullable("entityType", true)
it.addField("entityType", Int::class.java).setNullable("entityType", true) schema.get("FilterElement")?.let {
} it.setNullable("filterName", true)
schema.get("FilterElement")?.let { it.setNullable("sectionName", true)
it.setNullable("filterName", true)
it.setNullable("sectionName", true)
}
schema.get("FilterElementBlind")?.let {
it.renameField("code", "currencyCode")
}
currentVersion++
}
// Migrate to version 2
if (currentVersion == 1) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.rename("FilterElement", "FilterCondition")
schema.get("Filter")?.let {
it.renameField("filterElements", "filterConditions")
}
schema.get("SessionSet")?.let {
it.addField("id", String::class.java).setRequired("id", true)
it.addPrimaryKey("id")
}
currentVersion++
}
// Migrate to version 2
if (currentVersion == 2) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.rename("Report", "ReportSetup")
schema.get("Filter")?.let {
it.removeField("entityType")
} }
schema.get("FilterElementBlind")?.renameField("code", "currencyCode")
currentVersion++
}
// Migrate to version 2
if (currentVersion == 1) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.rename("FilterElement", "FilterCondition")
schema.get("Filter")?.renameField("filterElements", "filterConditions")
schema.get("SessionSet")?.let {
it.addField("id", String::class.java).setRequired("id", true)
it.addPrimaryKey("id")
}
currentVersion++
}
// Migrate to version 3
if (currentVersion == 2) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.rename("Report", "ReportSetup")
schema.get("Filter")?.removeField("entityType")
schema.get("Session")?.let { schema.get("Session")?.let {
it.addField("blinds", String::class.java).transform { it.addField("blinds", String::class.java).transform {
@ -74,33 +64,87 @@ class PokerAnalyticsMigration : RealmMigration {
it.addRealmListField("intValues", Integer::class.java) it.addRealmListField("intValues", Integer::class.java)
it.addField("doubleValue", Double::class.java).setNullable("doubleValue", true) it.addField("doubleValue", Double::class.java).setNullable("doubleValue", true)
it.addRealmListField("doubleValues", Double::class.java) it.addRealmListField("doubleValues", Double::class.java)
if(it.isRequired("doubleValues")) { if (it.isRequired("doubleValues")) {
it.setRequired("doubleValues", false) it.setRequired("doubleValues", false)
} }
it.addField("stringValue", String::class.java) it.addField("stringValue", String::class.java)
} }
schema.get("ComputableResult")?.let { schema.get("ComputableResult")?.removeField("sessionSet")
it.removeField("sessionSet")
}
schema.get("Bankroll")?.let { schema.get("Bankroll")?.addField("initialValue", Double::class.java)
it.addField("initialValue", Double::class.java)
}
currentVersion++ currentVersion++
} }
// Migrate to version 3 // Migrate to version 4
if (currentVersion == 3) { if (currentVersion == 3) {
Timber.d("*** Running migration ${currentVersion + 1}") Timber.d("*** Running migration ${currentVersion + 1}")
schema.get("Result")?.let { schema.get("Result")?.addField("numberOfRebuy", Double::class.java)?.setNullable("numberOfRebuy", true)
it.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 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)
}
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++ currentVersion++
} }
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return other is RealmMigration return other is RealmMigration

@ -1,11 +1,14 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
@ -13,66 +16,106 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import java.util.* import java.util.*
open class Bankroll() : RealmObject(), NameManageable, RowRepresentable { open class Bankroll : RealmObject(), NameManageable, RowRepresentable {
@PrimaryKey @PrimaryKey
override var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
override var name: String = "" override var name: String = ""
// Indicates whether the bankroll is live or online // Indicates whether the bankroll is live or online
var live: Boolean = true var live: Boolean = true
// The list of transactions of the bankroll /**
var transactions: RealmList<Transaction> = RealmList() * The list of transactions of the bankroll
*/
// The currency of the bankroll @LinkingObjects("bankroll")
var currency: Currency? = null val transactions: RealmResults<Transaction>? = null
// The initial value of the bankroll // The currency of the bankroll
var initialValue: Double = 0.0 var currency: Currency? = null
val rate: Double // The initial value of the bankroll
get() { var initialValue: Double = 0.0
return this.currency?.rate ?: 1.0
} val rate: Double
get() {
override fun getDisplayName(): String { return this.currency?.rate ?: 1.0
return this.name }
}
override fun getDisplayName(context: Context): String {
override fun updateValue(value: Any?, row: RowRepresentable) { return this.name
when (row) { }
SimpleRow.NAME -> this.name = value as String? ?: ""
BankrollRow.LIVE -> { override fun updateValue(value: Any?, row: RowRepresentable) {
this.live = if (value is Boolean) !value else false when (row) {
} SimpleRow.NAME -> this.name = value as String? ?: ""
BankrollRow.INITIAL_VALUE -> { BankrollRow.LIVE -> {
this.initialValue = value as Double? ?: 0.0 this.live = if (value is Boolean) !value else false
} }
BankrollRow.CURRENCY -> { BankrollRow.INITIAL_VALUE -> {
//TODO handle a use default currency option this.initialValue = value as Double? ?: 0.0
this.currency?.code = value as String? }
} BankrollRow.CURRENCY -> {
BankrollRow.RATE -> { //TODO handle a use default currency option
this.currency?.rate = value as Double? this.currency?.code = value as String?
} }
} BankrollRow.RATE -> {
} this.currency?.rate = value as Double?
}
override fun isValidForDelete(realm: Realm): Boolean { }
}
override fun isValidForDelete(realm: Realm): Boolean {
return realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty() return realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty()
&& realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty()
}
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 {
return when (status) {
SaveValidityStatus.DATA_INVALID -> R.string.empty_name_for_br_error
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_bankroll_name_error
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)
}
}
} }
override fun getFailedDeleteMessage(): Int {
return R.string.bankroll_relationship_error
}
override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
return when (status) {
SaveValidityStatus.DATA_INVALID -> R.string.empty_name_for_br_error
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_bankroll_name_error
else -> super.getFailedSaveMessage(status)
}
}
} }

@ -1,19 +1,313 @@
package net.pokeranalytics.android.model.realm 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.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey 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 java.util.*
import kotlin.collections.ArrayList
open class CustomField : RealmObject() { open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable {
@PrimaryKey /**
var id = UUID.randomUUID().toString() * 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 name of the currency field /**
var name: String = "" * 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)
}
// @todo @PrimaryKey
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
}
override val bottomSheetType: BottomSheetType
get() {
return when (type) {
Type.LIST.uniqueIdentifier -> BottomSheetType.LIST_STATIC
else -> BottomSheetType.NUMERIC_TEXT
}
}
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 package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.* import io.realm.*
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.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 net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import timber.log.Timber
import java.util.* import java.util.*
/** /**
@ -15,14 +23,16 @@ import java.util.*
* It contains a list of [FilterCondition] describing the complete query to launch * 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 * 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 { companion object {
// Create a new instance // Create a new instance
fun newInstance(realm: Realm): Filter { fun newInstance(filterableTypeUniqueIdentifier:Int): Filter {
val filter = Filter() val filter = Filter()
return realm.copyToRealm(filter) filter.filterableTypeUniqueIdentifier = filterableTypeUniqueIdentifier
return filter
//return realm.copyToRealm(filter)
} }
// Get a queryWith by its id // 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> { inline fun <reified T : Filterable> queryOn(realm: Realm, query: Query, sortField: String? = null): RealmResults<T> {
var realmQuery = realm.where<T>() val realmQuery = realm.where<T>()
query.conditions.forEach { sortField?.let {
realmQuery = it.queryWith(realmQuery) return query.queryWith(realmQuery).sort(it).findAll()
} } ?: run {
sortField?.let { return query.queryWith(realmQuery).findAll()
realmQuery.sort(it) }
} }
Timber.d(">>> Filter query: ${realmQuery.description}")
return realmQuery.findAll()
}
} }
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
// the queryWith name // the queryWith name
var name: String = "" var name: String = ""
get() {
if (field.isEmpty()) {
return this.query.defaultName
}
return field
}
// the number of use of the queryWith, override var useCount: Int = 0
// for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0)
var filterConditions: RealmList<FilterCondition> = RealmList() var filterConditions: RealmList<FilterCondition> = RealmList()
private set 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>) { fun createOrUpdateFilterConditions(filterConditionRows: ArrayList<QueryCondition>) {
println("list of querys saving: ${filterConditionRows.map { it.id }}") println("list of querys saving: ${filterConditionRows.map { it.id }}")
println("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}") println("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}")
filterConditionRows filterConditionRows
.map { .map {
it.filterSectionRow it.groupId
} }
.distinct() .distinct()
.forEach { filterName-> .forEach { groupId->
filterConditionRows filterConditionRows
.filter { .filter {
it.filterSectionRow == filterName it.groupId == groupId
} }
.apply { .apply {
@ -76,7 +97,7 @@ open class Filter : RealmObject() {
casted.addAll(this) casted.addAll(this)
val newFilterCondition = FilterCondition(casted) val newFilterCondition = FilterCondition(casted)
val previousCondition = filterConditions.filter { val previousCondition = filterConditions.filter {
it.filterName == newFilterCondition.filterName it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator
} }
filterConditions.removeAll(previousCondition) filterConditions.removeAll(previousCondition)
filterConditions.add(newFilterCondition) filterConditions.add(newFilterCondition)
@ -118,15 +139,40 @@ open class Filter : RealmObject() {
} }
} }
inline fun <reified T : Filterable> results(): RealmResults<T> { inline fun <reified T : Filterable> results(firstField: String? = null, secondField: String? = null): RealmResults<T> {
var realmQuery = realm.where<T>()
this.filterConditions.map { val realmQuery = realm.where<T>()
it.queryCondition
}.forEach { if (firstField != null && secondField != null) {
realmQuery = it.queryWith(realmQuery) return this.query.queryWith(realmQuery).distinct(firstField, secondField).findAll()
} }
if (firstField != null) {
return this.query.queryWith(realmQuery).distinct(firstField).findAll()
}
return realmQuery.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() val row = filterElementRows.first()
this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName
this.operator = row.operator.ordinal this.operator = row.operator.ordinal
if (row is QueryCondition.CustomFieldRelated) {
this.stringValue = row.customFieldId
}
when (row) { when (row) {
is QueryCondition.SingleInt -> this.setValue(row.singleValue) is QueryCondition.SingleInt -> this.setValue(row.singleValue?:throw PokerAnalyticsException.FilterElementExpectedValueMissing)
is QueryCondition.SingleDate -> this.setValue(row.singleValue) is QueryCondition.SingleDate -> this.setValue(row.singleValue?:throw PokerAnalyticsException.FilterElementExpectedValueMissing)
is QueryCondition.ListOfDouble -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfDouble).listOfValues }) 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.ListOfInt -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfInt).listOfValues })
is QueryCondition.ListOfString -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfString).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 var operator: Int? = null
inline fun <reified T:Any > getValues(): ArrayList < T > { inline fun <reified T:Any > getValues(): ArrayList < T > {
println("<<<< r $stringValues")
return when (T::class) { return when (T::class) {
Int::class -> ArrayList<T>().apply { intValues?.map { add(it as T) } } Int::class -> ArrayList<T>().apply { intValues?.map { add(it as T) } }
Double::class -> ArrayList<T>().apply { doubleValues?.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 package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm import io.realm.Realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
@ -47,7 +48,7 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
return this.name return this.name
} }
override fun getDisplayName(): String { override fun getDisplayName(context: Context): String {
return this.name return this.name
} }

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

@ -1,33 +1,98 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.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.* import java.util.*
enum class ReportDisplay {
TABLE,
GRAPH,
MAP
}
open class ReportSetup : RealmObject() { open class ReportSetup : RealmObject(), RowRepresentable, Deletable {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
// The name of the report // The name of the report
var name: String = "" var name: String = ""
// The type of display of the report // 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
/**
* Returns the Options based on the ReportSetup parameters
*/
val options: Calculator.Options
get() {
val realm = Realm.getDefaultInstance()
val stats = this.statIds.map { Stat.valueByIdentifier(it) }
// 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
)
}
// @todo define the configuration options // Deletable
// var criteria: List<Int> = listOf() override fun isValidForDelete(realm: Realm): Boolean {
// var stats: List<Int> = listOf() return true
}
// The filters associated with the report override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
var filters: RealmList<Filter> = RealmList() 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 transactionsSum = transactions.sumByDouble { it.amount }
val isLive = this.session?.bankroll?.live ?: true val isLive = this.session?.isLive ?: true
if (isLive) { if (isLive) {
val buyin = this.buyin ?: 0.0 val buyin = this.buyin ?: 0.0
val cashOut = this.cashout ?: 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.annotations.PrimaryKey
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.* 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.exceptions.ModelException
import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.TableSize import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.TournamentType import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.extensions.SessionState 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.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.fragment.GraphFragment 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.*
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.util.UserDefaults import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.* import net.pokeranalytics.android.util.extensions.*
import java.text.DateFormat import java.text.DateFormat
@ -47,7 +50,20 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
enum class Type { enum class Type {
CASH_GAME, 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 { companion object {
@ -76,19 +92,24 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
AnyTableSize::class.java -> "tableSize" AnyTableSize::class.java -> "tableSize"
AnyTournamentType::class.java -> "tournamentType" AnyTournamentType::class.java -> "tournamentType"
AnyBlind::class.java -> "blinds" AnyBlind::class.java -> "blinds"
NumberOfTable::class.java -> "numberOfTable" NumberOfTable::class.java -> "numberOfTables"
NetAmountWon::class.java, NetAmountLost::class.java -> "computableResults.ratedNet" NetAmountWon::class.java, NetAmountLost::class.java -> "computableResults.ratedNet"
NumberOfRebuy::class.java -> "result.numberOfRebuy" NumberOfRebuy::class.java -> "result.numberOfRebuy"
TournamentNumberOfPlayer::class.java -> "result.tournamentNumberOfPlayers" TournamentNumberOfPlayer::class.java -> "tournamentNumberOfPlayers"
TournamentFinalPosition::class.java -> "result.tournamentFinalPosition" TournamentFinalPosition::class.java -> "result.tournamentFinalPosition"
TournamentFee::class.java -> "tournamentEntryFee" TournamentFee::class.java -> "tournamentEntryFee"
StartedFromDate::class.java, StartedToDate::class.java -> "startDate" StartedFromDate::class.java, StartedToDate::class.java, EndedFromDate::class.java, EndedToDate::class.java -> "startDate"
EndedFromDate::class.java, EndedToDate::class.java -> "endDate"
AnyDayOfWeek::class.java, IsWeekEnd::class.java, IsWeekDay::class.java -> "dayOfWeek" AnyDayOfWeek::class.java, IsWeekEnd::class.java, IsWeekDay::class.java -> "dayOfWeek"
AnyMonthOfYear::class.java -> "month" AnyMonthOfYear::class.java -> "month"
AnyYear::class.java -> "year" 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"
else -> null 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 year: Int? = null
override var dayOfMonth: 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 * The start date of the session
*/ */
var startDate: Date? = null var startDate: Date? = null
set(value) { set(value) {
field = value field = value
if (field == null) {
startDateHourMinuteComponent = null
} else {
val cal = Calendar.getInstance()
cal.time = field
startDateHourMinuteComponent = cal.hourMinute()
}
this.updateTimeParameter(field) this.updateTimeParameter(field)
this.computeNetDuration() this.computeNetDuration()
@ -142,6 +190,14 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
var endDate: Date? = null var endDate: Date? = null
set(value) { set(value) {
field = value field = value
if (field == null) {
endDateHourMinuteComponent = null
} else {
val cal = Calendar.getInstance()
cal.time = field
endDateHourMinuteComponent = cal.hourMinute()
}
this.computeNetDuration() this.computeNetDuration()
this.dateChanged() this.dateChanged()
this.defineDefaultTournamentBuyinIfNecessary() this.defineDefaultTournamentBuyinIfNecessary()
@ -224,7 +280,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
field = value field = value
this.computeStats() this.computeStats()
formatBlinds() formatBlinds()
this.result?.computeNumberOfRebuy() this.result?.computeNumberOfRebuy()
} }
var blinds: String? = null var blinds: String? = null
@ -234,10 +290,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The entry fee of the tournament // The entry fee of the tournament
var tournamentEntryFee: Double? = null var tournamentEntryFee: Double? = null
set(value) { set(value) {
field = value field = value
this.result?.computeNumberOfRebuy() this.result?.computeNumberOfRebuy()
} }
// The total number of players who participated in the tournament // The total number of players who participated in the tournament
var tournamentNumberOfPlayers: Int? = null var tournamentNumberOfPlayers: Int? = null
@ -251,6 +307,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The features of the tournament, like Knockout, Shootout, Turbo... // The features of the tournament, like Knockout, Shootout, Turbo...
var tournamentFeatures: RealmList<TournamentFeature> = RealmList() var tournamentFeatures: RealmList<TournamentFeature> = RealmList()
// The custom fields values
var customFieldEntries: RealmList<CustomFieldEntry> = RealmList()
fun bankrollHasBeenUpdated() { fun bankrollHasBeenUpdated() {
formatBlinds() formatBlinds()
} }
@ -344,7 +403,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
/** /**
* Pre-compute various stats * Pre-compute various statIds
*/ */
fun computeStats() { fun computeStats() {
@ -373,8 +432,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
val numberOfHandsPerHour: Double val numberOfHandsPerHour: Double
get() { get() {
val tableSize = this.tableSize ?: 9 // 9 is the default table size if null val tableSize = this.tableSize ?: 9 // 9 is the default table size if null
val isLive = this.bankroll?.live ?: true val playerHandsPerHour = if (this.isLive) LIVE_PLAYER_HANDS_PER_HOUR else ONLINE_PLAYER_HANDS_PER_HOUR
val playerHandsPerHour = if (isLive) LIVE_PLAYER_HANDS_PER_HOUR else ONLINE_PLAYER_HANDS_PER_HOUR
return playerHandsPerHour / tableSize.toDouble() return playerHandsPerHour / tableSize.toDouble()
} }
@ -391,6 +449,24 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return this.bbNet / this.hourlyDuration 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 // Manageable
override fun isValidForSave(): Boolean { 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 * Example: NL Holdem
*/ */
fun getFormattedGame(): String { fun getFormattedGame(): String {
@ -532,7 +608,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return blinds ?: NULL_TEXT return blinds ?: NULL_TEXT
} }
fun formatBlinds() { fun formatBlinds() {
blinds = null blinds = null
if (cgBigBlind == null) return if (cgBigBlind == null) return
cgBigBlind?.let { bb -> cgBigBlind?.let { bb ->
@ -545,12 +621,13 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
/** /**
* Delete the object from realm * Delete the object from realm
* @TODO: Cascade delete?
*/ */
fun delete() { fun delete() {
realm.executeTransaction { if (isValid) {
cleanup() realm.executeTransaction {
deleteFromRealm() cleanup()
deleteFromRealm()
}
} }
} }
@ -572,7 +649,17 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
@Ignore @Ignore
override val viewType: Int = RowViewType.ROW_SESSION.ordinal 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}" return "Session ${this.creationDate}"
} }
@ -637,6 +724,13 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// Rows // Rows
rows.addAll(SessionRow.getRows(this)) rows.addAll(SessionRow.getRows(this))
// Add custom fields
realm?.let {
rows.add(SeparatorRow())
rows.addAll(it.sorted<CustomField>())
}
return rows return rows
} }
@ -670,10 +764,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
SessionRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT SessionRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT
SessionRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: NULL_TEXT SessionRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: NULL_TEXT
SessionRow.TIPS -> result?.tips?.toCurrency(currency) ?: NULL_TEXT SessionRow.TIPS -> result?.tips?.toCurrency(currency) ?: NULL_TEXT
SessionRow.TOURNAMENT_TYPE -> this.tournamentType?.let { SessionRow.TOURNAMENT_TYPE -> {
TournamentType.values()[it].localizedTitle(context) this.tournamentType?.let {
} ?: run { TournamentType.values()[it].localizedTitle(context)
NULL_TEXT } ?: run {
NULL_TEXT
}
} }
SessionRow.TOURNAMENT_FEATURE -> { SessionRow.TOURNAMENT_FEATURE -> {
if (tournamentFeatures.size > 2) { if (tournamentFeatures.size > 2) {
@ -689,7 +785,13 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
} }
SessionRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT 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( SessionRow.BANKROLL -> row.editingDescriptors(
mapOf( mapOf(
"defaultValue" to this.bankroll, "defaultValue" to this.bankroll,
"data" to LiveData.BANKROLL.items(realm) "data" to realm.sorted<Bankroll>() // LiveData.Bankroll.items(realm)
) )
) )
SessionRow.GAME -> row.editingDescriptors( SessionRow.GAME -> row.editingDescriptors(
mapOf( mapOf(
"limit" to this.limit, "limit" to this.limit,
"defaultValue" to this.game, "defaultValue" to this.game,
"data" to LiveData.GAME.items(realm) "data" to realm.sorted<Game>() //LiveData.Game.items(realm)
) )
) )
SessionRow.LOCATION -> row.editingDescriptors( SessionRow.LOCATION -> row.editingDescriptors(
mapOf( mapOf(
"defaultValue" to this.location, "defaultValue" to this.location,
"data" to LiveData.LOCATION.items(realm) "data" to realm.sorted<Location>() // LiveData.Location.items(realm)
) )
) )
SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors( SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(
mapOf( mapOf(
"defaultValue" to this.tournamentFeatures, "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( SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(
mapOf( mapOf(
"defaultValue" to this.tournamentName, "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( SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors(
@ -796,6 +898,19 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
"tips" to result?.tips "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 else -> null
} }
} }
@ -905,7 +1020,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
result = localResult result = localResult
} }
SessionRow.TOURNAMENT_NAME -> tournamentName = value as TournamentName? 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 -> { SessionRow.TOURNAMENT_FEATURE -> {
value?.let { value?.let {
@ -915,6 +1030,28 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
tournamentFeatures.removeAll(this.tournamentFeatures) 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,10 +1059,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// Stat Entry // Stat Entry
override val entryTitle: String override fun entryTitle(context: Context): String {
get() { return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate)
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate) }
}
override fun formattedValue(stat: Stat): TextFormat { override fun formattedValue(stat: Stat): TextFormat {
@ -962,7 +1098,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
} ?: run { } ?: run {
throw java.lang.IllegalStateException("Asking for stats on Session without Result") throw java.lang.IllegalStateException("Asking for statIds on Session without Result")
} }
} }
@ -973,16 +1109,14 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
style: GraphFragment.Style, style: GraphFragment.Style,
groupName: String, groupName: String,
context: Context context: Context
) : LegendContent { ): LegendContent {
when (style) { when (style) {
GraphFragment.Style.MULTILINE -> { GraphFragment.Style.MULTILINE -> {
val secondTitle = stat.localizedTitle(context) val secondTitle = stat.localizedTitle(context)
val entryValue = this.formattedValue(stat) val entryValue = this.formattedValue(stat)
val dateValue = TextFormat(this.entryTitle) val dateValue = TextFormat(this.entryTitle(context))
return MultilineLegendValues(groupName, secondTitle, entryValue, dateValue) 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 -> { else -> {
super.legendValues(stat, entry, style, groupName, context) super.legendValues(stat, entry, style, groupName, context)

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

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

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

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

@ -1,26 +1,36 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import android.content.Context
import com.github.mikephil.charting.data.Entry
import io.realm.Realm import io.realm.Realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.DatedValue import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.model.interfaces.Manageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource 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.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionRow 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 java.util.*
import kotlin.collections.ArrayList 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 { companion object {
val rowRepresentation: List<RowRepresentable> by lazy { val rowRepresentation: List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
rows.addAll(TransactionRow.values()) rows.addAll(TransactionRow.values())
@ -29,13 +39,23 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
fun fieldNameForQueryType(queryCondition: Class<out QueryCondition>): String? { fun fieldNameForQueryType(queryCondition: Class<out QueryCondition>): String? {
return when (queryCondition) { return when (queryCondition) {
QueryCondition.AnyBankroll::class.java -> "bankroll.id"
QueryCondition.StartedFromDate::class.java, QueryCondition.StartedToDate::class.java -> "date" QueryCondition.AnyBankroll::class.java -> "bankroll.id"
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 else -> null
} }
} }
} }
init {
this.updateTimeParameter(Date())
}
@PrimaryKey @PrimaryKey
override var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
@ -47,6 +67,10 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
// The date of the transaction // The date of the transaction
override var date: Date = Date() override var date: Date = Date()
set(value) {
field = value
this.updateTimeParameter(field)
}
// The type of the transaction // The type of the transaction
var type: TransactionType? = null var type: TransactionType? = null
@ -54,6 +78,12 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
// A user comment // A user comment
var comment: String = "" 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 @Ignore
override val viewType: Int = RowViewType.ROW_TRANSACTION.ordinal 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 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 { override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
return if (bankroll == null) { return if (bankroll == null) {
R.string.no_br_popup_message 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 { 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(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 getFailedDeleteMessage(): Int { override fun legendValues(
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. 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 package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm import io.realm.Realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey 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.model.interfaces.NameManageable
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource 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.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
@ -15,10 +19,19 @@ import kotlin.collections.ArrayList
open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable { open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable {
enum class Value(val additive: Boolean) { enum class Value(val additive: Boolean) : Localizable {
WITHDRAWAL(false), WITHDRAWAL(false),
DEPOSIT(true), 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 { companion object {
@ -42,10 +55,15 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
@PrimaryKey @PrimaryKey
override var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
// The name of the transaction type /**
* The name of the transaction type
*/
override var name: String = "" 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 var additive: Boolean = false
// Whether or not the type can be deleted by the user // 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 // The predefined kind, if necessary, like: Withdrawal, deposit, or tips
var kind: Int? = null var kind: Int? = null
override fun getDisplayName(): String { override fun getDisplayName(context: Context): String {
return this.name 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>? { override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf("defaultValue" to this.name)) 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) { override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) { when (row) {
SimpleRow.NAME -> this.name = value as String? ?: "" SimpleRow.NAME -> this.name = value as String? ?: ""
TransactionTypeRow.TRANSACTION_ADDITIVE -> this.additive = value as Boolean? ?: false
} }
} }
override fun isValidForDelete(realm: Realm): Boolean { 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 { override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. return R.string.transaction_relationship_error
} }
} }

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

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

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

@ -7,9 +7,7 @@ import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet import net.pokeranalytics.android.model.realm.SessionSet
import kotlin.math.max 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, * The manager is in charge of updating the abstract concept of timeline,
@ -169,7 +167,7 @@ class SessionSetManager {
sessionSet.deleteFromRealm() sessionSet.deleteFromRealm()
sessions.forEach { 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.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import io.realm.RealmResults
import net.pokeranalytics.android.R 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 import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
class BankrollActivity : PokerAnalyticsActivity() { class BankrollActivity : PokerAnalyticsActivity() {
private lateinit var computableResults: RealmResults<ComputableResult>
private lateinit var bankrolls: RealmResults<Bankroll>
private lateinit var transactions: RealmResults<Transaction>
companion object { companion object {
fun newInstance(context: Context) { fun newInstance(context: Context) {
val intent = Intent(context, BankrollActivity::class.java) val intent = Intent(context, BankrollActivity::class.java)
@ -28,6 +36,55 @@ class BankrollActivity : PokerAnalyticsActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bankroll) 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.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_data_list.* import kotlinx.android.synthetic.main.activity_data_list.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.DataListFragment import net.pokeranalytics.android.ui.fragment.DataListFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
class DataListActivity : PokerAnalyticsActivity() { class DataListActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) { enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"), 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 { companion object {
fun newInstance(context: Context, dataType: Int) { 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) val intent = Intent(context, DataListActivity::class.java)
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType) 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 * Init UI
*/ */
private fun initUI() { private fun initUI() {
val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0) val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0)
val showAddButton = intent.getBooleanExtra(IntentKey.SHOW_ADD_BUTTON.keyName, true)
val fragment = dataListFragment as DataListFragment val fragment = dataListFragment as DataListFragment
fragment.setData(dataType) fragment.setData(dataType)
} fragment.updateUI(showAddButton)
/**
* Init data
*/
private fun initData() {
} }
} }

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

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

@ -7,33 +7,37 @@ import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.FiltersFragment import net.pokeranalytics.android.ui.fragment.FiltersFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType
class FiltersActivity : PokerAnalyticsActivity() { class FiltersActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) { 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 private lateinit var fragment: FiltersFragment
companion object { 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) 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 { filterId?.let {
intent.putExtra(IntentKey.FILTER_ID.keyName, it) intent.putExtra(IntentKey.FILTER_ID.keyName, it)
} }
context.startActivity(intent) return 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)
} }
} }
@ -55,11 +59,15 @@ class FiltersActivity : PokerAnalyticsActivity() {
val fragmentManager = supportFragmentManager val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction() val fragmentTransaction = fragmentManager.beginTransaction()
val filterId = intent.getStringExtra(IntentKey.FILTER_ID.keyName) 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() fragment = FiltersFragment()
fragmentTransaction.add(R.id.container, fragment) fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit() 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.app.KeyguardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle 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 com.google.android.material.bottomnavigation.BottomNavigationView
import io.realm.RealmResults import io.realm.RealmResults
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Currency 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.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.adapter.HomePagerAdapter
import net.pokeranalytics.android.ui.extensions.showAlertDialog
import net.pokeranalytics.android.util.billing.AppGuard
import timber.log.Timber import timber.log.Timber
@ -30,24 +31,11 @@ class HomeActivity : PokerAnalyticsActivity() {
} }
} }
private var homeMenu: Menu? = null
private lateinit var currencies: RealmResults<Currency> private lateinit var currencies: RealmResults<Currency>
private var homePagerAdapter: HomePagerAdapter? = null
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item -> private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) { 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 -> { R.id.navigation_history -> {
displayFragment(0) displayFragment(0)
} }
@ -70,6 +58,11 @@ class HomeActivity : PokerAnalyticsActivity() {
return@OnNavigationItemSelectedListener true return@OnNavigationItemSelectedListener true
} }
override fun onResume() {
super.onResume()
AppGuard.requestPurchasesUpdate()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -87,22 +80,41 @@ class HomeActivity : PokerAnalyticsActivity() {
observeRealmObjects() observeRealmObjects()
initUI() initUI()
checkFirstLaunch() checkFirstLaunch()
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onNewIntent(intent: Intent?) {
menu?.clear() super.onNewIntent(intent)
menuInflater.inflate(R.menu.toolbar_home, menu)
this.homeMenu = menu intent?.let {
//TODO: Change queryWith button visibility
homeMenu?.findItem(R.id.filter)?.isVisible = false when (intent.action) {
return super.onCreateOptionsMenu(menu) "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 { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (item?.itemId) { super.onActivityResult(requestCode, resultCode, data)
R.id.filter -> manageFilters()
when (requestCode) {
RequestCode.IMPORT.value -> {
if (resultCode == ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) {
showAlertDialog(context = this, message = R.string.unknown_import_format_popup_message)
}
}
} }
return super.onOptionsItemSelected(item)
} }
private fun observeRealmObjects() { private fun observeRealmObjects() {
@ -112,14 +124,12 @@ class HomeActivity : PokerAnalyticsActivity() {
// observe currency changes // observe currency changes
this.currencies = realm.where(Currency::class.java).findAll() this.currencies = realm.where(Currency::class.java).findAll()
this.currencies.addChangeListener { t, _ -> this.currencies.addChangeListener { t, _ ->
realm.executeTransaction {
realm.beginTransaction() t.forEach {
t.forEach { it.refreshRelatedRatedValues()
it.refreshRelatedRatedValues() }
} }
realm.commitTransaction()
} }
} }
/** /**
@ -127,17 +137,13 @@ class HomeActivity : PokerAnalyticsActivity() {
*/ */
private fun initUI() { private fun initUI() {
setSupportActionBar(toolbar)
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener) navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
navigation.selectedItemId = R.id.navigation_history navigation.selectedItemId = R.id.navigation_history
val homePagerAdapter = HomePagerAdapter(supportFragmentManager) homePagerAdapter = HomePagerAdapter(supportFragmentManager)
viewPager.offscreenPageLimit = 5 viewPager.offscreenPageLimit = 5
viewPager.enablePaging = false viewPager.enablePaging = false
viewPager.adapter = homePagerAdapter viewPager.adapter = homePagerAdapter
updateToolbar(0)
} }
/** /**
@ -159,83 +165,16 @@ class HomeActivity : PokerAnalyticsActivity() {
*/ */
private fun displayFragment(index: Int) { private fun displayFragment(index: Int) {
viewPager.setCurrentItem(index, false) 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
}
*/
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
}
}
} }
/** // Import
* Manage filters
*/
private fun manageFilters() {
val filterSelected = false
val choices = ArrayList<CharSequence>()
choices.add(getString(R.string.new_str))
if (filterSelected) { private fun requestImportConfirmation(uri: Uri) {
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) showAlertDialog(context = this, title = R.string.import_confirmation, showCancelButton = true, positiveAction = {
builder.setTitle(R.string.filter_selection) ImportActivity.newInstanceForResult(this, uri)
.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")
}
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 package net.pokeranalytics.android.ui.activity
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_session.* import kotlinx.android.synthetic.main.activity_session.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
@ -16,6 +18,7 @@ class SessionActivity: PokerAnalyticsActivity() {
} }
companion object { companion object {
fun newInstance(context: Context, isTournament: Boolean? = false, sessionId: String? = "") { fun newInstance(context: Context, isTournament: Boolean? = false, sessionId: String? = "") {
val intent = Intent(context, SessionActivity::class.java) val intent = Intent(context, SessionActivity::class.java)
isTournament?.let { isTournament?.let {
@ -27,6 +30,17 @@ class SessionActivity: PokerAnalyticsActivity() {
context.startActivity(intent) 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?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -37,6 +51,7 @@ class SessionActivity: PokerAnalyticsActivity() {
} }
override fun onBackPressed() { override fun onBackPressed() {
setResult(Activity.RESULT_OK)
super.onBackPressed() super.onBackPressed()
(sessionFragment as SessionFragment).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 package net.pokeranalytics.android.ui.activity.components
import android.Manifest
import android.Manifest.permission.ACCESS_FINE_LOCATION import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle
import android.os.PersistableBundle
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.crashlytics.android.Crashlytics
import com.google.android.libraries.places.api.model.PlaceLikelihood import com.google.android.libraries.places.api.model.PlaceLikelihood
import io.realm.Realm import io.realm.Realm
import net.pokeranalytics.android.model.realm.Location import net.pokeranalytics.android.model.realm.Location
@ -18,18 +20,43 @@ open class PokerAnalyticsActivity : AppCompatActivity() {
companion object { companion object {
const val PERMISSION_REQUEST_ACCESS_FINE_LOCATION = 1000 const val PERMISSION_REQUEST_ACCESS_FINE_LOCATION = 1000
const val PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 1001
const val PLAY_SERVICES_RESOLUTION_REQUEST = 2000 const val PLAY_SERVICES_RESOLUTION_REQUEST = 2000
} }
private var realm: Realm? = null private var realm: Realm? = null
private var permissionCallback: ((granted: Boolean) -> Unit)? = 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) { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) { when (requestCode) {
PERMISSION_REQUEST_ACCESS_FINE_LOCATION -> { 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 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
) { ) {
permissionCallback?.invoke(true) permissionCallback?.invoke(true)
@ -53,11 +80,6 @@ open class PokerAnalyticsActivity : AppCompatActivity() {
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
override fun onDestroy() {
super.onDestroy()
this.realm?.close()
}
/** /**
* Return the realm instance * 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.R
import net.pokeranalytics.android.ui.fragment.CalendarFragment import net.pokeranalytics.android.ui.fragment.CalendarFragment
import net.pokeranalytics.android.ui.fragment.GraphFragment 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 net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -24,7 +24,7 @@ class ComparisonChartPagerAdapter(val context: Context, fragmentManager: Fragmen
0 -> GraphFragment() 0 -> GraphFragment()
1 -> GraphFragment() 1 -> GraphFragment()
2 -> CalendarFragment.newInstance() 2 -> CalendarFragment.newInstance()
else -> HistoryFragment.newInstance() else -> FeedFragment.newInstance()
} }
} }

@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.realm.RealmResults 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.R
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.view.BindableHolder import net.pokeranalytics.android.ui.view.BindableHolder
@ -24,7 +24,7 @@ import kotlin.collections.HashMap
* @param dataSource the datasource providing rows * @param dataSource the datasource providing rows
* @param delegate the delegate, notified of UI actions * @param delegate the delegate, notified of UI actions
*/ */
class HistorySessionRowRepresentableAdapter( class FeedSessionRowRepresentableAdapter(
var delegate: RowRepresentableDelegate? = null, var delegate: RowRepresentableDelegate? = null,
var realmResults: RealmResults<Session>, var realmResults: RealmResults<Session>,
var pendingRealmResults: RealmResults<Session>, var pendingRealmResults: RealmResults<Session>,
@ -43,7 +43,7 @@ class HistorySessionRowRepresentableAdapter(
* Display a session view * Display a session view
*/ */
inner class RowSessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { 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) itemView.sessionRow.setData(row as Session)
val listener = View.OnClickListener { val listener = View.OnClickListener {
@ -57,7 +57,7 @@ class HistorySessionRowRepresentableAdapter(
* Display a session view * Display a session view
*/ */
inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, title: String, adapter: HistorySessionRowRepresentableAdapter) { fun bind(title: String) {
// Title // Title
itemView.findViewById<AppCompatTextView>(R.id.title)?.let { itemView.findViewById<AppCompatTextView>(R.id.title)?.let {
it.text = title it.text = title
@ -66,17 +66,16 @@ class HistorySessionRowRepresentableAdapter(
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == RowViewType.ROW_SESSION.ordinal) { return if (viewType == RowViewType.ROW_SESSION.ordinal) {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_history_session, parent, false) val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_feed_session, parent, false)
return RowSessionViewHolder(layout) RowSessionViewHolder(layout)
} else { } else {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_header_title, parent, false) 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 { override fun getItemViewType(position: Int): Int {
if (sortedHeaders.containsKey(position)) { if (sortedHeaders.containsKey(position)) {
return RowViewType.HEADER_TITLE.ordinal return RowViewType.HEADER_TITLE.ordinal
@ -93,7 +92,7 @@ class HistorySessionRowRepresentableAdapter(
if (holder is RowSessionViewHolder) { if (holder is RowSessionViewHolder) {
holder.bind(position, getSessionForPosition(position), this) holder.bind(position, getSessionForPosition(position), this)
} else if (holder is HeaderTitleViewHolder) { } 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 { override fun getItem(position: Int): PokerAnalyticsFragment {
return when (position) { return when (position) {
0 -> HistoryFragment.newInstance() 0 -> FeedFragment.newInstance()
1 -> StatisticsFragment.newInstance() 1 -> StatisticsFragment.newInstance()
2 -> CalendarFragment.newInstance() 2 -> CalendarFragment.newInstance()
3 -> ReportsFragment.newInstance() 3 -> ReportsFragment.newInstance()
4 -> SettingsFragment.newInstance() // MoreFragment.newInstance() 4 -> MoreFragment.newInstance()
else -> HistoryFragment.newInstance() else -> FeedFragment.newInstance()
} }
} }
@ -43,7 +43,7 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
override fun getItemPosition(obj: Any): Int { override fun getItemPosition(obj: Any): Int {
return when (obj) { return when (obj) {
HistoryFragment::class.java -> 0 FeedFragment::class.java -> 0
StatisticsFragment::class.java -> 1 StatisticsFragment::class.java -> 1
CalendarFragment::class.java -> 2 CalendarFragment::class.java -> 2
ReportsFragment::class.java -> 3 ReportsFragment::class.java -> 3

@ -9,7 +9,7 @@ import androidx.viewpager.widget.PagerAdapter
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Report import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.ui.fragment.GraphFragment 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 net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import java.lang.ref.WeakReference 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) GraphFragment.newInstance(lineDataSets = dataSetList, style = GraphFragment.Style.MULTILINE)
} }
2 -> { 2 -> {
TableReportFragment.newInstance(report) ComposableTableReportFragment.newInstance(report)
} }
else -> PokerAnalyticsFragment() else -> PokerAnalyticsFragment()
} }

@ -10,6 +10,7 @@ import net.pokeranalytics.android.ui.view.RowViewType
interface RowRepresentableDelegate { interface RowRepresentableDelegate {
fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean = false) {} fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean = false) {}
fun onRowValueChanged(value: Any?, row: RowRepresentable) {} fun onRowValueChanged(value: Any?, row: RowRepresentable) {}
fun onRowDeleted(row: RowRepresentable) {}
} }
/** /**
@ -47,16 +48,20 @@ class RowRepresentableAdapter(
*/ */
fun refreshRow(row: RowRepresentable) { 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 // 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 return
} }
val index = this.dataSource.indexForRow(row) val rows = this.dataSource.adapterRows()
val index = rows?.indexOf(row) ?: -1
if (index >= 0) { if (index >= 0) {
notifyItemChanged(index) notifyItemChanged(index)
} }
//notifyDataSetChanged()
} }
/** /**

@ -1,9 +1,9 @@
package net.pokeranalytics.android.ui.adapter package net.pokeranalytics.android.ui.adapter
import android.content.Context import android.content.Context
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.util.TextFormat
/** /**
* Base Interface to provide the RowRepresentable to the adapter * Base Interface to provide the RowRepresentable to the adapter
@ -30,8 +30,6 @@ interface RowRepresentableDataSource: EditableDataSource, DisplayableDataSource,
*/ */
fun viewTypeForPosition(position:Int): Int 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") 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")
}
} }
@ -85,11 +77,11 @@ interface LiveRowRepresentableDataSource: RowRepresentableDataSource {
* Custom class providing the value to display and how to display them * Custom class providing the value to display and how to display them
*/ */
class DisplayDescriptor( class DisplayDescriptor(
var boolValue: Boolean? = null, var boolValue: Boolean? = null,
var stringValue: String? = null, var stringValue: String? = null,
var textFormat: TextFormat? = null, var textFormat: TextFormat? = null,
var actionIcon: Int? = null, var actionIcon: Int? = null,
var context: Context? = null) { var context: Context? = null) {
} }
class UnmanagedRowRepresentableException(message: String) : Exception(message) { class UnmanagedRowRepresentableException(message: String) : Exception(message) {
@ -118,6 +110,13 @@ interface DisplayableDataSource {
return false return false
} }
/**
* Returns a int for a specific row
*/
fun intForRow(row: RowRepresentable): Int {
return -1
}
/** /**
* Returns a localized string for a specific row * 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? { fun actionIconForRow(row: RowRepresentable): Int? {
return 0 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.Intent
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri import android.net.Uri
import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@ -15,18 +16,15 @@ import androidx.core.content.FileProvider
import androidx.core.view.isVisible import androidx.core.view.isVisible
import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.util.DeviceUtils import net.pokeranalytics.android.util.DeviceUtils
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.util.URL import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.billing.AppGuard
import java.io.File import java.io.File
// Sizes // Sizes
val Int.dp: Int val Int.dp: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt() get() = (this / Resources.getSystem().displayMetrics.density).toInt()
@ -69,8 +67,9 @@ fun PokerAnalyticsActivity.openPlayStorePage() {
} }
// Open email for "Contact us" // Open email for "Contact us"
fun PokerAnalyticsActivity.openContactMail(subjectStringRes: Int, filePath: String?= null) { 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) val emailIntent = Intent(Intent.ACTION_SEND)
@ -104,6 +103,7 @@ fun PokerAnalyticsActivity.openUrl(url: String) {
fun PokerAnalyticsActivity.showAlertDialog(title: Int? = null, message: Int? = null) { fun PokerAnalyticsActivity.showAlertDialog(title: Int? = null, message: Int? = null) {
showAlertDialog(this, title, message) showAlertDialog(this, title, message)
} }
fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = null) { fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = null) {
context?.let { context?.let {
showAlertDialog(it, title, message) showAlertDialog(it, title, message)
@ -113,7 +113,10 @@ fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = n
/** /**
* Create and show an alert dialog * 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) val builder = AlertDialog.Builder(context)
title?.let { title?.let {
builder.setTitle(title) builder.setTitle(title)
@ -121,7 +124,19 @@ fun showAlertDialog(context: Context, title: Int? = null, message: Int? = null)
message?.let { message?.let {
builder.setMessage(message) 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() builder.show()
} }
@ -141,3 +156,8 @@ fun View.showWithAnimation() {
animate().cancel() animate().cancel()
animate().alpha(1f).start() 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,72 +1,228 @@
package net.pokeranalytics.android.ui.fragment package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager 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_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.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator
class BankrollFragment : PokerAnalyticsFragment() { 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
*/
fun newInstance(): BankrollFragment {
val fragment = BankrollFragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
}
}
companion object { 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>
* Create new instance
*/
fun newInstance(): BankrollFragment {
val fragment = BankrollFragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
}
}
private lateinit var parentActivity: PokerAnalyticsActivity override fun deletableItems() : List<Deletable> {
return this.bankrolls
}
// Life Cycle // Life Cycle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_bankroll, container, false) 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)
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
/**
* Init data
*/
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()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // Graph
super.onViewCreated(view, savedInstanceState) val globalBankrollReportSetup = BankrollReportSetup()
initData() val globalBankrollReport = BankrollCalculator.computeReport(getRealm(), globalBankrollReportSetup)
initUI() 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))
// Business Timber.d("initData: ${System.currentTimeMillis() - startDate.time}ms")
/** // val bankrolls = LiveData.Bankroll.items(getRealm()) as RealmResults<Bankroll>
* Init data
*/
private fun initData() {
} 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()
}
}
}
}
/** /**
* Init UI * Init UI
*/ */
private fun initUI() { private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity setDisplayHomeAsUpEnabled(true)
parentActivity.setSupportActionBar(toolbar) dataListAdapter = RowRepresentableAdapter(this, this)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
val viewManager = LinearLayoutManager(requireContext()) val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply { recyclerView.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = viewManager 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.calculus.Stat
import net.pokeranalytics.android.model.filter.Query import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.ui.activity.StatisticDetailsActivity import net.pokeranalytics.android.ui.activity.ProgressReportActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource 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 lateinit var statsAdapter: RowRepresentableAdapter
private var title: String? = "" private var title: String? = ""
@ -80,14 +78,7 @@ class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
*/ */
private fun initUI() { private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity setDisplayHomeAsUpEnabled(true)
// Avoid a bug during setting the title
toolbar.title = ""
parentActivity.setSupportActionBar(toolbar)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
var tabIndexToSelect = 0 var tabIndexToSelect = 0
sessionTypeCondition?.let { sessionTypeCondition?.let {
@ -134,11 +125,7 @@ class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
* Display data * Display data
*/ */
private fun displayData() { private fun displayData() {
setToolbarTitle(title)
title?.let {
toolbar.title = it
}
} }
// StaticRowRepresentableDataSource // StaticRowRepresentableDataSource
@ -150,8 +137,12 @@ class CalendarDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) { when (row) {
is GraphRow -> { is GraphRow -> {
row.report.results.firstOrNull()?.group?.let { computableGroup -> val report = row.report
StatisticDetailsActivity.newInstance(requireContext(), row.stat, computableGroup, row.report, false, row.title) 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 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 options = Calculator.Options(
val report = Calculator.computeStatsWithCriterias(realm, listOf(), query, 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") Timber.d("Report take: ${System.currentTimeMillis() - startDate.time}ms")
report.results.firstOrNull()?.let { report.results.firstOrNull()?.let {
// Create rows // 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.clear()
rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.net_result)) 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.NET_RESULT), it.computedStat(Stat.HOURLY_RATE)))
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.LOCATIONS_PLAYED), it.computedStat(Stat.LONGEST_STREAKS))) 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(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(StatDoubleRow(it.computedStat(Stat.WIN_RATIO), it.computedStat(Stat.MAXIMUM_NETRESULT)))
rowRepresentables.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.volume)) 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.HOURLY_DURATION), it.computedStat(Stat.AVERAGE_HOURLY_DURATION)))
rowRepresentables.add(StatDoubleRow(it.computedStat(Stat.DAYS_PLAYED), it.computedStat(Stat.MAXIMUM_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 androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import io.realm.Realm import io.realm.Realm
import io.realm.RealmModel
import kotlinx.android.synthetic.main.fragment_calendar.* import kotlinx.android.synthetic.main.fragment_calendar.*
import kotlinx.android.synthetic.main.fragment_stats.recyclerView
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputedResults import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.filter.QueryCondition 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.activity.CalendarDetailsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.extensions.hideWithAnimation import net.pokeranalytics.android.ui.extensions.hideWithAnimation
import net.pokeranalytics.android.ui.extensions.showWithAnimation 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.CalendarTabs
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
@ -36,7 +38,8 @@ import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRepresentableDataSource, RowRepresentableDelegate { class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentableDataSource,
RowRepresentableDelegate {
enum class TimeFilter { enum class TimeFilter {
MONTH, YEAR MONTH, YEAR
@ -73,7 +76,8 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
// Life Cycle // Life Cycle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -94,7 +98,12 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
TimeFilter.MONTH -> { TimeFilter.MONTH -> {
val date = datesForRows[row] val date = datesForRows[row]
sortedMonthlyReports[datesForRows[row]]?.let { sortedMonthlyReports[datesForRows[row]]?.let {
CalendarDetailsActivity.newInstance(requireContext(), it, sessionTypeCondition, date?.getMonthAndYear()) CalendarDetailsActivity.newInstance(
requireContext(),
it,
sessionTypeCondition,
date?.getMonthAndYear()
)
} }
} }
TimeFilter.YEAR -> { 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() launchStatComputation()
} }
@ -235,8 +246,8 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
val monthlyReports: HashMap<Date, ComputedResults> = HashMap() val monthlyReports: HashMap<Date, ComputedResults> = HashMap()
val yearlyReports: 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 requiredStats: List<Stat> =
val options = Calculator.Options(evolutionValues = Calculator.Options.EvolutionValues.STANDARD, stats = requiredStats) listOf(Stat.LOCATIONS_PLAYED, Stat.LONGEST_STREAKS, Stat.DAYS_PLAYED, Stat.STANDARD_DEVIATION_HOURLY)
// Compute data per AnyYear and AnyMonthOfYear // Compute data per AnyYear and AnyMonthOfYear
@ -249,15 +260,23 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
} }
monthlyQueries.forEach { query -> 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 -> report.results.forEach { computedResults ->
if (!computedResults.isEmpty) { if (!computedResults.isEmpty) {
// Set date data // Set date data
query.conditions.forEach { condition -> query.conditions.forEach { condition ->
when (condition) { when (condition) {
is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first()) 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()
)
}
} }
monthlyReports[calendar.time] = computedResults monthlyReports[calendar.time] = computedResults
@ -275,14 +294,19 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
} }
yearConditions.forEach { query -> 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 -> report.results.forEach { computedResults ->
if (!computedResults.isEmpty) { if (!computedResults.isEmpty) {
// Set date data // Set date data
query.conditions.forEach { condition -> query.conditions.forEach { condition ->
when (condition) { when (condition) {
is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first()) is QueryCondition.AnyYear -> calendar.set(Calendar.YEAR, condition.listOfValues.first())
} }
} }
yearlyReports[calendar.time] = computedResults yearlyReports[calendar.time] = computedResults
} }

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

@ -1,6 +1,7 @@
package net.pokeranalytics.android.ui.fragment package net.pokeranalytics.android.ui.fragment
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -9,7 +10,6 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_data_list.* import kotlinx.android.synthetic.main.fragment_data_list.*
import net.pokeranalytics.android.R 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.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -21,114 +21,108 @@ import java.util.*
class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
companion object { companion object {
const val INTENT_CURRENCY_CODE = "INTENT_CURRENCY_CODE" const val INTENT_CURRENCY_CODE = "INTENT_CURRENCY_CODE"
val rowRepresentation : List<RowRepresentable> by lazy { val rowRepresentation: List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
rows.addAll(mostUsedCurrencies) rows.addAll(mostUsedCurrencies)
rows.add(SeparatorRow()) rows.add(SeparatorRow())
rows.addAll(availableCurrencies) rows.addAll(availableCurrencies)
rows rows
} }
private val mostUsedCurrencyCodes = arrayListOf("EUR", "USD", "CAD", "GBP", "AUD", "CNY") private val mostUsedCurrencyCodes = arrayListOf("EUR", "USD", "CAD", "GBP", "AUD", "CNY")
private val systemCurrencies = Currency.getAvailableCurrencies() private val systemCurrencies = Currency.getAvailableCurrencies()
private val mostUsedCurrencies = this.mostUsedCurrencyCodes.map { code -> private val mostUsedCurrencies = this.mostUsedCurrencyCodes.map { code ->
CurrencyRow( CurrencyRow(
this.systemCurrencies.filter { this.systemCurrencies.filter {
it.currencyCode == code it.currencyCode == code
}.first() }.first()
) )
} }
private val availableCurrencies = this.systemCurrencies.filter { private val availableCurrencies = this.systemCurrencies.filter {
!mostUsedCurrencyCodes.contains(it.currencyCode) !mostUsedCurrencyCodes.contains(it.currencyCode)
}.filter { }.filter {
Locale.getAvailableLocales().filter {locale -> Locale.getAvailableLocales().filter { locale ->
try { try {
Currency.getInstance(locale).currencyCode == it.currencyCode Currency.getInstance(locale).currencyCode == it.currencyCode
} catch (e: Exception) { } catch (e: Exception) {
false false
} }
}.isNotEmpty() }.isNotEmpty()
}.sortedBy { }.sortedBy {
it.displayName it.displayName
}.map { }.map {
CurrencyRow(it) CurrencyRow(it)
} }
} }
private class CurrencyRow(var currency:Currency) : RowRepresentable { private class CurrencyRow(var currency: Currency) : RowRepresentable {
override fun getDisplayName(): String { override fun getDisplayName(context: Context): String {
return currency.getDisplayName(Locale.getDefault()).capitalize() return currency.getDisplayName(Locale.getDefault()).capitalize()
} }
var currencyCode: String = currency.currencyCode var currencyCode: String = currency.currencyCode
var currencySymbole: String = currency.getSymbol(Locale.getDefault()) var currencySymbole: String = currency.getSymbol(Locale.getDefault())
var currencyCodeAndSymbol: String = "${this.currencyCode} (${this.currencySymbole})" var currencyCodeAndSymbol: String = "${this.currencyCode} (${this.currencySymbole})"
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_currencies, container, false) return inflater.inflate(R.layout.fragment_currencies, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initData() initData()
initUI() initUI()
} }
// StaticRowRepresentableDataSource // StaticRowRepresentableDataSource
override fun adapterRows(): List<RowRepresentable>? { override fun adapterRows(): List<RowRepresentable>? {
return CurrenciesFragment.rowRepresentation return CurrenciesFragment.rowRepresentation
} }
override fun stringForRow(row: RowRepresentable): String { override fun stringForRow(row: RowRepresentable): String {
return (row as CurrencyRow).currencyCodeAndSymbol return (row as CurrencyRow).currencyCodeAndSymbol
} }
// RowRepresentableDelegate // RowRepresentableDelegate
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
val intent = Intent() val intent = Intent()
intent.putExtra(INTENT_CURRENCY_CODE, (row as CurrencyRow).currency.currencyCode) intent.putExtra(INTENT_CURRENCY_CODE, (row as CurrencyRow).currency.currencyCode)
this.activity?.setResult(Activity.RESULT_OK, intent) this.activity?.setResult(Activity.RESULT_OK, intent)
this.activity?.finish() this.activity?.finish()
} }
private fun initData() { private fun initData() {
} }
/** /**
* Init UI * Init UI
*/ */
private fun initUI() { private fun initUI() {
val activity = activity as PokerAnalyticsActivity setDisplayHomeAsUpEnabled(true)
setToolbarTitle(getString(R.string.currency))
// Avoid a bug during setting the title
toolbar.title = this.getString( R.string.currency) val viewManager = LinearLayoutManager(requireContext())
val dataListAdapter = RowRepresentableAdapter(this, this)
activity.setSupportActionBar(toolbar)
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) recyclerView.apply {
setHasOptionsMenu(true) setHasFixedSize(true)
layoutManager = viewManager
val viewManager = LinearLayoutManager(requireContext()) adapter = dataListAdapter
val dataListAdapter = RowRepresentableAdapter(this, this) }
}
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = dataListAdapter
}
}
} }

@ -6,109 +6,70 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import androidx.core.view.isVisible
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar import io.realm.Realm
import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.kotlin.isValid import io.realm.kotlin.isValid
import kotlinx.android.synthetic.main.fragment_data_list.* 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.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable 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.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.LiveRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback 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.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType 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 { companion object {
const val REQUEST_CODE_DETAILS = 1000 const val REQUEST_CODE_DETAILS = 1000
} }
private lateinit var dataType: SettingRow private lateinit var identifiableClass: Class<out Deletable>
private lateinit var items: RealmResults<*>
private lateinit var dataListAdapter: RowRepresentableAdapter
private var deletedItem: RealmObject? = null private lateinit var dataType: LiveData
private var lastDeletedItemPosition: Int = 0 private lateinit var items: RealmResults<out Deletable>
private var lastItemClickedPosition: Int = 0
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { /**
return inflater.inflate(R.layout.fragment_data_list, container, false) * Set fragment data
} */
fun setData(dataType: Int) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { this.dataType = LiveData.values()[dataType]
super.onViewCreated(view, savedInstanceState) this.identifiableClass = this.dataType.relatedEntity
initUI() setToolbarTitle(this.dataType.pluralLocalizedTitle(requireContext()))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { this.items = this.retrieveItems(getRealm())
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() { open fun retrieveItems(realm: Realm): RealmResults<out Deletable> {
super.onResume() return realm.sorted(this.identifiableClass, editableOnly = true, filterableTypeUniqueIdentifier = dataType.subType)
this.recyclerView?.adapter?.notifyDataSetChanged()
} }
override fun rowRepresentableForPosition(position: Int): RowRepresentable? { override fun deletableItems() : List<Deletable> {
return this.items[position] as RowRepresentable return this.items
} }
override fun numberOfRows(): Int { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return this.items.size super.onCreateView(inflater, container, savedInstanceState)
} return inflater.inflate(R.layout.fragment_data_list, container, false)
override fun viewTypeForPosition(position: Int): Int {
val viewType = (this.items[position] as RowRepresentable).viewType
return if (viewType != -1) viewType else RowViewType.DATA.ordinal
}
override fun indexForRow(row: RowRepresentable): Int {
return this.items.indexOf(row)
} }
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (row is Identifiable && !row.isValid()) { initUI()
Toast.makeText(requireContext(), R.string.invalid_object, Toast.LENGTH_LONG)
return
}
this.dataType.relatedResultsRepresentable?.let {
lastItemClickedPosition = position
EditableDataActivity.newInstanceForResult(
this,
it.ordinal,
(row as Identifiable).id,
REQUEST_CODE_DETAILS
)
}
} }
/** /**
@ -116,20 +77,19 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
*/ */
private fun initUI() { private fun initUI() {
val activity = activity as PokerAnalyticsActivity setDisplayHomeAsUpEnabled(true)
// Avoid a bug during setting the title
toolbar.title = ""
activity.setSupportActionBar(toolbar)
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
val viewManager = LinearLayoutManager(requireContext()) val viewManager = LinearLayoutManager(requireContext())
dataListAdapter = RowRepresentableAdapter(this, this) dataListAdapter = RowRepresentableAdapter(this, this)
val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position -> 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) val itemTouchHelper = ItemTouchHelper(swipeToDelete)
@ -142,74 +102,53 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
} }
this.addButton.setOnClickListener { this.addButton.setOnClickListener {
this.dataType.relatedResultsRepresentable?.let { EditableDataActivity.newInstance(
EditableDataActivity.newInstance( requireContext(),
requireContext(), dataType = this.dataType.ordinal,
dataType = it.ordinal, primaryKey = null
primaryKey = null )
)
}
} }
} }
/** override fun onResume() {
* Delete item super.onResume()
*/ this.recyclerView?.adapter?.notifyDataSetChanged()
private fun deleteItem(position: Int) { }
if (isDetached || activity == null) {
return
}
// Save the delete position & create a copy of the object override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
val mRecentlyDeletedItem = rowRepresentableForPosition(position) return this.items[position] as RowRepresentable
lastDeletedItemPosition = position }
if (mRecentlyDeletedItem is RealmObject) { override fun numberOfRows(): Int {
return this.items.size
}
// Check if the object is valid for the deletion override fun viewTypeForPosition(position: Int): Int {
if ((mRecentlyDeletedItem as Deletable).isValidForDelete(this.getRealm())) { val viewType = (this.items[position] as RowRepresentable).viewType
deletedItem = getRealm().copyFromRealm(mRecentlyDeletedItem) return if (viewType != -1) viewType else RowViewType.DATA.ordinal
getRealm().executeTransaction {
mRecentlyDeletedItem.deleteFromRealm()
}
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()
}
}
} }
/** override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
* Show undo snack bar
*/ when (this.dataType) {
private fun showUndoSnackBar() { LiveData.FILTER -> {
val message = String.format(getString(R.string.data_deleted), this.dataType.localizedTitle(requireContext())) val intent = Intent()
val snackBar = Snackbar.make(constraintLayout, message, Snackbar.LENGTH_INDEFINITE) intent.putExtra(FiltersActivity.IntentKey.FILTER_ID.keyName, (row as Filter).id)
snackBar.setAction(R.string.cancel) { activity?.setResult(Activity.RESULT_OK, intent)
getRealm().executeTransaction { realm -> activity?.finish()
deletedItem?.let { }
realm.copyToRealmOrUpdate(it) else -> {
dataListAdapter.notifyItemInserted(lastDeletedItemPosition) val identifier = (row as Identifiable).id
} EditableDataActivity.newInstanceForResult(this, this.dataType, identifier, REQUEST_CODE_DETAILS)
} }
} }
snackBar.show()
} }
/** /**
* Set fragment data * Update UI
*/ */
fun setData(dataType: Int) { fun updateUI(showAddButton: Boolean) {
this.dataType = SettingRow.values()[dataType] this.addButton.isVisible = showAddButton
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 package net.pokeranalytics.android.ui.fragment
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager 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.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.FilterDetailsActivity 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.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource 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.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.helpers.DateTimePickerManager import net.pokeranalytics.android.ui.helpers.DateTimePickerManager
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
@ -27,30 +26,24 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.util.NULL_TEXT 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 timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { open class FilterDetailsFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
lateinit var parentActivity: PokerAnalyticsActivity
lateinit var rowRepresentableAdapter: RowRepresentableAdapter lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private lateinit var primaryKey: String private lateinit var primaryKey: String
private lateinit var filterCategoryRow: FilterCategoryRow private lateinit var filterCategoryRow: FilterCategoryRow
private var currentFilter: Filter? = null private var currentFilter: Filter? = null
private var rows: ArrayList<RowRepresentable> = ArrayList() private var rows: ArrayList<RowRepresentable> = ArrayList()
private var rowsForFilterSubcategoryRow: HashMap<FilterSectionRow, ArrayList<RowRepresentable>> = HashMap() private var rowsForFilterSubcategoryRow: HashMap<FilterSectionRow, ArrayList<RowRepresentable>> = HashMap()
private var filterMenu: Menu? = null
private val selectedRows = ArrayList<QueryCondition>() private val selectedRows = ArrayList<QueryCondition>()
private var isUpdating = false
private var shouldOpenKeyboard = true
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 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) 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) { override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
super.onRowSelected(position, row, fromAction) super.onRowSelected(position, row, fromAction)
Timber.d("Row: $row") if (row.viewType == RowViewType.TITLE_CHECK.ordinal) {
updateRowsSelection(row)
if (row.viewType == RowViewType.TITLE_CHECK.ordinal) { return
updateRowsSelection(row) }
return
}
when (row) { 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 -> { is QueryCondition.Duration -> {
val hours = if (row.minutes / 60 > 0) (row.minutes / 60).toString() else "" var hours: String? = null
val minutes = if (row.minutes % 60 > 0) (row.minutes % 60).toString() else "" 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)) val data = row.editingDescriptors(mapOf("hours" to hours, "minutes" to minutes))
BottomSheetFragment.create(fragmentManager, row, this, data, true) BottomSheetFragment.create(fragmentManager, row, this, data, true)
} }
is QueryCondition.ListOfValues<*> -> {
is QueryCondition.ListOfValues<*> -> { var valueAsString: String? = null
val valueAsString = row.listOfValues.firstOrNull()?.toString() ?: "" row.listOfValues.firstOrNull()?.let {
val data = row.editingDescriptors(mapOf("valueAsString" to valueAsString)) valueAsString = row.listOfValues.firstOrNull()?.toString()
BottomSheetFragment.create(fragmentManager, row, this, data, true) }
} 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) { return when (row) {
is QueryCondition.DateQuery -> if (row.showTime) row.singleValue.shortTime() else row.singleValue.shortDate() is QueryCondition.ListOfValues<*> -> row.firstValue(context)
is QueryCondition.Duration -> row.minutes.toMinutes(requireContext())
is QueryCondition.ListOfValues<*> -> row.listOfValues.firstOrNull()?.toString() ?: NULL_TEXT
else -> super.stringForRow(row) else -> super.stringForRow(row)
} } ?: NULL_TEXT
} }
override fun isSelected(row: RowRepresentable): Boolean { override fun isSelected(row: RowRepresentable): Boolean {
@ -110,28 +112,40 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
Timber.d("onRowValueChanged: $row $value") Timber.d("onRowValueChanged: $row $value")
when (row) { 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 -> { is QueryCondition.Duration -> {
if (value is ArrayList<*>) { if (value is ArrayList<*>) {
val hours = try { val hours: Int? = try {
(value[0] as String? ?: "0").toInt() (value[0] as String?)?.toInt()
} catch (e: Exception) { } catch (e: Exception) {
0 null
} }
val minutes = try { val minutes = try {
(value[1] as String? ?: "0").toInt() (value[1] as String?)?.toInt()
} catch (e: Exception) { } 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
} }
row.minutes = hours * 60 + minutes
} else { } else {
row.minutes = 0 row.minutes = null
} }
} }
is QueryCondition.SingleInt -> row.singleValue = 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(if (value != null && value is String) value.toDouble() else 0.0) is QueryCondition.ListOfDouble -> row.listOfValues = arrayListOf<Double>().apply {
is QueryCondition.ListOfInt-> row.listOfValues = arrayListOf(if (value != null && value is String) value.toInt() else 0) 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 // Remove the row before updating the selected rows list
@ -153,10 +167,8 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
* Init UI * Init UI
*/ */
private fun initUI() { private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
parentActivity.setSupportActionBar(toolbar) setDisplayHomeAsUpEnabled(true)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
this.appBar.toolbar.title = getString(R.string.filter) this.appBar.toolbar.title = getString(R.string.filter)
@ -173,24 +185,24 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
*/ */
private fun initData() { private fun initData() {
currentFilter = Filter.getFilterBydId(getRealm(), primaryKey) //currentFilter = Filter.getFilterBydId(getRealm(), primaryKey)
currentFilter = FiltersFragment.currentFilter
this.appBar.toolbar.title = filterCategoryRow.localizedTitle(requireContext())
this.appBar.toolbar.title = filterCategoryRow.localizedTitle(requireContext()) this.rows.clear()
this.rowsForFilterSubcategoryRow.clear()
this.rows.addAll(filterCategoryRow.filterElements)
this.rows.clear() this.rows.forEach { element ->
this.rowsForFilterSubcategoryRow.clear() if (element is QueryCondition && currentFilter?.contains(element) == true) {
this.rows.addAll(filterCategoryRow.filterElements) currentFilter?.loadValueForElement(element)
this.selectedRows.add(element)
this.rows.forEach { element ->
if (element is QueryCondition && currentFilter?.contains(element) == true) {
currentFilter?.loadValueForElement(element)
this.selectedRows.add(element)
}
} }
}
this.rowRepresentableAdapter = RowRepresentableAdapter(this, this) this.rowRepresentableAdapter = RowRepresentableAdapter(this, this)
this.recyclerView.adapter = rowRepresentableAdapter this.recyclerView.adapter = rowRepresentableAdapter
} }
/** /**
@ -215,7 +227,7 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
} }
} }
println("list of selected rows : $selectedRows") println("list of selected rows : $selectedRows")
// Update UI // Update UI
rowRepresentableAdapter.refreshRow(row) rowRepresentableAdapter.refreshRow(row)
@ -233,7 +245,7 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
val realm = getRealm() val realm = getRealm()
realm.beginTransaction() realm.beginTransaction()
currentFilter?.remove(filterCategoryRow) currentFilter?.remove(filterCategoryRow)
currentFilter?.createOrUpdateFilterConditions(selectedRows) currentFilter?.createOrUpdateFilterConditions(selectedRows)
realm.commitTransaction() realm.commitTransaction()
@ -241,7 +253,6 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
Timber.d("Condition: $it") Timber.d("Condition: $it")
} }
finishActivityWithResult(currentFilter?.id) finishActivityWithResult(currentFilter?.id)
} }

@ -1,47 +1,61 @@
package net.pokeranalytics.android.ui.fragment package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.kotlin.where import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.fragment_editable_data.* import kotlinx.android.synthetic.main.fragment_editable_data.appBar
import kotlinx.android.synthetic.main.fragment_filters.view.* 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.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.FilterDetailsActivity import net.pokeranalytics.android.ui.activity.FilterDetailsActivity
import net.pokeranalytics.android.ui.activity.FiltersActivity 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.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource 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
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow 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 import timber.log.Timber
open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
companion object { companion object {
const val REQUEST_CODE_FILTER_DETAILS = 100 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 lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private var currentFilter: Filter? = null
private var filterCopy: Filter? = null private var filterCopy: Filter? = null
private var rows: ArrayList<RowRepresentable> = ArrayList() private var rows: ArrayList<RowRepresentable> = ArrayList()
private var filterMenu: Menu? = null private var filterMenu: Menu? = null
private var primaryKey: String? = null private var primaryKey: String? = null
private lateinit var filterableType: FilterableType
private var selectedRow: RowRepresentable? = null private var selectedRow: RowRepresentable? = null
private var isUpdating = false private var isUpdating = false
private var showMostUsedFiltersLayout = true
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_filters, container, false) return inflater.inflate(R.layout.fragment_filters, container, false)
} }
@ -49,24 +63,34 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initUI() initUI()
initData() initData()
updateMostUsedFilters()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) 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") currentFilter?.id?.let { currentFilterId ->
if (data != null && data.hasExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)) { Filter.getFilterBydId(getRealm(), currentFilterId)?.let { filter ->
val filterId = data.getStringExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName) currentFilter = filter
Timber.d("Updated queryWith: ${filterId}") }
} }
*/ */
selectedRow?.let { selectedRow?.let {
rowRepresentableAdapter.refreshRow(it) 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) { if (isUpdating) {
cancelUpdates() cancelUpdates()
} else { } else {
deleteFilter() activity?.finish()
} }
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) { when (item!!.itemId) {
R.id.save -> validUpdates() R.id.save -> validateUpdates()
R.id.delete -> deleteFilter()
} }
return true return true
} }
@ -123,19 +146,23 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
* Init UI * Init UI
*/ */
private fun initUI() { private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
parentActivity.setSupportActionBar(toolbar) setDisplayHomeAsUpEnabled(true)
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
this.appBar.toolbar.title = getString(R.string.filter) this.appBar.toolbar.title = getString(R.string.filter)
val viewManager = LinearLayoutManager(requireContext()) val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply { recyclerView.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = viewManager 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() val realm = getRealm()
//TODO: Remove that
val filters = realm.where<Filter>().findAll()
Timber.d("Filters: ${filters.size}")
primaryKey?.let { primaryKey?.let {
currentFilter = Filter.getFilterBydId(realm, it) currentFilter = realm.copyFromRealm(Filter.getFilterBydId(realm, it))
isUpdating = true isUpdating = true
} ?: run { } ?: run {
realm.beginTransaction() currentFilter = Filter.newInstance(this.filterableType.uniqueIdentifier) //realm.copyFromRealm(Filter.newInstanceForResult(realm, this.filterableType.ordinal))
currentFilter = Filter.newInstance(realm)
realm.commitTransaction()
} }
// Create a copy if the user cancels the updates // Create a copy if the user cancels the updates
currentFilter?.let { currentFilter?.let {
filterCopy = getRealm().copyFromRealm(it) if (it.isValid && it.isManaged) {
filterCopy = getRealm().copyFromRealm(it)
}
} }
rows.clear() rows.clear()
rows.addAll(FilterCategoryRow.values()) rows.addAll(FilterCategoryRow.values(this.filterableType))
this.rowRepresentableAdapter = RowRepresentableAdapter(this, this) this.rowRepresentableAdapter = RowRepresentableAdapter(this, this)
this.recyclerView.adapter = rowRepresentableAdapter 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 * Update menu UI
*/ */
private fun updateMenuUI() { private fun updateMenuUI() {
filterMenu?.findItem(R.id.delete)?.isVisible = isUpdating
filterMenu?.findItem(R.id.save)?.isVisible = true 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() { private fun validateUpdates() {
Timber.d("Valid queryWith updates") val realm = getRealm()
realm.beginTransaction()
currentFilter?.let {
it.name = it.query.getName(requireContext())
realm.copyToRealmOrUpdate(it)
}
realm.commitTransaction()
val filterId = currentFilter?.id ?: "" val filterId = currentFilter?.id ?: ""
finishActivityWithResult(filterId) finishActivityWithResult(filterId)
} }
@ -192,10 +267,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
* Cancel the latest updates of the queryWith * Cancel the latest updates of the queryWith
*/ */
private fun cancelUpdates() { private fun cancelUpdates() {
Timber.d("Cancel queryWith updates")
val filterId = filterCopy?.id ?: "" val filterId = filterCopy?.id ?: ""
val realm = getRealm() val realm = getRealm()
realm.beginTransaction() realm.beginTransaction()
filterCopy?.let { filterCopy?.let {
@ -205,19 +277,6 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
finishActivityWithResult(filterId) 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 * Finish the activity with a result
*/ */
@ -231,8 +290,18 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
/** /**
* Set fragment data * Set fragment data
*/ */
fun setData(primaryKey: String?) { fun setData(primaryKey: String?, filterableType: FilterableType) {
this.primaryKey = primaryKey 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 com.github.mikephil.charting.listener.OnChartValueSelectedListener
import kotlinx.android.synthetic.main.fragment_graph.* import kotlinx.android.synthetic.main.fragment_graph.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.graph.AxisFormatting import net.pokeranalytics.android.ui.graph.AxisFormatting
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry 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.graph.setStyle
import net.pokeranalytics.android.ui.view.LegendView import net.pokeranalytics.android.ui.view.LegendView
import net.pokeranalytics.android.ui.view.MultiLineLegendView import net.pokeranalytics.android.ui.view.MultiLineLegendView
class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { class GraphFragment : RealmFragment(), OnChartValueSelectedListener {
enum class Style { enum class Style {
LINE, LINE,
@ -49,8 +48,6 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
} }
private lateinit var parentActivity: PokerAnalyticsActivity
private var style: Style = Style.LINE private var style: Style = Style.LINE
private lateinit var legendView: LegendView private lateinit var legendView: LegendView
@ -63,6 +60,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
private var axisFormatting: AxisFormatting = AxisFormatting.DEFAULT private var axisFormatting: AxisFormatting = AxisFormatting.DEFAULT
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_graph, container, false) return inflater.inflate(R.layout.fragment_graph, container, false)
} }
@ -100,9 +98,6 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
*/ */
private fun initUI() { private fun initUI() {
parentActivity = activity as PokerAnalyticsActivity
parentActivity.title = stat.localizedTitle(requireContext())
this.legendView = when (this.style) { this.legendView = when (this.style) {
Style.MULTILINE -> MultiLineLegendView(requireContext()) Style.MULTILINE -> MultiLineLegendView(requireContext())
else -> LegendView(requireContext()) else -> LegendView(requireContext())
@ -129,15 +124,12 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
this.chartView = lineChart this.chartView = lineChart
dataSets.firstOrNull()?.let { dataSet -> dataSets.firstOrNull()?.let { dataSet ->
this.legendView.prepareWithStat(this.stat, dataSet.entryCount, this.style) this.legendView.prepareWithStat(this.stat, dataSet.entryCount, this.style)
if (dataSet.entryCount > 0) { if (dataSet.entryCount > 0) {
val entry = dataSet.getEntryForIndex(dataSet.entryCount - 1) val entry = dataSet.getEntryForIndex(dataSet.entryCount - 1)
this.selectValue(entry, dataSet) this.selectValue(entry, dataSet)
} }
} }
} }
this.barDataSetList?.let { dataSets -> this.barDataSetList?.let { dataSets ->
@ -146,10 +138,10 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
val barChart = BarChart(context) val barChart = BarChart(context)
barChart.setOnChartValueSelectedListener(this) barChart.setOnChartValueSelectedListener(this)
if (stat.showXAxisZero) { if (stat.graphShowsXAxisZero) {
barChart.xAxis.axisMinimum = 0.0f barChart.xAxis.axisMinimum = 0.0f
} }
if (stat.showYAxisZero) { if (stat.graphShowsYAxisZero) {
barChart.axisLeft.axisMinimum = 0.0f barChart.axisLeft.axisMinimum = 0.0f
} }
this.chartView = barChart this.chartView = barChart
@ -178,21 +170,13 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
override fun onValueSelected(e: Entry?, h: Highlight?) { 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) val dataSet = this.chartView?.data?.getDataSetForEntry(e)
e?.let { entry -> e?.let { entry ->
this.selectValue(entry, dataSet) this.selectValue(entry, dataSet)
} }
} }
private fun selectValue(entry: Entry, dataSet: IBarLineScatterCandleBubbleDataSet<out Entry>? = null) { private fun selectValue(entry: Entry, dataSet: IBarLineScatterCandleBubbleDataSet<out Entry>? = null) {
val statEntry = when (entry.data) { val statEntry = when (entry.data) {
is ObjectIdentifier -> { is ObjectIdentifier -> {

@ -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.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager 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.R
import net.pokeranalytics.android.ui.activity.BankrollActivity import net.pokeranalytics.android.ui.activity.BankrollActivity
import net.pokeranalytics.android.ui.activity.SettingsActivity 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