fix seed issue

feature/top10
Razmig Sarkissian 7 years ago
commit 46adf8c068
  1. 3
      .gitlab-ci.yml
  2. 43
      app/build.gradle
  3. 38
      app/proguard-rules.pro
  4. BIN
      app/src/androidTest/assets/schema_0.realm
  5. BIN
      app/src/androidTest/assets/schema_2.realm
  6. 3
      app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt
  7. 8
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/FavoriteSessionUnitTest.kt
  8. 49
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt
  9. 313
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt
  10. 30
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt
  11. 11
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt
  12. 180
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt
  13. 35
      app/src/main/AndroidManifest.xml
  14. 17
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  15. 126
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  16. 121
      app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt
  17. 109
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  18. 40
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  19. 3
      app/src/main/java/net/pokeranalytics/android/model/StatRepresentable.kt
  20. 42
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  21. 218
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  22. 3
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  23. 51
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  24. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt
  25. 16
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  26. 104
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  27. 125
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt
  28. 144
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterElement.kt
  29. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterElementBlind.kt
  30. 90
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  31. 46
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  32. 12
      app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt
  33. 5
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt
  34. 2
      app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt
  35. 1
      app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt
  36. 10
      app/src/main/java/net/pokeranalytics/android/ui/activity/FilterDetailsActivity.kt
  37. 13
      app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt
  38. 69
      app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt
  39. 6
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  40. 2
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt
  41. 10
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt
  42. 86
      app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
  43. 18
      app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt
  44. 195
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt
  45. 132
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt
  46. 97
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  47. 24
      app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt
  48. 22
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt
  49. 89
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt
  50. 7
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt
  51. 5
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt
  52. 8
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt
  53. 66
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt
  54. 4
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  55. 9
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt
  56. 321
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt
  57. 247
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt
  58. 1
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt
  59. 24
      app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt
  60. 20
      app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt
  61. 7
      app/src/main/res/layout/activity_graph.xml
  62. 37
      app/src/main/res/layout/bottom_sheet_game_list.xml
  63. 1
      app/src/main/res/layout/fragment_bottom_sheet.xml
  64. 25
      app/src/main/res/layout/fragment_graph.xml
  65. 8
      app/src/main/res/layout/fragment_session.xml
  66. 1
      app/src/main/res/layout/row_title_value_check.xml
  67. 6
      app/src/main/res/values/strings.xml
  68. 4
      app/src/main/res/values/styles.xml

@ -42,6 +42,9 @@ assembleDebug:
instrumentedTests: instrumentedTests:
stage: test stage: test
only:
refs:
- master
script: script:
- wget --quiet --output-document=android-wait-for-emulator https://raw.githubusercontent.com/travis-ci/travis-cookbooks/0f497eb71291b52a703143c5cd63a217c8766dc9/community-cookbooks/android-sdk/files/default/android-wait-for-emulator - wget --quiet --output-document=android-wait-for-emulator https://raw.githubusercontent.com/travis-ci/travis-cookbooks/0f497eb71291b52a703143c5cd63a217c8766dc9/community-cookbooks/android-sdk/files/default/android-wait-for-emulator
- chmod +x android-wait-for-emulator - chmod +x android-wait-for-emulator

@ -7,6 +7,7 @@ apply plugin: 'io.fabric'
repositories { repositories {
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
maven { url 'https://jitpack.io' }
} }
android { android {
@ -27,17 +28,32 @@ android {
applicationId "net.pokeranalytics.android" applicationId "net.pokeranalytics.android"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 28 targetSdkVersion 28
versionCode 5 versionCode 8
versionName "1.0" versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
// buildTypes { buildTypes {
// release { release {
// minifyEnabled true minifyEnabled false
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// } applicationVariants.all { variant ->
// } variant.outputs.all { output ->
def date = new Date()
def formattedDate = date.format('yyMMdd_HHmm')
def appName = "PokerAnalytics"
def buildType = variant.variantData.variantConfiguration.buildType.name
def newName
if (buildType == 'debug'){
newName = "${appName}_${defaultConfig.versionName}(${defaultConfig.versionCode})_${formattedDate}_debug.apk"
} else {
newName = "${appName}_${defaultConfig.versionName}(${defaultConfig.versionCode})_${formattedDate}_release.apk"
}
outputFileName = newName
}
}
}
}
configurations { configurations {
all*.exclude group: 'com.google.guava', module: 'listenablefuture' all*.exclude group: 'com.google.guava', module: 'listenablefuture'
@ -47,7 +63,12 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
// Kotlin
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
// Android // Android
implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.appcompat:appcompat:1.0.2'
@ -72,13 +93,13 @@ dependencies {
// Crashlytics // Crashlytics
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9' implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9'
// Kotlin
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
// Logs // Logs
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
// MPAndroidChart
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
// 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'
androidTestImplementation 'androidx.test:rules:1.1.1' androidTestImplementation 'androidx.test:rules:1.1.1'

@ -20,5 +20,41 @@
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keep class com.google.j2objc.annotations.** { *; } # Realm
-keep class io.realm.annotations.RealmModule
-keep @io.realm.annotations.RealmModule class *
-keep class io.realm.internal.Keep
-keep @io.realm.internal.Keep class *
-dontwarn javax.**
-dontwarn io.realm.**
-keep class net.pokeranalytics.android.model.** { *; }
# Retrofit
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
# Guava
-dontwarn com.google.j2objc.annotations.** -dontwarn com.google.j2objc.annotations.**
-keep class com.google.j2objc.annotations.** { *; }

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

@ -9,6 +9,7 @@ import net.pokeranalytics.android.model.utils.FavoriteSessionFinder
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.util.*
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -25,9 +26,16 @@ class FavoriteSessionUnitTest : RealmInstrumentedUnitTest() {
val s2 = newSessionInstance(realm) val s2 = newSessionInstance(realm)
val s3 = newSessionInstance(realm) val s3 = newSessionInstance(realm)
s1.endDate = Date()
s2.endDate = Date()
s3.endDate = Date()
s1.cgBigBlind = 4.0 s1.cgBigBlind = 4.0
s1.cgSmallBlind = 2.0
s2.cgBigBlind = 4.0 s2.cgBigBlind = 4.0
s2.cgSmallBlind = 2.0
s3.cgBigBlind = 1.0 s3.cgBigBlind = 1.0
s3.cgSmallBlind = 1.0
realm.insert(s1) realm.insert(s1)
realm.insert(s2) realm.insert(s2)

@ -1,8 +1,11 @@
package net.pokeranalytics.android.unitTests.filter package net.pokeranalytics.android.unitTests.filter
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.QueryType import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.FilterCondition
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.view.rowrepresentable.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 org.junit.Assert import org.junit.Assert
@ -39,17 +42,15 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.BLINDS val filter = QueryCondition.BLINDS
val blind = FilterElementRow.Blind(0.5, 1.0, null) val blind = FilterElementRow.Blind(0.5, 1.0, null)
blind.filterSectionRow = FilterSectionRow.BLINDS blind.filterSectionRow = FilterSectionRow.BLINDS
val filterElement = FilterElement(filterElementRows = arrayListOf(blind)) val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn(
realm,
Session, val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
arrayListOf(filter)
)
Assert.assertEquals(2, sessions.size) Assert.assertEquals(2, sessions.size)
sessions.map { sessions.map {
@ -85,18 +86,14 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.BLINDS val filter = QueryCondition.BLINDS
val blind = FilterElementRow.Blind(null, 1.0, null) val blind = FilterElementRow.Blind(null, 1.0, null)
blind.filterSectionRow = FilterSectionRow.BLINDS blind.filterSectionRow = FilterSectionRow.BLINDS
val filterElement = FilterElement(filterElementRows = arrayListOf(blind)) val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(2, sessions.size) Assert.assertEquals(2, sessions.size)
sessions.map { sessions.map {
@ -132,18 +129,14 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.BLINDS val filter = QueryCondition.BLINDS
val blind = FilterElementRow.Blind(1.0, 2.0, "AUD") val blind = FilterElementRow.Blind(1.0, 2.0, "AUD")
blind.filterSectionRow = FilterSectionRow.BLINDS blind.filterSectionRow = FilterSectionRow.BLINDS
val filterElement = FilterElement(filterElementRows = arrayListOf(blind)) val filterElement = FilterCondition(filterElementRows = arrayListOf(blind))
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions.map { sessions.map {
@ -179,20 +172,16 @@ class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.BLINDS val filter = QueryCondition.BLINDS
val blind1 = FilterElementRow.Blind(1.0, 2.0, null) val blind1 = FilterElementRow.Blind(1.0, 2.0, null)
blind1.filterSectionRow = FilterSectionRow.BLINDS blind1.filterSectionRow = FilterSectionRow.BLINDS
val blind2 = FilterElementRow.Blind(0.5, 1.0, null) val blind2 = FilterElementRow.Blind(0.5, 1.0, null)
blind2.filterSectionRow = FilterSectionRow.BLINDS blind2.filterSectionRow = FilterSectionRow.BLINDS
val filterElement = FilterElement(filterElementRows = arrayListOf(blind1, blind2)) val filterElement = FilterCondition(filterElementRows = arrayListOf(blind1, blind2))
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(2, sessions.size) Assert.assertEquals(2, sessions.size)
sessions.map { sessions.map {

@ -2,12 +2,13 @@ package net.pokeranalytics.android.unitTests.filter
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.QueryType 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.model.realm.FilterElement 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.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.extensions.startOfDay
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -29,19 +30,15 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, cal.time) Session.testInstance(100.0, true, cal.time)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.DAY_OF_WEEK val filter = QueryCondition.DAY_OF_WEEK
cal.time = s1.startDate cal.time = s1.startDate
val filterElementRow = FilterElementRow.Day(cal.get(Calendar.DAY_OF_WEEK)) val filterElementRow = FilterElementRow.Day(cal.get(Calendar.DAY_OF_WEEK))
filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
val filterElement = FilterElement(filterElementRow) val filterElement = FilterCondition(filterElementRow)
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -62,19 +59,15 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, cal.time) Session.testInstance(100.0, true, cal.time)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.MONTH val filter = QueryCondition.MONTH
cal.time = s1.startDate cal.time = s1.startDate
val filterElementRow = FilterElementRow.Month(cal.get(Calendar.MONTH)) val filterElementRow = FilterElementRow.Month(cal.get(Calendar.MONTH))
filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
val filterElement = FilterElement(filterElementRow) val filterElement = FilterCondition(filterElementRow)
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -95,18 +88,14 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, cal.time) Session.testInstance(100.0, true, cal.time)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.YEAR val filter = QueryCondition.YEAR
cal.time = s1.startDate cal.time = s1.startDate
val filterElementRow = FilterElementRow.Year(cal.get(Calendar.YEAR)) val filterElementRow = FilterElementRow.Year(cal.get(Calendar.YEAR))
filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
val filterElement = FilterElement(filterElementRow) val filterElement = FilterCondition(filterElementRow)
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -128,11 +117,8 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, cal.time) Session.testInstance(100.0, true, cal.time)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn(
realm, val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.WEEK_END))
Session,
arrayListOf(QueryType.WEEK_END)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -145,19 +131,19 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val realm = this.mockRealm val realm = this.mockRealm
realm.beginTransaction() realm.beginTransaction()
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.time = Date() cal.time = Date()
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
val s1 = Session.testInstance(100.0, false, cal.time) val s1 = Session.testInstance(100.0, false, cal.time)
cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY)
cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY)
Session.testInstance(100.0, true, cal.time) Session.testInstance(100.0, true, cal.time)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn(
realm, val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.WEEK_DAY))
Session,
arrayListOf(QueryType.WEEK_DAY)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -165,6 +151,223 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
} }
} }
@Test
fun testTodayFilter() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = Session.testInstance(100.0, false)
val cal = Calendar.getInstance()
cal.time = Date()
cal.add(Calendar.HOUR_OF_DAY, -72)
Session.testInstance(100.0, false, cal.time)
realm.commitTransaction()
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.TODAY))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
Assert.assertEquals(s1.id, this.id)
}
}
@Test
fun testTodayNoonFilter() {
val realm = this.mockRealm
realm.beginTransaction()
val cal = Calendar.getInstance()
cal.time = Date().startOfDay()
val s1 = Session.testInstance(100.0, false, cal.time)
cal.add(Calendar.HOUR_OF_DAY, -72)
Session.testInstance(100.0, false, cal.time)
realm.commitTransaction()
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.TODAY))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
Assert.assertEquals(s1.id, this.id)
}
}
@Test
fun testYesterdayFilter() {
val realm = this.mockRealm
realm.beginTransaction()
val cal = Calendar.getInstance()
cal.time = Date()
cal.add(Calendar.HOUR_OF_DAY, -24)
val s1 = Session.testInstance(100.0, false, cal.time)
Session.testInstance(100.0, false)
cal.add(Calendar.HOUR_OF_DAY, -72)
Session.testInstance(100.0, false, cal.time)
realm.commitTransaction()
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.YESTERDAY))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
Assert.assertEquals(s1.id, this.id)
}
}
@Test
fun testYesterdayNoonFilter() {
val realm = this.mockRealm
realm.beginTransaction()
val cal = Calendar.getInstance()
cal.time = Date()
cal.add(Calendar.HOUR_OF_DAY, -24)
val s1 = Session.testInstance(100.0, false, cal.time.startOfDay())
Session.testInstance(100.0, false)
cal.add(Calendar.HOUR_OF_DAY, -72)
Session.testInstance(100.0, false, cal.time)
realm.commitTransaction()
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.YESTERDAY))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
Assert.assertEquals(s1.id, this.id)
}
}
@Test
fun testTodayAndYesterdayFilter() {
val realm = this.mockRealm
realm.beginTransaction()
val cal = Calendar.getInstance()
cal.time = Date()
cal.add(Calendar.HOUR_OF_DAY, -24)
val s1 = Session.testInstance(100.0, false, cal.time)
val s2 = Session.testInstance(100.0, false)
cal.add(Calendar.HOUR_OF_DAY, -72)
Session.testInstance(100.0, false, cal.time)
realm.commitTransaction()
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.TODAY_AND_YESTERDAY))
Assert.assertEquals(2, sessions.size)
Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2)))
}
@Test
fun testThisYear() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = Session.testInstance(100.0, false)
val cal = Calendar.getInstance()
cal.time = Date()
cal.add(Calendar.HOUR_OF_DAY, -24)
val s2 = Session.testInstance(100.0, false, cal.time)
cal.add(Calendar.HOUR_OF_DAY, -72)
val s3 = Session.testInstance(100.0, false, cal.time)
cal.add(Calendar.YEAR, -4)
Session.testInstance(100.0, false, cal.time)
cal.add(Calendar.YEAR, -1)
Session.testInstance(100.0, false, cal.time)
realm.commitTransaction()
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 5)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.THIS_YEAR))
Assert.assertEquals(3, sessions.size)
Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2, s3)))
}
@Test
fun testThisMonth() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = Session.testInstance(100.0, false)
val cal = Calendar.getInstance()
cal.time = Date()
cal.set(Calendar.DAY_OF_MONTH, 1)
cal.time = cal.time.startOfDay()
val s2 = Session.testInstance(100.0, false, cal.time)
cal.add(Calendar.HOUR_OF_DAY, -1)
Session.testInstance(100.0, false, cal.time)
cal.add(Calendar.MONTH, -1)
Session.testInstance(100.0, false, cal.time)
cal.add(Calendar.YEAR, -1)
Session.testInstance(100.0, false, cal.time)
realm.commitTransaction()
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 5)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.THIS_MONTH))
Assert.assertEquals(2, sessions.size)
Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2)))
}
@Test
fun testThisWeek() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = Session.testInstance(100.0, false)
val cal = Calendar.getInstance()
cal.time = Date()
cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, 1)
cal.time = cal.time.startOfDay()
cal.add(Calendar.HOUR_OF_DAY, -1)
Session.testInstance(100.0, false, cal.time)
realm.commitTransaction()
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.THIS_WEEK))
Assert.assertEquals(1, sessions.size)
Assert.assertTrue(sessions.containsAll(arrayListOf(s1)))
}
@Test @Test
fun testStartedFomDateFilter() { fun testStartedFomDateFilter() {
@ -180,17 +383,12 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
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 = QueryType.STARTED_FROM_DATE val filter = QueryCondition.STARTED_FROM_DATE
val filterElementRow = FilterElementRow.From(s2.startDate!!) val filterElementRow = FilterElementRow.From(s2.startDate!!)
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterElement(filterElementRow)) filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -213,16 +411,12 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.STARTED_TO_DATE val filter = QueryCondition.STARTED_TO_DATE
val filterElementRow = FilterElementRow.From(s1.startDate!!) val filterElementRow = FilterElementRow.From(s1.startDate!!)
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterElement(filterElementRow)) filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -246,16 +440,12 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.ENDED_FROM_DATE val filter = QueryCondition.ENDED_FROM_DATE
val filterElementRow = FilterElementRow.From(s2.endDate()) val filterElementRow = FilterElementRow.From(s2.endDate())
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterElement(filterElementRow)) filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -279,17 +469,12 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.ENDED_TO_DATE val filter = QueryCondition.ENDED_TO_DATE
val filterElementRow = FilterElementRow.From(s1.endDate()) val filterElementRow = FilterElementRow.From(s1.endDate())
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterElement(filterElementRow)) filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {

@ -2,10 +2,10 @@ package net.pokeranalytics.android.unitTests.filter
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.exceptions.FilterValueMapException import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.filter.QueryType 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.model.realm.FilterElement import net.pokeranalytics.android.model.realm.FilterCondition
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -13,29 +13,19 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ExceptionFilterInstrumentedTest: BaseFilterInstrumentedUnitTest() { class ExceptionFilterInstrumentedTest: BaseFilterInstrumentedUnitTest() {
@Test(expected = FilterValueMapException::class) @Test(expected = PokerAnalyticsException.FilterElementExpectedValueMissing::class)
fun testValueKeyFilterException() { fun testValueKeyFilterException() {
val filter = QueryType.STARTED_FROM_DATE val filter = QueryCondition.STARTED_FROM_DATE
val filterElement = FilterElement() val filterElement = FilterCondition()
filter.updateValueMap(filterElement) filter.updateValueMap(filterElement)
val realm = this.mockRealm val realm = this.mockRealm
Filter.queryOn( Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
} }
@Test(expected = FilterValueMapException::class) @Test(expected = PokerAnalyticsException.FilterElementUnknownName::class)
fun testFilterException() { fun testFilterException() {
val realm = this.mockRealm FilterCondition().queryCondition
val filter = QueryType.BLINDS
filter.updateValueMap(FilterElement())
Filter.queryOn(
realm,
Session,
arrayListOf(filter)
)
} }
} }

@ -2,7 +2,8 @@ package net.pokeranalytics.android.unitTests.filter
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.QueryType import net.pokeranalytics.android.exceptions.PokerAnalyticsException
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.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
@ -26,7 +27,7 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val filterElement = FilterElementRow.Cash val filterElement = FilterElementRow.Cash
filterElement.filterSectionRow = FilterSectionRow.CASH_TOURNAMENT filterElement.filterSectionRow = FilterSectionRow.CASH_TOURNAMENT
filter.createOrUpdateFilterElements(arrayListOf(filterElement)) filter.createOrUpdateFilterConditions(arrayListOf(filterElement))
val useCount = filter.countBy(FilterCategoryRow.GENERAL) val useCount = filter.countBy(FilterCategoryRow.GENERAL)
Assert.assertEquals(1, useCount) Assert.assertEquals(1, useCount)
@ -34,10 +35,10 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val isCash = filter.contains(filterElement) val isCash = filter.contains(filterElement)
Assert.assertEquals(true, isCash) Assert.assertEquals(true, isCash)
val filterComponent = filter.filterElements.first() val filterComponent = filter.filterConditions.first()
filterComponent?.let { filterComponent?.let {
Assert.assertEquals(QueryType.CASH, QueryType.valueOf(it.filterName)) Assert.assertEquals(QueryCondition.CASH, QueryCondition.valueOf(it.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName))
} ?: run { } ?: run {
Assert.fail() Assert.fail()
} }
@ -50,7 +51,7 @@ class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val newRealm = this.mockRealm val newRealm = this.mockRealm
newRealm.where(Filter::class.java).equalTo("name", "testSaveLoadCashFilter").findFirst()?.let { foundFilter -> newRealm.where(Filter::class.java).equalTo("name", "testSaveLoadCashFilter").findFirst()?.let { foundFilter ->
val sessions = foundFilter.queryOn(Session) val sessions = foundFilter.results<Session>()
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {

@ -4,7 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.RealmList import io.realm.RealmList
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.TableSize import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.filter.QueryType import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
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
@ -26,11 +26,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, Date(), 1) Session.testInstance(100.0, true, Date(), 1)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.CASH))
realm,
Session,
arrayListOf(QueryType.CASH)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -48,11 +44,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, Date(), 1) Session.testInstance(100.0, true, Date(), 1)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.TOURNAMENT))
realm,
Session,
arrayListOf(QueryType.TOURNAMENT)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
sessions[0]?.run { sessions[0]?.run {
@ -75,11 +67,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, Date(), 1, b2) Session.testInstance(100.0, true, Date(), 1, b2)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.LIVE))
realm,
Session,
arrayListOf(QueryType.LIVE)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).bankroll?.run { (sessions[0] as Session).bankroll?.run {
@ -101,11 +89,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, Date(), 1, b2) Session.testInstance(100.0, true, Date(), 1, b2)
realm.commitTransaction() realm.commitTransaction()
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.ONLINE))
realm,
Session,
arrayListOf(QueryType.ONLINE)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).bankroll?.run { (sessions[0] as Session).bankroll?.run {
@ -125,16 +109,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(bankroll = b2) Session.testInstance(bankroll = b2)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.BANKROLL val filter = QueryCondition.BANKROLL
val filterElementRow = FilterElementRow.Bankroll(b1) val filterElementRow = FilterElementRow.Bankroll(b1)
filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).bankroll?.run { (sessions[0] as Session).bankroll?.run {
@ -160,19 +140,15 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(bankroll = b3) Session.testInstance(bankroll = b3)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.BANKROLL val filter = QueryCondition.BANKROLL
val filterElementRow = FilterElementRow.Bankroll(b1) val filterElementRow = FilterElementRow.Bankroll(b1)
filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL filterElementRow.filterSectionRow = FilterSectionRow.BANKROLL
val filterElementRow2 = FilterElementRow.Bankroll(b2) val filterElementRow2 = FilterElementRow.Bankroll(b2)
filterElementRow2.filterSectionRow = FilterSectionRow.BANKROLL filterElementRow2.filterSectionRow = FilterSectionRow.BANKROLL
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(6, sessions.size) Assert.assertEquals(6, sessions.size)
@ -193,16 +169,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(game = g2) Session.testInstance(game = g2)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.GAME val filter = QueryCondition.GAME
val filterElementRow = FilterElementRow.Game(g2) val filterElementRow = FilterElementRow.Game(g2)
filterElementRow.filterSectionRow = FilterSectionRow.GAME filterElementRow.filterSectionRow = FilterSectionRow.GAME
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).game?.run { (sessions[0] as Session).game?.run {
@ -228,19 +200,15 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(game = g3) Session.testInstance(game = g3)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.GAME val filter = QueryCondition.GAME
val filterElementRow = FilterElementRow.Game(g2) val filterElementRow = FilterElementRow.Game(g2)
filterElementRow.filterSectionRow = FilterSectionRow.GAME filterElementRow.filterSectionRow = FilterSectionRow.GAME
val filterElementRow2 = FilterElementRow.Game(g3) val filterElementRow2 = FilterElementRow.Game(g3)
filterElementRow2.filterSectionRow = FilterSectionRow.GAME filterElementRow2.filterSectionRow = FilterSectionRow.GAME
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(6, sessions.size) Assert.assertEquals(6, sessions.size)
@ -261,16 +229,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(location = l2) Session.testInstance(location = l2)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.LOCATION val filter = QueryCondition.LOCATION
val filterElementRow = FilterElementRow.Location(l1) val filterElementRow = FilterElementRow.Location(l1)
filterElementRow.filterSectionRow = FilterSectionRow.LOCATION filterElementRow.filterSectionRow = FilterSectionRow.LOCATION
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).location?.run { (sessions[0] as Session).location?.run {
@ -296,20 +260,16 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(location = l3) Session.testInstance(location = l3)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.LOCATION val filter = QueryCondition.LOCATION
val filterElementRow = FilterElementRow.Location(l1) val filterElementRow = FilterElementRow.Location(l1)
filterElementRow.filterSectionRow = FilterSectionRow.LOCATION filterElementRow.filterSectionRow = FilterSectionRow.LOCATION
val filterElementRow2 = FilterElementRow.Location(l3) val filterElementRow2 = FilterElementRow.Location(l3)
filterElementRow2.filterSectionRow = FilterSectionRow.LOCATION filterElementRow2.filterSectionRow = FilterSectionRow.LOCATION
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(6, sessions.size) Assert.assertEquals(6, sessions.size)
@ -330,17 +290,13 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentName = t2) Session.testInstance(tournamentName = t2)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.TOURNAMENT_NAME val filter = QueryCondition.TOURNAMENT_NAME
val filterElementRow = FilterElementRow.TournamentName(t1) val filterElementRow = FilterElementRow.TournamentName(t1)
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).tournamentName?.run { (sessions[0] as Session).tournamentName?.run {
@ -366,18 +322,14 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentName = t3) Session.testInstance(tournamentName = t3)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.TOURNAMENT_NAME val filter = QueryCondition.TOURNAMENT_NAME
val filterElementRow = FilterElementRow.TournamentName(t1) val filterElementRow = FilterElementRow.TournamentName(t1)
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME
val filterElementRow2 = FilterElementRow.TournamentName(t2) val filterElementRow2 = FilterElementRow.TournamentName(t2)
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_NAME
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(6, sessions.size) Assert.assertEquals(6, sessions.size)
@ -406,20 +358,16 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentFeatures = RealmList(t1)) Session.testInstance(tournamentFeatures = RealmList(t1))
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.ALL_TOURNAMENT_FEATURES val filter = QueryCondition.ALL_TOURNAMENT_FEATURES
val filterElementRow = FilterElementRow.AllTournamentFeature(t1) val filterElementRow = FilterElementRow.AllTournamentFeature(t1)
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
val filterElementRow2 = FilterElementRow.AllTournamentFeature(t2) val filterElementRow2 = FilterElementRow.AllTournamentFeature(t2)
filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow2.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
val filterElementRow3 = FilterElementRow.AllTournamentFeature(t4) val filterElementRow3 = FilterElementRow.AllTournamentFeature(t4)
filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)
(sessions[0] as Session).run { (sessions[0] as Session).run {
@ -445,7 +393,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentFeatures = RealmList(t1)) Session.testInstance(tournamentFeatures = RealmList(t1))
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.ANY_TOURNAMENT_FEATURES val filter = QueryCondition.ANY_TOURNAMENT_FEATURES
val filterElementRow = FilterElementRow.AnyTournamentFeature(t1) val filterElementRow = FilterElementRow.AnyTournamentFeature(t1)
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
val filterElementRow2 = FilterElementRow.AnyTournamentFeature(t2) val filterElementRow2 = FilterElementRow.AnyTournamentFeature(t2)
@ -454,13 +402,9 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow3.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
val filterElementRow4 = FilterElementRow.AnyTournamentFeature(t4) val filterElementRow4 = FilterElementRow.AnyTournamentFeature(t4)
filterElementRow4.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow4.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3, filterElementRow4))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2, filterElementRow3, filterElementRow4)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(8, sessions.size) Assert.assertEquals(8, sessions.size)
} }
@ -483,16 +427,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tournamentFeatures = RealmList(t1)) Session.testInstance(tournamentFeatures = RealmList(t1))
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.ANY_TOURNAMENT_FEATURES val filter = QueryCondition.ANY_TOURNAMENT_FEATURES
val filterElementRow = FilterElementRow.AnyTournamentFeature(t2) val filterElementRow = FilterElementRow.AnyTournamentFeature(t2)
filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE filterElementRow.filterSectionRow = FilterSectionRow.TOURNAMENT_FEATURE
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
val result = arrayListOf(s1.id, s2.id, s3.id, s4.id) val result = arrayListOf(s1.id, s2.id, s3.id, s4.id)
@ -512,18 +452,14 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(tableSize = 10) Session.testInstance(tableSize = 10)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.TABLE_SIZE val filter = QueryCondition.TABLE_SIZE
val filterElementRow = FilterElementRow.TableSize(TableSize(2)) val filterElementRow = FilterElementRow.TableSize(TableSize(2))
filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE
val filterElementRow2 = FilterElementRow.TableSize(TableSize(4)) val filterElementRow2 = FilterElementRow.TableSize(TableSize(4))
filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE filterElementRow.filterSectionRow = FilterSectionRow.TABLE_SIZE
filter.updateValueMap(FilterElement(filterElementRows = arrayListOf(filterElementRow, filterElementRow2))) filter.updateValueMap(FilterCondition(filterElementRows = arrayListOf(filterElementRow, filterElementRow2)))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(2, sessions.size) Assert.assertEquals(2, sessions.size)
@ -543,16 +479,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val s2 = Session.testInstance(netResult = 570.0) val s2 = Session.testInstance(netResult = 570.0)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.MORE_THAN_NET_RESULT val filter = QueryCondition.MORE_THAN_NET_RESULT
val filterElementRow = FilterElementRow.ResultMoreThan(204.0) val filterElementRow = FilterElementRow.ResultMoreThan(204.0)
filterElementRow.filterSectionRow = FilterSectionRow.VALUE filterElementRow.filterSectionRow = FilterSectionRow.VALUE
filter.updateValueMap(FilterElement(filterElementRow)) filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(2, sessions.size) Assert.assertEquals(2, sessions.size)
@ -572,16 +504,12 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(netResult = 570.0) Session.testInstance(netResult = 570.0)
realm.commitTransaction() realm.commitTransaction()
val filter = QueryType.LESS_THAN_NET_RESULT val filter = QueryCondition.LESS_THAN_NET_RESULT
val filterElementRow = FilterElementRow.ResultLessThan(540.0) val filterElementRow = FilterElementRow.ResultLessThan(540.0)
filterElementRow.filterSectionRow = FilterSectionRow.VALUE filterElementRow.filterSectionRow = FilterSectionRow.VALUE
filter.updateValueMap(FilterElement(filterElementRow)) filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
realm,
Session,
arrayListOf(filter)
)
Assert.assertEquals(3, sessions.size) Assert.assertEquals(3, sessions.size)
@ -601,21 +529,17 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(netResult = 570.0) Session.testInstance(netResult = 570.0)
realm.commitTransaction() realm.commitTransaction()
val filterMore = QueryType.MORE_THAN_NET_RESULT val filterMore = QueryCondition.MORE_THAN_NET_RESULT
val filterElementRow = FilterElementRow.ResultMoreThan(200.0) val filterElementRow = FilterElementRow.ResultMoreThan(200.0)
filterElementRow.filterSectionRow = FilterSectionRow.VALUE filterElementRow.filterSectionRow = FilterSectionRow.VALUE
filterMore.updateValueMap(FilterElement(filterElementRow)) filterMore.updateValueMap(FilterCondition(filterElementRow))
val filterLess = QueryType.LESS_THAN_NET_RESULT val filterLess = QueryCondition.LESS_THAN_NET_RESULT
val filterElementRow2 = FilterElementRow.ResultLessThan(400.0) val filterElementRow2 = FilterElementRow.ResultLessThan(400.0)
filterElementRow2.filterSectionRow = FilterSectionRow.VALUE filterElementRow2.filterSectionRow = FilterSectionRow.VALUE
filterLess.updateValueMap(FilterElement(filterElementRow2)) filterLess.updateValueMap(FilterCondition(filterElementRow2))
val sessions = Filter.queryOn( val sessions = Filter.queryOn<Session>(realm, arrayListOf(filterMore, filterLess))
realm,
Session,
arrayListOf(filterMore, filterLess)
)
Assert.assertEquals(1, sessions.size) Assert.assertEquals(1, sessions.size)

