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. 128
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  16. 301
      app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt
  17. 315
      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. 44
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  21. 232
      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. 194
      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. 252
      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. 14
      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/RowRepresentableAdapter.kt
  41. 2
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt
  42. 10
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt
  43. 92
      app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
  44. 38
      app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt
  45. 211
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt
  46. 134
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt
  47. 97
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  48. 24
      app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt
  49. 54
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt
  50. 89
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt
  51. 7
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt
  52. 5
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt
  53. 8
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt
  54. 66
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt
  55. 4
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  56. 13
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt
  57. 329
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt
  58. 327
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt
  59. 1
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt
  60. 24
      app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt
  61. 20
      app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt
  62. 7
      app/src/main/res/layout/activity_graph.xml
  63. 37
      app/src/main/res/layout/bottom_sheet_game_list.xml
  64. 1
      app/src/main/res/layout/fragment_bottom_sheet.xml
  65. 25
      app/src/main/res/layout/fragment_graph.xml
  66. 8
      app/src/main/res/layout/fragment_session.xml
  67. 1
      app/src/main/res/layout/row_title_value_check.xml
  68. 6
      app/src/main/res/values/strings.xml
  69. 4
      app/src/main/res/values/styles.xml

@ -42,6 +42,9 @@ assembleDebug:
instrumentedTests:
stage: test
only:
refs:
- master
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
- chmod +x android-wait-for-emulator