@ -14,6 +14,10 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/PokerAnalyticsTheme"> android:theme="@style/PokerAnalyticsTheme">
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<activity <activity
android:name=".ui.activity.HomeActivity" android:name=".ui.activity.HomeActivity"
android:label="@string/app_name"> android:label="@string/app_name">
@ -26,39 +30,44 @@
<activity <activity
android:name=".ui.activity.SessionActivity" android:name=".ui.activity.SessionActivity"
android:screenOrientation="portrait"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing" /> android:windowSoftInputMode="adjustNothing" />
<activity <activity
android:name=".ui.activity.DataListActivity" android:name=".ui.activity.DataListActivity"
android:screenOrientation="portrait" android:launchMode="singleTop"
android:launchMode="singleTop" /> android:screenOrientation="portrait" />
<activity <activity
android:name=".ui.activity.EditableDataActivity" android:name=".ui.activity.EditableDataActivity"
android:screenOrientation="portrait" android:launchMode="singleTop"
android:launchMode="singleTop" /> android:screenOrientation="portrait" />
<activity <activity
android:name=".ui.activity.CurrenciesActivity" android:name=".ui.activity.CurrenciesActivity"
android:screenOrientation="portrait" android:launchMode="singleTop"
android:launchMode="singleTop" /> android:screenOrientation="portrait" />
<activity <activity
android:name=".ui.activity.FiltersActivity" android:name=".ui.activity.FiltersActivity"
android:screenOrientation="portrait" android:launchMode="singleTop"
android:launchMode="singleTop" /> android:screenOrientation="portrait" />
<activity <activity
android:name=".ui.activity.FilterDetailsActivity" android:name=".ui.activity.FilterDetailsActivity"
android:screenOrientation="portrait" android:launchMode="singleTop"
android:launchMode="singleTop" /> android:screenOrientation="portrait" />
<activity <activity
android:name=".ui.activity.GDPRActivity" android:name=".ui.activity.GDPRActivity"
android:screenOrientation="portrait" android:launchMode="singleTop"
android:launchMode="singleTop" /> android:screenOrientation="portrait" />
<activity
android:name=".ui.activity.GraphActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<meta-data <meta-data
android:name="preloaded_fonts" android:name="preloaded_fonts"

@ -2,6 +2,7 @@ package net.pokeranalytics.android
import android.app.Application import android.app.Application
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.crashlytics.android.core.CrashlyticsCore
import io.fabric.sdk.android.Fabric import io.fabric.sdk.android.Fabric
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
@ -16,6 +17,7 @@ import net.pokeranalytics.android.util.PokerAnalyticsLogs
import timber.log.Timber import timber.log.Timber
class PokerAnalyticsApplication : Application() { class PokerAnalyticsApplication : Application() {
override fun onCreate() { override fun onCreate() {
@ -25,21 +27,28 @@ class PokerAnalyticsApplication : Application() {
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(0) .schemaVersion(2)
.migration(PokerAnalyticsMigration()) .migration(PokerAnalyticsMigration())
.initialData(Seed(this)) .initialData(Seed(this))
.build() .build()
Realm.setDefaultConfiguration(realmConfiguration) Realm.setDefaultConfiguration(realmConfiguration)
// Set up Crashlytics, disabled for debug builds
val crashlyticsKit = Crashlytics.Builder()
.core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
.build()
// Initialize Fabric with the debug-disabled crashlytics.
Fabric.with(this, crashlyticsKit)
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
// Logs // Logs
Timber.plant(PokerAnalyticsLogs()) Timber.plant(PokerAnalyticsLogs())
} else {
Fabric.with(this, Crashlytics())
} }
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
// this.createFakeSessions() // debug // this.createFakeSessions()
} }
} }

@ -1,7 +1,9 @@
package net.pokeranalytics.android.calculus package net.pokeranalytics.android.calculus
import io.realm.Realm
import net.pokeranalytics.android.calculus.Stat.* import net.pokeranalytics.android.calculus.Stat.*
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 timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -58,26 +60,37 @@ class Calculator {
companion object { companion object {
// fun computePreAggregation(sets: List<SessionSet>, options: Options): List<ComputedResults> { fun computeStatsWithFilters(realm: Realm, filters: List<Filter>, options: Options): List<ComputedResults> {
// Timber.d("sets = ${sets.size}")
// return listOf() var computableGroups: MutableList<ComputableGroup> = mutableListOf()
// } filters.forEach { filter ->
val group = ComputableGroup(filter.name, filter.filterConditions.map { it.queryCondition })
computableGroups.add(group)
}
return Calculator.computeGroups(realm, computableGroups, options)
}
/** /**
* Computes all stats for list of Session sessionGroup * Computes all stats for list of Session sessionGroup
*/ */
fun computeGroups(groups: List<ComputableGroup>, options: Options): List<ComputedResults> { fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options): List<ComputedResults> {
val computedResults = mutableListOf<ComputedResults>() val computedResults = mutableListOf<ComputedResults>()
groups.forEach { group -> groups.forEach { group ->
val s = Date() val s = Date()
// Clean existing computables / sessionSets if group is reused
group.cleanup()
// Computes actual sessionGroup stats // Computes actual sessionGroup stats
val results: ComputedResults = Calculator.compute(group, options = options) val results: ComputedResults = Calculator.compute(realm, group, options = options)
// Computes the compared sessionGroup if existing // Computes the compared sessionGroup if existing
val comparedGroup = group.comparedComputables val comparedGroup = group.comparedComputables
if (comparedGroup != null) { if (comparedGroup != null) {
val comparedResults = Calculator.compute(comparedGroup, options = options) val comparedResults = Calculator.compute(realm, comparedGroup, options = options)
group.comparedComputedResults = comparedResults group.comparedComputedResults = comparedResults
results.computeStatVariations(comparedResults) results.computeStatVariations(comparedResults)
} }
@ -94,29 +107,13 @@ class Calculator {
return computedResults return computedResults
} }
// fun compute(sessionGroup: ComputableGroup, options: Options): ComputedResults {
//
// var sum: Double = sessionGroup.computables.sum("ratedNet").toDouble()
// val results: ComputedResults = ComputedResults(sessionGroup)
//
// results.addStats(
// setOf(
// ComputedStat(NETRESULT, sum)
// )
// )
//
// return results
// }
/** /**
* Computes stats for a SessionSet * Computes stats for a SessionSet
*/ */
fun compute(computableGroup: ComputableGroup, options: Options): ComputedResults { fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options): ComputedResults {
Timber.d(">>>> Start computing group ${computableGroup.name}, ${computableGroup.computables.size} computables") val computables = computableGroup.computables(realm)
Timber.d(">>>> Start computing group ${computableGroup.name}, ${computables.size} computables")
val computables = computableGroup.computables
val results: ComputedResults = ComputedResults(computableGroup) val results: ComputedResults = ComputedResults(computableGroup)
@ -140,30 +137,29 @@ class Calculator {
var tBuyinSum = 0.0 var tBuyinSum = 0.0
var tHands = 0.0 var tHands = 0.0
computables.forEach { s -> computables.forEach { computable ->
index++ index++
tSum += s.ratedNet tSum += computable.ratedNet
tBBSum += s.bbNet tBBSum += computable.bbNet
tBBSessionCount += s.hasBigBlind tBBSessionCount += computable.hasBigBlind
tWinningSessionCount += s.isPositive tWinningSessionCount += computable.isPositive
tBuyinSum += s.ratedBuyin tBuyinSum += computable.ratedBuyin
tHands += s.estimatedHands tHands += computable.estimatedHands
results.addEvolutionValue(tSum, NETRESULT) val session = computable.session ?: throw IllegalStateException("Computing lone ComputableResult")
results.addEvolutionValue(tSum / index, AVERAGE) results.addEvolutionValue(tSum, NETRESULT, session)
results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES) results.addEvolutionValue(tSum / index, AVERAGE, session)
results.addEvolutionValue(tBBSum / tBBSessionCount, AVERAGE_NET_BB) results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES, session)
results.addEvolutionValue((tWinningSessionCount / index).toDouble(), WIN_RATIO) results.addEvolutionValue(tBBSum / tBBSessionCount, AVERAGE_NET_BB, session)
results.addEvolutionValue(tBuyinSum / index, AVERAGE_BUYIN) results.addEvolutionValue((tWinningSessionCount / index).toDouble(), WIN_RATIO, session)
results.addEvolutionValue(tBuyinSum / index, AVERAGE_BUYIN, session)
val netBB100 = Stat.netBBPer100Hands(tBBSum, tHands)
if (netBB100 != null) { Stat.netBBPer100Hands(tBBSum, tHands)?.let { netBB100 ->
results.addEvolutionValue(netBB100, NET_BB_PER_100_HANDS) results.addEvolutionValue(netBB100, NET_BB_PER_100_HANDS, session)
} }
val roi = Stat.returnOnInvestment(tSum, tBuyinSum) Stat.returnOnInvestment(tSum, tBuyinSum)?.let { roi ->
if (roi != null) { results.addEvolutionValue(roi, ROI, session)
results.addEvolutionValue(roi, ROI)
} }
} }
@ -173,16 +169,17 @@ class Calculator {
} }
} }
val sessionSets = computableGroup.sets val sessionSets = computableGroup.sessionSets(realm)
// Compute for each serie // Compute for each serie
val gHourlyDuration = sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours) val gHourlyDuration =
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours)
val gSum = sessionSets.sum(SessionSet.Field.RATED_NET.identifier).toDouble() val gSum = sessionSets.sum(SessionSet.Field.RATED_NET.identifier).toDouble()
val gTotalHands = sessionSets.sum(SessionSet.Field.ESTIMATED_HANDS.identifier).toDouble() val gTotalHands = sessionSets.sum(SessionSet.Field.ESTIMATED_HANDS.identifier).toDouble()
val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble() val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
val hourlyRate = gSum / gHourlyDuration val hourlyRate = gSum / gHourlyDuration
// var hourlyRateBB = gBBSum / gDuration // var bbHourlyRate = gBBSum / gDuration
when (options.evolutionValues) { when (options.evolutionValues) {
Options.EvolutionValues.DATED -> { Options.EvolutionValues.DATED -> {
@ -205,20 +202,18 @@ class Calculator {
tHourlyRate = gSum / tHourlyDuration tHourlyRate = gSum / tHourlyDuration
tHourlyRateBB = gBBSum / tHourlyDuration tHourlyRateBB = gBBSum / tHourlyDuration
results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT) results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, sessionSet)
results.addEvolutionValue(tSum / tHourlyDuration, tHourlyDuration, HOURLY_RATE) results.addEvolutionValue(tSum / tHourlyDuration, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE) results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue(tIndex.toDouble(), tHourlyDuration, NUMBER_OF_SETS) results.addEvolutionValue(tIndex.toDouble(), tHourlyDuration, NUMBER_OF_SETS, sessionSet)
results.addEvolutionValue(sessionSet.netDuration.toDouble(), tHourlyDuration, DURATION) results.addEvolutionValue(sessionSet.netDuration.toDouble(), tHourlyDuration, DURATION, sessionSet)
results.addEvolutionValue(tHourlyDuration / tIndex, tHourlyDuration, AVERAGE_DURATION) results.addEvolutionValue(tHourlyDuration / tIndex, tHourlyDuration, AVERAGE_DURATION, sessionSet)
results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB) results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet)
val netBB100 = Stat.netBBPer100Hands(gBBSum, gTotalHands) Stat.netBBPer100Hands(gBBSum, gTotalHands)?.let { netBB100 ->
if (netBB100 != null) { results.addEvolutionValue(netBB100, tHourlyDuration, NET_BB_PER_100_HANDS, sessionSet)
results.addEvolutionValue(netBB100, tHourlyDuration, NET_BB_PER_100_HANDS)
} else { //@todo maybe not
results.addEvolutionValue(0.0, tHourlyDuration, NET_BB_PER_100_HANDS)
} }
} }
} }
else -> { else -> {
@ -265,13 +260,10 @@ class Calculator {
) )
) )
val roi = Stat.returnOnInvestment(sum, totalBuyin) Stat.returnOnInvestment(sum, totalBuyin)?.let { roi ->
val netBB100 = Stat.netBBPer100Hands(bbSum, totalHands)
if (roi != null) {
results.addStats(setOf(ComputedStat(ROI, roi))) results.addStats(setOf(ComputedStat(ROI, roi)))
} }
if (netBB100 != null) { Stat.netBBPer100Hands(bbSum, totalHands)?.let { netBB100 ->
results.addStats(setOf(ComputedStat(NET_BB_PER_100_HANDS, netBB100))) results.addStats(setOf(ComputedStat(NET_BB_PER_100_HANDS, netBB100)))
} }

@ -1,13 +1,19 @@
package net.pokeranalytics.android.calculus package net.pokeranalytics.android.calculus
import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.data.Entry
import io.realm.Realm
import io.realm.RealmResults import io.realm.RealmResults
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Timed
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
/** /**
* A sessionGroup of computable items identified by a name * A sessionGroup of computable items identified by a name
*/ */
class ComputableGroup(name: String, computables: RealmResults<ComputableResult>, sets: RealmResults<SessionSet>, stats: List<Stat>? = null) { class ComputableGroup(name: String, conditions: List<QueryCondition>, stats: List<Stat>? = null) {
/** /**
* The display name of the group * The display name of the group
@ -15,14 +21,52 @@ class ComputableGroup(name: String, computables: RealmResults<ComputableResult>,
var name: String = name var name: String = name
/** /**
* The list of endedSessions to compute * A list of conditions to get
*/ */
var computables: RealmResults<ComputableResult> = computables var conditions: List<QueryCondition> = conditions
/** /**
* The list of endedSessions to compute * The list of endedSessions to compute
*/ */
var sets: RealmResults<SessionSet> = sets private var _computables: RealmResults<ComputableResult>? = null
/**
* Retrieves the computables on the relative [realm] filtered with the provided [conditions]
*/
fun computables(realm: Realm): RealmResults<ComputableResult> {
// if computables exists and is valid (previous realm not closed)
this._computables?.let {
if (it.isValid) {
return it
}
}
val computables: RealmResults<ComputableResult> = Filter.queryOn(realm, this.conditions)
this._computables = computables
return computables
}
/**
* The list of sets to compute
*/
private var _sessionSets: RealmResults<SessionSet>? = null
/**
* Retrieves the session sets on the relative [realm] filtered with the provided [conditions]
*/
fun sessionSets(realm: Realm): RealmResults<SessionSet> {
// if computables exists and is valid (previous realm not closed)
this._sessionSets?.let {
if (it.isValid) {
return it
}
}
val sets: RealmResults<SessionSet> = Filter.queryOn(realm, this.conditions)
this._sessionSets = sets
return sets
}
/** /**
* The list of stats to display * The list of stats to display
@ -39,6 +83,11 @@ class ComputableGroup(name: String, computables: RealmResults<ComputableResult>,
*/ */
var comparedComputedResults: ComputedResults? = null var comparedComputedResults: ComputedResults? = null
fun cleanup() {
this._computables = null
this._sessionSets = null
}
} }
class ComputedResults(group: ComputableGroup) { class ComputedResults(group: ComputableGroup) {
@ -58,20 +107,24 @@ class ComputedResults(group: ComputableGroup) {
return this._computedStats.values return this._computedStats.values
} }
fun addEvolutionValue(value: Double, stat: Stat) { /**
this._addEvolutionValue(Point(value), stat = stat) * Adds a value to the evolution values
*/
fun addEvolutionValue(value: Double, stat: Stat, data: Any) {
this._addEvolutionValue(Point(value, data), stat = stat)
} }
fun addEvolutionValue(value: Double, duration: Double, stat: Stat) { fun addEvolutionValue(value: Double, duration: Double, stat: Stat, data: Timed) {
this._addEvolutionValue(Point(value, y = duration), stat = stat) stat.underlyingClass = data::class.java
this._addEvolutionValue(Point(value, y = duration, data = data.id), stat = stat)
} }
private fun _addEvolutionValue(point: Point, stat: Stat) { private fun _addEvolutionValue(point: Point, stat: Stat) {
var evolutionValues = this._evolutionValues[stat] val evolutionValues = this._evolutionValues[stat]
if (evolutionValues != null) { if (evolutionValues != null) {
evolutionValues.add(point) evolutionValues.add(point)
} else { } else {
var values: MutableList<Point> = mutableListOf(point) val values: MutableList<Point> = mutableListOf(point)
this._evolutionValues[stat] = values this._evolutionValues[stat] = values
} }
} }
@ -88,7 +141,7 @@ class ComputedResults(group: ComputableGroup) {
fun computeStatVariations(resultsToCompare: ComputedResults) { fun computeStatVariations(resultsToCompare: ComputedResults) {
this._computedStats.keys.forEach { stat -> this._computedStats.keys.forEach { stat ->
var computedStat = this.computedStat(stat) val computedStat = this.computedStat(stat)
val comparedStat = resultsToCompare.computedStat(stat) val comparedStat = resultsToCompare.computedStat(stat)
if (computedStat != null && comparedStat != null) { if (computedStat != null && comparedStat != null) {
computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value
@ -116,12 +169,50 @@ class ComputedResults(group: ComputableGroup) {
return this._computedStats.size return this._computedStats.size
} }
// MPAndroidChart
fun defaultStatEntries(stat: Stat): List<Entry> {
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES -> this.barEntries(stat)
else -> this.singleLineEntries(stat)
}
}
fun singleLineEntries(stat: Stat): List<Entry> {
val entries = mutableListOf<Entry>()
this._evolutionValues[stat]?.let { points ->
points.forEachIndexed { index, p ->
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data))
}
}
return entries
}
fun durationEntries(stat: Stat): List<Entry> {
val entries = mutableListOf<Entry>()
this._evolutionValues[stat]?.let { points ->
points.forEach { p ->
entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data))
}
}
return entries
}
fun barEntries(stat: Stat): List<BarEntry> {
val entries = mutableListOf<BarEntry>()
this._evolutionValues[stat]?.let { points ->
points.forEach { p ->
entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data))
}
}
return entries
}
} }
class Point(x: Double, y: Double) { class Point(val x: Double, val y: Double, val data: Any) {
val x: Double = x
val y: Double = y
constructor(x: Double) : this(x, 1.0) constructor(y: Double, data: Any) : this(0.0, y, data)
} }

@ -1,8 +1,10 @@
package net.pokeranalytics.android.calculus package net.pokeranalytics.android.calculus
import android.content.Context import android.content.Context
import io.realm.RealmModel
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.interfaces.Timed
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.CurrencyUtils import net.pokeranalytics.android.util.CurrencyUtils
@ -11,10 +13,20 @@ import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import java.util.* import java.util.*
class StatFormattingException(message: String) : Exception(message) {
}
interface StatBase : RealmModel {
fun formattedValue(stat: Stat, context: Context): TextFormat
}
/** /**
* An enum representing all the types of Session statistics * An enum representing all the types of Session statistics
*/ */
enum class Stat : RowRepresentable { enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentable {
NETRESULT, NETRESULT,
HOURLY_RATE, HOURLY_RATE,
@ -85,6 +97,48 @@ enum class Stat : RowRepresentable {
} }
} }
/**
* Formats the value of the stat to be suitable for display
*/
fun format(value: Double, currency: Currency? = null, context: Context): TextFormat {
if (value.isNaN()) {
return TextFormat(NULL_TEXT, R.color.white)
}
when (this) {
// Amounts + red/green
Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> {
val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency)
val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(numberFormat.format(value), color)
}
// Red/green numericValues
Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> {
val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(value.formatted(), color)
}
// white integers
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> {
return TextFormat("${value.toInt()}")
} // white durations
Stat.DURATION, Stat.AVERAGE_DURATION -> {
return TextFormat(value.formattedHourlyDuration())
} // red/green percentages
Stat.WIN_RATIO, Stat.ROI -> {
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr
Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY,
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency)
return TextFormat(numberFormat.format(value))
}
else -> throw FormattingException("Stat formatting of ${this.name} not handled")
}
}
val threshold: Double val threshold: Double
get() { get() {
return when (this) { return when (this) {
@ -94,6 +148,23 @@ enum class Stat : RowRepresentable {
} }
fun cumulativeLabelResId(context: Context) : String {
val resId = when (this) {
AVERAGE, AVERAGE_DURATION, NETRESULT, NET_BB_PER_100_HANDS,
HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, WIN_RATIO, HOURLY_RATE -> R.string.average
NETRESULT, DURATION -> R.string.total
STANDARD_DEVIATION -> R.string.net_result
STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
else -> null
}
resId?.let {
return context.getString(it)
} ?: run {
return NULL_TEXT
}
}
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
} }
@ -117,41 +188,7 @@ class ComputedStat(var stat: Stat, var value: Double, var currency: Currency? =
* Formats the value of the stat to be suitable for display * Formats the value of the stat to be suitable for display
*/ */
fun format(context: Context): TextFormat { fun format(context: Context): TextFormat {
return this.stat.format(this.value, this.currency, context)
if (this.value.isNaN()) {
return TextFormat(NULL_TEXT, R.color.white)
}
when (this.stat) {
// Amounts + red/green
Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> {
val numberFormat= CurrencyUtils.getCurrencyFormatter(context, currency)
val color = if (this.value >= this.stat.threshold) R.color.green else R.color.red
return TextFormat(numberFormat.format(this.value), color)
}
// Red/green numericValues
Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> {
val color = if (this.value >= this.stat.threshold) R.color.green else R.color.red
return TextFormat(this.value.formatted(), color)
}
// white integers
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> {
return TextFormat("${value.toInt()}")
} // white durations
Stat.DURATION, Stat.AVERAGE_DURATION -> {
return TextFormat(value.formattedHourlyDuration())
} // red/green percentages
Stat.WIN_RATIO, Stat.ROI -> {
val color = if (value * 100 >= this.stat.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr
Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY,
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val numberFormat= CurrencyUtils.getCurrencyFormatter(context, currency)
return TextFormat(numberFormat.format(this.value))
}
else -> throw FormattingException("Stat formatting of ${this.stat.name} not handled")
}
} }
/** /**

@ -1,23 +1,23 @@
package net.pokeranalytics.android.exceptions package net.pokeranalytics.android.exceptions
class ModelException(message: String) : Exception(message) { import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
} class ModelException(message: String) : Exception(message)
class FormattingException(message: String) : Exception(message)
class FormattingException(message: String) : Exception(message) { class RowRepresentableEditDescriptorException(message: String) : Exception(message)
} class ConfigurationException(message: String) : Exception(message)
class RowRepresentableEditDescriptorException(message: String) : Exception(message) { sealed class PokerAnalyticsException(message: String) : Exception(message) {
object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition")
} object FilterElementUnknownSectionName: PokerAnalyticsException(message = "No filterElement section name was found to identify the queryCondition")
object FilterMissingEntity: PokerAnalyticsException(message = "This filter has no entity initialized")
class FilterValueMapException(message: String) : Exception(message) { object FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable")
init { object QueryValueMapUnknown: PokerAnalyticsException(message = "fieldName is missing")
println("FilterValueMapException(): $message") object QueryTypeUnhandled: PokerAnalyticsException(message = "filter type not handled")
} object QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected")
} object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "filter is empty or null")
data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "filter element '$filterElementRow' type is missing")
class ConfigurationException(message: String) : Exception(message) { data class QueryValueMapMissingKeys(val missingKeys: List<String>) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys")
data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no filter type for $filterElementRow")
} }

@ -6,10 +6,11 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
class StatRepresentable(stat: Stat, computedStat: ComputedStat?) : RowRepresentable { class StatRepresentable(stat: Stat, computedStat: ComputedStat?, groupName: String = "") : RowRepresentable {
var stat: Stat = stat var stat: Stat = stat
var computedStat: ComputedStat? = computedStat var computedStat: ComputedStat? = computedStat
var groupName: String = groupName
override val viewType: Int override val viewType: Int
get() = RowViewType.STAT.ordinal get() = RowViewType.STAT.ordinal

@ -1,6 +1,9 @@
package net.pokeranalytics.android.model.filter package net.pokeranalytics.android.model.filter
import io.realm.RealmObject import io.realm.RealmModel
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
/** /**
* We want to be able to store filters in the database: * We want to be able to store filters in the database:
@ -28,22 +31,45 @@ import io.realm.RealmObject
* *
*/ */
class UnmanagedFilterField(message: String) : Exception(message) {
}
/** /**
* Interface to set at companion object level of a realm object to provide the entity and the fieldName (eg: parameter's path) * Companion-level Interface to indicate an RealmObject class can be filtered and to provide all the fieldNames (eg: parameter's path) needed to be query on.
*/ */
interface Filterable { interface Filterable : RealmModel {
val relatedEntity: Class<out RealmObject>
/** /**
* return the path of the parameter used in the [QueryType] related to this entity * return the path of the parameter used in the [QueryCondition] related to this entity
*/ */
fun fieldNameForQueryType(queryType: QueryType) : String? // fun fieldNameForQueryType(queryCondition: QueryCondition) : String?
}
class FilterHelper {
companion object {
inline fun <reified T : Filterable> fieldNameForQueryType(queryCondition: QueryCondition): String? {
return when (T::class.java) {
Session::class.java -> Session.fieldNameForQueryType(queryCondition)
ComputableResult::class.java -> ComputableResult.fieldNameForQueryType(queryCondition)
SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition)
else -> {
throw UnmanagedFilterField("Filterable type fields are not defined for class ${T::class}")
}
}
}
}
} }
// //
//fun MutableList<Filterable>.filter(filter: FilterElement) : List<Filterable> { //fun MutableList<Filterable>.filter(filter: FilterCondition) : List<Filterable> {
// //
// return this.filter { f -> // return this.filter { f ->
// return@filter true // return@filter true

@ -1,12 +1,13 @@
package net.pokeranalytics.android.model.filter package net.pokeranalytics.android.model.filter
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject
import io.realm.RealmQuery import io.realm.RealmQuery
import net.pokeranalytics.android.exceptions.FilterValueMapException import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.realm.FilterCondition
import net.pokeranalytics.android.model.realm.FilterElementBlind import net.pokeranalytics.android.model.realm.FilterElementBlind
import net.pokeranalytics.android.model.realm.FilterElement
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.extensions.endOfDay
import net.pokeranalytics.android.util.extensions.startOfDay
import java.util.* import java.util.*
@ -16,7 +17,7 @@ import java.util.*
* To handle that, the enum has a public [valueMap] variable * To handle that, the enum has a public [valueMap] variable
* A new type should also set the expected numericValues required in the [filterValuesExpectedKeys] * A new type should also set the expected numericValues required in the [filterValuesExpectedKeys]
*/ */
enum class QueryType(private var subType:SubType? = null) { enum class QueryCondition(var operator: Operator? = null) {
LIVE, LIVE,
CASH, CASH,
ONLINE, ONLINE,
@ -31,23 +32,27 @@ enum class QueryType(private var subType:SubType? = null) {
TABLE_SIZE, TABLE_SIZE,
TOURNAMENT_TYPE, TOURNAMENT_TYPE,
BLINDS, BLINDS,
MORE_NUMBER_OF_TABLE(SubType.MORE), LAST_GAMES,
LESS_NUMBER_OF_TABLE(SubType.LESS), LAST_SESSIONS,
BETWEEN_NUMBER_OF_TABLE(SubType.BETWEEN), MORE_NUMBER_OF_TABLE(Operator.MORE),
MORE_THAN_NET_RESULT(SubType.MORE), LESS_NUMBER_OF_TABLE(Operator.LESS),
LESS_THAN_NET_RESULT(SubType.LESS), BETWEEN_NUMBER_OF_TABLE(Operator.BETWEEN),
MORE_THAN_BUY_IN(SubType.MORE), MORE_THAN_NET_RESULT(Operator.MORE),
LESS_THAN_BUY_IN(SubType.LESS), LESS_THAN_NET_RESULT(Operator.LESS),
MORE_THAN_CASH_OUT(SubType.MORE), MORE_THAN_BUY_IN(Operator.MORE),
LESS_THAN_CASH_OUT(SubType.LESS), LESS_THAN_BUY_IN(Operator.LESS),
MORE_THAN_TIPS(SubType.MORE), MORE_THAN_CASH_OUT(Operator.MORE),
LESS_THAN_TIPS(SubType.LESS), LESS_THAN_CASH_OUT(Operator.LESS),
MORE_THAN_NUMBER_OF_PLAYER(SubType.MORE), MORE_THAN_TIPS(Operator.MORE),
LESS_THAN_NUMBER_OF_PLAYER(SubType.LESS), LESS_THAN_TIPS(Operator.LESS),
BETWEEN_NUMBER_OF_PLAYER(SubType.BETWEEN), MORE_THAN_NUMBER_OF_PLAYER(Operator.MORE),
MORE_THAN_TOURNAMENT_FEE(SubType.MORE), LESS_THAN_NUMBER_OF_PLAYER(Operator.LESS),
LESS_THAN_TOURNAMENT_FEE(SubType.LESS), BETWEEN_NUMBER_OF_PLAYER(Operator.BETWEEN),
BETWEEN_TOURNAMENT_FEE(SubType.BETWEEN), MORE_THAN_TOURNAMENT_FEE(Operator.MORE),
LESS_THAN_TOURNAMENT_FEE(Operator.LESS),
BETWEEN_TOURNAMENT_FEE(Operator.BETWEEN),
MIN_RE_BUY(Operator.MORE),
MAX_RE_BUY(Operator.LESS),
// Dates // Dates
STARTED_FROM_DATE, STARTED_FROM_DATE,
@ -59,6 +64,15 @@ enum class QueryType(private var subType:SubType? = null) {
YEAR, YEAR,
WEEK_DAY, WEEK_DAY,
WEEK_END, WEEK_END,
TODAY,
YESTERDAY,
TODAY_AND_YESTERDAY,
THIS_WEEK,
THIS_MONTH,
THIS_YEAR,
PAST_DAYS,
MORE_THAN_DURATION(Operator.MORE),
LESS_THAN_DURATION(Operator.LESS),
CURRENCY, CURRENCY,
CURRENCY_CODE, CURRENCY_CODE,
@ -68,17 +82,33 @@ enum class QueryType(private var subType:SubType? = null) {
; ;
private enum class SubType { enum class Operator {
BETWEEN, BETWEEN,
MORE, MORE,
LESS; LESS;
} }
var valueMap : Map<String, Any?>? = null
get() {
this.filterValuesExpectedKeys?.let { valueMapExceptedKeys ->
field?.let { map ->
val missingKeys = map.keys.filter { !valueMapExceptedKeys.contains(it) }
if (map.keys.size == valueMapExceptedKeys.size && missingKeys.isNotEmpty()) {
throw PokerAnalyticsException.QueryValueMapMissingKeys(missingKeys)
}
} ?: run {
throw PokerAnalyticsException.QueryValueMapUnexpectedValue
}
}
return field
}
private set
private val filterValuesExpectedKeys : Array<String>? private val filterValuesExpectedKeys : Array<String>?
get() { get() {
this.subType?.let { this.operator?.let {
return when (it) { return when (it) {
SubType.BETWEEN -> arrayOf("leftValue", "rightValue") Operator.BETWEEN -> arrayOf("leftValue", "rightValue")
else -> arrayOf("value") else -> arrayOf("value")
} }
} }
@ -98,15 +128,16 @@ enum class QueryType(private var subType:SubType? = null) {
* main method of the enum * main method of the enum
* providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery] * providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery]
*/ */
fun filter(realmQuery: RealmQuery<out RealmObject>, filterable: Filterable): RealmQuery<out RealmObject> { inline fun <reified T : Filterable> filter(realmQuery: RealmQuery<T>): RealmQuery<T> {
when { when {
this == BLINDS -> { this == BLINDS -> {
val smallBlindFieldName = filterable.fieldNameForQueryType(SMALL_BLIND)
val bigBlindFieldName = filterable.fieldNameForQueryType(BIG_BLIND) val smallBlindFieldName = FilterHelper.fieldNameForQueryType<T>(SMALL_BLIND)
val currencyCodeFieldName = filterable.fieldNameForQueryType(CURRENCY_CODE) val bigBlindFieldName = FilterHelper.fieldNameForQueryType<T>(BIG_BLIND)
smallBlindFieldName ?: throw FilterValueMapException("fieldName is missing") val currencyCodeFieldName = FilterHelper.fieldNameForQueryType<T>(CURRENCY_CODE)
bigBlindFieldName ?: throw FilterValueMapException("fieldName is missing") smallBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
currencyCodeFieldName ?: throw FilterValueMapException("fieldName is missing") bigBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
currencyCodeFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
val blinds: RealmList<FilterElementBlind> by valueMap val blinds: RealmList<FilterElementBlind> by valueMap
blinds.forEachIndexed { index, blind -> blinds.forEachIndexed { index, blind ->
@ -123,7 +154,7 @@ enum class QueryType(private var subType:SubType? = null) {
.equalTo(bigBlindFieldName, blind.bb) .equalTo(bigBlindFieldName, blind.bb)
.and() .and()
blind.code?.let { blind.currencyCode?.let {
realmQuery.equalTo(currencyCodeFieldName, it) realmQuery.equalTo(currencyCodeFieldName, it)
} ?: run { } ?: run {
realmQuery.isNull(currencyCodeFieldName) realmQuery.isNull(currencyCodeFieldName)
@ -137,34 +168,31 @@ enum class QueryType(private var subType:SubType? = null) {
} }
return realmQuery return realmQuery
} }
this == ONLINE -> return LIVE.filter(realmQuery.not(), filterable)
this == TOURNAMENT -> return CASH.filter(realmQuery.not(), filterable)
this == WEEK_DAY -> return WEEK_END.filter(realmQuery.not(), filterable)
else -> { else -> {
val fieldName = filterable.fieldNameForQueryType(this)
fieldName ?: throw FilterValueMapException("fieldName is missing")
this.subType?.let { subType -> val fieldName = FilterHelper.fieldNameForQueryType<T>(this)
return when (subType) { fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
SubType.LESS -> {
when (operator) {
Operator.LESS -> {
val value: Double by valueMap val value: Double by valueMap
realmQuery.lessThanOrEqualTo(fieldName, value) return realmQuery.lessThanOrEqualTo(fieldName, value)
} }
SubType.MORE -> { Operator.MORE -> {
val value: Double by valueMap val value: Double by valueMap
realmQuery.greaterThanOrEqualTo(fieldName, value) return realmQuery.greaterThanOrEqualTo(fieldName, value)
} }
SubType.BETWEEN -> { Operator.BETWEEN -> {
val leftValue: Double by valueMap val leftValue: Double by valueMap
val rightValue: Double by valueMap val rightValue: Double by valueMap
realmQuery.between(fieldName, leftValue, rightValue) return realmQuery.between(fieldName, leftValue, rightValue)
}
} }
} }
return when (this) { return when (this) {
LIVE -> realmQuery.equalTo(fieldName, true) LIVE, ONLINE -> realmQuery.equalTo(fieldName, this == LIVE)
CASH -> realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal) CASH -> realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal)
TOURNAMENT -> realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal)
ALL_TOURNAMENT_FEATURES -> { ALL_TOURNAMENT_FEATURES -> {
val ids: Array<String> by valueMap val ids: Array<String> by valueMap
ids.forEach { ids.forEach {
@ -212,11 +240,53 @@ enum class QueryType(private var subType:SubType? = null) {
val year: Int by valueMap val year: Int by valueMap
realmQuery.equalTo(fieldName, year) realmQuery.equalTo(fieldName, year)
} }
WEEK_END -> { WEEK_END, WEEK_DAY -> {
realmQuery.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY)) var query = realmQuery
if (this == WEEK_DAY) {
query = realmQuery.not()
}
query.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY))
}
TODAY -> {
val startDate = Date()
realmQuery.between(fieldName, startDate.startOfDay(), startDate.endOfDay())
}
TODAY_AND_YESTERDAY-> {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.add(Calendar.HOUR_OF_DAY, -24)
realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
}
YESTERDAY -> {
val calendar = Calendar.getInstance()
calendar.time = Date()
calendar.add(Calendar.HOUR_OF_DAY, -24)
realmQuery.between(fieldName, calendar.time.startOfDay(), calendar.time.endOfDay())
}
THIS_WEEK -> {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY)
realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
}
THIS_MONTH -> {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.set(Calendar.DAY_OF_MONTH, 1)
realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
}
THIS_YEAR -> {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.set(Calendar.DAY_OF_YEAR, 1)
realmQuery.between(fieldName, calendar.time.startOfDay(), startDate.endOfDay())
} }
else -> { else -> {
throw FilterValueMapException("filter type not handled") throw PokerAnalyticsException.QueryTypeUnhandled
} }
} }
} }
@ -224,69 +294,43 @@ enum class QueryType(private var subType:SubType? = null) {
} }
fun updateValueMap(filterElement: FilterElement) { fun updateValueMap(filterCondition: FilterCondition) {
if (filterValuesExpectedKeys == null) { if (filterValuesExpectedKeys == null) {
return return
} }
this.subType?.let { subType -> this.operator?.let {
valueMap = when (subType) { valueMap = mapOf("value" to filterCondition.value)
SubType.LESS, SubType.MORE -> {
mapOf("value" to filterElement.value)
}
SubType.BETWEEN -> {
mapOf(
"leftValue" to filterElement.leftValue,
"rightValue" to filterElement.rightValue
)
}
}
return return
} }
when (this) { when (this) {
ALL_TOURNAMENT_FEATURES, ANY_TOURNAMENT_FEATURES, BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> { ALL_TOURNAMENT_FEATURES, ANY_TOURNAMENT_FEATURES, BANKROLL, GAME, LOCATION, TOURNAMENT_NAME -> {
valueMap = mapOf("ids" to filterElement.ids) valueMap = mapOf("ids" to filterCondition.ids)
} }
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> { LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> {
valueMap = mapOf("values" to filterElement.values) valueMap = mapOf("values" to filterCondition.values)
} }
BLINDS -> { BLINDS -> {
valueMap = mapOf("blinds" to filterElement.blinds) valueMap = mapOf("blinds" to filterCondition.blinds)
} }
STARTED_FROM_DATE, STARTED_TO_DATE, ENDED_FROM_DATE, ENDED_TO_DATE -> { STARTED_FROM_DATE, STARTED_TO_DATE, ENDED_FROM_DATE, ENDED_TO_DATE -> {
valueMap = mapOf("date" to filterElement.date) valueMap = mapOf("date" to filterCondition.date)
} }
DAY_OF_WEEK -> { DAY_OF_WEEK -> {
valueMap = mapOf("dayOfWeek" to filterElement.dayOfWeek) valueMap = mapOf("dayOfWeek" to filterCondition.dayOfWeek)
} }
MONTH -> { MONTH -> {
valueMap = mapOf("month" to filterElement.month) valueMap = mapOf("month" to filterCondition.month)
} }
YEAR -> { YEAR -> {
valueMap = mapOf("year" to filterElement.year) valueMap = mapOf("year" to filterCondition.year)
} }
else -> { else -> {
throw FilterValueMapException("filter type not handled") throw PokerAnalyticsException.QueryValueMapUnexpectedValue
} }
} }
} }
var valueMap : Map<String, Any?>? = null
get() {
this.filterValuesExpectedKeys?.let { valueMapExceptedKeys ->
field?.let { map ->
val missingKeys = map.keys.filter { !valueMapExceptedKeys.contains(it) }
if (map.keys.size == valueMapExceptedKeys.size && missingKeys.isNotEmpty()) {
throw FilterValueMapException("valueMap does not contain $missingKeys")
}
} ?: run {
throw FilterValueMapException("valueMap null not expected")
}
}
return field
}
private set
} }

@ -1,8 +1,9 @@
package net.pokeranalytics.android.model.interfaces package net.pokeranalytics.android.model.interfaces
import net.pokeranalytics.android.calculus.StatBase
import java.util.* import java.util.*
interface Timed { interface Timed : StatBase, Identifiable {
fun startDate() : Date? fun startDate() : Date?

@ -15,41 +15,36 @@ class PokerAnalyticsMigration : RealmMigration {
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: Add a new class. // Migrate to version 1
// Example:
// public Person extends RealmObject {
// private String name;
// private int age;
// // getters and setters left out for brevity
// }
/*
if (currentVersion == 0) { if (currentVersion == 0) {
Timber.d("*** Running migration 1") Timber.d("*** Running migration 1")
schema.get("Session")!!
.addField("isUpdating", Boolean::class.java) schema.get("Filter")?.let {
it.addField("entityType", Int::class.java).setNullable("entityType", true)
}
schema.get("FilterElement")?.let {
it.setNullable("filterName", true)
it.setNullable("sectionName", true)
}
schema.get("FilterElementBlind")?.let {
it.renameField("code", "currencyCode")
}
currentVersion++ currentVersion++
} }
*/
// Migrate to version 2
// Migrate to version 2: Add a primary key + object references
// Example:
// public Person extends RealmObject {
// private String name;
// @PrimaryKey
// private int age;
// private Dog favoriteDog;
// private RealmList<Dog> dogs;
// // getters and setters left out for brevity
// }
/*
if (currentVersion == 1) { if (currentVersion == 1) {
schema.get("Person")!! Timber.d("*** Running migration ${currentVersion + 1}")
.addField("id", Long::class.javaPrimitiveType!!, FieldAttribute.PRIMARY_KEY)
.addRealmObjectField("favoriteDog", schema.get("Dog")!!) schema.rename("FilterElement", "FilterCondition")
.addRealmListField("dogs", schema.get("Dog")!!) schema.get("Filter")?.let {
it.renameField("filterElements", "filterConditions")
}
schema.get("SessionSet")?.let {
it.addPrimaryKey("id")
}
currentVersion++ currentVersion++
} }
*/
} }

@ -6,8 +6,8 @@ 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
@ -70,7 +70,13 @@ open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDat
} }
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? { override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf("defaultValue" to this.name)) return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name))
BankrollRow.RATE -> row.editingDescriptors(mapOf())
else -> {
row.editingDescriptors(mapOf())
}
}
} }
override fun updateValue(value: Any?, row: RowRepresentable) { override fun updateValue(value: Any?, row: RowRepresentable) {

@ -2,12 +2,14 @@ package net.pokeranalytics.android.model.realm
import io.realm.RealmObject import io.realm.RealmObject
import net.pokeranalytics.android.calculus.interfaces.Computable import net.pokeranalytics.android.calculus.interfaces.Computable
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
open class ComputableResult : RealmObject(), Computable { open class ComputableResult() : RealmObject(), Computable, Filterable {
override var ratedNet: Double = 0.0 override var ratedNet: Double = 0.0
override var bbNet: Double = 0.0 override var bbNet: BB = 0.0
override var hasBigBlind: Int = 0 override var hasBigBlind: Int = 0
@ -17,7 +19,7 @@ open class ComputableResult : RealmObject(), Computable {
override var estimatedHands: Double = 0.0 override var estimatedHands: Double = 0.0
override var bbPer100Hands: Double = 0.0 override var bbPer100Hands: BB = 0.0
override var sessionSet: SessionSet? = null override var sessionSet: SessionSet? = null
@ -52,4 +54,12 @@ open class ComputableResult : RealmObject(), Computable {
BB_PER100HANDS("bbPer100Hands") BB_PER100HANDS("bbPer100Hands")
} }
companion object {
fun fieldNameForQueryType(queryCondition: QueryCondition): String? {
return "session." + Session.fieldNameForQueryType(queryCondition)
}
}
} }

@ -2,28 +2,51 @@ package net.pokeranalytics.android.model.realm
import io.realm.* import io.realm.*
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryType import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow 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 org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import timber.log.Timber
import java.util.* import java.util.*
/** /**
* A [Filter] is the top level representation of the filtering system * A [Filter] is the top level representation of the filtering system
* It contains a list of [FilterElement] 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() {
private var entityType: Int? = Entity.SESSION.ordinal
private enum class Entity {
SESSION,
;
}
companion object { companion object {
// Create a new instance
fun newInstance(realm: Realm): Filter {
val filter = Filter()
return realm.copyToRealm(filter)
}
// Get a filter by its id
fun getFilterBydId(realm: Realm, filterId: String): Filter? {
return realm.where<Filter>().equalTo("id", filterId).findFirst()
}
@TestOnly @TestOnly
fun queryOn(realm: Realm, entity: Filterable, queries:List<QueryType>): RealmResults<*> { inline fun <reified T : Filterable> queryOn(realm: Realm, queries: List<QueryCondition>): RealmResults<T> {
var realmQuery : RealmQuery<out RealmObject> = realm.where(entity.relatedEntity) var realmQuery = realm.where<T>()
queries.forEach { queries.forEach {
realmQuery = (it.filter(realmQuery, entity)) realmQuery = it.filter(realmQuery)
} }
Timber.d(">>> Filter query: ${realmQuery.description}")
return realmQuery.findAll() return realmQuery.findAll()
} }
} }
@ -38,44 +61,38 @@ open class Filter : RealmObject() {
// for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters // for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0) val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0)
var filterElements: RealmList<FilterElement> = RealmList() var filterConditions: RealmList<FilterCondition> = RealmList()
private set private set
fun createOrUpdateFilterElements(filterElementRows: ArrayList<FilterElementRow>) { fun createOrUpdateFilterConditions(filterConditionRows: ArrayList<FilterElementRow>) {
filterElements.clear() filterConditions.clear()
filterElementRows filterConditionRows
.map { .map {
it.filterSectionRow it.filterName
} }
.distinct() .distinct()
.forEach { section -> .forEach { filterName->
filterElementRows filterConditionRows
.filter { .filter {
it.filterSectionRow == section it.filterName == filterName
} }
.apply { .apply {
if (this.size == 1) {
filterElements.add(FilterElement(this.first()))
} else {
val casted = arrayListOf<FilterElementRow>() val casted = arrayListOf<FilterElementRow>()
casted.addAll(this) casted.addAll(this)
filterElements.add(FilterElement(casted)) filterConditions.add(FilterCondition(casted))
}
} }
} }
} }
fun countBy(filterCategoryRow: FilterCategoryRow): Int { fun countBy(filterCategoryRow: FilterCategoryRow): Int {
val sections = filterCategoryRow.filterSectionRows val sections = filterCategoryRow.filterSectionRows
return filterElements.count { return filterConditions.count {
sections.contains(FilterSectionRow.valueOf(it.sectionName)) sections.contains(FilterSectionRow.valueOf(it.sectionName ?: throw PokerAnalyticsException.FilterElementUnknownSectionName))
} }
} }
fun contains(filterElementRow: FilterElementRow): Boolean { fun contains(filterElementRow: FilterElementRow): Boolean {
val filtered = filterElements.filter { val filtered = filterConditions.filter {
it.filterName == filterElementRow.filterName it.filterName == filterElementRow.filterName
} }
if (filtered.isEmpty()) { if (filtered.isEmpty()) {
@ -84,14 +101,45 @@ open class Filter : RealmObject() {
return filterElementRow.contains(filtered) return filterElementRow.contains(filtered)
} }
fun queryOn(entity: Filterable) : RealmResults<*> { /**
var realmQuery : RealmQuery<out RealmObject> = realm.where(entity.relatedEntity) * Set the saved value in the filter for the given [filterElementRow]
this.filterElements.map { */
it.queryType fun setSavedValueForElement(filterElementRow: FilterElementRow) {
when (filterElementRow) {
is FilterElementRow.PastDays -> {
val values = getSavedValueForElement(filterElementRow) as Array<*>
if (values.isNotEmpty() && values.first() is Int) {
filterElementRow.lastDays = values.first() as Int
}
}
is FilterElementRow.DateFilterElementRow -> filterElementRow.dateValue = getSavedValueForElement(filterElementRow) as Date? ?: Date()
}
}
/**
* Get the saved value for the given [filterElementRow]
*/
private fun getSavedValueForElement(filterElementRow: FilterElementRow): Any? {
val filtered = filterConditions.filter {
it.filterName == filterElementRow.filterName
}
if (filtered.isNotEmpty()) {
return filtered.first().getFilterConditionValue(filterElementRow)
}
return null
}
inline fun <reified T : Filterable> results(): RealmResults<T> {
var realmQuery = realm.where<T>()
this.filterConditions.map {
it.queryCondition
}.forEach { }.forEach {
realmQuery = (it.filter(realmQuery, entity)) realmQuery = it.filter(realmQuery)
} }
return realmQuery.findAll() return realmQuery.findAll()
} }
} }