@ -7,6 +7,7 @@ apply plugin: 'io.fabric'
repositories {
maven { url 'https://maven.fabric.io/public' }
maven { url 'https://jitpack.io' }
}
android {
@ -27,17 +28,32 @@ android {
applicationId "net.pokeranalytics.android"
minSdkVersion 23
targetSdkVersion 28
versionCode 5
versionCode 8
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
// buildTypes {
// release {
// minifyEnabled true
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// }
// }
buildTypes {
release {
minifyEnabled false
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 {
all*.exclude group: 'com.google.guava', module: 'listenablefuture'
@ -47,7 +63,12 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Kotlin
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
implementation 'androidx.appcompat:appcompat:1.0.2'
@ -72,13 +93,13 @@ dependencies {
// Crashlytics
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
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:runner:1.1.1'
androidTestImplementation 'androidx.test:rules:1.1.1'

@ -20,5 +20,41 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# 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.**
-keep class com.google.j2objc.annotations.** { *; }
-dontwarn com.google.j2objc.annotations.**

@ -15,9 +15,10 @@ open class RealmInstrumentedUnitTest {
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())
session.startDate = Date()
session.type = if (isCashGame) Session.Type.CASH_GAME.ordinal else Session.Type.TOURNAMENT.ordinal
session.result = realm.createObject(Result::class.java)
return session
}

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

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

@ -2,12 +2,13 @@ package net.pokeranalytics.android.unitTests.filter
import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.QueryType
import net.pokeranalytics.android.model.filter.QueryCondition
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.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.util.extensions.startOfDay
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
@ -29,19 +30,15 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, cal.time)
realm.commitTransaction()
val filter = QueryType.DAY_OF_WEEK
val filter = QueryCondition.DAY_OF_WEEK
cal.time = s1.startDate
val filterElementRow = FilterElementRow.Day(cal.get(Calendar.DAY_OF_WEEK))
filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
val filterElement = FilterElement(filterElementRow)
val filterElement = FilterCondition(filterElementRow)
filter.updateValueMap(filterElement)
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(filter)
)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
@ -62,19 +59,15 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, cal.time)
realm.commitTransaction()
val filter = QueryType.MONTH
val filter = QueryCondition.MONTH
cal.time = s1.startDate
val filterElementRow = FilterElementRow.Month(cal.get(Calendar.MONTH))
filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
val filterElement = FilterElement(filterElementRow)
val filterElement = FilterCondition(filterElementRow)
filter.updateValueMap(filterElement)
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(filter)
)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
@ -95,18 +88,14 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, cal.time)
realm.commitTransaction()
val filter = QueryType.YEAR
val filter = QueryCondition.YEAR
cal.time = s1.startDate
val filterElementRow = FilterElementRow.Year(cal.get(Calendar.YEAR))
filterElementRow.filterSectionRow = FilterSectionRow.DYNAMIC_DATE
val filterElement = FilterElement(filterElementRow)
val filterElement = FilterCondition(filterElementRow)
filter.updateValueMap(filterElement)
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(filter)
)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
@ -128,11 +117,8 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Session.testInstance(100.0, true, cal.time)
realm.commitTransaction()
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(QueryType.WEEK_END)
)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.WEEK_END))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
@ -145,19 +131,19 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val realm = this.mockRealm
realm.beginTransaction()
val cal = Calendar.getInstance()
cal.time = Date()
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
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)
realm.commitTransaction()
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(QueryType.WEEK_DAY)
)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(QueryCondition.WEEK_DAY))
Assert.assertEquals(1, sessions.size)
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
fun testStartedFomDateFilter() {
@ -180,17 +383,12 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val s2 = Session.testInstance(100.0, true, cal.time, 1)
realm.commitTransaction()
val filter = QueryType.STARTED_FROM_DATE
val filter = QueryCondition.STARTED_FROM_DATE
val filterElementRow = FilterElementRow.From(s2.startDate!!)
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterElement(filterElementRow))
filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(filter)
)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
@ -213,16 +411,12 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction()
val filter = QueryType.STARTED_TO_DATE
val filter = QueryCondition.STARTED_TO_DATE
val filterElementRow = FilterElementRow.From(s1.startDate!!)
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterElement(filterElementRow))
filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(filter)
)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
@ -246,16 +440,12 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction()
val filter = QueryType.ENDED_FROM_DATE
val filter = QueryCondition.ENDED_FROM_DATE
val filterElementRow = FilterElementRow.From(s2.endDate())
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterElement(filterElementRow))
filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(filter)
)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
@ -279,17 +469,12 @@ class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction()
val filter = QueryType.ENDED_TO_DATE
val filter = QueryCondition.ENDED_TO_DATE
val filterElementRow = FilterElementRow.From(s1.endDate())
filterElementRow.filterSectionRow = FilterSectionRow.FIXED_DATE
filter.updateValueMap(FilterElement(filterElementRow))
filter.updateValueMap(FilterCondition(filterElementRow))
val sessions = Filter.queryOn(
realm,
Session,
arrayListOf(filter)
)
val sessions = Filter.queryOn<Session>(realm, arrayListOf(filter))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {

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

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

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

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

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

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

@ -1,127 +1,218 @@
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 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.Filter
import net.pokeranalytics.android.model.realm.SessionSet
/**
* A sessionGroup of computable items identified by a name
*/
class ComputableGroup(name: String, computables: RealmResults<ComputableResult>, sets: RealmResults<SessionSet>, stats: List<Stat>? = null) {
/**
* The display name of the group
*/
var name: String = name
/**
* The list of endedSessions to compute
*/
var computables: RealmResults<ComputableResult> = computables
/**
* The list of endedSessions to compute
*/
var sets: RealmResults<SessionSet> = sets
/**
* The list of stats to display
*/
var stats: List<Stat>? = stats
/**
* A subgroup used to compute stat variation
*/
var comparedComputables: ComputableGroup? = null
/**
* The computed stats of the comparable sessionGroup
*/
var comparedComputedResults: ComputedResults? = null
class ComputableGroup(name: String, conditions: List<QueryCondition>, stats: List<Stat>? = null) {
/**
* The display name of the group
*/
var name: String = name
/**
* A list of conditions to get
*/
var conditions: List<QueryCondition> = conditions
/**
* The list of endedSessions to compute
*/
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
*/
var stats: List<Stat>? = stats
/**
* A subgroup used to compute stat variation
*/
var comparedComputables: ComputableGroup? = null
/**
* The computed stats of the comparable sessionGroup
*/
var comparedComputedResults: ComputedResults? = null
fun cleanup() {
this._computables = null
this._sessionSets = null
}
}
class ComputedResults(group: ComputableGroup) {
/**
* The session group used to computed the stats
*/
var group: ComputableGroup = group
// The computed stats of the sessionGroup
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf()
// The map containing all evolution numericValues for all stats
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf()
fun allStats() : Collection<ComputedStat> {
return this._computedStats.values
}
fun addEvolutionValue(value: Double, stat: Stat) {
this._addEvolutionValue(Point(value), stat = stat)
}
fun addEvolutionValue(value: Double, duration: Double, stat: Stat) {
this._addEvolutionValue(Point(value, y = duration), stat = stat)
}
private fun _addEvolutionValue(point: Point, stat: Stat) {
var evolutionValues = this._evolutionValues[stat]
if (evolutionValues != null) {
evolutionValues.add(point)
} else {
var values: MutableList<Point> = mutableListOf(point)
this._evolutionValues[stat] = values
}
}
fun addStats(computedStats: Set<ComputedStat>) {
computedStats.forEach {
this._computedStats[it.stat] = it
}
}
fun computedStat(stat: Stat) : ComputedStat? {
return this._computedStats[stat]
}
fun computeStatVariations(resultsToCompare: ComputedResults) {
this._computedStats.keys.forEach { stat ->
var computedStat = this.computedStat(stat)
val comparedStat = resultsToCompare.computedStat(stat)
if (computedStat != null && comparedStat != null) {
computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value
}
}
}
fun finalize(options: Calculator.Options) {
if (options.evolutionValues != Calculator.Options.EvolutionValues.NONE) {
// Sort points as a distribution
this._computedStats.keys.filter { it.hasDistributionSorting() }.forEach { _ ->
// @todo sort
/**
* The session group used to computed the stats
*/
var group: ComputableGroup = group
// The computed stats of the sessionGroup
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf()
// The map containing all evolution numericValues for all stats
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf()
fun allStats(): Collection<ComputedStat> {
return this._computedStats.values
}
/**
* 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, data: Timed) {
stat.underlyingClass = data::class.java
this._addEvolutionValue(Point(value, y = duration, data = data.id), stat = stat)
}
private fun _addEvolutionValue(point: Point, stat: Stat) {
val evolutionValues = this._evolutionValues[stat]
if (evolutionValues != null) {
evolutionValues.add(point)
} else {
val values: MutableList<Point> = mutableListOf(point)
this._evolutionValues[stat] = values
}
}
fun addStats(computedStats: Set<ComputedStat>) {
computedStats.forEach {
this._computedStats[it.stat] = it
}
}
fun computedStat(stat: Stat): ComputedStat? {
return this._computedStats[stat]
}
fun computeStatVariations(resultsToCompare: ComputedResults) {
this._computedStats.keys.forEach { stat ->
val computedStat = this.computedStat(stat)
val comparedStat = resultsToCompare.computedStat(stat)
if (computedStat != null && comparedStat != null) {
computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value
}
}
}
fun finalize(options: Calculator.Options) {
if (options.evolutionValues != Calculator.Options.EvolutionValues.NONE) {
// Sort points as a distribution
this._computedStats.keys.filter { it.hasDistributionSorting() }.forEach { _ ->
// @todo sort
// var evolutionValues = this._evolutionValues[stat]
// evolutionValues.so
}
}
}
/**
* Returns the number of computed stats
*/
fun numberOfStats() : Int {
return this._computedStats.size
}
}
}
}
/**
* Returns the number of computed stats
*/
fun numberOfStats(): Int {
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) {
val x: Double = x
val y: Double = y
class Point(val x: Double, val y: Double, val data: Any) {
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
import android.content.Context
import io.realm.RealmModel
import net.pokeranalytics.android.R
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.RowViewType
import net.pokeranalytics.android.util.CurrencyUtils
@ -11,90 +13,159 @@ import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
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
*/
enum class Stat : RowRepresentable {
NETRESULT,
HOURLY_RATE,
AVERAGE,
NUMBER_OF_SETS,
NUMBER_OF_GAMES,
DURATION,
AVERAGE_DURATION,
NET_BB_PER_100_HANDS,
HOURLY_RATE_BB,
AVERAGE_NET_BB,
WIN_RATIO,
AVERAGE_BUYIN,
ROI,
STANDARD_DEVIATION,
STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS,
HANDS_PLAYED;
/**
* Returns whether the stat evolution numericValues requires a distribution sorting
*/
fun hasDistributionSorting() : Boolean {
return when (this) {
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true
else -> false
}
}
companion object {
fun returnOnInvestment(netResult: Double, buyin: Double) : Double? {
if (buyin == 0.0) {
return null
}
return netResult / buyin
}
fun netBBPer100Hands(netBB: Double, numberOfHands: Double) : Double? {
if (numberOfHands == 0.0) {
return null
}
return netBB / numberOfHands * 100
}
}
override val resId: Int?
get() {
return when (this) {
NETRESULT -> R.string.net_result
HOURLY_RATE -> R.string.average_hour_rate
AVERAGE -> R.string.average
NUMBER_OF_SETS -> R.string.number_of_sessions
NUMBER_OF_GAMES -> R.string.number_of_records
DURATION -> R.string.duration
AVERAGE_DURATION -> R.string.average_hours_played
NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
HOURLY_RATE_BB -> R.string.average_hour_rate_bb_
AVERAGE_NET_BB -> R.string.average_net_result_bb_
WIN_RATIO -> R.string.win_ratio
AVERAGE_BUYIN -> R.string.average_buyin
ROI -> R.string.tournament_roi
STANDARD_DEVIATION -> R.string.standard_deviation
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands
HANDS_PLAYED -> R.string.number_of_hands
}
}
val threshold: Double
get() {
return when (this) {
WIN_RATIO -> 50.0
else -> 0.0
}
}
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentable {
NETRESULT,
HOURLY_RATE,
AVERAGE,
NUMBER_OF_SETS,
NUMBER_OF_GAMES,
DURATION,
AVERAGE_DURATION,
NET_BB_PER_100_HANDS,
HOURLY_RATE_BB,
AVERAGE_NET_BB,
WIN_RATIO,
AVERAGE_BUYIN,
ROI,
STANDARD_DEVIATION,
STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS,
HANDS_PLAYED;
/**
* Returns whether the stat evolution numericValues requires a distribution sorting
*/
fun hasDistributionSorting(): Boolean {
return when (this) {
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true
else -> false
}
}
companion object {
fun returnOnInvestment(netResult: Double, buyin: Double): Double? {
if (buyin == 0.0) {
return null
}
return netResult / buyin
}
fun netBBPer100Hands(netBB: Double, numberOfHands: Double): Double? {
if (numberOfHands == 0.0) {
return null
}
return netBB / numberOfHands * 100
}
}
override val resId: Int?
get() {
return when (this) {
NETRESULT -> R.string.net_result
HOURLY_RATE -> R.string.average_hour_rate
AVERAGE -> R.string.average
NUMBER_OF_SETS -> R.string.number_of_sessions
NUMBER_OF_GAMES -> R.string.number_of_records
DURATION -> R.string.duration
AVERAGE_DURATION -> R.string.average_hours_played
NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
HOURLY_RATE_BB -> R.string.average_hour_rate_bb_
AVERAGE_NET_BB -> R.string.average_net_result_bb_
WIN_RATIO -> R.string.win_ratio
AVERAGE_BUYIN -> R.string.average_buyin
ROI -> R.string.tournament_roi
STANDARD_DEVIATION -> R.string.standard_deviation
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands
HANDS_PLAYED -> R.string.number_of_hands
}
}
/**
* 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
get() {
return when (this) {
WIN_RATIO -> 50.0
else -> 0.0
}
}
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
}
/**
@ -102,63 +173,29 @@ enum class Stat : RowRepresentable {
*/
class ComputedStat(var stat: Stat, var value: Double, var currency: Currency? = null) {
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) {
if (previousValue != null) {
this.variation = (value - previousValue) / previousValue
}
}
/**
* The variation of the stat
*/
var variation: Double? = null
/**
* Formats the value of the stat to be suitable for display
*/
fun format(context: Context): TextFormat {
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")
}
}
/**
* Returns a TextFormat instance for an evolution value located at the specified [index]
*/
fun evolutionValueFormat(index: Int) : TextFormat {
return TextFormat("undef ${index}")
}
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) {
if (previousValue != null) {
this.variation = (value - previousValue) / previousValue
}
}
/**
* The variation of the stat
*/
var variation: Double? = null
/**
* Formats the value of the stat to be suitable for display
*/
fun format(context: Context): TextFormat {
return this.stat.format(this.value, this.currency, context)
}
/**
* Returns a TextFormat instance for an evolution value located at the specified [index]
*/
fun evolutionValueFormat(index: Int): TextFormat {
return TextFormat("undef ${index}")
}
}

@ -1,23 +1,23 @@
package net.pokeranalytics.android.exceptions
class ModelException(message: String) : Exception(message) {
}
class FormattingException(message: String) : Exception(message) {
}
class RowRepresentableEditDescriptorException(message: String) : Exception(message) {
}
class FilterValueMapException(message: String) : Exception(message) {
init {
println("FilterValueMapException(): $message")
}
}
class ConfigurationException(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 RowRepresentableEditDescriptorException(message: String) : Exception(message)
class ConfigurationException(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")
object FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable")
object QueryValueMapUnknown: PokerAnalyticsException(message = "fieldName is missing")
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")
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
class StatRepresentable(stat: Stat, computedStat: ComputedStat?) : RowRepresentable {
class StatRepresentable(stat: Stat, computedStat: ComputedStat?, groupName: String = "") : RowRepresentable {
var stat: Stat = stat
var computedStat: ComputedStat? = computedStat
var groupName: String = groupName
override val viewType: Int
get() = RowViewType.STAT.ordinal

@ -1,6 +1,9 @@
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:
@ -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 {
/**
* return the path of the parameter used in the [QueryCondition] related to this entity
*/
// 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}")
}
}
}
val relatedEntity: Class<out RealmObject>
}
/**
* return the path of the parameter used in the [QueryType] related to this entity
*/
fun fieldNameForQueryType(queryType: QueryType) : String?
}
//
//fun MutableList<Filterable>.filter(filter: FilterElement) : List<Filterable> {
//fun MutableList<Filterable>.filter(filter: FilterCondition) : List<Filterable> {
//
// return this.filter { f ->
// return@filter true

@ -1,12 +1,13 @@
package net.pokeranalytics.android.model.filter
import io.realm.RealmList
import io.realm.RealmObject
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.FilterElement
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.extensions.endOfDay
import net.pokeranalytics.android.util.extensions.startOfDay
import java.util.*
@ -16,7 +17,7 @@ import java.util.*
* To handle that, the enum has a public [valueMap] variable
* 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,
CASH,
ONLINE,
@ -31,23 +32,27 @@ enum class QueryType(private var subType:SubType? = null) {
TABLE_SIZE,
TOURNAMENT_TYPE,
BLINDS,
MORE_NUMBER_OF_TABLE(SubType.MORE),
LESS_NUMBER_OF_TABLE(SubType.LESS),
BETWEEN_NUMBER_OF_TABLE(SubType.BETWEEN),
MORE_THAN_NET_RESULT(SubType.MORE),
LESS_THAN_NET_RESULT(SubType.LESS),
MORE_THAN_BUY_IN(SubType.MORE),
LESS_THAN_BUY_IN(SubType.LESS),
MORE_THAN_CASH_OUT(SubType.MORE),
LESS_THAN_CASH_OUT(SubType.LESS),
MORE_THAN_TIPS(SubType.MORE),
LESS_THAN_TIPS(SubType.LESS),
MORE_THAN_NUMBER_OF_PLAYER(SubType.MORE),
LESS_THAN_NUMBER_OF_PLAYER(SubType.LESS),
BETWEEN_NUMBER_OF_PLAYER(SubType.BETWEEN),
MORE_THAN_TOURNAMENT_FEE(SubType.MORE),
LESS_THAN_TOURNAMENT_FEE(SubType.LESS),
BETWEEN_TOURNAMENT_FEE(SubType.BETWEEN),
LAST_GAMES,
LAST_SESSIONS,
MORE_NUMBER_OF_TABLE(Operator.MORE),
LESS_NUMBER_OF_TABLE(Operator.LESS),
BETWEEN_NUMBER_OF_TABLE(Operator.BETWEEN),
MORE_THAN_NET_RESULT(Operator.MORE),
LESS_THAN_NET_RESULT(Operator.LESS),
MORE_THAN_BUY_IN(Operator.MORE),
LESS_THAN_BUY_IN(Operator.LESS),
MORE_THAN_CASH_OUT(Operator.MORE),
LESS_THAN_CASH_OUT(Operator.LESS),
MORE_THAN_TIPS(Operator.MORE),
LESS_THAN_TIPS(Operator.LESS),
MORE_THAN_NUMBER_OF_PLAYER(Operator.MORE),
LESS_THAN_NUMBER_OF_PLAYER(Operator.LESS),
BETWEEN_NUMBER_OF_PLAYER(Operator.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
STARTED_FROM_DATE,
@ -59,6 +64,15 @@ enum class QueryType(private var subType:SubType? = null) {
YEAR,
WEEK_DAY,
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_CODE,
@ -68,17 +82,33 @@ enum class QueryType(private var subType:SubType? = null) {
;
private enum class SubType {
enum class Operator {
BETWEEN,
MORE,
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>?
get() {
this.subType?.let {
this.operator?.let {
return when (it) {
SubType.BETWEEN -> arrayOf("leftValue", "rightValue")
Operator.BETWEEN -> arrayOf("leftValue", "rightValue")
else -> arrayOf("value")
}
}
@ -98,18 +128,19 @@ enum class QueryType(private var subType:SubType? = null) {
* 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]
*/
fun filter(realmQuery: RealmQuery<out RealmObject>, filterable: Filterable): RealmQuery<out RealmObject> {
inline fun <reified T : Filterable> filter(realmQuery: RealmQuery<T>): RealmQuery<T> {
when {
this == BLINDS -> {
val smallBlindFieldName = filterable.fieldNameForQueryType(SMALL_BLIND)
val bigBlindFieldName = filterable.fieldNameForQueryType(BIG_BLIND)
val currencyCodeFieldName = filterable.fieldNameForQueryType(CURRENCY_CODE)
smallBlindFieldName ?: throw FilterValueMapException("fieldName is missing")
bigBlindFieldName ?: throw FilterValueMapException("fieldName is missing")
currencyCodeFieldName ?: throw FilterValueMapException("fieldName is missing")
val smallBlindFieldName = FilterHelper.fieldNameForQueryType<T>(SMALL_BLIND)
val bigBlindFieldName = FilterHelper.fieldNameForQueryType<T>(BIG_BLIND)
val currencyCodeFieldName = FilterHelper.fieldNameForQueryType<T>(CURRENCY_CODE)
smallBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
bigBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
currencyCodeFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
val blinds: RealmList<FilterElementBlind> by valueMap
blinds.forEachIndexed {index, blind ->
blinds.forEachIndexed { index, blind ->
realmQuery
.beginGroup()
@ -123,7 +154,7 @@ enum class QueryType(private var subType:SubType? = null) {
.equalTo(bigBlindFieldName, blind.bb)
.and()
blind.code?.let {
blind.currencyCode?.let {
realmQuery.equalTo(currencyCodeFieldName, it)
} ?: run {
realmQuery.isNull(currencyCodeFieldName)
@ -137,34 +168,31 @@ enum class QueryType(private var subType:SubType? = null) {
}
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 -> {
val fieldName = filterable.fieldNameForQueryType(this)
fieldName ?: throw FilterValueMapException("fieldName is missing")
this.subType?.let { subType ->
return when (subType) {
SubType.LESS -> {
val value: Double by valueMap
realmQuery.lessThanOrEqualTo(fieldName, value)
}
SubType.MORE -> {
val value: Double by valueMap
realmQuery.greaterThanOrEqualTo(fieldName, value)
}
SubType.BETWEEN -> {
val leftValue: Double by valueMap
val rightValue: Double by valueMap
realmQuery.between(fieldName, leftValue, rightValue)
}
val fieldName = FilterHelper.fieldNameForQueryType<T>(this)
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
when (operator) {
Operator.LESS -> {
val value: Double by valueMap
return realmQuery.lessThanOrEqualTo(fieldName, value)
}
Operator.MORE -> {
val value: Double by valueMap
return realmQuery.greaterThanOrEqualTo(fieldName, value)
}
Operator.BETWEEN -> {
val leftValue: Double by valueMap
val rightValue: Double by valueMap
return realmQuery.between(fieldName, leftValue, rightValue)
}
}
return when (this) {
LIVE -> realmQuery.equalTo(fieldName, true)
LIVE, ONLINE -> realmQuery.equalTo(fieldName, this == LIVE)
CASH -> realmQuery.equalTo(fieldName, Session.Type.CASH_GAME.ordinal)
TOURNAMENT -> realmQuery.equalTo(fieldName, Session.Type.TOURNAMENT.ordinal)
ALL_TOURNAMENT_FEATURES -> {
val ids: Array<String> by valueMap
ids.forEach {
@ -212,11 +240,53 @@ enum class QueryType(private var subType:SubType? = null) {
val year: Int by valueMap
realmQuery.equalTo(fieldName, year)
}
WEEK_END -> {
realmQuery.`in`(fieldName, arrayOf(Calendar.SATURDAY, Calendar.SUNDAY))
WEEK_END, WEEK_DAY -> {
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 -> {
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) {
return
}
this.subType?.let { subType ->
valueMap = when (subType) {
SubType.LESS, SubType.MORE -> {
mapOf("value" to filterElement.value)
}
SubType.BETWEEN -> {
mapOf(
"leftValue" to filterElement.leftValue,
"rightValue" to filterElement.rightValue
)
}
}
this.operator?.let {
valueMap = mapOf("value" to filterCondition.value)
return
}
when (this) {
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 -> {
valueMap = mapOf("values" to filterElement.values)
valueMap = mapOf("values" to filterCondition.values)
}
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 -> {
valueMap = mapOf("date" to filterElement.date)
valueMap = mapOf("date" to filterCondition.date)
}
DAY_OF_WEEK -> {
valueMap = mapOf("dayOfWeek" to filterElement.dayOfWeek)
valueMap = mapOf("dayOfWeek" to filterCondition.dayOfWeek)
}
MONTH -> {
valueMap = mapOf("month" to filterElement.month)
valueMap = mapOf("month" to filterCondition.month)
}
YEAR -> {
valueMap = mapOf("year" to filterElement.year)
valueMap = mapOf("year" to filterCondition.year)
}
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
import net.pokeranalytics.android.calculus.StatBase
import java.util.*
interface Timed {
interface Timed : StatBase, Identifiable {
fun startDate() : Date?

@ -15,41 +15,36 @@ class PokerAnalyticsMigration : RealmMigration {
var currentVersion = oldVersion.toInt()
Timber.d("*** migrate from $oldVersion to $newVersion")
// Migrate to version 1: Add a new class.
// Example:
// public Person extends RealmObject {
// private String name;
// private int age;
// // getters and setters left out for brevity
// }
/*
// Migrate to version 1
if (currentVersion == 0) {
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++
}
*/
// 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
// }
/*
// Migrate to version 2
if (currentVersion == 1) {
schema.get("Person")!!
.addField("id", Long::class.javaPrimitiveType!!, FieldAttribute.PRIMARY_KEY)
.addRealmObjectField("favoriteDog", schema.get("Dog")!!)
.addRealmListField("dogs", schema.get("Dog")!!)
Timber.d("*** Running migration ${currentVersion + 1}")
schema.rename("FilterElement", "FilterCondition")
schema.get("Filter")?.let {
it.renameField("filterElements", "filterConditions")
}
schema.get("SessionSet")?.let {
it.addPrimaryKey("id")
}
currentVersion++
}
*/
}

@ -6,8 +6,8 @@ import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
@ -70,7 +70,13 @@ open class Bankroll() : RealmObject(), NameManageable, StaticRowRepresentableDat
}
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) {

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

@ -2,96 +2,144 @@ package net.pokeranalytics.android.model.realm
import io.realm.*
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.QueryType
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import org.jetbrains.annotations.TestOnly
import timber.log.Timber
import java.util.*
/**
* 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
*/
open class Filter : RealmObject() {
companion object {
@TestOnly
fun queryOn(realm: Realm, entity: Filterable, queries:List<QueryType>): RealmResults<*> {
var realmQuery : RealmQuery<out RealmObject> = realm.where(entity.relatedEntity)
queries.forEach {
realmQuery = (it.filter(realmQuery, entity))
}
return realmQuery.findAll()
}
}
private var entityType: Int? = Entity.SESSION.ordinal
@PrimaryKey
var id = UUID.randomUUID().toString()
// the filter name
var name: String = ""
// the number of use of the filter,
// for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0)
var filterElements: RealmList<FilterElement> = RealmList()
private set
fun createOrUpdateFilterElements(filterElementRows: ArrayList<FilterElementRow>) {
filterElements.clear()
filterElementRows
.map {
it.filterSectionRow
}
.distinct()
.forEach { section ->
filterElementRows
.filter {
it.filterSectionRow == section
}
.apply {
if (this.size == 1) {
filterElements.add(FilterElement(this.first()))
} else {
val casted = arrayListOf<FilterElementRow>()
casted.addAll(this)
filterElements.add(FilterElement(casted))
}
}
}
}
private enum class Entity {
SESSION,
;
}
fun countBy(filterCategoryRow: FilterCategoryRow) : Int {
val sections = filterCategoryRow.filterSectionRows
return filterElements.count {
sections.contains(FilterSectionRow.valueOf(it.sectionName))
}
}
companion object {
fun contains(filterElementRow:FilterElementRow) : Boolean {
val filtered = filterElements.filter {
it.filterName == filterElementRow.filterName
}
if (filtered.isEmpty()) {
return false
}
return filterElementRow.contains(filtered)
}
// Create a new instance
fun newInstance(realm: Realm): Filter {
val filter = Filter()
return realm.copyToRealm(filter)
}
fun queryOn(entity: Filterable) : RealmResults<*> {
var realmQuery : RealmQuery<out RealmObject> = realm.where(entity.relatedEntity)
this.filterElements.map {
it.queryType
}.forEach {
realmQuery = (it.filter(realmQuery, entity))
}
// Get a filter by its id
fun getFilterBydId(realm: Realm, filterId: String): Filter? {
return realm.where<Filter>().equalTo("id", filterId).findFirst()
}
return realmQuery.findAll()
@TestOnly
inline fun <reified T : Filterable> queryOn(realm: Realm, queries: List<QueryCondition>): RealmResults<T> {
var realmQuery = realm.where<T>()
queries.forEach {
realmQuery = it.filter(realmQuery)
}
Timber.d(">>> Filter query: ${realmQuery.description}")
return realmQuery.findAll()
}
}
@PrimaryKey
var id = UUID.randomUUID().toString()
// the filter name
var name: String = ""
// the number of use of the filter,
// for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0)
var filterConditions: RealmList<FilterCondition> = RealmList()
private set
fun createOrUpdateFilterConditions(filterConditionRows: ArrayList<FilterElementRow>) {
filterConditions.clear()
filterConditionRows
.map {
it.filterName
}
.distinct()
.forEach { filterName->
filterConditionRows
.filter {
it.filterName == filterName
}
.apply {
val casted = arrayListOf<FilterElementRow>()
casted.addAll(this)
filterConditions.add(FilterCondition(casted))
}
}
}
fun countBy(filterCategoryRow: FilterCategoryRow): Int {
val sections = filterCategoryRow.filterSectionRows
return filterConditions.count {
sections.contains(FilterSectionRow.valueOf(it.sectionName ?: throw PokerAnalyticsException.FilterElementUnknownSectionName))
}
}
fun contains(filterElementRow: FilterElementRow): Boolean {
val filtered = filterConditions.filter {
it.filterName == filterElementRow.filterName
}
if (filtered.isEmpty()) {
return false
}
return filterElementRow.contains(filtered)
}
/**
* Set the saved value in the filter for the given [filterElementRow]
*/
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 {
realmQuery = it.filter(realmQuery)
}
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,
var bb : Double? = null,
var code : String? = null
var currencyCode : String? = null
) : RealmObject()

@ -13,6 +13,8 @@ import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedStat
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.model.Limit
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.getState
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryType
import net.pokeranalytics.android.model.filter.QueryType.*
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.filter.QueryCondition.*
import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -40,15 +42,18 @@ import java.util.*
import java.util.Currency
import kotlin.collections.ArrayList
typealias BB = Double
open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed,
TimeFilterable {
TimeFilterable, Filterable {
enum class Type {
CASH_GAME,
TOURNAMENT
}
companion object : Filterable {
companion object {
fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session {
val session = Session()
session.result = Result()
@ -61,12 +66,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return realm.copyToRealm(session)
}
override val relatedEntity: Class<out RealmObject> = Session::class.java
override fun fieldNameForQueryType(queryType: QueryType): String? {
return when (queryType) {
LIVE -> "bankroll.live"
CASH -> "type"
fun fieldNameForQueryType(queryCondition: QueryCondition): String? {
return when (queryCondition) {
LIVE, ONLINE -> "bankroll.live"
CASH, TOURNAMENT -> "type"
BANKROLL -> "bankroll.id"
GAME -> "game.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"
STARTED_FROM_DATE, STARTED_TO_DATE -> "startDate"
ENDED_FROM_DATE, ENDED_TO_DATE -> "endDate"
DAY_OF_WEEK, WEEK_END -> "dayOfWeek"
DAY_OF_WEEK, WEEK_END, WEEK_DAY -> "dayOfWeek"
MONTH -> "month"
YEAR -> "year"
TODAY, YESTERDAY, TODAY_AND_YESTERDAY, THIS_YEAR, THIS_MONTH, THIS_WEEK -> "startDate"
else -> null
}
}
@ -292,7 +296,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
/**
* The net result in big blinds
*/
val bbNet: Double
val bbNet: BB
get() {
val bb = this.cgBigBlind; val result = this.result
if (bb != null && result != null) {
@ -323,8 +327,8 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
val computableResult = realm.createObject(ComputableResult::class.java)
computableResult.session = this
} // if a ComputableResult exists and the session is not completed, delete it
else if (this.startDate == null || this.endDate == null) {
this.computableResult?.deleteFromRealm()
else if ((this.startDate == null || this.endDate == null) && this.computableResult != null && this.computableResult.isValid) {
this.computableResult.deleteFromRealm()
}
// Update the ComputableResult
@ -366,6 +370,11 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
throw ModelException("Session should have an existing Result relationship")
}
val bbHourlyRate: BB
get() {
return this.bbNet / this.hourlyDuration
}
// Manageable
override fun isValidForSave(): Boolean {
@ -443,9 +452,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/
fun restart() {
realm.executeTransaction {
// this.timeFrame?.paused = false
this.pauseDate = null
this.startDate = Date() // timeFrame?.setDate(Date(), null)
this.startDate = Date()
this.endDate = null
this.breakDuration = 0L
}
}
@ -517,7 +526,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/
fun cleanup() {
this.sessionSet?.let { set ->
this.sessionSet?.let {
// Updates the timeline
SessionSetManager.removeFromTimeline(this)
@ -718,107 +727,150 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
override fun updateValue(value: Any?, row: RowRepresentable) {
realm.beginTransaction()
when (row) {
SessionRow.BANKROLL -> bankroll = value as Bankroll?
SessionRow.BLINDS -> if (value is ArrayList<*>) {
cgSmallBlind = try {
(value[0] as String? ?: "0").toDouble()
} catch (e: Exception) {
null
}
cgBigBlind = try {
(value[1] as String? ?: "0").toDouble()
} catch (e: Exception) {
null
realm.executeTransaction {
when (row) {
SessionRow.BANKROLL -> bankroll = value as Bankroll?
SessionRow.BLINDS -> if (value is ArrayList<*>) {
cgSmallBlind = try {
(value[0] as String? ?: "0").toDouble()
} catch (e: Exception) {
null
}
cgBigBlind = try {
(value[1] as String? ?: "0").toDouble()
} catch (e: Exception) {
null
}
cgBigBlind?.let {
if (cgSmallBlind == null || cgSmallBlind == 0.0) {
cgSmallBlind = it / 2.0
}
}
} else if (value == null) {
cgSmallBlind = null
cgBigBlind = null
}
SessionRow.BREAK_TIME -> {
this.breakDuration = if (value != null) (value as String).toLong() * 60 * 1000 else 0
}
SessionRow.BUY_IN -> {
val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
localResult.buyin = value as Double?
this.result = localResult
this.updateRowRepresentation()
}
SessionRow.CASHED_OUT, SessionRow.PRIZE -> {
val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
if (value == null) {
localResult.cashout = null
} else {
localResult.cashout = (value as String).toDouble()
}
cgBigBlind?.let {
if (cgSmallBlind == null || cgSmallBlind == 0.0) {
cgSmallBlind = it / 2.0
this.result = localResult
}
SessionRow.NET_RESULT -> {
this.result?.let { result ->
result.netResult = (value as String).toDouble()
}
}
} else if (value == null) {
cgSmallBlind = null
cgBigBlind = null
}
SessionRow.BREAK_TIME -> {
this.breakDuration = if (value != null) (value as String).toLong() * 60 * 1000 else 0
}
SessionRow.BUY_IN -> {
val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
localResult.buyin = value as Double?
this.result = localResult
this.updateRowRepresentation()
}
SessionRow.CASHED_OUT, SessionRow.PRIZE -> {
val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
SessionRow.COMMENT -> comment = value as String? ?: ""
SessionRow.END_DATE -> if (value is Date?) {
this.endDate = value
if (value == null) {
localResult.cashout = null
} else {
localResult.cashout = (value as String).toDouble()
}
SessionRow.GAME -> {
if (value is ArrayList<*>) {
limit = try {
(value[0] as Int?)
} catch (e: Exception) {
null
}
game = try {
(value[1] as Game?)
} catch (e: Exception) {
null
}
} else if (value is Game) {
game = value
} else if (value == null) {
limit = null
game = null
this.result = localResult
}
SessionRow.NET_RESULT -> {
this.result?.let { result ->
result.netResult = (value as String).toDouble()
}
}
SessionRow.INITIAL_BUY_IN -> tournamentEntryFee = if (value == null) null else (value as String).toDouble()
SessionRow.LOCATION -> location = value as Location?
SessionRow.PLAYERS -> tournamentNumberOfPlayers = if (value != null) (value as String).toInt() else null
SessionRow.POSITION -> {
val localResult = if (result != null) result as Result else realm.createObject(Result::class.java)
localResult.tournamentFinalPosition = if (value == null) null else (value as String).toInt()
result = localResult
}
SessionRow.START_DATE -> if (value is Date) {
this.startDate = value
}
SessionRow.TABLE_SIZE -> tableSize = value as Int?
SessionRow.TIPS -> {
val localResult = if (result != null) result as Result else realm.createObject(Result::class.java)
localResult.tips = value as Double?
result = localResult
}
SessionRow.TOURNAMENT_NAME -> tournamentName = value as TournamentName?
SessionRow.TOURNAMENT_TYPE -> tournamentType = value as Int?
SessionRow.TOURNAMENT_FEATURE -> value?.let {
tournamentFeatures = RealmList()
tournamentFeatures.addAll((it as ArrayList<TournamentFeature>))
}
}
SessionRow.COMMENT -> comment = value as String? ?: ""
}
SessionRow.END_DATE -> if (value is Date?) {
this.endDate = value
}
}
SessionRow.GAME -> {
if (value is ArrayList<*>) {
limit = try {
(value[0] as Int?)
} catch (e: Exception) {
null
}
game = try {
(value[1] as Game?)
} catch (e: Exception) {
// 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
}
} else if (value is Game) {
game = value
} else if (value == null) {
limit = null
game = 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}")
}
SessionRow.INITIAL_BUY_IN -> tournamentEntryFee = if (value == null) null else (value as String).toDouble()
SessionRow.LOCATION -> location = value as Location?
SessionRow.PLAYERS -> tournamentNumberOfPlayers = if (value != null) (value as String).toInt() else null
SessionRow.POSITION -> {
val localResult = if (result != null) result as Result else realm.createObject(Result::class.java)
localResult.tournamentFinalPosition = if (value == null) null else (value as String).toInt()
result = localResult
}
SessionRow.START_DATE -> if (value is Date) {
this.startDate = value
}
SessionRow.TABLE_SIZE -> tableSize = value as Int?
SessionRow.TIPS -> {
val localResult = if (result != null) result as Result else realm.createObject(Result::class.java)
localResult.tips = value as Double?
result = localResult
}
SessionRow.TOURNAMENT_NAME -> tournamentName = value as TournamentName?
SessionRow.TOURNAMENT_TYPE -> tournamentType = value as Int?
SessionRow.TOURNAMENT_FEATURE -> value?.let {
tournamentFeatures = RealmList()
tournamentFeatures.addAll((it as ArrayList<TournamentFeature>))
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")
}
realm.commitTransaction()
}
}

@ -1,15 +1,25 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.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 java.util.*
open class SessionSet : RealmObject(), Timed {
open class SessionSet() : RealmObject(), Timed, Filterable {
@PrimaryKey
override var id = UUID.randomUUID().toString()
var startDate: Date = Date()
set(value) {
@ -47,15 +57,6 @@ open class SessionSet : RealmObject(), Timed {
*/
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() {
this.ratedNet = this.sessions?.sumByDouble { it.computableResult?.ratedNet ?: 0.0 } ?: 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 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) {
RATED_NET("ratedNet"),
@ -85,5 +96,18 @@ open class SessionSet : RealmObject(), Timed {
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> {
when (this.type) {
Session.Type.CASH_GAME.ordinal -> {
Session.Type.TOURNAMENT.ordinal -> {
return listOf(
SessionRow.GAME,
SessionRow.INITIAL_BUY_IN,
SessionRow.BANKROLL,
SessionRow.TABLE_SIZE,
SessionRow.TOURNAMENT_NAME
SessionRow.TOURNAMENT_NAME,
SessionRow.TOURNAMENT_TYPE
)
}
Session.Type.TOURNAMENT.ordinal -> {
Session.Type.CASH_GAME.ordinal -> {
return listOf(
SessionRow.GAME,
SessionRow.BLINDS,
SessionRow.BANKROLL,
SessionRow.TABLE_SIZE,
SessionRow.TOURNAMENT_TYPE
SessionRow.TABLE_SIZE
)
}
}
@ -124,7 +124,7 @@ class FavoriteSessionFinder {
}
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.RealmResults
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import kotlin.math.max
@ -28,10 +29,10 @@ class SessionSetManager {
}
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) {
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 startDate = session.startDate!!

@ -9,8 +9,10 @@ import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.DataListFragment
class DataListActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"),
ITEM_DELETED("ITEM_DELETED")
}
companion object {

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

@ -8,9 +8,10 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.FilterDetailsFragment
class FilterDetailsActivity : PokerAnalyticsActivity() {
class FilterDetailsActivity : PokerAnalyticsActivity() {
enum class IntentKey(val keyName: String) {
FILTER_ID("FILTER_ID"),
FILTER_CATEGORY_ORDINAL("FILTER_CATEGORY_ORDINAL")
}
@ -18,8 +19,9 @@ class FilterDetailsActivity : PokerAnalyticsActivity() {
/**
* Default constructor
*/
fun newInstance(context: Context, filterCategoryOrdinal: Int) {
fun newInstance(context: Context, filterId: String, filterCategoryOrdinal: Int) {
val intent = Intent(context, FilterDetailsActivity::class.java)
intent.putExtra(IntentKey.FILTER_ID.keyName, filterId)
intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal)
context.startActivity(intent)
}
@ -27,14 +29,15 @@ class FilterDetailsActivity : PokerAnalyticsActivity() {
/**
* 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)
intent.putExtra(IntentKey.FILTER_ID.keyName, filterId)
intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal)
fragment.startActivityForResult(intent, requestCode)
}
}
private lateinit var fragment:FilterDetailsFragment
private lateinit var fragment: FilterDetailsFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -53,12 +56,13 @@ class FilterDetailsActivity : PokerAnalyticsActivity() {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val filterId = intent.getStringExtra(IntentKey.FILTER_ID.keyName)
val filterCategoryOrdinal = intent.getIntExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, 0)
fragment = FilterDetailsFragment()
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
fragment.setData(filterCategoryOrdinal)
fragment.setData(filterId, filterCategoryOrdinal)
}

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

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

@ -8,7 +8,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
interface RowRepresentableDelegate {
fun onRowSelected(position: Int, row: RowRepresentable, fromAction:Boolean = false) {}
fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean = false) {}
fun onRowValueChanged(value: Any?, row: RowRepresentable) {}
}

@ -102,7 +102,7 @@ class UnmanagedRowRepresentableException(message: String) : Exception(message) {
* - string
* - booleans
* - 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 {

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

@ -1,5 +1,7 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -11,9 +13,14 @@ import com.google.android.material.snackbar.Snackbar
import io.realm.RealmObject
import io.realm.RealmResults
import kotlinx.android.synthetic.main.fragment_data_list.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.Deletable
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.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource
@ -28,12 +35,17 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow
class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_DETAILS = 1000
}
private lateinit var dataType: SettingRow
private lateinit var items: RealmResults<*>
private lateinit var dataListAdapter: RowRepresentableAdapter
private var deletedItem: RealmObject? = null
private var lastDeletedItemPosition: Int = 0
private var lastItemClickedPosition: Int = 0
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_data_list, container, false)
@ -44,6 +56,19 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
initUI()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_DETAILS && resultCode == Activity.RESULT_OK) {
val needToDeleteItem = data?.getBooleanExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, false) ?: false
if (needToDeleteItem) {
GlobalScope.launch(Dispatchers.Main) {
delay(300)
deleteItem(lastItemClickedPosition)
}
}
}
}
override fun onResume() {
super.onResume()
this.recyclerView?.adapter?.notifyDataSetChanged()
@ -68,10 +93,12 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
this.dataType.relatedResultsRepresentable?.let {
EditableDataActivity.newInstance(
requireContext(),
lastItemClickedPosition = position
EditableDataActivity.newInstanceForResult(
this,
it.ordinal,
(row as Identifiable).id
(row as Identifiable).id,
REQUEST_CODE_DETAILS
)
}
}
@ -94,29 +121,7 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
dataListAdapter = RowRepresentableAdapter(this, this)
val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position ->
// Save the delete position & create a copy of the object
val mRecentlyDeletedItem = rowRepresentableForPosition(position)
lastDeletedItemPosition = position
if (mRecentlyDeletedItem is RealmObject) {
// Check if the object is valid for the deletion
if ((mRecentlyDeletedItem as Deletable).isValidForDelete(this.getRealm())) {
deletedItem = getRealm().copyFromRealm(mRecentlyDeletedItem)
getRealm().executeTransaction {
mRecentlyDeletedItem.deleteFromRealm()
}
dataListAdapter.notifyItemRemoved(position)
showUndoSnackBar()
} else {
dataListAdapter.notifyItemChanged(position)
val builder = AlertDialog.Builder(requireContext())
.setMessage((mRecentlyDeletedItem as Deletable).getFailedDeleteMessage())
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
deleteItem(position)
}
val itemTouchHelper = ItemTouchHelper(swipeToDelete)
@ -139,12 +144,45 @@ class DataListFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSourc
}
}
/**
* Delete item
*/
private fun deleteItem(position: Int) {
if (isDetached || activity == null) {
return
}
// Save the delete position & create a copy of the object
val mRecentlyDeletedItem = rowRepresentableForPosition(position)
lastDeletedItemPosition = position
if (mRecentlyDeletedItem is RealmObject) {
// Check if the object is valid for the deletion
if ((mRecentlyDeletedItem as Deletable).isValidForDelete(this.getRealm())) {
deletedItem = getRealm().copyFromRealm(mRecentlyDeletedItem)
getRealm().executeTransaction {
mRecentlyDeletedItem.deleteFromRealm()
}
dataListAdapter.notifyItemRemoved(position)
showUndoSnackBar()
} else {
dataListAdapter.notifyItemChanged(position)
val builder = AlertDialog.Builder(requireContext())
.setMessage((mRecentlyDeletedItem as Deletable).getFailedDeleteMessage())
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
}
/**
* Show undo snack bar
*/
private fun showUndoSnackBar() {
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) {
getRealm().executeTransaction { realm ->
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.Savable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -185,27 +186,22 @@ open class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDele
* Delete data
*/
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 realm = this.getRealm()
if (deletable.isValidForDelete(realm)) {
realm.executeTransaction {
this.liveDataType.deleteData(it, deletable)
}
this.activity?.finish()
} else {
val message = deletable.getFailedDeleteMessage()
val builder = AlertDialog.Builder(requireContext())
.setMessage(message)
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
builder.show()
val deletable = this.item as Deletable
val realm = this.getRealm()
if (deletable.isValidForDelete(realm)) {
val intent = Intent()
intent.putExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, true)
activity?.setResult(RESULT_OK, intent)
activity?.finish()
} else {
val message = deletable.getFailedDeleteMessage()
val builder = AlertDialog.Builder(requireContext())
.setMessage(message)
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
/**

@ -1,46 +1,52 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
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.view.*
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.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.helpers.DateTimePickerManager
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.shortDate
import net.pokeranalytics.android.util.extensions.toMinutes
import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList
open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
lateinit var parentActivity: PokerAnalyticsActivity
lateinit var item: RealmObject
lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private var currentFilter: Filter? = null
private var rows: ArrayList<RowRepresentable> = ArrayList()
private var rowsForFilterSubcategoryRow: HashMap<FilterSectionRow, ArrayList<RowRepresentable>> = HashMap()
private var primaryKey: String? = null
private var filterMenu: Menu? = null
private var filterCategoryRow: FilterCategoryRow? = null
val selectedRows = ArrayList<FilterElementRow>()
var isUpdating = false
var shouldOpenKeyboard = true
private val selectedRows = ArrayList<FilterElementRow>()
private var isUpdating = false
private var shouldOpenKeyboard = true
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
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) {
super.onRowSelected(position, row, fromAction)
val oldRows = ArrayList<RowRepresentable>()
oldRows.addAll(rows)
if (selectedRows.contains(row)) {
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)
Timber.d("Row: $row")
when (row) {
is FilterElementRow.DateFilterElementRow -> DateTimePickerManager.create(requireContext(), row, this, row.dateValue, onlyDate = true)
is FilterElementRow.PastDays -> {
val pastDays = if (row.lastDays > 0) row.lastDays.toString() else ""
val data = row.editingDescriptors(mapOf("pastDays" to pastDays))
BottomSheetFragment.create(fragmentManager, row, this, data, true)
}
}
/*
Timber.d("Row: $row")
when (row) {
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 -> {
selectedRows.add(row)
}
is FilterElementRow.LastGames -> {
val lastGames = if (row.lastGames > 0) row.lastGames.toString() else ""
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)
}
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 ""
val data = row.editingDescriptors(mapOf("amount" to amount))
BottomSheetFragment.create(fragmentManager, row, this, data, true)
}
else -> {
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 {
@ -103,8 +120,38 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
super.onRowValueChanged(value, row)
selectedRows.add(row as FilterElementRow)
rowRepresentableAdapter.refreshRow(row)
Timber.d("onRowValueChanged: $row $value")
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>? {
@ -125,6 +172,8 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true)
this.appBar.toolbar.title = getString(R.string.filter)
val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply {
@ -138,9 +187,9 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
*/
private fun initData() {
Timber.d("initData")
this.appBar.toolbar.title = getString(R.string.filter)
primaryKey?.let {
currentFilter = Filter.getFilterBydId(getRealm(), it)
}
filterCategoryRow?.let {
@ -150,58 +199,84 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresenta
this.rowsForFilterSubcategoryRow.clear()
this.rows.addAll(it.filterElements)
//TODO
/*
var filter = Filter()
this.rows.forEach {element ->
if (filter.isFilterElementExists(element as FilterElementRow)) {
this.rows.forEach { element ->
if (element is FilterElementRow && currentFilter?.contains(element) == true) {
currentFilter?.setSavedValueForElement(element)
this.selectedRows.add(element)
}
}
*/
this.rowRepresentableAdapter = RowRepresentableAdapter(this, this)
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
*/
private fun saveData() {
//TODO: Save currentFilter details data
Timber.d("Save data for filter: ${currentFilter?.id}")
selectedRows?.forEach {
Timber.d("Selected rows: $it")
}
//TODO: Save filter details data
Timber.d("Save filter details data")
finishActivityWithResult("")
val realm = getRealm()
realm.beginTransaction()
currentFilter?.createOrUpdateFilterConditions(selectedRows)
realm.commitTransaction()
currentFilter?.filterConditions?.forEach {
Timber.d("Condition: $it")
}
finishActivityWithResult(currentFilter?.id)
}
/**
* Finish the activity with a result
*/
private fun finishActivityWithResult(uniqueIdentifier: String) {
/*
private fun finishActivityWithResult(uniqueIdentifier: String?) {
val intent = Intent()
intent.putExtra(EditableDataActivity.IntentKey.DATA_TYPE.keyName, dataType)
intent.putExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName, uniqueIdentifier)
intent.putExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName, uniqueIdentifier)
activity?.setResult(RESULT_OK, intent)
*/
activity?.finish()
}
/**
* Set fragment data
*/
fun setData(filterCategory: Int) {
this.filterCategoryRow = FilterCategoryRow.values()[filterCategory]
/*
this.dataType = dataType
this.liveDataType = LiveData.numericValues()[dataType]
fun setData(primaryKey: String, filterCategory: Int) {
this.primaryKey = primaryKey
*/
this.filterCategoryRow = FilterCategoryRow.values()[filterCategory]
}
}

@ -1,15 +1,18 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.*
import androidx.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_filters.view.*
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.FiltersActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
@ -26,18 +29,17 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
const val REQUEST_CODE_FILTER_DETAILS = 100
}
lateinit var parentActivity: PokerAnalyticsActivity
lateinit var item: RealmObject
lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private lateinit var parentActivity: PokerAnalyticsActivity
private lateinit var rowRepresentableAdapter: RowRepresentableAdapter
private var currentFilter: Filter? = null
private var filterCopy: Filter? = null
private var rows: ArrayList<RowRepresentable> = ArrayList()
private var filterMenu: Menu? = null
private var primaryKey: String? = null
private var selectedRow: RowRepresentable? = null
private var filter: Filter? = null
var isUpdating = false
private var isUpdating = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
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?) {
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")
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 {
rowRepresentableAdapter.refreshRow(it)
}
@ -71,17 +78,17 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
}
override fun onBackPressed() {
super.onBackPressed()
//TODO: Cancel changes on the Filter object
finishActivityWithResult("")
if (isUpdating) {
cancelUpdates()
} else {
deleteFilter()
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
R.id.save -> saveData()
R.id.delete -> deleteData()
R.id.save -> validUpdates()
R.id.delete -> deleteFilter()
}
return true
}
@ -90,25 +97,25 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
return rows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
super.onRowSelected(position, row, fromAction)
override fun stringForRow(row: RowRepresentable): String {
// Return the number of selected filters for this category
var selectedFilters = ""
if (row is FilterCategoryRow) {
selectedRow = row
FilterDetailsActivity.newInstanceForResult(this, row.ordinal, REQUEST_CODE_FILTER_DETAILS)
currentFilter?.countBy(row)?.let { nbFilters ->
if (nbFilters > 0) {
selectedFilters = nbFilters.toString()
}
}
}
return selectedFilters
}
//TODO
/*
override fun stringForRow(row: RowRepresentable): String {
return this.filter?.numberOfElementIn(row as FilterCategoryRow).toString()
}
*/
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
super.onRowValueChanged(value, row)
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
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)
setHasOptionsMenu(true)
this.appBar.toolbar.title = getString(R.string.filter)
val viewManager = LinearLayoutManager(requireContext())
recyclerView.apply {
@ -133,8 +142,27 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
*/
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())
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
*/
private fun deleteData() {
// TODO: Delete the filter object here
private fun deleteFilter() {
Timber.d("Delete filter")
val realm = getRealm()
realm.beginTransaction()
currentFilter?.deleteFromRealm()
realm.commitTransaction()
finishActivityWithResult("")
}
/**
* Finish the activity with a result
*/
private fun finishActivityWithResult(uniqueIdentifier: String) {
/*
val intent = Intent()
intent.putExtra(EditableDataActivity.IntentKey.DATA_TYPE.keyName, dataType)
intent.putExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName, uniqueIdentifier)
intent.putExtra(FiltersActivity.IntentKey.FILTER_ID.keyName, uniqueIdentifier)
activity?.setResult(RESULT_OK, intent)
*/
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.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import io.realm.RealmResults
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.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.util.Preferences
import java.text.SimpleDateFormat
import java.util.*
class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
@ -67,12 +69,27 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
disclaimerContainer.isVisible = Preferences.shouldShowDisclaimer(requireContext())
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val betaLimitDate = sdf.parse("17/7/2019 10:00")
newCashGame.setOnClickListener {
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return@setOnClickListener
}
SessionActivity.newInstance(requireContext(), false)
newSessionCreated = true
}
newTournament.setOnClickListener {
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return@setOnClickListener
}
SessionActivity.newInstance(requireContext(), true)
newSessionCreated = true
}
@ -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
*/
private fun initData() {
this.realmSessions = getRealm().where<Session>().findAll().sort("startDate", Sort.DESCENDING)
this.realmSessions.addChangeListener { t, changeSet ->
this.realmSessions.addChangeListener { _, changeSet ->
this.historyAdapter.refreshData()
this.historyAdapter.notifyDataSetChanged()
}

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

@ -11,11 +11,11 @@ import kotlinx.coroutines.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.model.StatRepresentable
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.ui.activity.GraphActivity
import net.pokeranalytics.android.ui.adapter.DisplayDescriptor
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.SessionObserverFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
@ -25,7 +25,8 @@ import timber.log.Timber
import java.util.*
import kotlin.coroutines.CoroutineContext
class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope {
class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope,
RowRepresentableDelegate {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
@ -36,6 +37,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
private var stringTournament = ""
private lateinit var statsAdapter: RowRepresentableAdapter
private var computedResults : List<ComputedResults>? = null
companion object {
@ -114,7 +116,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
this.stringCashGame = getString(R.string.cash_game)
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() {
GlobalScope.launch(coroutineContext) {
@ -143,6 +144,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
val realm = Realm.getDefaultInstance()
results = createSessionGroupsAndStartCompute(realm)
computedResults = results
realm.close()
val e = Date()
@ -161,32 +163,16 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
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 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 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 tSessionGroup = ComputableGroup(stringTournament, tSessions, tSessionSets, tStats)
val tSessionGroup = ComputableGroup(stringTournament, listOf(QueryCondition.TOURNAMENT), tStats)
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 ->
rows.add(CustomizableRowRepresentable(title = result.group.name))
result.group.stats?.forEach { stat ->
rows.add(StatRepresentable(stat, result.computedStat(stat)))
rows.add(StatRepresentable(stat, result.computedStat(stat), result.group.name))
}
}
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? {
this.value?.let {
return it.trim()
val value = it.trim()
if (value.isEmpty()) { // avoid returning empty strings
return null
} else {
return value
}
}
return null
}

@ -9,6 +9,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.view.ContextThemeWrapper
import androidx.fragment.app.FragmentManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.realm.RealmObject
@ -57,7 +58,9 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
}
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?) {

@ -3,6 +3,7 @@ package net.pokeranalytics.android.ui.fragment.components.bottomsheet
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.core.view.get
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.chip.Chip
import io.realm.RealmResults
@ -13,6 +14,7 @@ import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorExcep
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.util.extensions.px
/**
* Bottom Sheet List Game Fragment
@ -73,6 +75,8 @@ class BottomSheetListGameFragment : BottomSheetListFragment() {
val chip = Chip(requireContext())
chip.id = it.ordinal
chip.text = it.shortName
chip.chipStartPadding = 8f.px
chip.chipEndPadding = 8f.px
chip.isChecked = it.ordinal == limit
chipGroup.addView(chip)
}
@ -81,6 +85,10 @@ class BottomSheetListGameFragment : BottomSheetListFragment() {
values[0] = i
}
if (limit == null) {
(chipGroup[0] as Chip).isChecked = true
}
val viewManager2 = LinearLayoutManager(requireContext())
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,
LOCATION_TITLE, INFO,
TITLE, TITLE_ARROW, TITLE_VALUE, TITLE_VALUE_ARROW, TITLE_GRID, TITLE_SWITCH, TITLE_CHECK, TITLE_VALUE_CHECK,
DATA, BOTTOM_SHEET_DATA, LOADER -> RowViewHolder(
layout
)
DATA, BOTTOM_SHEET_DATA, LOADER -> RowViewHolder(layout)
// Row Session
ROW_SESSION -> RowSessionViewHolder(layout)

@ -9,7 +9,7 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In
GENERAL(R.string.general),
DATE(R.string.date),
TIME_FRAME(R.string.duration),
SESSION(R.string.session),
SESSIONS(R.string.sessions),
CASH(R.string.cash),
TOURNAMENT(R.string.tournament),
ONLINE(R.string.online),
@ -20,14 +20,14 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In
PLAYERS(R.string.players),
;
val filterElements : List < RowRepresentable >
val filterElements: List<RowRepresentable>
get() {
return filterSectionRows.flatMap {
it.filterElements
}
}
val filterSectionRows : List < FilterSectionRow >
val filterSectionRows: List<FilterSectionRow>
get() {
return when (this) {
GENERAL -> arrayListOf(
@ -45,6 +45,11 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In
DAY_OF_WEEK,
MONTH_OF_YEAR
)
TIME_FRAME -> arrayListOf(
SESSION_DURATION,
RANGE
)
SESSIONS -> arrayListOf(FilterSectionRow.SESSIONS)
BANKROLLS -> arrayListOf(
BANKROLL
)
@ -74,8 +79,6 @@ enum class FilterCategoryRow(override val resId: Int?, override val viewType: In
VALUE
)
TIME_FRAME -> arrayListOf()
SESSION -> arrayListOf()
TRANSACTION_TYPES -> arrayListOf()
}
}

@ -1,103 +1,205 @@
package net.pokeranalytics.android.ui.view.rowrepresentable
import android.content.Context
import android.text.InputType
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FilterValueMapException
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.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.RowRepresentableEditDescriptor
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.*
sealed class FilterElementRow : RowRepresentable {
object Cash : FilterElementRow()
object Tournament : FilterElementRow()
object Live : FilterElementRow()
object Online : FilterElementRow()
object Today : FilterElementRow()
object Yesterday : FilterElementRow()
object TodayAndYesterday : FilterElementRow()
object CurrentWeek : FilterElementRow()
object CurrentMonth : FilterElementRow()
object CurrentYear: FilterElementRow()
object Weekday: FilterElementRow()
object Weekend : FilterElementRow()
open class DataFilterElementRow(data:Manageable) : FilterElementRow() {
val id : String = data.id
val name : String = (data as RowRepresentable).getDisplayName()
interface Duration {
var minutes: Int
}
interface Operator
interface MoreOperator : Operator
interface LessOperator : Operator
open class BoolFilterElementRow : FilterElementRow()
open class DateFilterElementRow(var dateValue: Date = Date()) : FilterElementRow()
open class NumericFilterElementRow(open val doubleValue: Double = 0.0) : FilterElementRow()
open class StringFilterElementRow(val stringValue: String = "") : 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 name: String = (data as RowRepresentable).getDisplayName()
}
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 SingleValueFilterElementRow(val value:Int) : FilterElementRow()
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 Month(val month: Int) : SingleValueFilterElementRow(month)
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 TableSize(val tableSize : net.pokeranalytics.android.model.TableSize) : FilterElementRow()
data class Blind(var sb: Double? = null, var bb: Double? = null, var code: String? = null) : 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 Game(val game: Manageable) : DataFilterElementRow(game)
data class Location(val location: Manageable) : DataFilterElementRow(location)
data class TournamentName(val tournamentName: Manageable) : DataFilterElementRow(tournamentName)
data class AllTournamentFeature(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
val filterName : String = this.queryType.name
val filterName: String = this.queryCondition.name
private val queryType : QueryType
private val queryCondition: QueryCondition
get() {
return when (this) {
is Cash -> QueryType.CASH
is Tournament -> QueryType.TOURNAMENT
is Blind -> QueryType.BLINDS
is From -> QueryType.STARTED_FROM_DATE
is To -> QueryType.ENDED_TO_DATE
is Month -> QueryType.MONTH
is Day -> QueryType.DAY_OF_WEEK
is Year -> QueryType.YEAR
is Live -> QueryType.LIVE
is Online -> QueryType.ONLINE
is Weekday -> QueryType.WEEK_DAY
is Weekend-> QueryType.WEEK_END
/* is Today -> QueryType.
is Yesterday -> R.string.yesterday
is TodayAndYesterday -> R.string.yesterday_and_today
is CurrentWeek -> R.string.current_week
is CurrentMonth -> R.string.current_month
is CurrentYear -> R.string.current_year
is PastDays -> R.string.period_in_days
is Limit -> R.string.limit
*/
is TableSize -> QueryType.TABLE_SIZE
is Game -> QueryType.GAME
is Bankroll -> QueryType.BANKROLL
is Location -> QueryType.LOCATION
is TournamentName -> QueryType.TOURNAMENT_NAME
is AnyTournamentFeature -> QueryType.ANY_TOURNAMENT_FEATURES
is AllTournamentFeature -> QueryType.ALL_TOURNAMENT_FEATURES
is ResultMoreThan -> QueryType.MORE_THAN_NET_RESULT
is ResultLessThan -> QueryType.LESS_THAN_NET_RESULT
else -> throw FilterValueMapException("no filter type for $this") //TODO create exception
is Cash -> QueryCondition.CASH
is Tournament -> QueryCondition.TOURNAMENT
is Blind -> QueryCondition.BLINDS
is From -> QueryCondition.STARTED_FROM_DATE
is To -> QueryCondition.ENDED_TO_DATE
is Month -> QueryCondition.MONTH
is Day -> QueryCondition.DAY_OF_WEEK
is Year -> QueryCondition.YEAR
is Live -> QueryCondition.LIVE
is Online -> QueryCondition.ONLINE
is Weekday -> QueryCondition.WEEK_DAY
is Weekend -> QueryCondition.WEEK_END
is Today -> QueryCondition.TODAY
is Yesterday -> QueryCondition.YESTERDAY
is TodayAndYesterday -> QueryCondition.TODAY_AND_YESTERDAY
is CurrentWeek -> QueryCondition.THIS_WEEK
is CurrentMonth -> QueryCondition.THIS_MONTH
is CurrentYear -> QueryCondition.THIS_YEAR
is PastDays -> QueryCondition.PAST_DAYS
is Limit -> QueryCondition.LIMIT
is TableSize -> QueryCondition.TABLE_SIZE
is Game -> QueryCondition.GAME
is Bankroll -> QueryCondition.BANKROLL
is Location -> QueryCondition.LOCATION
is TournamentName -> QueryCondition.TOURNAMENT_NAME
is AnyTournamentFeature -> QueryCondition.ANY_TOURNAMENT_FEATURES
is AllTournamentFeature -> QueryCondition.ALL_TOURNAMENT_FEATURES
is ResultMoreThan -> QueryCondition.MORE_THAN_NET_RESULT
is ResultLessThan -> QueryCondition.LESS_THAN_NET_RESULT
is DurationMoreThan -> QueryCondition.MORE_THAN_DURATION
is DurationLessThan -> QueryCondition.LESS_THAN_DURATION
//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) {
is DataFilterElementRow -> filterElements.any {
is SingleValueFilterElementRow -> filterConditions.any {
it.values.contains(this.intValue)
}
is DataFilterElementRow -> filterConditions.any {
it.ids.contains(this.id)
}
else -> return true
else -> true
}
}
@ -118,30 +220,101 @@ sealed class FilterElementRow : RowRepresentable {
is Live -> R.string.live
is Online -> R.string.online
is Weekday -> R.string.week_days
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 Weekend -> R.string.weekend
is PastDays -> R.string.period_in_days
is Limit -> R.string.limit
is TableSize -> R.string.table_size
is Blind -> TODO()
is ResultMoreThan -> TODO()
is ResultLessThan -> TODO()
is LastGames -> R.string.last_records
is LastSessions -> R.string.last_sessions
is ReBuyMoreThan -> R.string.minimum
is ReBuyLessThan -> R.string.maximum
is MoreOperator -> R.string.more_than
is LessOperator -> R.string.less_than
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 {
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
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() {
val excluded = arrayListOf<FilterSectionRow>()
if (!this.filterSectionRow.allowMultiSelection) {

@ -1,10 +1,16 @@
package net.pokeranalytics.android.ui.view.rowrepresentable
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.realm.Session
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow.*
import java.text.DateFormatSymbols
import java.util.*
enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
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),
SESSION_DURATION(net.pokeranalytics.android.R.string.session_duration),
RANGE(net.pokeranalytics.android.R.string.hour_slot),
SESSIONS(R.string.sessions),
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_NAME(net.pokeranalytics.android.R.string.tournament_name),
TOURNAMENT_FEATURE(net.pokeranalytics.android.R.string.tournament_feature),
@ -49,91 +56,162 @@ enum class FilterSectionRow(override val resId: Int?) : RowRepresentable {
val allowMultiSelection: Boolean
get() = (this.selectionType == SelectionType.MULTIPLE)
val filterElements: List<RowRepresentable> by lazy {
arrayListOf<RowRepresentable>(this).apply {
this.addAll(
when (this@FilterSectionRow) {
CASH_TOURNAMENT -> arrayListOf(Cash, Tournament)
LIVE_ONLINE -> arrayListOf(Live, Online)
LIMIT_TYPE -> {
val limits = arrayListOf<FilterElementRow.Limit>()
net.pokeranalytics.android.model.Limit.values().forEach {
limits.add(Limit(it))
val filterElements: List<RowRepresentable>
get() {
val data = arrayListOf<RowRepresentable>().apply {
this.addAll(
when (this@FilterSectionRow) {
// General
CASH_TOURNAMENT -> arrayListOf(Cash, Tournament)
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
}
limits
}
TABLE_SIZE -> {
val tableSizes = arrayListOf<FilterElementRow.TableSize>()
net.pokeranalytics.android.model.TableSize.all.forEach {
tableSizes.add(TableSize(it))
LIMIT_TYPE -> {
val limits = arrayListOf<FilterElementRow.Limit>()
net.pokeranalytics.android.model.Limit.values().forEach {
limits.add(Limit(it))
}
limits
}
tableSizes
}
TOURNAMENT_NAME -> arrayListOf()
TOURNAMENT_FEATURE -> arrayListOf()
DYNAMIC_DATE -> arrayListOf(
Today,
Yesterday,
TodayAndYesterday,
CurrentWeek,
CurrentMonth,
CurrentYear
)
FIXED_DATE -> arrayListOf(From(), To())
DURATION -> arrayListOf()
WEEKDAYS_OR_WEEKEND -> arrayListOf(Weekday, Weekend)
DAY_OF_WEEK -> arrayListOf()
MONTH_OF_YEAR -> arrayListOf()
YEAR -> arrayListOf()
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)
TABLE_SIZE -> {
val tableSizes = arrayListOf<FilterElementRow.TableSize>()
val realm = Realm.getDefaultInstance()
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
}
realm.close()
games
}
LOCATION -> arrayListOf()
BANKROLL -> arrayListOf()
MULTI_TABLING -> arrayListOf()
BLINDS -> arrayListOf()
CASH_RE_BUY_COUNT -> arrayListOf()
BUY_IN -> arrayListOf()
COMPLETION_PERCENTAGE -> arrayListOf()
NUMBER_OF_PLAYERS -> arrayListOf()
TOURNAMENT_TYPE -> arrayListOf()
PLAYERS_COUNT -> arrayListOf()
PLACE -> arrayListOf()
TOURNAMENT_RE_BUY_COUNT -> arrayListOf()
MULTI_PLAYER -> arrayListOf()
RANGE -> arrayListOf()
// Date
DYNAMIC_DATE -> arrayListOf(
Today,
Yesterday,
TodayAndYesterday,
CurrentWeek,
CurrentMonth,
CurrentYear
)
FIXED_DATE -> arrayListOf(From, To)
DURATION -> arrayListOf(PastDays(0))
YEAR -> {
val years = arrayListOf<FilterElementRow.Year>()
val realm = Realm.getDefaultInstance()
val distinctYears = realm.where<Session>().distinct("year").findAll().sort("year", Sort.DESCENDING)
distinctYears.forEach { session ->
session.year?.let { year ->
years.add(Year(year))
}
}
realm.close()
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
}
SESSION_DURATION -> arrayListOf()
// Duration
SESSION_DURATION -> arrayListOf(DurationMoreThan as FilterElementRow, DurationLessThan as FilterElementRow)
RANGE -> arrayListOf(From, To)
// Sessions
SESSIONS -> arrayListOf(LastGames(0), LastSessions(0))
// Cash
BLINDS -> {
// TODO: Improve the way we get the blinds distinctly
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()
COMPLETION_PERCENTAGE -> arrayListOf()
PLACE -> arrayListOf()
PLAYERS_COUNT -> arrayListOf()
TOURNAMENT_RE_BUY_COUNT -> arrayListOf()
BUY_IN -> arrayListOf()
TOURNAMENT_NAME -> arrayListOf()
TOURNAMENT_FEATURE -> arrayListOf()
LOCATION -> arrayListOf()
BANKROLL -> arrayListOf()
MULTI_TABLING -> arrayListOf()
NUMBER_OF_PLAYERS -> arrayListOf()
MULTI_PLAYER -> arrayListOf()
VALUE -> arrayListOf()
}.apply {
this.forEach {
it.filterSectionRow = this@FilterSectionRow
}
}
)
}
VALUE -> arrayListOf()
// Add the section row only if we have data for this section
if (data.isNotEmpty()) {
data.add(0, this@FilterSectionRow)
}
}.apply {
this.forEach {
it.filterSectionRow = this@FilterSectionRow
}
}
)
return data
}
}
private val selectionType: SelectionType
get() {
return when (this) {
CASH_TOURNAMENT, DYNAMIC_DATE, LIVE_ONLINE -> SelectionType.SINGLE
CASH_TOURNAMENT, DYNAMIC_DATE, LIVE_ONLINE, SESSIONS -> SelectionType.SINGLE
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
)
}
else -> arrayListOf()
}
}
Session.Type.CASH_GAME.ordinal -> {

@ -77,7 +77,7 @@ fun Date.getDayNumber() : String {
}
// Return the 3 first letters of the date's day
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
fun Date.getMonthAndYear(): String {
@ -96,3 +96,25 @@ fun Date.getFormattedDuration(toDate: Date) : String {
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
import android.content.Context
import net.pokeranalytics.android.R
import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.*
@ -35,6 +37,23 @@ fun Double.formattedHourlyDuration() : String {
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
fun Long.toMinutes() : String {
val totalMinutes = this / (1000 * 60)
@ -45,5 +64,4 @@ fun Long.toMinutes() : String {
duration += ":"
duration += if (minutesLeft < 10) "0$minutesLeft" else minutesLeft.toString()
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
android:id="@+id/chips"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -22,41 +22,8 @@
android:layout_gravity="center"
android:orientation="horizontal"
app:chipSpacingHorizontal="16dp"
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" />
-->
app:singleSelection="true" />
</com.google.android.material.chip.ChipGroup>
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView

@ -8,7 +8,6 @@
<androidx.appcompat.widget.Toolbar
android:id="@+id/bottomSheetToolbar"
style="@style/PokerAnalyticsTheme.Toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
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_gravity="bottom|center"
android:layout_marginBottom="28dp"
android:alpha="0"
android:elevation="16dp"
android:scaleX="0"
android:scaleY="0"
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>

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

@ -2,6 +2,12 @@
<string name="app_name">Poker Analytics</string>
<!-- 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="suggestions">Suggestions</string>
<string name="data_deleted" formatted="false">%s deleted</string>

@ -6,9 +6,10 @@
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</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: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="toolbarStyle">@style/PokerAnalyticsTheme.Toolbar</item>
@ -61,7 +62,6 @@
<item name="android:colorPrimary">@color/white</item>
<item name="android:titleTextColor">@color/white</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
<item name="actionMenuTextColor">@color/white</item>
</style>
<!-- TextView -->

Loading…
Cancel
Save