@ -0,0 +1,125 @@
package net.pokeranalytics.android.model.realm
import io.realm.RealmList
import io.realm.RealmObject
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow.*
import java.util.*
open class FilterCondition() : RealmObject() {
private constructor(filterName:String, sectionName:String) : this() {
this.filterName = filterName
this.sectionName = sectionName
}
constructor(filterElementRows: ArrayList<FilterElementRow>) : this(filterElementRows.first().filterName, filterElementRows.first().filterSectionRow.name) {
val row = filterElementRows.first()
this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName
when (row) {
is DateFilterElementRow -> {
this.dateValue = row.dateValue
}
is StringFilterElementRow -> {
this.stringValues = RealmList<String>().apply {
this.addAll(filterElementRows.map {
(it as StringFilterElementRow).stringValue
})
}
}
is NumericFilterElementRow -> {
this.numericValues = RealmList<Double>().apply {
this.addAll(filterElementRows.map {
(it as NumericFilterElementRow).doubleValue
})
}
}
is FilterElementBlind -> {
this.blindValues = RealmList<FilterElementBlind>().apply {
this.addAll(filterElementRows.map {
FilterElementBlind((it as FilterElementRow.Blind).sb, it.bb, it.code)
})
}
}
}
}
var filterName: String? = null
var sectionName: String? = null
val queryCondition : QueryCondition
get() = QueryCondition.valueOf(this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName)
.apply {
this.updateValueMap(this@FilterCondition)
}
private var numericValues: RealmList<Double>? = null
private var dateValue: Date? = null
private var stringValues: RealmList<String>? = null
private var blindValues: RealmList<FilterElementBlind>? = null
val ids: Array<String>
get() = stringValues?.toTypedArray() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
val blinds: RealmList<FilterElementBlind>
get() {
blindValues?.let {
if (it.isNotEmpty()) {
return it
} else {
throw PokerAnalyticsException.FilterElementExpectedValueMissing
}
}
throw PokerAnalyticsException.FilterElementExpectedValueMissing
}
val date: Date
get() = dateValue ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
val values: Array<Int>
get() = numericValues?.map {
it.toInt()
}?.toTypedArray() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
val value: Double
get() = numericValues?.first() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
val leftValue: Double
get() = numericValues?.first() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
val rightValue: Double
get() = numericValues?.last() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
val dayOfWeek: Int
get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
val month: Int
get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
val year: Int
get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing
/**
* Return the value associated with the given [filterElementRow]
*/
fun getFilterConditionValue(filterElementRow: FilterElementRow): Any? {
return when (filterElementRow) {
is From, is To -> dateValue //TODO: Probably change by 'date' (doesn't work now because the value isn't correctly saved
is PastDays -> values
else -> throw PokerAnalyticsException.FilterElementTypeMissing(filterElementRow)
}
}
}

@ -1,144 +0,0 @@
package net.pokeranalytics.android.model.realm
import io.realm.RealmList
import io.realm.RealmObject
import net.pokeranalytics.android.exceptions.FilterValueMapException
import net.pokeranalytics.android.model.filter.QueryType
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow.*
import java.util.*
import kotlin.collections.ArrayList
open class FilterElement(var filterName : String = "", var sectionName: String = "") : RealmObject() {
constructor(filterElementRows: ArrayList<FilterElementRow>) : this(filterElementRows.first().filterName, filterElementRows.first().filterSectionRow.name) {
this.stringValues = when (QueryType.valueOf(this.filterName)) {
QueryType.GAME, QueryType.BANKROLL, QueryType.TOURNAMENT_NAME, QueryType.ALL_TOURNAMENT_FEATURES, QueryType.ANY_TOURNAMENT_FEATURES, QueryType.LOCATION -> {
RealmList<String>().apply {
this.addAll(filterElementRows.map {
(it as DataFilterElementRow).id
})
}
}
else -> null
}
this.numericValues = when (QueryType.valueOf(filterName)) {
QueryType.LIMIT -> {
RealmList<Double>().apply {
this.addAll(filterElementRows.map {
(it as FilterElementRow.Limit).limit.ordinal.toDouble()
})
}
}
QueryType.TABLE_SIZE -> {
RealmList<Double>().apply {
this.addAll(filterElementRows.map {
(it as FilterElementRow.TableSize).tableSize.numberOfPlayer.toDouble()
})
}
}
QueryType.YEAR, QueryType.MONTH, QueryType.DAY_OF_WEEK -> {
RealmList<Double>().apply {
this.addAll(filterElementRows.map {
(it as SingleValueFilterElementRow).value.toDouble()
})
}
}
QueryType.LESS_THAN_NET_RESULT -> {
RealmList<Double>().apply {
this.addAll(filterElementRows.map {
(it as ResultLessThan).value
})
}
}
QueryType.MORE_THAN_NET_RESULT -> {
RealmList<Double>().apply {
this.addAll(filterElementRows.map {
(it as ResultMoreThan).value
})
}
}
else -> null
}
this.blindValues = when (QueryType.valueOf(filterName)) {
QueryType.BLINDS -> {
RealmList<FilterElementBlind>().apply {
this.addAll(filterElementRows.map {
FilterElementBlind((it as FilterElementRow.Blind).sb, it.bb, it.code)
})
}
}
else -> null
}
}
constructor(filterElementRow:FilterElementRow) : this(arrayListOf(filterElementRow)) {
when (filterElementRow) {
is From -> dateValue = filterElementRow.date
is To -> dateValue= filterElementRow.date
}
}
val queryType : QueryType
get() = QueryType.valueOf(filterName)
.apply {
this.updateValueMap(this@FilterElement)
}
private var numericValues: RealmList<Double>? = null
private var dateValue : Date? = null
private var stringValues : RealmList<String>? = null
private var blindValues : RealmList<FilterElementBlind>? = null
val ids : Array<String>
get() = stringValues?.toTypedArray()?: throw FilterValueMapException("filter type not handled")
val blinds : RealmList<FilterElementBlind>
get() {
blindValues?.let {
if (it.isNotEmpty()) {
return it
} else {
throw FilterValueMapException("filter is empty or null")
}
}
throw FilterValueMapException("filter is empty or null")
}
val date : Date
get() = dateValue?: throw FilterValueMapException("filter type not handled")
val values : Array<Int>
get() = numericValues?.map {
it.toInt()
}?.toTypedArray()?: throw FilterValueMapException("filter type not handled")
val value : Double
get() = numericValues?.first()?: throw FilterValueMapException("filter type not handled")
val leftValue : Double
get() = numericValues?.first()?: throw FilterValueMapException("filter type not handled")
val rightValue : Double
get() = numericValues?.last()?: throw FilterValueMapException("filter type not handled")
val dayOfWeek : Int
get() = numericValues?.first()?.toInt()?: throw FilterValueMapException("filter type not handled")
val month : Int
get() = numericValues?.first()?.toInt()?: throw FilterValueMapException("filter type not handled")
val year : Int
get() = numericValues?.first()?.toInt()?: throw FilterValueMapException("filter type not handled")
}

@ -4,5 +4,5 @@ import io.realm.RealmObject
open class FilterElementBlind(var sb : Double? = null, open class FilterElementBlind(var sb : Double? = null,
var bb : Double? = null, var bb : Double? = null,
var code : String? = null var currencyCode : String? = null
) : RealmObject() ) : RealmObject()

@ -13,6 +13,8 @@ import io.realm.kotlin.where
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedStat import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.calculus.TextFormat
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.LiveData
@ -21,8 +23,8 @@ import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.extensions.SessionState import net.pokeranalytics.android.model.extensions.SessionState
import net.pokeranalytics.android.model.extensions.getState import net.pokeranalytics.android.model.extensions.getState
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryType import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.filter.QueryType.* import net.pokeranalytics.android.model.filter.QueryCondition.*
import net.pokeranalytics.android.model.interfaces.* import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.model.utils.SessionSetManager import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -40,15 +42,18 @@ import java.util.*
import java.util.Currency import java.util.Currency
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
typealias BB = Double
open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed, open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed,
TimeFilterable { TimeFilterable, Filterable {
enum class Type { enum class Type {
CASH_GAME, CASH_GAME,
TOURNAMENT TOURNAMENT
} }
companion object : Filterable { companion object {
fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session { fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session {
val session = Session() val session = Session()
session.result = Result() session.result = Result()
@ -61,12 +66,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return realm.copyToRealm(session) return realm.copyToRealm(session)
} }
override val relatedEntity: Class<out RealmObject> = Session::class.java fun fieldNameForQueryType(queryCondition: QueryCondition): String? {
return when (queryCondition) {
override fun fieldNameForQueryType(queryType: QueryType): String? { LIVE, ONLINE -> "bankroll.live"
return when (queryType) { CASH, TOURNAMENT -> "type"
LIVE -> "bankroll.live"
CASH -> "type"
BANKROLL -> "bankroll.id" BANKROLL -> "bankroll.id"
GAME -> "game.id" GAME -> "game.id"
TOURNAMENT_NAME -> "tournamentName.id" TOURNAMENT_NAME -> "tournamentName.id"
@ -89,9 +92,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
MORE_THAN_TOURNAMENT_FEE, LESS_THAN_TOURNAMENT_FEE, BETWEEN_TOURNAMENT_FEE -> "tournamentEntryFee" MORE_THAN_TOURNAMENT_FEE, LESS_THAN_TOURNAMENT_FEE, BETWEEN_TOURNAMENT_FEE -> "tournamentEntryFee"
STARTED_FROM_DATE, STARTED_TO_DATE -> "startDate" STARTED_FROM_DATE, STARTED_TO_DATE -> "startDate"
ENDED_FROM_DATE, ENDED_TO_DATE -> "endDate" ENDED_FROM_DATE, ENDED_TO_DATE -> "endDate"
DAY_OF_WEEK, WEEK_END -> "dayOfWeek" DAY_OF_WEEK, WEEK_END, WEEK_DAY -> "dayOfWeek"
MONTH -> "month" MONTH -> "month"
YEAR -> "year" YEAR -> "year"
TODAY, YESTERDAY, TODAY_AND_YESTERDAY, THIS_YEAR, THIS_MONTH, THIS_WEEK -> "startDate"
else -> null else -> null
} }
} }
@ -292,7 +296,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
/** /**
* The net result in big blinds * The net result in big blinds
*/ */
val bbNet: Double val bbNet: BB
get() { get() {
val bb = this.cgBigBlind; val result = this.result val bb = this.cgBigBlind; val result = this.result
if (bb != null && result != null) { if (bb != null && result != null) {
@ -323,8 +327,8 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
val computableResult = realm.createObject(ComputableResult::class.java) val computableResult = realm.createObject(ComputableResult::class.java)
computableResult.session = this computableResult.session = this
} // if a ComputableResult exists and the session is not completed, delete it } // if a ComputableResult exists and the session is not completed, delete it
else if (this.startDate == null || this.endDate == null) { else if ((this.startDate == null || this.endDate == null) && this.computableResult != null && this.computableResult.isValid) {
this.computableResult?.deleteFromRealm() this.computableResult.deleteFromRealm()
} }
// Update the ComputableResult // Update the ComputableResult
@ -366,6 +370,11 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
throw ModelException("Session should have an existing Result relationship") throw ModelException("Session should have an existing Result relationship")
} }
val bbHourlyRate: BB
get() {
return this.bbNet / this.hourlyDuration
}
// Manageable // Manageable
override fun isValidForSave(): Boolean { override fun isValidForSave(): Boolean {
@ -443,9 +452,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/ */
fun restart() { fun restart() {
realm.executeTransaction { realm.executeTransaction {
// this.timeFrame?.paused = false
this.pauseDate = null this.pauseDate = null
this.startDate = Date() // timeFrame?.setDate(Date(), null) this.startDate = Date()
this.endDate = null
this.breakDuration = 0L this.breakDuration = 0L
} }
} }
@ -517,7 +526,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/ */
fun cleanup() { fun cleanup() {
this.sessionSet?.let { set -> this.sessionSet?.let {
// Updates the timeline // Updates the timeline
SessionSetManager.removeFromTimeline(this) SessionSetManager.removeFromTimeline(this)
@ -718,7 +727,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
override fun updateValue(value: Any?, row: RowRepresentable) { override fun updateValue(value: Any?, row: RowRepresentable) {
realm.beginTransaction()
realm.executeTransaction {
when (row) { when (row) {
SessionRow.BANKROLL -> bankroll = value as Bankroll? SessionRow.BANKROLL -> bankroll = value as Bankroll?
SessionRow.BLINDS -> if (value is ArrayList<*>) { SessionRow.BLINDS -> if (value is ArrayList<*>) {
@ -818,7 +829,48 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
tournamentFeatures.addAll((it as ArrayList<TournamentFeature>)) tournamentFeatures.addAll((it as ArrayList<TournamentFeature>))
} }
} }
realm.commitTransaction()
} }
}
// Stat Base
override fun formattedValue(stat: Stat, context: Context) : TextFormat {
this.result?.let { result ->
val value: Double? = when (stat) {
Stat.NETRESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> result.net
Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> 1.0
Stat.AVERAGE_BUYIN -> result.buyin
Stat.ROI -> {
result.buyin?.let {
Stat.returnOnInvestment(result.net, it)
} ?: run {
null
}
}
Stat.HOURLY_RATE_BB -> this.bbHourlyRate
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> Stat.netBBPer100Hands(this.bbNet, this.estimatedHands)
Stat.AVERAGE_NET_BB -> this.bbNet
Stat.DURATION, Stat.AVERAGE_DURATION -> this.netDuration.toDouble()
Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate
Stat.HANDS_PLAYED -> this.estimatedHands
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
value?.let {
return stat.format(it, CurrencyUtils.getCurrency(this.bankroll), context)
} ?: run {
return TextFormat(NULL_TEXT)
}
} ?: run {
throw java.lang.IllegalStateException("Asking for stats on Session without Result")
}
}
} }

@ -1,15 +1,25 @@
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.Ignore
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.model.interfaces.Timed
import java.util.* import java.util.*
open class SessionSet : RealmObject(), Timed { open class SessionSet() : RealmObject(), Timed, Filterable {
@PrimaryKey
override var id = UUID.randomUUID().toString()
var startDate: Date = Date() var startDate: Date = Date()
set(value) { set(value) {
@ -47,15 +57,6 @@ open class SessionSet : RealmObject(), Timed {
*/ */
override var netDuration: Long = 0L override var netDuration: Long = 0L
companion object {
fun newInstance(realm: Realm) : SessionSet {
val sessionSet: SessionSet = realm.createObject(SessionSet::class.java)
return realm.copyToRealm(sessionSet)
}
}
fun computeStats() { fun computeStats() {
this.ratedNet = this.sessions?.sumByDouble { it.computableResult?.ratedNet ?: 0.0 } ?: 0.0 this.ratedNet = this.sessions?.sumByDouble { it.computableResult?.ratedNet ?: 0.0 } ?: 0.0
this.estimatedHands = this.sessions?.sumByDouble { it.estimatedHands } ?: 0.0 this.estimatedHands = this.sessions?.sumByDouble { it.estimatedHands } ?: 0.0
@ -75,7 +76,17 @@ open class SessionSet : RealmObject(), Timed {
var estimatedHands: Double = 0.0 var estimatedHands: Double = 0.0
var bbNet: Double = 0.0 var bbNet: BB = 0.0
override fun formattedValue(stat: Stat, context: Context) : TextFormat {
return when (stat) {
Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, null, context)
Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), null, context)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, null, context)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, null, context)
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
}
enum class Field(val identifier: String) { enum class Field(val identifier: String) {
RATED_NET("ratedNet"), RATED_NET("ratedNet"),
@ -85,5 +96,18 @@ open class SessionSet : RealmObject(), Timed {
NET_DURATION("netDuration") NET_DURATION("netDuration")
} }
companion object {
fun newInstance(realm: Realm) : SessionSet {
val sessionSet = SessionSet()
return realm.copyToRealm(sessionSet)
}
fun fieldNameForQueryType(queryCondition: QueryCondition): String? {
return "sessions." + Session.fieldNameForQueryType(queryCondition)
}
}
} }

@ -26,22 +26,22 @@ fun Session.parameterRepresentation(context: Context): String {
*/ */
private fun Session.significantFields(): List<SessionRow> { private fun Session.significantFields(): List<SessionRow> {
when (this.type) { when (this.type) {
Session.Type.CASH_GAME.ordinal -> { Session.Type.TOURNAMENT.ordinal -> {
return listOf( return listOf(
SessionRow.GAME, SessionRow.GAME,
SessionRow.INITIAL_BUY_IN, SessionRow.INITIAL_BUY_IN,
SessionRow.BANKROLL, SessionRow.BANKROLL,
SessionRow.TABLE_SIZE, SessionRow.TABLE_SIZE,
SessionRow.TOURNAMENT_NAME SessionRow.TOURNAMENT_NAME,
SessionRow.TOURNAMENT_TYPE
) )
} }
Session.Type.TOURNAMENT.ordinal -> { Session.Type.CASH_GAME.ordinal -> {
return listOf( return listOf(
SessionRow.GAME, SessionRow.GAME,
SessionRow.BLINDS, SessionRow.BLINDS,
SessionRow.BANKROLL, SessionRow.BANKROLL,
SessionRow.TABLE_SIZE, SessionRow.TABLE_SIZE
SessionRow.TOURNAMENT_TYPE
) )
} }
} }
@ -124,7 +124,7 @@ class FavoriteSessionFinder {
} }
val sortedCounters = counters.values.sortedBy { it.counter } val sortedCounters = counters.values.sortedBy { it.counter }
return sortedCounters.firstOrNull()?.session return sortedCounters.lastOrNull()?.session
} }
} }

@ -2,6 +2,7 @@ package net.pokeranalytics.android.model.utils
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.RealmResults import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.realm.Session 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
@ -28,10 +29,10 @@ class SessionSetManager {
} }
if (session.startDate == null) { if (session.startDate == null) {
throw IllegalStateException("Start date should never be null here") throw ModelException("Start date should never be null here")
} }
if (session.endDate == null) { if (session.endDate == null) {
throw IllegalStateException("End date should never be null here") throw ModelException("End date should never be null here")
} }
val endDate = session.endDate!! // tested above val endDate = session.endDate!! // tested above
val startDate = session.startDate!! val startDate = session.startDate!!

@ -9,8 +9,10 @@ import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.DataListFragment import net.pokeranalytics.android.ui.fragment.DataListFragment
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")
} }
companion object { companion object {

@ -72,4 +72,5 @@ class EditableDataActivity : PokerAnalyticsActivity() {
fragmentTransaction.commit() fragmentTransaction.commit()
fragment.setData(dataType, primaryKey) fragment.setData(dataType, primaryKey)
} }
} }

@ -11,6 +11,7 @@ import net.pokeranalytics.android.ui.fragment.FilterDetailsFragment
class FilterDetailsActivity : PokerAnalyticsActivity() { class FilterDetailsActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) { enum class IntentKey(val keyName: String) {
FILTER_ID("FILTER_ID"),
FILTER_CATEGORY_ORDINAL("FILTER_CATEGORY_ORDINAL") FILTER_CATEGORY_ORDINAL("FILTER_CATEGORY_ORDINAL")
} }
@ -18,8 +19,9 @@ class FilterDetailsActivity : PokerAnalyticsActivity() {
/** /**
* Default constructor * Default constructor
*/ */
fun newInstance(context: Context, filterCategoryOrdinal: Int) { fun newInstance(context: Context, filterId: String, filterCategoryOrdinal: Int) {
val intent = Intent(context, FilterDetailsActivity::class.java) val intent = Intent(context, FilterDetailsActivity::class.java)
intent.putExtra(IntentKey.FILTER_ID.keyName, filterId)
intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal) intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal)
context.startActivity(intent) context.startActivity(intent)
} }
@ -27,8 +29,9 @@ class FilterDetailsActivity : PokerAnalyticsActivity() {
/** /**
* Create a new instance for result * Create a new instance for result
*/ */
fun newInstanceForResult(fragment: Fragment, filterCategoryOrdinal: Int, requestCode: Int) { fun newInstanceForResult(fragment: Fragment, filterId: String, filterCategoryOrdinal: Int, requestCode: Int) {
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_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal) intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal)
fragment.startActivityForResult(intent, requestCode) fragment.startActivityForResult(intent, requestCode)
} }
@ -53,12 +56,13 @@ class FilterDetailsActivity : PokerAnalyticsActivity() {
val fragmentManager = supportFragmentManager val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction() val fragmentTransaction = fragmentManager.beginTransaction()
val filterId = intent.getStringExtra(IntentKey.FILTER_ID.keyName)
val filterCategoryOrdinal = intent.getIntExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, 0) val filterCategoryOrdinal = intent.getIntExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, 0)
fragment = FilterDetailsFragment() fragment = FilterDetailsFragment()
fragmentTransaction.add(R.id.container, fragment) fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit() fragmentTransaction.commit()
fragment.setData(filterCategoryOrdinal) fragment.setData(filterId, filterCategoryOrdinal)
} }

@ -11,8 +11,7 @@ import net.pokeranalytics.android.ui.fragment.FiltersFragment
class FiltersActivity : PokerAnalyticsActivity() { class FiltersActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) { enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"), FILTER_ID("FILTER_ID");
PRIMARY_KEY("PRIMARY_KEY");
} }
private lateinit var fragment: FiltersFragment private lateinit var fragment: FiltersFragment
@ -21,8 +20,11 @@ class FiltersActivity : PokerAnalyticsActivity() {
/** /**
* Default constructor * Default constructor
*/ */
fun newInstance(context: Context) { fun newInstance(context: Context, filterId: String? = null) {
val intent = Intent(context, FiltersActivity::class.java) val intent = Intent(context, FiltersActivity::class.java)
filterId?.let {
intent.putExtra(IntentKey.FILTER_ID.keyName, it)
}
context.startActivity(intent) context.startActivity(intent)
} }
@ -52,14 +54,13 @@ 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)
fragment = FiltersFragment() fragment = FiltersFragment()
fragmentTransaction.add(R.id.container, fragment) fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit() fragmentTransaction.commit()
//TODO: send primary key fragment.setData(filterId)
fragment.setData("")
} }
} }

@ -0,0 +1,69 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.github.mikephil.charting.data.Entry
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.GraphFragment
class GraphParameters(stat: Stat, entries: List<Entry>) {
var stat: Stat = stat
var entries: List<Entry> = entries
}
class GraphActivity : PokerAnalyticsActivity() {
private enum class IntentKey(val keyName: String) {
STAT("STAT"),
ENTRIES("ENTRIES"),
}
companion object {
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects
var parameters: GraphParameters? = null
/**
* Default constructor
*/
fun newInstance(context: Context, stat: Stat, entries: List<Entry>) {
GraphActivity.parameters = GraphParameters(stat, entries)
val intent = Intent(context, GraphActivity::class.java)
context.startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_editable_data)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment = GraphFragment()
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
GraphActivity.parameters?.let {
fragment.setData(it.stat, it.entries)
GraphActivity.parameters = null
} ?: run {
throw Exception("Missing graph parameters")
}
}
}

@ -21,7 +21,7 @@ class HomeActivity : PokerAnalyticsActivity() {
companion object { companion object {
fun newInstance(context: Context, id: Int) { fun newInstance(context: Context, id: Int) {
val intent = Intent(context, HomeActivity::class.java) val intent = Intent(context, HomeActivity::class.java)
intent.putExtra("id", 10) intent.putExtra("id", id)
context.startActivity(intent) context.startActivity(intent)
} }
} }
@ -78,7 +78,7 @@ 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, set -> this.currencies.addChangeListener { t, _ ->
realm.beginTransaction() realm.beginTransaction()
t.forEach { t.forEach {
@ -177,7 +177,7 @@ class HomeActivity : PokerAnalyticsActivity() {
} }
} }
.setNegativeButton(R.string.cancel) { _, which -> .setNegativeButton(R.string.cancel) { _, _ ->
Timber.d("Click on cancel") Timber.d("Click on cancel")
} }

@ -102,7 +102,7 @@ class UnmanagedRowRepresentableException(message: String) : Exception(message) {
* - string * - string
* - booleans * - booleans
* - actionIcon * - actionIcon
* to display the appropriate numericValues in graphical filterElements, such as labels, textfields, switchs... * to display the appropriate numericValues in graphical filterConditions, such as labels, textfields, switchs...
*/ */
interface DisplayableDataSource { interface DisplayableDataSource {

@ -24,6 +24,8 @@ import retrofit2.Call
import retrofit2.Response import retrofit2.Response
import java.util.* import java.util.*
/** /**
* Custom EditableDataFragment to manage the Bankroll data * Custom EditableDataFragment to manage the Bankroll data
*/ */
@ -44,7 +46,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
private var isRefreshingRate = false private var isRefreshingRate = false
private var lastRefreshRateCall = 0L private var lastRefreshRateCall = 0L
private val differentCurrency : Boolean private val shouldShowCurrencyRate : Boolean
get() { get() {
return bankroll.currency?.let { bankrollCurrency -> return bankroll.currency?.let { bankrollCurrency ->
bankrollCurrency.code != defaultCurrency.currencyCode bankrollCurrency.code != defaultCurrency.currencyCode
@ -67,7 +69,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
data?.let { data?.let {
val currencyCode = it.getStringExtra(CurrenciesFragment.INTENT_CURRENCY_CODE) val currencyCode = it.getStringExtra(CurrenciesFragment.INTENT_CURRENCY_CODE)
onRowValueChanged(currencyCode, BankrollRow.CURRENCY) onRowValueChanged(currencyCode, BankrollRow.CURRENCY)
if (differentCurrency) { if (shouldShowCurrencyRate) {
refreshRate() refreshRate()
} }
} }
@ -119,7 +121,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
this.bankroll.currency?.rate?.let { rate -> this.bankroll.currency?.rate?.let { rate ->
row.editingDescriptors(mapOf("defaultValue" to CurrencyUtils.getCurrencyRateFormatter().format(rate))) row.editingDescriptors(mapOf("defaultValue" to CurrencyUtils.getCurrencyRateFormatter().format(rate)))
} ?: run { } ?: run {
row.editingDescriptors(mapOf("defaultValue" to "")) row.editingDescriptors(mapOf())
} }
} }
else -> null else -> null
@ -169,7 +171,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
rows.add(BankrollRow.LIVE) rows.add(BankrollRow.LIVE)
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency)) rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency))
rows.add(BankrollRow.CURRENCY) rows.add(BankrollRow.CURRENCY)
if (this.differentCurrency) { if (this.shouldShowCurrencyRate) {
rows.add(BankrollRow.RATE) rows.add(BankrollRow.RATE)
rows.add(BankrollRow.REFRESH_RATE) rows.add(BankrollRow.REFRESH_RATE)
} }

@ -1,5 +1,7 @@
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
@ -11,9 +13,14 @@ import com.google.android.material.snackbar.Snackbar
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
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.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.ui.activity.EditableDataActivity import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource
@ -28,12 +35,17 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow
class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate { class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_DETAILS = 1000
}
private lateinit var dataType: SettingRow private lateinit var dataType: SettingRow
private lateinit var items: RealmResults<*> private lateinit var items: RealmResults<*>
private lateinit var dataListAdapter: RowRepresentableAdapter private lateinit var dataListAdapter: RowRepresentableAdapter
private var deletedItem: RealmObject? = null private var deletedItem: RealmObject? = null
private var lastDeletedItemPosition: Int = 0 private var lastDeletedItemPosition: Int = 0
private var lastItemClickedPosition: Int = 0
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_data_list, container, false) return inflater.inflate(R.layout.fragment_data_list, container, false)
@ -44,6 +56,19 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
initUI() initUI()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_DETAILS && resultCode == Activity.RESULT_OK) {
val needToDeleteItem = data?.getBooleanExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, false) ?: false
if (needToDeleteItem) {
GlobalScope.launch(Dispatchers.Main) {
delay(300)
deleteItem(lastItemClickedPosition)
}
}
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
this.recyclerView?.adapter?.notifyDataSetChanged() this.recyclerView?.adapter?.notifyDataSetChanged()
@ -68,10 +93,12 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
this.dataType.relatedResultsRepresentable?.let { this.dataType.relatedResultsRepresentable?.let {
EditableDataActivity.newInstance( lastItemClickedPosition = position
requireContext(), EditableDataActivity.newInstanceForResult(
this,
it.ordinal, it.ordinal,
(row as Identifiable).id (row as Identifiable).id,
REQUEST_CODE_DETAILS
) )
} }
} }
@ -94,6 +121,37 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
dataListAdapter = RowRepresentableAdapter(this, this) dataListAdapter = RowRepresentableAdapter(this, this)
val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position -> val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position ->
deleteItem(position)
}
val itemTouchHelper = ItemTouchHelper(swipeToDelete)
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = dataListAdapter
itemTouchHelper.attachToRecyclerView(this)
}
this.addButton.setOnClickListener {
this.dataType.relatedResultsRepresentable?.let {
EditableDataActivity.newInstance(
requireContext(),
dataType = it.ordinal,
primaryKey = null
)
}
}
}
/**
* Delete item
*/
private fun deleteItem(position: Int) {
if (isDetached || activity == null) {
return
}
// Save the delete position & create a copy of the object // Save the delete position & create a copy of the object
val mRecentlyDeletedItem = rowRepresentableForPosition(position) val mRecentlyDeletedItem = rowRepresentableForPosition(position)
@ -119,32 +177,12 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
} }
} }
val itemTouchHelper = ItemTouchHelper(swipeToDelete)
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = dataListAdapter
itemTouchHelper.attachToRecyclerView(this)
}
this.addButton.setOnClickListener {
this.dataType.relatedResultsRepresentable?.let {
EditableDataActivity.newInstance(
requireContext(),
dataType = it.ordinal,
primaryKey = null
)
}
}
}
/** /**
* Show undo snack bar * Show undo snack bar
*/ */
private fun showUndoSnackBar() { private fun showUndoSnackBar() {
val message = String.format(getString(R.string.data_deleted), this.dataType.localizedTitle(requireContext())) val message = String.format(getString(R.string.data_deleted), this.dataType.localizedTitle(requireContext()))
val snackBar = Snackbar.make(constraintLayout, message, Snackbar.LENGTH_LONG) val snackBar = Snackbar.make(constraintLayout, message, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction(R.string.cancel) { snackBar.setAction(R.string.cancel) {
getRealm().executeTransaction { realm -> getRealm().executeTransaction { realm ->
deletedItem?.let { deletedItem?.let {

@ -16,6 +16,7 @@ import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Editable import net.pokeranalytics.android.model.interfaces.Editable
import net.pokeranalytics.android.model.interfaces.Savable import net.pokeranalytics.android.model.interfaces.Savable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus 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.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -185,18 +186,15 @@ open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDele
* Delete data * Delete data
*/ */
private fun deleteData() { private fun deleteData() {
val builder = AlertDialog.Builder(requireContext())
builder
.setMessage(R.string.are_you_sure_you_want_to_delete)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.yes) { _, _ ->
val deletable = this.item as Deletable val deletable = this.item as Deletable
val realm = this.getRealm() val realm = this.getRealm()
if (deletable.isValidForDelete(realm)) { if (deletable.isValidForDelete(realm)) {
realm.executeTransaction { val intent = Intent()
this.liveDataType.deleteData(it, deletable) intent.putExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, true)
} activity?.setResult(RESULT_OK, intent)
this.activity?.finish() activity?.finish()
} else { } else {
val message = deletable.getFailedDeleteMessage() val message = deletable.getFailedDeleteMessage()
val builder = AlertDialog.Builder(requireContext()) val builder = AlertDialog.Builder(requireContext())
@ -205,8 +203,6 @@ open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDele
builder.show() builder.show()
} }
} }
builder.show()
}
/** /**
* Finish the activity with a result * Finish the activity with a result

@ -1,46 +1,52 @@
package net.pokeranalytics.android.ui.fragment package net.pokeranalytics.android.ui.fragment
import android.app.Activity.RESULT_OK
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.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
import io.realm.RealmObject
import kotlinx.android.synthetic.main.fragment_filter_details.* import kotlinx.android.synthetic.main.fragment_filter_details.*
import kotlinx.android.synthetic.main.fragment_filter_details.view.* import kotlinx.android.synthetic.main.fragment_filter_details.view.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.FilterDetailsActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.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.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.helpers.DateTimePickerManager
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.FilterCategoryRow 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.extensions.shortDate
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
open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
lateinit var parentActivity: PokerAnalyticsActivity lateinit var parentActivity: PokerAnalyticsActivity
lateinit var item: RealmObject
lateinit var rowRepresentableAdapter: RowRepresentableAdapter lateinit var rowRepresentableAdapter: RowRepresentableAdapter
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 primaryKey: String? = null
private var filterMenu: Menu? = null private var filterMenu: Menu? = null
private var filterCategoryRow: FilterCategoryRow? = null private var filterCategoryRow: FilterCategoryRow? = null
private val selectedRows = ArrayList<FilterElementRow>()
private var isUpdating = false
val selectedRows = ArrayList<FilterElementRow>() private var shouldOpenKeyboard = true
var isUpdating = false
var shouldOpenKeyboard = true
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_filter_details, container, false) return inflater.inflate(R.layout.fragment_filter_details, container, false)
@ -60,41 +66,52 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
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)
val oldRows = ArrayList<RowRepresentable>() Timber.d("Row: $row")
oldRows.addAll(rows)
if (selectedRows.contains(row)) { when (row) {
selectedRows.remove(row) is FilterElementRow.DateFilterElementRow -> DateTimePickerManager.create(requireContext(), row, this, row.dateValue, onlyDate = true)
} else { is FilterElementRow.PastDays -> {
if (row is FilterElementRow) { val pastDays = if (row.lastDays > 0) row.lastDays.toString() else ""
row.sectionToExclude?.let { filterSectionToExclude -> val data = row.editingDescriptors(mapOf("pastDays" to pastDays))
val excludedFilters = selectedRows.filter { BottomSheetFragment.create(fragmentManager, row, this, data, true)
filterSectionToExclude.contains(it.filterSectionRow)
} }
excludedFilters .forEach { is FilterElementRow.LastGames -> {
selectedRows.remove(it) val lastGames = if (row.lastGames > 0) row.lastGames.toString() else ""
rowRepresentableAdapter.refreshRow(it) val data = row.editingDescriptors(mapOf("lastGames" to lastGames))
BottomSheetFragment.create(fragmentManager, row, this, data, true)
} }
is FilterElementRow.LastSessions -> {
val lastSessions = if (row.lastSessions > 0) row.lastSessions.toString() else ""
val data = row.editingDescriptors(mapOf("lastSessions" to lastSessions))
BottomSheetFragment.create(fragmentManager, row, this, data, true)
} }
selectedRows.add(row) is FilterElementRow.DurationFilterElement -> {
val hours = if (row.minutes / 60 > 0) (row.minutes / 60).toString() else ""
val minutes = if (row.minutes % 60 > 0) (row.minutes % 60).toString() else ""
val data = row.editingDescriptors(mapOf("hours" to hours, "minutes" to minutes))
BottomSheetFragment.create(fragmentManager, row, this, data, true)
} }
} is FilterElementRow.AmountFilterElement -> {
/* val amount = if (row.amount > 0) row.amount.toString() else ""
Timber.d("Row: $row") val data = row.editingDescriptors(mapOf("amount" to amount))
when (row) { BottomSheetFragment.create(fragmentManager, row, this, data, true)
FilterRow.FROM -> DateTimePickerManager.create(requireContext(), row, this, Date(), onlyDate = true)
FilterRow.TO -> DateTimePickerManager.create(requireContext(), row, this, Date(), onlyDate = true)
FilterRow.PAST_DAYS -> {
val data = row.editingDescriptors(mapOf("defaultValue" to ""))
BottomSheetFragment.create(fragmentManager, row, this, data, null)
} }
else -> { else -> {
selectedRows.add(row) updateRowsSelection(row)
}
} }
} }
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
is FilterElementRow.PastDays -> if (row.lastDays > 0) row.lastDays.toString() else NULL_TEXT
is FilterElementRow.LastGames -> if (row.lastGames > 0) row.lastGames.toString() else NULL_TEXT
is FilterElementRow.LastSessions -> if (row.lastSessions > 0) row.lastSessions.toString() else NULL_TEXT
is FilterElementRow.DateFilterElementRow -> row.dateValue.shortDate()
is FilterElementRow.AmountFilterElement -> if (row.amount > 0) row.amount.toString() else NULL_TEXT
is FilterElementRow.DurationFilterElement -> row.minutes.toMinutes(requireContext())
else -> super.stringForRow(row)
} }
*/
rowRepresentableAdapter.refreshRow(row)
} }
override fun isSelected(row: RowRepresentable): Boolean { override fun isSelected(row: RowRepresentable): Boolean {
@ -103,8 +120,38 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
override fun onRowValueChanged(value: Any?, row: RowRepresentable) { override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
super.onRowValueChanged(value, row) super.onRowValueChanged(value, row)
selectedRows.add(row as FilterElementRow) Timber.d("onRowValueChanged: $row $value")
rowRepresentableAdapter.refreshRow(row)
when (row) {
is FilterElementRow.DateFilterElementRow -> row.dateValue = if (value != null && value is Date) value else Date()
is FilterElementRow.PastDays -> row.lastDays = if (value != null && value is String) value.toInt() else 0
is FilterElementRow.LastGames -> row.lastGames = if (value != null && value is String) value.toInt() else 0
is FilterElementRow.LastSessions -> row.lastSessions = if (value != null && value is String) value.toInt() else 0
is FilterElementRow.AmountFilterElement -> row.amount = if (value != null && value is String) value.toDouble() else 0.0
is FilterElementRow.DurationFilterElement -> {
if (value is ArrayList<*>) {
val hours = try {
(value[0] as String? ?: "0").toInt()
} catch (e: Exception) {
0
}
val minutes = try {
(value[1] as String? ?: "0").toInt()
} catch (e: Exception) {
0
}
row.minutes = hours * 60 + minutes
} else {
row.minutes = 0
}
}
}
// Remove the row before updating the selected rows list
selectedRows.remove(row as FilterElementRow)
updateRowsSelection(row, value == null)
} }
override fun adapterRows(): List<RowRepresentable>? { override fun adapterRows(): List<RowRepresentable>? {
@ -125,6 +172,8 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true) setHasOptionsMenu(true)
this.appBar.toolbar.title = getString(R.string.filter)
val viewManager = LinearLayoutManager(requireContext()) val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply { recyclerView.apply {
@ -138,9 +187,9 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
*/ */
private fun initData() { private fun initData() {
Timber.d("initData") primaryKey?.let {
currentFilter = Filter.getFilterBydId(getRealm(), it)
this.appBar.toolbar.title = getString(R.string.filter) }
filterCategoryRow?.let { filterCategoryRow?.let {
@ -150,58 +199,84 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
this.rowsForFilterSubcategoryRow.clear() this.rowsForFilterSubcategoryRow.clear()
this.rows.addAll(it.filterElements) this.rows.addAll(it.filterElements)
//TODO
/*
var filter = Filter()
this.rows.forEach { element -> this.rows.forEach { element ->
if (filter.isFilterElementExists(element as FilterElementRow)) { if (element is FilterElementRow && currentFilter?.contains(element) == true) {
currentFilter?.setSavedValueForElement(element)
this.selectedRows.add(element) this.selectedRows.add(element)
} }
} }
*/
this.rowRepresentableAdapter = RowRepresentableAdapter(this, this) this.rowRepresentableAdapter = RowRepresentableAdapter(this, this)
this.recyclerView.adapter = rowRepresentableAdapter this.recyclerView.adapter = rowRepresentableAdapter
} }
}
/**
* Update rows selection
*/
private fun updateRowsSelection(row: RowRepresentable, forceDeselection: Boolean = false) {
if (selectedRows.contains(row) || forceDeselection) {
selectedRows.remove(row)
} else {
if (row is FilterElementRow) {
row.sectionToExclude?.let { filterSectionToExclude ->
val excludedFilters = selectedRows.filter {
filterSectionToExclude.contains(it.filterSectionRow)
}
excludedFilters.forEach {
selectedRows.remove(it)
rowRepresentableAdapter.refreshRow(it)
}
}
selectedRows.add(row)
}
} }
// Update UI
rowRepresentableAdapter.refreshRow(row)
}
/** /**
* Save data * Save data
*/ */
private fun saveData() { private fun saveData() {
//TODO: Save currentFilter details data
Timber.d("Save data for filter: ${currentFilter?.id}")
selectedRows?.forEach {
Timber.d("Selected rows: $it")
}
val realm = getRealm()
realm.beginTransaction()
currentFilter?.createOrUpdateFilterConditions(selectedRows)
realm.commitTransaction()
//TODO: Save filter details data currentFilter?.filterConditions?.forEach {
Timber.d("Save filter details data") Timber.d("Condition: $it")
finishActivityWithResult("") }
finishActivityWithResult(currentFilter?.id)
} }
/** /**
* Finish the activity with a result * Finish the activity with a result
*/ */
private fun finishActivityWithResult(uniqueIdentifier: String) { private fun finishActivityWithResult(uniqueIdentifier: String?) {
/*
val intent = Intent() val intent = Intent()
intent.putExtra(EditableDataActivity.IntentKey.DATA_TYPE.keyName, dataType) intent.putExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName, uniqueIdentifier)
intent.putExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName, uniqueIdentifier)
activity?.setResult(RESULT_OK, intent) activity?.setResult(RESULT_OK, intent)
*/
activity?.finish() activity?.finish()
} }
/** /**
* Set fragment data * Set fragment data
*/ */
fun setData(filterCategory: Int) { fun setData(primaryKey: String, filterCategory: Int) {
this.filterCategoryRow = FilterCategoryRow.values()[filterCategory]
/*
this.dataType = dataType
this.liveDataType = LiveData.numericValues()[dataType]
this.primaryKey = primaryKey this.primaryKey = primaryKey
*/ this.filterCategoryRow = FilterCategoryRow.values()[filterCategory]
} }
} }

@ -1,15 +1,18 @@
package net.pokeranalytics.android.ui.fragment package net.pokeranalytics.android.ui.fragment
import android.app.Activity
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.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.RealmObject import io.realm.kotlin.where
import kotlinx.android.synthetic.main.fragment_editable_data.* import kotlinx.android.synthetic.main.fragment_editable_data.*
import kotlinx.android.synthetic.main.fragment_filters.view.* import kotlinx.android.synthetic.main.fragment_filters.view.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
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.FiltersActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity 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
@ -26,18 +29,17 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
const val REQUEST_CODE_FILTER_DETAILS = 100 const val REQUEST_CODE_FILTER_DETAILS = 100
} }
lateinit var parentActivity: PokerAnalyticsActivity private lateinit var parentActivity: PokerAnalyticsActivity
lateinit var item: RealmObject private lateinit var rowRepresentableAdapter: RowRepresentableAdapter
lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private var currentFilter: 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 var selectedRow: RowRepresentable? = null private var selectedRow: RowRepresentable? = null
private var isUpdating = false
private var filter: Filter? = null
var isUpdating = false
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_filters, container, false) return inflater.inflate(R.layout.fragment_filters, container, false)
@ -52,11 +54,16 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
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) { if (requestCode == REQUEST_CODE_FILTER_DETAILS && resultCode == Activity.RESULT_OK) {
/*
Timber.d("onActivityResult: $requestCode") Timber.d("onActivityResult: $requestCode")
if (data != null && data.hasExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)) {
val filterId = data.getStringExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)
Timber.d("Updated filter: ${filterId}")
}
*/
// TODO: Update the selected row here...
selectedRow?.let { selectedRow?.let {
rowRepresentableAdapter.refreshRow(it) rowRepresentableAdapter.refreshRow(it)
} }
@ -71,17 +78,17 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
} }
override fun onBackPressed() { override fun onBackPressed() {
super.onBackPressed() if (isUpdating) {
cancelUpdates()
//TODO: Cancel changes on the Filter object } else {
deleteFilter()
finishActivityWithResult("") }
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) { when (item!!.itemId) {
R.id.save -> saveData() R.id.save -> validUpdates()
R.id.delete -> deleteData() R.id.delete -> deleteFilter()
} }
return true return true
} }
@ -90,25 +97,25 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
return rows return rows
} }
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun stringForRow(row: RowRepresentable): String {
super.onRowSelected(position, row, fromAction) // Return the number of selected filters for this category
var selectedFilters = ""
if (row is FilterCategoryRow) { if (row is FilterCategoryRow) {
selectedRow = row currentFilter?.countBy(row)?.let { nbFilters ->
FilterDetailsActivity.newInstanceForResult(this, row.ordinal, REQUEST_CODE_FILTER_DETAILS) if (nbFilters > 0) {
selectedFilters = nbFilters.toString()
} }
} }
//TODO
/*
override fun stringForRow(row: RowRepresentable): String {
return this.filter?.numberOfElementIn(row as FilterCategoryRow).toString()
} }
*/ return selectedFilters
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) { override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
super.onRowValueChanged(value, row) super.onRowSelected(position, row, fromAction)
selectedRow = row
currentFilter?.id?.let { filterId ->
FilterDetailsActivity.newInstanceForResult(this, filterId, (row as FilterCategoryRow).ordinal, REQUEST_CODE_FILTER_DETAILS)
}
} }
/** /**
@ -120,6 +127,8 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true) setHasOptionsMenu(true)
this.appBar.toolbar.title = getString(R.string.filter)
val viewManager = LinearLayoutManager(requireContext()) val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply { recyclerView.apply {
@ -133,8 +142,27 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
*/ */
private fun initData() { private fun initData() {
this.appBar.toolbar.title = getString(R.string.filter) val realm = getRealm()
//TODO: Remove that
val filters = realm.where<Filter>().findAll()
Timber.d("Filters: ${filters.size}")
primaryKey?.let {
currentFilter = Filter.getFilterBydId(realm, it)
isUpdating = true
} ?: run {
realm.beginTransaction()
currentFilter = Filter.newInstance(realm)
realm.commitTransaction()
}
// Create a copy if the user cancels the updates
currentFilter?.let {
filterCopy = getRealm().copyFromRealm(it)
}
rows.clear()
rows.addAll(FilterCategoryRow.values()) rows.addAll(FilterCategoryRow.values())
this.rowRepresentableAdapter = RowRepresentableAdapter(this, this) this.rowRepresentableAdapter = RowRepresentableAdapter(this, this)
@ -151,35 +179,51 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
} }
/** /**
* Save data * Valid the updates of the filter
*/ */
private fun saveData() { private fun validUpdates() {
Timber.d("Valid filter updates")
val filterId = currentFilter?.id ?: ""
finishActivityWithResult(filterId)
}
// TODO: Save the filter object here /**
// Maybe we don't need to do anything because the object is already up to date * Cancel the latest updates of the filter
*/
private fun cancelUpdates() {
Timber.d("Cancel filter updates")
finishActivityWithResult("") val filterId = filterCopy?.id ?: ""
val realm = getRealm()
realm.beginTransaction()
filterCopy?.let {
realm.copyToRealmOrUpdate(it)
}
realm.commitTransaction()
finishActivityWithResult(filterId)
} }
/** /**
* Delete data * Delete data
*/ */
private fun deleteData() { private fun deleteFilter() {
Timber.d("Delete filter")
// TODO: Delete the filter object here val realm = getRealm()
realm.beginTransaction()
currentFilter?.deleteFromRealm()
realm.commitTransaction()
finishActivityWithResult("")
} }
/** /**
* Finish the activity with a result * Finish the activity with a result
*/ */
private fun finishActivityWithResult(uniqueIdentifier: String) { private fun finishActivityWithResult(uniqueIdentifier: String) {
/*
val intent = Intent() val intent = Intent()
intent.putExtra(EditableDataActivity.IntentKey.DATA_TYPE.keyName, dataType) intent.putExtra(FiltersActivity.IntentKey.FILTER_ID.keyName, uniqueIdentifier)
intent.putExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName, uniqueIdentifier)
activity?.setResult(RESULT_OK, intent) activity?.setResult(RESULT_OK, intent)
*/
activity?.finish() activity?.finish()
} }

@ -0,0 +1,97 @@
package net.pokeranalytics.android.ui.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.highlight.Highlight
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import kotlinx.android.synthetic.main.fragment_graph.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.graph.setStyle
interface GraphDataSource {
}
class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
lateinit var dataSource: GraphDataSource
lateinit var stat: Stat
lateinit var entries: List<Entry>
companion object {
}
fun setData(stat: Stat, entries: List<Entry>) {
this.stat = stat
this.entries = entries
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_graph, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
}
private fun initUI() {
val dataSet = LineDataSet(this.entries, this.stat.name)
val colors = arrayOf(R.color.green_light).toIntArray()
dataSet.setColors(colors, context)
val lineData = LineData(listOf(dataSet))
this.chart.setStyle()
this.chart.data = lineData
this.chart.setOnChartValueSelectedListener(this)
}
// OnChartValueSelectedListener
override fun onNothingSelected() {
// nothing to do
}
override fun onValueSelected(e: Entry?, h: Highlight?) {
e?.let { entry ->
h?.let { highlight ->
val id = entry.data as String
val item = getRealm().where(this.stat.underlyingClass).equalTo("id", id).findAll().firstOrNull()
item?.let {
val date = it.startDate()
val entryStatName = this.stat.localizedTitle(requireContext())
val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatName = this.stat.cumulativeLabelResId(requireContext())
val totalStatValue = this.stat.format(e.y.toDouble(), null, requireContext())
}
this.text.text = ""
}
}
}
}

@ -4,6 +4,7 @@ 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.core.view.isVisible
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.Sort import io.realm.Sort
@ -20,6 +21,7 @@ import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.util.Preferences import net.pokeranalytics.android.util.Preferences
import java.text.SimpleDateFormat
import java.util.* import java.util.*
class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate { class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
@ -67,12 +69,27 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
disclaimerContainer.isVisible = Preferences.shouldShowDisclaimer(requireContext()) disclaimerContainer.isVisible = Preferences.shouldShowDisclaimer(requireContext())
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val betaLimitDate = sdf.parse("17/7/2019 10:00")
newCashGame.setOnClickListener { newCashGame.setOnClickListener {
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return@setOnClickListener
}
SessionActivity.newInstance(requireContext(), false) SessionActivity.newInstance(requireContext(), false)
newSessionCreated = true newSessionCreated = true
} }
newTournament.setOnClickListener { newTournament.setOnClickListener {
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return@setOnClickListener
}
SessionActivity.newInstance(requireContext(), true) SessionActivity.newInstance(requireContext(), true)
newSessionCreated = true newSessionCreated = true
} }
@ -84,13 +101,18 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
} }
private fun showEndOfBetaMessage() {
Toast.makeText(context, "Beta has ended. Please update with the Google Play version", Toast.LENGTH_LONG).show()
}
/** /**
* Init data * Init data
*/ */
private fun initData() { private fun initData() {
this.realmSessions = getRealm().where<Session>().findAll().sort("startDate", Sort.DESCENDING) this.realmSessions = getRealm().where<Session>().findAll().sort("startDate", Sort.DESCENDING)
this.realmSessions.addChangeListener { t, changeSet -> this.realmSessions.addChangeListener { _, changeSet ->
this.historyAdapter.refreshData() this.historyAdapter.refreshData()
this.historyAdapter.notifyDataSetChanged() this.historyAdapter.notifyDataSetChanged()
} }

@ -89,13 +89,21 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
val data = currentSession.editDescriptors(row) val data = currentSession.editDescriptors(row)
when (row) { when (row) {
SessionRow.START_DATE -> DateTimePickerManager.create(requireContext(), row, this, currentSession.startDate) SessionRow.START_DATE -> DateTimePickerManager.create(requireContext(), row, this, currentSession.startDate)
SessionRow.END_DATE -> DateTimePickerManager.create( SessionRow.END_DATE -> {
if (this.currentSession.startDate == null) {
Toast.makeText(context, R.string.session_missing_start_date, Toast.LENGTH_SHORT).show()
} else {
DateTimePickerManager.create(
requireContext(), requireContext(),
row, row,
this, this,
currentSession.endDate ?: currentSession.startDate ?: Date(), currentSession.endDate ?: currentSession.startDate ?: Date(),
currentSession.startDate currentSession.startDate
) )
}
}
SessionRow.BANKROLL -> { SessionRow.BANKROLL -> {
BottomSheetFragment.create(fragmentManager, row, this, data, false, CurrencyUtils.getCurrency(currentSession.bankroll)) BottomSheetFragment.create(fragmentManager, row, this, data, false, CurrencyUtils.getCurrency(currentSession.bankroll))
} }
@ -151,12 +159,14 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
* Update the UI with the session data * Update the UI with the session data
* Should be called after the initialization of the session * Should be called after the initialization of the session
*/ */
private fun updateSessionUI() { private fun updateSessionUI(firstDisplay: Boolean = false) {
this.currentSession.updateRowRepresentation() this.currentSession.updateRowRepresentation()
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
val animationDuration = if (firstDisplay) 0L else 300L
when (currentSession.getState()) { when (currentSession.getState()) {
SessionState.PENDING, SessionState.PLANNED -> { SessionState.PENDING, SessionState.PLANNED -> {
state.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) state.setTextColor(ContextCompat.getColor(requireContext(), R.color.white))
@ -164,6 +174,7 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
floatingActionButton.setImageResource(R.drawable.ic_outline_play) floatingActionButton.setImageResource(R.drawable.ic_outline_play)
sessionMenu?.findItem(R.id.stop)?.isVisible = false sessionMenu?.findItem(R.id.stop)?.isVisible = false
floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f) floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f)
.setDuration(animationDuration)
.setInterpolator(OvershootInterpolator()).start() .setInterpolator(OvershootInterpolator()).start()
} }
SessionState.STARTED -> { SessionState.STARTED -> {
@ -172,6 +183,7 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
floatingActionButton.setImageResource(R.drawable.ic_outline_pause) floatingActionButton.setImageResource(R.drawable.ic_outline_pause)
sessionMenu?.findItem(R.id.stop)?.isVisible = true sessionMenu?.findItem(R.id.stop)?.isVisible = true
floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f) floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f)
.setDuration(animationDuration)
.setInterpolator(OvershootInterpolator()).start() .setInterpolator(OvershootInterpolator()).start()
handler.postDelayed(refreshTimer, 30000) handler.postDelayed(refreshTimer, 30000)
} }
@ -181,6 +193,7 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
floatingActionButton.setImageResource(R.drawable.ic_outline_play) floatingActionButton.setImageResource(R.drawable.ic_outline_play)
sessionMenu?.findItem(R.id.stop)?.isVisible = true sessionMenu?.findItem(R.id.stop)?.isVisible = true
floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f) floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f)
.setDuration(animationDuration)
.setInterpolator(OvershootInterpolator()).start() .setInterpolator(OvershootInterpolator()).start()
} }
SessionState.FINISHED -> { SessionState.FINISHED -> {
@ -188,6 +201,7 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
sessionMenu?.findItem(R.id.restart)?.isVisible = true sessionMenu?.findItem(R.id.restart)?.isVisible = true
sessionMenu?.findItem(R.id.stop)?.isVisible = false sessionMenu?.findItem(R.id.stop)?.isVisible = false
floatingActionButton.animate().scaleX(0f).scaleY(0f).alpha(0f) floatingActionButton.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setDuration(animationDuration)
.setInterpolator(FastOutSlowInInterpolator()).start() .setInterpolator(FastOutSlowInInterpolator()).start()
} }
} }
@ -299,7 +313,7 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
currentSession.location = location currentSession.location = location
realm.commitTransaction() realm.commitTransaction()
updateSessionUI() updateSessionUI(true)
} }
} }
sessionHasBeenCustomized = false sessionHasBeenCustomized = false
@ -312,7 +326,7 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
sessionAdapter = RowRepresentableAdapter(currentSession, this) sessionAdapter = RowRepresentableAdapter(currentSession, this)
recyclerView.adapter = sessionAdapter recyclerView.adapter = sessionAdapter
updateSessionUI() updateSessionUI(true)
} }
/** /**

@ -11,11 +11,11 @@ import kotlinx.coroutines.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.* import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.model.StatRepresentable import net.pokeranalytics.android.model.StatRepresentable
import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.ui.activity.GraphActivity
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.ui.adapter.DisplayDescriptor import net.pokeranalytics.android.ui.adapter.DisplayDescriptor
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.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.SessionObserverFragment import net.pokeranalytics.android.ui.fragment.components.SessionObserverFragment
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
@ -25,7 +25,8 @@ import timber.log.Timber
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope { class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope,
RowRepresentableDelegate {
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.Main get() = Dispatchers.Main
@ -36,6 +37,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
private var stringTournament = "" private var stringTournament = ""
private lateinit var statsAdapter: RowRepresentableAdapter private lateinit var statsAdapter: RowRepresentableAdapter
private var computedResults : List<ComputedResults>? = null
companion object { companion object {
@ -114,7 +116,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
this.stringCashGame = getString(R.string.cash_game) this.stringCashGame = getString(R.string.cash_game)
this.stringTournament = getString(R.string.tournament) this.stringTournament = getString(R.string.tournament)
this.statsAdapter = RowRepresentableAdapter(this) this.statsAdapter = RowRepresentableAdapter(this, this)
} }
/** /**
@ -131,7 +133,6 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
} }
} }
private fun launchStatComputation() { private fun launchStatComputation() {
GlobalScope.launch(coroutineContext) { GlobalScope.launch(coroutineContext) {
@ -143,6 +144,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
results = createSessionGroupsAndStartCompute(realm) results = createSessionGroupsAndStartCompute(realm)
computedResults = results
realm.close() realm.close()
val e = Date() val e = Date()
@ -161,32 +163,16 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
private fun createSessionGroupsAndStartCompute(realm: Realm) : List<ComputedResults> { private fun createSessionGroupsAndStartCompute(realm: Realm) : List<ComputedResults> {
val s = Date()
val allSessions = realm.where(ComputableResult::class.java).findAll()
val allSessionSets = realm.where(SessionSet::class.java).findAll()
Timber.d(">>>>> number of computables to compute = ${allSessions.size}")
val cgSessions = allSessions.where().equalTo("session.type", Session.Type.CASH_GAME.ordinal).findAll()
val cgSessionSets = realm.where(SessionSet::class.java).equalTo("sessions.type", Session.Type.CASH_GAME.ordinal).findAll()
val tSessions = allSessions.where().equalTo("session.type", Session.Type.TOURNAMENT.ordinal).findAll()
val tSessionSets = realm.where(SessionSet::class.java).equalTo("sessions.type", Session.Type.TOURNAMENT.ordinal).findAll()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> filtering in ${duration} seconds")
val allStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE, Stat.NUMBER_OF_SETS, Stat.AVERAGE_DURATION, Stat.DURATION) val allStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE, Stat.NUMBER_OF_SETS, Stat.AVERAGE_DURATION, Stat.DURATION)
val allSessionGroup = ComputableGroup(stringAll, allSessions, allSessionSets, allStats) val allSessionGroup = ComputableGroup(stringAll, listOf(), allStats)
val cgStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.NET_BB_PER_100_HANDS, Stat.HOURLY_RATE_BB, Stat.AVERAGE, Stat.STANDARD_DEVIATION_HOURLY, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) val cgStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.NET_BB_PER_100_HANDS, Stat.HOURLY_RATE_BB, Stat.AVERAGE, Stat.STANDARD_DEVIATION_HOURLY, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)
val cgSessionGroup = ComputableGroup(stringCashGame, cgSessions, cgSessionSets, cgStats) val cgSessionGroup = ComputableGroup(stringCashGame, listOf(QueryCondition.CASH), cgStats)
val tStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) val tStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)
val tSessionGroup = ComputableGroup(stringTournament, tSessions, tSessionSets, tStats) val tSessionGroup = ComputableGroup(stringTournament, listOf(QueryCondition.TOURNAMENT), tStats)
Timber.d(">>>>> Start computations...") Timber.d(">>>>> Start computations...")
return Calculator.computeGroups(listOf(allSessionGroup, cgSessionGroup, tSessionGroup), Calculator.Options()) return Calculator.computeGroups(realm, listOf(allSessionGroup, cgSessionGroup, tSessionGroup), Calculator.Options())
} }
@ -202,11 +188,60 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
results.forEach { result -> results.forEach { result ->
rows.add(CustomizableRowRepresentable(title = result.group.name)) rows.add(CustomizableRowRepresentable(title = result.group.name))
result.group.stats?.forEach { stat -> result.group.stats?.forEach { stat ->
rows.add(StatRepresentable(stat, result.computedStat(stat))) rows.add(StatRepresentable(stat, result.computedStat(stat), result.group.name))
} }
} }
return rows return rows
} }
// RowRepresentableDelegate
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
// if (row is StatRepresentable) {
//
// // filter groups
// val groupResults = this.computedResults?.filter {
// it.group.name == row.groupName
// }
//
// groupResults?.firstOrNull()?.let {
// this.launchStatComputationWithEvolution(row.stat, it.group)
// }
// }
}
private fun launchStatComputationWithEvolution(stat: Stat, computableGroup: ComputableGroup) {
GlobalScope.launch(coroutineContext) {
var results = listOf<ComputedResults>()
val test = GlobalScope.async {
val s = Date()
Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
val options = Calculator.Options()
options.evolutionValues = Calculator.Options.EvolutionValues.STANDARD
results = Calculator.computeGroups(realm, listOf(computableGroup), options)
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in ${duration} seconds")
}
test.await()
if (!isDetached) {
results.firstOrNull()?.defaultStatEntries(stat)?.let { entries ->
GraphActivity.newInstance(requireContext(), stat, entries)
}
}
}
}
} }

@ -29,7 +29,12 @@ class BottomSheetEditTextFragment : BottomSheetFragment() {
override fun getValue(): Any? { override fun getValue(): Any? {
this.value?.let { this.value?.let {
return it.trim() val value = it.trim()
if (value.isEmpty()) { // avoid returning empty strings
return null
} else {
return value
}
} }
return null return null
} }

@ -9,6 +9,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.view.ContextThemeWrapper
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.realm.RealmObject import io.realm.RealmObject
@ -57,7 +58,9 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
} }
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_bottom_sheet, container, false) //TODO: When dependency 'com.google.android.material:material:1.1.0' will be available in stable version, upgrade and remove that
val contextThemeWrapper = ContextThemeWrapper(activity, R.style.PokerAnalyticsTheme)
return inflater.cloneInContext(contextThemeWrapper).inflate(R.layout.fragment_bottom_sheet, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

@ -3,6 +3,7 @@ package net.pokeranalytics.android.ui.fragment.components.bottomsheet
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 androidx.core.view.get
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import io.realm.RealmResults import io.realm.RealmResults
@ -13,6 +14,7 @@ import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorExcep
import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.util.extensions.px
/** /**
* Bottom Sheet List Game Fragment * Bottom Sheet List Game Fragment
@ -73,6 +75,8 @@ class BottomSheetListGameFragment : BottomSheetListFragment() {
val chip = Chip(requireContext()) val chip = Chip(requireContext())
chip.id = it.ordinal chip.id = it.ordinal
chip.text = it.shortName chip.text = it.shortName
chip.chipStartPadding = 8f.px
chip.chipEndPadding = 8f.px
chip.isChecked = it.ordinal == limit chip.isChecked = it.ordinal == limit
chipGroup.addView(chip) chipGroup.addView(chip)
} }
@ -81,6 +85,10 @@ class BottomSheetListGameFragment : BottomSheetListFragment() {
values[0] = i values[0] = i
} }
if (limit == null) {
(chipGroup[0] as Chip).isChecked = true
}
val viewManager2 = LinearLayoutManager(requireContext()) val viewManager2 = LinearLayoutManager(requireContext())
dataAdapter = RowRepresentableAdapter(this, this) dataAdapter = RowRepresentableAdapter(this, this)

@ -0,0 +1,66 @@
package net.pokeranalytics.android.ui.graph
import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.BarLineChartBase
import com.github.mikephil.charting.charts.LineChart
fun BarChart.setStyle() {
GraphHelper.setStyle(this)
}
fun LineChart.setStyle() {
GraphHelper.setStyle(this)
}
class GraphHelper {
companion object {
fun setStyle(chart: BarLineChartBase<*>) {
// this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.) ChartAppearance.defaultColor
// this.xAxis.axisLineWidth = ChartAppearance.lineWidth
// this.xAxis.enableGridDashedLine(3.0f, 5.0f, 1.0f)
//
// this.xAxis.labelTextColor = ChartAppearance.defaultColor
// this.xAxis.labelFont = Fonts.graphAxis
// this.xAxis.labelCount = 4
// this.xAxis.labelPosition = .bottom
//
// this.xAxis.drawLabelsEnabled = true
// this.xAxis.drawGridLinesEnabled = true
// this.xAxis.granularity = 1.0
// this.xAxis.granularityEnabled = true
// this.xAxis.enabled = true
//
// // Y Axis
// this.leftAxis.drawAxisLineEnabled = false
// this.leftAxis.drawGridLinesEnabled = true
// this.leftAxis.gridLineDashLengths = [3.0, 5.0]
//
// this.leftAxis.drawZeroLineEnabled = true
// this.leftAxis.zeroLineWidth = ChartAppearance.lineWidth
// this.leftAxis.zeroLineColor = ChartAppearance.defaultColor
//
// this.leftAxis.granularityEnabled = true
// this.leftAxis.granularity = 1.0
//
// this.leftAxis.labelTextColor = ChartAppearance.defaultColor
// this.leftAxis.labelFont = Fonts.graphAxis
// this.leftAxis.labelCount = small ? 1 : 7 // @todo not great if interval is [0..2] for number of records as we get decimals
//
// if timeYAxis {
// this.leftAxis.valueFormatter = HourValueFormatter()
// } else {
// this.leftAxis.valueFormatter = LargeNumberFormatter()
// }
//
// this.rightAxis.enabled = false
//
// this.legend.enabled = false
}
}
}

@ -76,9 +76,7 @@ enum class RowViewType(private var layoutRes: Int) {
HEADER_TITLE, HEADER_TITLE_VALUE, HEADER_TITLE_AMOUNT, HEADER_TITLE_AMOUNT_BIG, HEADER_TITLE, HEADER_TITLE_VALUE, HEADER_TITLE_AMOUNT, HEADER_TITLE_AMOUNT_BIG,
LOCATION_TITLE, INFO, LOCATION_TITLE, INFO,
TITLE, TITLE_ARROW, TITLE_VALUE, TITLE_VALUE_ARROW, TITLE_GRID, TITLE_SWITCH, TITLE_CHECK, TITLE_VALUE_CHECK, TITLE, TITLE_ARROW, TITLE_VALUE, TITLE_VALUE_ARROW, TITLE_GRID, TITLE_SWITCH, TITLE_CHECK, TITLE_VALUE_CHECK,
DATA, BOTTOM_SHEET_DATA, LOADER -> RowViewHolder( DATA, BOTTOM_SHEET_DATA, LOADER -> RowViewHolder(layout)
layout
)
// Row Session // Row Session
ROW_SESSION -> RowSessionViewHolder(layout) ROW_SESSION -> RowSessionViewHolder(layout)

@ -9,7 +9,7 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In
GENERAL(R.string.general), GENERAL(R.string.general),
DATE(R.string.date), DATE(R.string.date),
TIME_FRAME(R.string.duration), TIME_FRAME(R.string.duration),
SESSION(R.string.session), SESSIONS(R.string.sessions),
CASH(R.string.cash), CASH(R.string.cash),
TOURNAMENT(R.string.tournament), TOURNAMENT(R.string.tournament),
ONLINE(R.string.online), ONLINE(R.string.online),
@ -45,6 +45,11 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In
DAY_OF_WEEK, DAY_OF_WEEK,
MONTH_OF_YEAR MONTH_OF_YEAR
) )
TIME_FRAME -> arrayListOf(
SESSION_DURATION,
RANGE
)
SESSIONS -> arrayListOf(FilterSectionRow.SESSIONS)
BANKROLLS -> arrayListOf( BANKROLLS -> arrayListOf(
BANKROLL BANKROLL
) )
@ -74,8 +79,6 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In
VALUE VALUE
) )
TIME_FRAME -> arrayListOf()
SESSION -> arrayListOf()
TRANSACTION_TYPES -> arrayListOf() TRANSACTION_TYPES -> arrayListOf()
} }
} }

@ -1,103 +1,205 @@
package net.pokeranalytics.android.ui.view.rowrepresentable package net.pokeranalytics.android.ui.view.rowrepresentable
import android.content.Context
import android.text.InputType
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FilterValueMapException import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.filter.QueryType import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Manageable import net.pokeranalytics.android.model.interfaces.Manageable
import net.pokeranalytics.android.model.realm.FilterElement import net.pokeranalytics.android.model.realm.FilterCondition
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
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.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.round
import java.text.DateFormatSymbols
import java.util.* import java.util.*
sealed class FilterElementRow : RowRepresentable { sealed class FilterElementRow : RowRepresentable {
object Cash : FilterElementRow() interface Duration {
object Tournament : FilterElementRow() var minutes: Int
object Live : FilterElementRow() }
object Online : FilterElementRow()
object Today : FilterElementRow() interface Operator
object Yesterday : FilterElementRow() interface MoreOperator : Operator
object TodayAndYesterday : FilterElementRow() interface LessOperator : Operator
object CurrentWeek : FilterElementRow()
object CurrentMonth : FilterElementRow() open class BoolFilterElementRow : FilterElementRow()
object CurrentYear: FilterElementRow() open class DateFilterElementRow(var dateValue: Date = Date()) : FilterElementRow()
object Weekday: FilterElementRow() open class NumericFilterElementRow(open val doubleValue: Double = 0.0) : FilterElementRow()
object Weekend : FilterElementRow() open class StringFilterElementRow(val stringValue: String = "") : FilterElementRow()
open class DataFilterElementRow(data:Manageable) : FilterElementRow() { // Subclasses
open class SingleValueFilterElementRow(open val intValue: Int) : NumericFilterElementRow() {
override val doubleValue: Double
get() {
return intValue.toDouble()
}
}
open class DataFilterElementRow(data: Manageable) : StringFilterElementRow(data.id) {
val id: String = data.id val id: String = data.id
val name: String = (data as RowRepresentable).getDisplayName() val name: String = (data as RowRepresentable).getDisplayName()
} }
open class SingleValueFilterElementRow(val value:Int) : FilterElementRow() open class StaticDataFilterElementRow(val row: RowRepresentable, val id: Int) : NumericFilterElementRow(id.toDouble()) {
override val resId: Int? = row.resId
val name: String = row.getDisplayName()
fun getDataLocalizedTitle(context: Context): String {
return row.localizedTitle(context)
}
}
open class DurationFilterElement : NumericFilterElementRow(), Duration {
override var minutes: Int = 0
override val doubleValue: Double
get() {
return minutes.toDouble()
}
}
open class AmountFilterElement : NumericFilterElementRow() {
var amount: Double = 0.0
override val doubleValue: Double
get() {
return amount
}
}
object Cash : BoolFilterElementRow()
object Tournament : BoolFilterElementRow()
object Live : BoolFilterElementRow()
object Online : BoolFilterElementRow()
object Today : BoolFilterElementRow()
object Yesterday : BoolFilterElementRow()
object TodayAndYesterday : BoolFilterElementRow()
object CurrentWeek : BoolFilterElementRow()
object CurrentMonth : BoolFilterElementRow()
object CurrentYear : BoolFilterElementRow()
object Weekday : BoolFilterElementRow()
object Weekend : BoolFilterElementRow()
object From : DateFilterElementRow()
object To : DateFilterElementRow()
// Data classes - holding value
object ResultMoreThan : AmountFilterElement(), MoreOperator
object ResultLessThan : AmountFilterElement(), LessOperator
object DurationMoreThan : DurationFilterElement(), MoreOperator
object DurationLessThan : DurationFilterElement(), LessOperator
object ReBuyMoreThan : AmountFilterElement(), MoreOperator
object ReBuyLessThan : AmountFilterElement(), LessOperator
data class Blind(var sb: Double? = null, var bb: Double? = null, var code: String? = null) : FilterElementRow()
data class From(var date: Date = Date()) : FilterElementRow()
data class To(var date: Date = Date()) : FilterElementRow()
data class Year(val year: Int) : SingleValueFilterElementRow(year) data class Year(val year: Int) : SingleValueFilterElementRow(year)
data class Month(val month: Int) : SingleValueFilterElementRow(month) data class Month(val month: Int) : SingleValueFilterElementRow(month)
data class Day(val day: Int) : SingleValueFilterElementRow(day) data class Day(val day: Int) : SingleValueFilterElementRow(day)
data class PastDays(var lastDays : Int = 0) : FilterElementRow()
data class Limit(val limit : net.pokeranalytics.android.model.Limit) : FilterElementRow() data class Blind(var sb: Double? = null, var bb: Double? = null, var code: String? = null) : FilterElementRow() {
data class TableSize(val tableSize : net.pokeranalytics.android.model.TableSize) : FilterElementRow() val name: String
get() {
val currencyCode = code ?: Currency.getInstance(Locale.getDefault()).currencyCode
val currencySymbol = Currency.getInstance(currencyCode).symbol
return if (sb == null) NULL_TEXT else "$currencySymbol ${sb?.formatted()}/${bb?.round()}"
}
}
//TODO: Refactor?
data class PastDays(var lastDays: Int = 0) : SingleValueFilterElementRow(lastDays) {
override val intValue: Int
get() {
return lastDays
}
}
data class LastGames(var lastGames: Int) : SingleValueFilterElementRow(lastGames) {
override val intValue: Int
get() {
return lastGames
}
}
data class LastSessions(var lastSessions: Int) : SingleValueFilterElementRow(lastSessions) {
override val intValue: Int
get() {
return lastSessions
}
}
data class Limit(val limit: net.pokeranalytics.android.model.Limit) : StaticDataFilterElementRow(limit, limit.ordinal)
data class TableSize(val tableSize: net.pokeranalytics.android.model.TableSize) : StaticDataFilterElementRow(tableSize, tableSize.numberOfPlayer)
data class Bankroll(val bankroll: Manageable) : DataFilterElementRow(bankroll) data class Bankroll(val bankroll: Manageable) : DataFilterElementRow(bankroll)
data class Game(val game: Manageable) : DataFilterElementRow(game) data class Game(val game: Manageable) : DataFilterElementRow(game)
data class Location(val location: Manageable) : DataFilterElementRow(location) data class Location(val location: Manageable) : DataFilterElementRow(location)
data class TournamentName(val tournamentName: Manageable) : DataFilterElementRow(tournamentName) data class TournamentName(val tournamentName: Manageable) : DataFilterElementRow(tournamentName)
data class AllTournamentFeature(val tournamentFeature: Manageable) : DataFilterElementRow(tournamentFeature) data class AllTournamentFeature(val tournamentFeature: Manageable) : DataFilterElementRow(tournamentFeature)
data class AnyTournamentFeature(val tournamentFeature: Manageable) : DataFilterElementRow(tournamentFeature) data class AnyTournamentFeature(val tournamentFeature: Manageable) : DataFilterElementRow(tournamentFeature)
data class ResultMoreThan(var value:Double) : FilterElementRow()
data class ResultLessThan(var value:Double) : FilterElementRow()
lateinit var filterSectionRow: FilterSectionRow lateinit var filterSectionRow: FilterSectionRow
val filterName : String = this.queryType.name val filterName: String = this.queryCondition.name
private val queryType : QueryType private val queryCondition: QueryCondition
get() { get() {
return when (this) { return when (this) {
is Cash -> QueryType.CASH is Cash -> QueryCondition.CASH
is Tournament -> QueryType.TOURNAMENT is Tournament -> QueryCondition.TOURNAMENT
is Blind -> QueryType.BLINDS is Blind -> QueryCondition.BLINDS
is From -> QueryType.STARTED_FROM_DATE is From -> QueryCondition.STARTED_FROM_DATE
is To -> QueryType.ENDED_TO_DATE is To -> QueryCondition.ENDED_TO_DATE
is Month -> QueryType.MONTH is Month -> QueryCondition.MONTH
is Day -> QueryType.DAY_OF_WEEK is Day -> QueryCondition.DAY_OF_WEEK
is Year -> QueryType.YEAR is Year -> QueryCondition.YEAR
is Live -> QueryType.LIVE is Live -> QueryCondition.LIVE
is Online -> QueryType.ONLINE is Online -> QueryCondition.ONLINE
is Weekday -> QueryType.WEEK_DAY is Weekday -> QueryCondition.WEEK_DAY
is Weekend-> QueryType.WEEK_END is Weekend -> QueryCondition.WEEK_END
is Today -> QueryCondition.TODAY
/* is Today -> QueryType. is Yesterday -> QueryCondition.YESTERDAY
is Yesterday -> R.string.yesterday is TodayAndYesterday -> QueryCondition.TODAY_AND_YESTERDAY
is TodayAndYesterday -> R.string.yesterday_and_today is CurrentWeek -> QueryCondition.THIS_WEEK
is CurrentWeek -> R.string.current_week is CurrentMonth -> QueryCondition.THIS_MONTH
is CurrentMonth -> R.string.current_month is CurrentYear -> QueryCondition.THIS_YEAR
is CurrentYear -> R.string.current_year is PastDays -> QueryCondition.PAST_DAYS
is PastDays -> R.string.period_in_days is Limit -> QueryCondition.LIMIT
is Limit -> R.string.limit is TableSize -> QueryCondition.TABLE_SIZE
*/ is Game -> QueryCondition.GAME
is TableSize -> QueryType.TABLE_SIZE is Bankroll -> QueryCondition.BANKROLL
is Game -> QueryType.GAME is Location -> QueryCondition.LOCATION
is Bankroll -> QueryType.BANKROLL is TournamentName -> QueryCondition.TOURNAMENT_NAME
is Location -> QueryType.LOCATION is AnyTournamentFeature -> QueryCondition.ANY_TOURNAMENT_FEATURES
is TournamentName -> QueryType.TOURNAMENT_NAME is AllTournamentFeature -> QueryCondition.ALL_TOURNAMENT_FEATURES
is AnyTournamentFeature -> QueryType.ANY_TOURNAMENT_FEATURES is ResultMoreThan -> QueryCondition.MORE_THAN_NET_RESULT
is AllTournamentFeature -> QueryType.ALL_TOURNAMENT_FEATURES is ResultLessThan -> QueryCondition.LESS_THAN_NET_RESULT
is ResultMoreThan -> QueryType.MORE_THAN_NET_RESULT is DurationMoreThan -> QueryCondition.MORE_THAN_DURATION
is ResultLessThan -> QueryType.LESS_THAN_NET_RESULT is DurationLessThan -> QueryCondition.LESS_THAN_DURATION
else -> throw FilterValueMapException("no filter type for $this") //TODO create exception
//TODO: Check the conditions
is LastGames -> QueryCondition.LAST_GAMES
is LastSessions -> QueryCondition.LAST_SESSIONS
is ReBuyMoreThan -> QueryCondition.MIN_RE_BUY
is ReBuyLessThan -> QueryCondition.MAX_RE_BUY
else -> throw PokerAnalyticsException.UnknownQueryTypeForRow(this)
} }
} }
fun contains(filterElements: List<FilterElement>) : Boolean { fun contains(filterConditions: List<FilterCondition>): Boolean {
return when (this) { return when (this) {
is DataFilterElementRow -> filterElements.any { is SingleValueFilterElementRow -> filterConditions.any {
it.values.contains(this.intValue)
}
is DataFilterElementRow -> filterConditions.any {
it.ids.contains(this.id) it.ids.contains(this.id)
} }
else -> return true else -> true
} }
} }
@ -119,27 +221,98 @@ sealed class FilterElementRow : RowRepresentable {
is Online -> R.string.online is Online -> R.string.online
is Weekday -> R.string.week_days is Weekday -> R.string.week_days
is Weekend -> R.string.weekend is Weekend -> R.string.weekend
is Year-> R.string.year
is Month-> R.string.month_of_the_year
is Day -> R.string.day_of_the_week
is PastDays -> R.string.period_in_days is PastDays -> R.string.period_in_days
is Limit -> R.string.limit is LastGames -> R.string.last_records
is TableSize -> R.string.table_size is LastSessions -> R.string.last_sessions
is Blind -> TODO() is ReBuyMoreThan -> R.string.minimum
is ResultMoreThan -> TODO() is ReBuyLessThan -> R.string.maximum
is ResultLessThan -> TODO() is MoreOperator -> R.string.more_than
is LessOperator -> R.string.less_than
else -> null else -> null
} }
} }
override val viewType: Int
get() {
return when (this) {
is PastDays, is From, is To, is LastGames, is LastSessions, is ReBuyMoreThan, is ReBuyLessThan,
is DurationMoreThan, is DurationLessThan -> RowViewType.TITLE_VALUE_CHECK.ordinal
else -> RowViewType.TITLE_CHECK.ordinal
}
}
override val bottomSheetType: BottomSheetType
get() {
return when (this) {
is PastDays, is LastGames, is LastSessions, is ReBuyMoreThan, is ReBuyLessThan -> BottomSheetType.EDIT_TEXT
is DurationMoreThan, is DurationLessThan -> BottomSheetType.DOUBLE_EDIT_TEXT
else -> BottomSheetType.NONE
}
}
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
return when (this) {
is PastDays -> {
val pastDays: String? by map
arrayListOf(
RowRepresentableEditDescriptor(pastDays, R.string.period_in_days, inputType = InputType.TYPE_CLASS_NUMBER)
)
}
is LastGames -> {
val lastGames: String? by map
arrayListOf(
RowRepresentableEditDescriptor(lastGames, R.string.last_records, inputType = InputType.TYPE_CLASS_NUMBER)
)
}
is LastSessions -> {
val lastSessions: String? by map
arrayListOf(
RowRepresentableEditDescriptor(lastSessions, R.string.last_sessions, inputType = InputType.TYPE_CLASS_NUMBER)
)
}
//TODO: Refactor that
is AmountFilterElement -> {
val amount: String? by map
arrayListOf(
RowRepresentableEditDescriptor(amount, R.string.amount, inputType = InputType.TYPE_CLASS_NUMBER)
)
}
is DurationFilterElement -> {
val hours: String? by map
val minutes: String? by map
arrayListOf(
RowRepresentableEditDescriptor(hours, R.string.hour, inputType = InputType.TYPE_CLASS_NUMBER),
RowRepresentableEditDescriptor(minutes, R.string.minute, inputType = InputType.TYPE_CLASS_NUMBER)
)
}
else -> super.editingDescriptors(map)
}
}
override fun getDisplayName(): String { override fun getDisplayName(): String {
return when (this) { return when (this) {
is SingleValueFilterElementRow -> {
when (this) {
is Day -> DateFormatSymbols.getInstance(Locale.getDefault()).weekdays[this.intValue]
is Month -> DateFormatSymbols.getInstance(Locale.getDefault()).months[this.intValue]
else -> "${this.intValue}"
}
}
is DataFilterElementRow -> this.name is DataFilterElementRow -> this.name
else -> return super.getDisplayName() is StaticDataFilterElementRow -> this.name
is Blind -> this.name
else -> super.getDisplayName()
} }
} }
override val viewType: Int = RowViewType.TITLE_CHECK.ordinal override fun localizedTitle(context: Context): String {
return when (this) {
is StaticDataFilterElementRow -> this.getDataLocalizedTitle(context)
else -> super.localizedTitle(context)
}
}
val sectionToExclude: List<FilterSectionRow>? val sectionToExclude: List<FilterSectionRow>?
get() { get() {

@ -1,10 +1,16 @@
package net.pokeranalytics.android.ui.view.rowrepresentable package net.pokeranalytics.android.ui.view.rowrepresentable
import io.realm.Realm import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Session
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.FilterElementRow.* import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow.*
import java.text.DateFormatSymbols
import java.util.*
enum class FilterSectionRow(override val resId: Int?) : RowRepresentable { enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
CASH_TOURNAMENT(net.pokeranalytics.android.R.string.cash_or_tournament), CASH_TOURNAMENT(net.pokeranalytics.android.R.string.cash_or_tournament),
@ -21,8 +27,9 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
MONTH_OF_YEAR(net.pokeranalytics.android.R.string.month_of_the_year), MONTH_OF_YEAR(net.pokeranalytics.android.R.string.month_of_the_year),
SESSION_DURATION(net.pokeranalytics.android.R.string.session_duration), SESSION_DURATION(net.pokeranalytics.android.R.string.session_duration),
RANGE(net.pokeranalytics.android.R.string.hour_slot), RANGE(net.pokeranalytics.android.R.string.hour_slot),
SESSIONS(R.string.sessions),
BLINDS(net.pokeranalytics.android.R.string.blinds), BLINDS(net.pokeranalytics.android.R.string.blinds),
CASH_RE_BUY_COUNT(net.pokeranalytics.android.R.string.cash_game), CASH_RE_BUY_COUNT(net.pokeranalytics.android.R.string.rebuy_count),
TOURNAMENT_TYPE(net.pokeranalytics.android.R.string.tournament_types), TOURNAMENT_TYPE(net.pokeranalytics.android.R.string.tournament_types),
TOURNAMENT_NAME(net.pokeranalytics.android.R.string.tournament_name), TOURNAMENT_NAME(net.pokeranalytics.android.R.string.tournament_name),
TOURNAMENT_FEATURE(net.pokeranalytics.android.R.string.tournament_feature), TOURNAMENT_FEATURE(net.pokeranalytics.android.R.string.tournament_feature),
@ -49,12 +56,26 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
val allowMultiSelection: Boolean val allowMultiSelection: Boolean
get() = (this.selectionType == SelectionType.MULTIPLE) get() = (this.selectionType == SelectionType.MULTIPLE)
val filterElements: List<RowRepresentable> by lazy { val filterElements: List<RowRepresentable>
arrayListOf<RowRepresentable>(this).apply { get() {
val data = arrayListOf<RowRepresentable>().apply {
this.addAll( this.addAll(
when (this@FilterSectionRow) { when (this@FilterSectionRow) {
// General
CASH_TOURNAMENT -> arrayListOf(Cash, Tournament) CASH_TOURNAMENT -> arrayListOf(Cash, Tournament)
LIVE_ONLINE -> arrayListOf(Live, Online) LIVE_ONLINE -> arrayListOf(Live, Online)
GAME -> {
val games = arrayListOf<FilterElementRow.Game>()
val realm = Realm.getDefaultInstance()
LiveData.GAME.items(realm).forEach {
val game = Game(it as net.pokeranalytics.android.model.realm.Game)
games.add(game)
}
realm.close()
games
}
LIMIT_TYPE -> { LIMIT_TYPE -> {
val limits = arrayListOf<FilterElementRow.Limit>() val limits = arrayListOf<FilterElementRow.Limit>()
net.pokeranalytics.android.model.Limit.values().forEach { net.pokeranalytics.android.model.Limit.values().forEach {
@ -64,13 +85,18 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
} }
TABLE_SIZE -> { TABLE_SIZE -> {
val tableSizes = arrayListOf<FilterElementRow.TableSize>() val tableSizes = arrayListOf<FilterElementRow.TableSize>()
net.pokeranalytics.android.model.TableSize.all.forEach { val realm = Realm.getDefaultInstance()
tableSizes.add(TableSize(it)) val distinctTableSizes = realm.where<Session>().distinct("tableSize").findAll().sort("tableSize", Sort.ASCENDING)
distinctTableSizes.forEach { session ->
session.tableSize?.let { tableSize ->
tableSizes.add(TableSize(net.pokeranalytics.android.model.TableSize(tableSize)))
}
} }
realm.close()
tableSizes tableSizes
} }
TOURNAMENT_NAME -> arrayListOf()
TOURNAMENT_FEATURE -> arrayListOf() // Date
DYNAMIC_DATE -> arrayListOf( DYNAMIC_DATE -> arrayListOf(
Today, Today,
Yesterday, Yesterday,
@ -79,45 +105,90 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
CurrentMonth, CurrentMonth,
CurrentYear CurrentYear
) )
FIXED_DATE -> arrayListOf(From(), To()) FIXED_DATE -> arrayListOf(From, To)
DURATION -> arrayListOf() DURATION -> arrayListOf(PastDays(0))
WEEKDAYS_OR_WEEKEND -> arrayListOf(Weekday, Weekend) YEAR -> {
DAY_OF_WEEK -> arrayListOf() val years = arrayListOf<FilterElementRow.Year>()
MONTH_OF_YEAR -> arrayListOf()
YEAR -> arrayListOf()
GAME -> {
val games = arrayListOf<FilterElementRow.Game>()
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
LiveData.GAME.items(realm).forEach { val distinctYears = realm.where<Session>().distinct("year").findAll().sort("year", Sort.DESCENDING)
val game = Game(it as net.pokeranalytics.android.model.realm.Game) distinctYears.forEach { session ->
games.add(game) session.year?.let { year ->
years.add(Year(year))
}
} }
realm.close() realm.close()
games years
}
WEEKDAYS_OR_WEEKEND -> arrayListOf(Weekday, Weekend)
DAY_OF_WEEK -> {
val daysOfWeek = arrayListOf<FilterElementRow.Day>()
DateFormatSymbols.getInstance(Locale.getDefault()).weekdays.forEachIndexed { index, day ->
if (day.isNotEmpty()) {
daysOfWeek.add(Day(index))
}
}
daysOfWeek
}
MONTH_OF_YEAR -> {
val months = arrayListOf<FilterElementRow.Month>()
DateFormatSymbols.getInstance(Locale.getDefault()).months.forEachIndexed { index, month ->
if (month.isNotEmpty()) {
months.add(Month(index))
}
}
months
} }
LOCATION -> arrayListOf() // Duration
BANKROLL -> arrayListOf() SESSION_DURATION -> arrayListOf(DurationMoreThan as FilterElementRow, DurationLessThan as FilterElementRow)
RANGE -> arrayListOf(From, To)
MULTI_TABLING -> arrayListOf() // Sessions
SESSIONS -> arrayListOf(LastGames(0), LastSessions(0))
BLINDS -> arrayListOf() // Cash
CASH_RE_BUY_COUNT -> arrayListOf() BLINDS -> {
BUY_IN -> arrayListOf()
COMPLETION_PERCENTAGE -> arrayListOf() // TODO: Improve the way we get the blinds distinctly
NUMBER_OF_PLAYERS -> arrayListOf() val blinds = arrayListOf<FilterElementRow.Blind>()
val realm = Realm.getDefaultInstance()
val sessions = realm.where<Session>().findAll().sort("cgSmallBlind", Sort.ASCENDING)
val distinctBlinds: ArrayList<Session> = ArrayList()
val blindsHashMap: ArrayList<String> = ArrayList()
sessions.forEach {
if (!blindsHashMap.contains(it.getBlinds())) {
blindsHashMap.add(it.getBlinds())
distinctBlinds.add(it)
}
}
distinctBlinds.forEach { session ->
blinds.add(Blind(session.cgSmallBlind, session.cgBigBlind, session.bankroll?.currency?.code))
session.getBlinds()
}
realm.close()
blinds
}
CASH_RE_BUY_COUNT -> arrayListOf(ReBuyMoreThan as FilterElementRow, ReBuyLessThan as FilterElementRow)
// Tournament
TOURNAMENT_TYPE -> arrayListOf() TOURNAMENT_TYPE -> arrayListOf()
PLAYERS_COUNT -> arrayListOf() COMPLETION_PERCENTAGE -> arrayListOf()
PLACE -> arrayListOf() PLACE -> arrayListOf()
PLAYERS_COUNT -> arrayListOf()
TOURNAMENT_RE_BUY_COUNT -> arrayListOf() TOURNAMENT_RE_BUY_COUNT -> arrayListOf()
BUY_IN -> arrayListOf()
MULTI_PLAYER -> arrayListOf()
RANGE -> arrayListOf()
SESSION_DURATION -> arrayListOf() TOURNAMENT_NAME -> arrayListOf()
TOURNAMENT_FEATURE -> arrayListOf()
LOCATION -> arrayListOf()
BANKROLL -> arrayListOf()
MULTI_TABLING -> arrayListOf()
NUMBER_OF_PLAYERS -> arrayListOf()
MULTI_PLAYER -> arrayListOf()
VALUE -> arrayListOf() VALUE -> arrayListOf()
@ -128,12 +199,19 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
} }
) )
} }
// Add the section row only if we have data for this section
if (data.isNotEmpty()) {
data.add(0, this@FilterSectionRow)
}
return data
} }
private val selectionType: SelectionType private val selectionType: SelectionType
get() { get() {
return when (this) { return when (this) {
CASH_TOURNAMENT, DYNAMIC_DATE, LIVE_ONLINE -> SelectionType.SINGLE CASH_TOURNAMENT, DYNAMIC_DATE, LIVE_ONLINE, SESSIONS -> SelectionType.SINGLE
else -> SelectionType.MULTIPLE else -> SelectionType.MULTIPLE
} }
} }
@ -147,104 +225,3 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
} }
} }
} }
/*
/**
* Return the type of the selection
*/
fun getType(): Type {
return when (this) {
GAME, LIMIT_TYPE -> Type.MULTIPLE
FIXED_DATE, YEAR, WEEKDAYS_OR_WEEKEND, DAY_OF_WEEK, MONTH_OF_YEAR -> Type.MULTIPLE
else -> Type.SINGLE
}
}
/**
* Returns the filter rows
*/
fun getFilterRows(realm: Realm): ArrayList<RowRepresentable> {
val rows = ArrayList<RowRepresentable>()
when (this) {
// General
CASH_TOURNAMENT -> rows.addAll(arrayListOf(FilterRow.CASH_GAME, FilterRow.TOURNAMENT))
LIVE_ONLINE -> rows.addAll(arrayListOf(FilterRow.LIVE, FilterRow.ONLINE))
GAME -> {
val games = realm.copyFromRealm(LiveData.GAME.items(realm) as RealmResults<Game>)
rows.addAll(games)
}
LIMIT_TYPE -> rows.addAll(Limit.numericValues())
TABLE_SIZE -> {
val sessions = realm.where<Session>().sort("tableSize").distinct("tableSize").findAll()
for (session in sessions) {
session.tableSize?.let {
rows.add(TableSize(it, RowViewType.TITLE_CHECK.ordinal))
}
}
}
// Date
DYNAMIC_DATE -> rows.addAll(
arrayListOf(
FilterRow.TODAY, FilterRow.YESTERDAY, FilterRow.TODAY_AND_YESTERDAY, FilterRow.CURRENT_WEEK,
FilterRow.CURRENT_MONTH, FilterRow.CURRENT_YEAR
)
)
FIXED_DATE -> {
rows.addAll(arrayListOf(FilterRow.FROM, FilterRow.TO))
}
DURATION -> rows.addAll(arrayListOf(FilterRow.PAST_DAYS))
YEAR -> {
//TODO: Maybe move this code
val calendar = Calendar.getInstance()
val sessions = realm.where<Session>().sort("endDate", Sort.DESCENDING).findAll()
val years = ArrayList<Int>()
for (session in sessions) {
session.endDate?.let {
calendar.time = it
val year = calendar.get(Calendar.YEAR)
if (!years.contains(year)) {
years.add(year)
}
}
}
for (year in years) {
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.TITLE_CHECK, title = year.toString(), isSelectable = true))
}
}
WEEKDAYS_OR_WEEKEND -> rows.addAll(arrayListOf(FilterRow.WEEKDAYS, FilterRow.WEEKEND))
DAY_OF_WEEK -> {
val dfs = DateFormatSymbols.getInstance(Locale.getDefault())
for (day in dfs.weekdays) {
if (day.isNotEmpty()) {
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.TITLE_CHECK, title = day, isSelectable = true))
}
}
}
MONTH_OF_YEAR -> {
val dfs = DateFormatSymbols.getInstance(Locale.getDefault())
for (month in dfs.months) {
if (month.isNotEmpty()) {
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.TITLE_CHECK, title = month, isSelectable = true))
}
}
}
else -> rows.addAll(arrayListOf())
}
return rows
}
}
*/

@ -62,7 +62,6 @@ enum class SessionRow : RowRepresentable {
START_DATE, END_DATE, BREAK_TIME, COMMENT START_DATE, END_DATE, BREAK_TIME, COMMENT
) )
} }
else -> arrayListOf()
} }
} }
Session.Type.CASH_GAME.ordinal -> { Session.Type.CASH_GAME.ordinal -> {

@ -77,7 +77,7 @@ fun Date.getDayNumber() : String {
} }
// Return the 3 first letters of the date's day // Return the 3 first letters of the date's day
fun Date.getShortDayName() : String { fun Date.getShortDayName() : String {
return SimpleDateFormat("EE", Locale.getDefault()).format(this).substring(0, 3) return SimpleDateFormat("EEE", Locale.getDefault()).format(this)
} }
// Return the month & year of the date // Return the month & year of the date
fun Date.getMonthAndYear(): String { fun Date.getMonthAndYear(): String {
@ -96,3 +96,25 @@ fun Date.getFormattedDuration(toDate: Date) : String {
return "$hoursStr:$minutesStr" return "$hoursStr:$minutesStr"
} }
// Return the date of the beginning of the current date
fun Date.startOfDay() : Date {
val calendar = Calendar.getInstance()
calendar.time = this
calendar.set(Calendar.HOUR_OF_DAY, 0)
calendar.set(Calendar.MINUTE, 0)
calendar.set(Calendar.SECOND, 0)
calendar.set(Calendar.MILLISECOND, 0)
return calendar.time
}
// Return the date of the end of the current date
fun Date.endOfDay() : Date {
val calendar = Calendar.getInstance()
calendar.time = this
calendar.set(Calendar.HOUR_OF_DAY, 23)
calendar.set(Calendar.MINUTE, 59)
calendar.set(Calendar.SECOND, 59)
calendar.set(Calendar.MILLISECOND, 999)
return calendar.time
}

@ -1,5 +1,7 @@
package net.pokeranalytics.android.util.extensions package net.pokeranalytics.android.util.extensions
import android.content.Context
import net.pokeranalytics.android.R
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.NumberFormat import java.text.NumberFormat
import java.util.* import java.util.*
@ -35,6 +37,23 @@ fun Double.formattedHourlyDuration() : String {
return (this * 1000 * 3600).toLong().toMinutes() return (this * 1000 * 3600).toLong().toMinutes()
} }
// Return the time from minutes to hours:minutes
fun Int.toMinutes(context: Context) : String {
val hours = this / 60
val minutesLeft = this % 60
var duration = ""
if (hours < 1) {
duration += "$minutesLeft ${context.getString(if (minutesLeft > 1) R.string.mins else R.string.min)}"
} else {
duration += hours.toString()
duration += ":"
duration += if (minutesLeft < 10) "0$minutesLeft" else minutesLeft.toString()
}
return duration
}
// Return the time from milliseconds to hours:minutes // Return the time from milliseconds to hours:minutes
fun Long.toMinutes() : String { fun Long.toMinutes() : String {
val totalMinutes = this / (1000 * 60) val totalMinutes = this / (1000 * 60)
@ -45,5 +64,4 @@ fun Long.toMinutes() : String {
duration += ":" duration += ":"
duration += if (minutesLeft < 10) "0$minutesLeft" else minutesLeft.toString() duration += if (minutesLeft < 10) "0$minutesLeft" else minutesLeft.toString()
return duration return duration
} }

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>

@ -8,9 +8,9 @@
<FrameLayout <FrameLayout
android:id="@+id/chips" android:id="@+id/chips"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -22,41 +22,8 @@
android:layout_gravity="center" android:layout_gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
app:chipSpacingHorizontal="16dp" app:chipSpacingHorizontal="16dp"
app:singleSelection="true"> app:singleSelection="true" />
<!--
<com.google.android.material.chip.Chip
android:id="@+id/chip1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="NL" />
<com.google.android.material.chip.Chip
android:id="@+id/chip2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PL" />
<com.google.android.material.chip.Chip
android:id="@+id/chip3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="FL" />
<com.google.android.material.chip.Chip
android:id="@+id/chip4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SL" />
<com.google.android.material.chip.Chip
android:id="@+id/chip5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ML" />
-->
</com.google.android.material.chip.ChipGroup>
</FrameLayout> </FrameLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView

@ -8,7 +8,6 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/bottomSheetToolbar" android:id="@+id/bottomSheetToolbar"
style="@style/PokerAnalyticsTheme.Toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?actionBarSize" android:layout_height="?actionBarSize"
tools:title="Test" /> tools:title="Test" />

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:textColor="@color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintTop_toTopOf="@+id/chart"
android:layout_marginTop="8dp"/>
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -145,8 +145,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|center" android:layout_gravity="bottom|center"
android:layout_marginBottom="28dp" android:layout_marginBottom="28dp"
android:alpha="0"
android:elevation="16dp" android:elevation="16dp"
android:scaleX="0"
android:scaleY="0"
android:src="@drawable/ic_outline_play" android:src="@drawable/ic_outline_play"
android:tint="@color/black" /> android:tint="@color/black"
tools:alpha="1"
tools:scaleX="1"
tools:scaleY="1" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -26,6 +26,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:ellipsize="none"
android:gravity="end" android:gravity="end"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/check" app:layout_constraintEnd_toStartOf="@+id/check"

@ -2,6 +2,12 @@
<string name="app_name">Poker Analytics</string> <string name="app_name">Poker Analytics</string>
<!-- Not translated --> <!-- Not translated -->
<string name="session_missing_start_date">Please set a start date for the session</string>
<string name="hour">Hour</string>
<string name="minute">Minute</string>
<!--<string name="session_missing_end_date">Please set the end date for the session</string>-->
<!--<string name="default_error_message">Sorry, something went wrong...please contact us!</string>-->
<string name="address">Address</string> <string name="address">Address</string>
<string name="suggestions">Suggestions</string> <string name="suggestions">Suggestions</string>
<string name="data_deleted" formatted="false">%s deleted</string> <string name="data_deleted" formatted="false">%s deleted</string>

@ -6,9 +6,10 @@
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
<item name="colorControlHighlight">@color/green_transparent</item>
<item name="android:windowBackground">@color/gray_dark</item> <item name="android:windowBackground">@color/gray_dark</item>
<item name="android:navigationBarColor">@color/colorPrimary</item> <item name="android:navigationBarColor">@color/colorPrimary</item>
<item name="colorControlHighlight">@color/green_transparent</item> <item name="android:actionMenuTextColor">@color/white</item>
<item name="bottomNavigationStyle">@style/PokerAnalyticsTheme.BottomNavigationView</item> <item name="bottomNavigationStyle">@style/PokerAnalyticsTheme.BottomNavigationView</item>
<item name="toolbarStyle">@style/PokerAnalyticsTheme.Toolbar</item> <item name="toolbarStyle">@style/PokerAnalyticsTheme.Toolbar</item>
@ -61,7 +62,6 @@
<item name="android:colorPrimary">@color/white</item> <item name="android:colorPrimary">@color/white</item>
<item name="android:titleTextColor">@color/white</item> <item name="android:titleTextColor">@color/white</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item> <item name="popupTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
<item name="actionMenuTextColor">@color/white</item>
</style> </style>
<!-- TextView --> <!-- TextView -->

Loading…
Cancel
Save