diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b65342e..8d3b6941 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 diff --git a/app/build.gradle b/app/build.gradle index c6cc3504..18f07644 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 3e1009ea..baeaae1c 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -20,5 +20,41 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile --keep class com.google.j2objc.annotations.** { *; } --dontwarn com.google.j2objc.annotations.** +# Realm + +-keep class io.realm.annotations.RealmModule +-keep @io.realm.annotations.RealmModule class * +-keep class io.realm.internal.Keep +-keep @io.realm.internal.Keep class * +-dontwarn javax.** +-dontwarn io.realm.** +-keep class net.pokeranalytics.android.model.** { *; } + +# Retrofit + +# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and +# EnclosingMethod is required to use InnerClasses. +-keepattributes Signature, InnerClasses, EnclosingMethod +# Retrofit does reflection on method and parameter annotations. +-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations +# Retain service method parameters when optimizing. +-keepclassmembers,allowshrinking,allowobfuscation interface * { + @retrofit2.http.* ; +} + +# 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.* ; } +-keep,allowobfuscation interface <1> + +# Guava +-dontwarn com.google.j2objc.annotations.** +-keep class com.google.j2objc.annotations.** { *; } \ No newline at end of file diff --git a/app/src/androidTest/assets/schema_0.realm b/app/src/androidTest/assets/schema_0.realm index b6888bbb..0c2eef32 100644 Binary files a/app/src/androidTest/assets/schema_0.realm and b/app/src/androidTest/assets/schema_0.realm differ diff --git a/app/src/androidTest/assets/schema_2.realm b/app/src/androidTest/assets/schema_2.realm new file mode 100644 index 00000000..9366060a Binary files /dev/null and b/app/src/androidTest/assets/schema_2.realm differ diff --git a/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt index 7df48545..d44ea94d 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/components/RealmInstrumentedUnitTest.kt @@ -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 } diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/FavoriteSessionUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/FavoriteSessionUnitTest.kt index c8e5074c..2a23d062 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/FavoriteSessionUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/FavoriteSessionUnitTest.kt @@ -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) diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt index 76f7c100..d19cee4a 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/BlindFilterInstrumentedTest.kt @@ -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(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(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(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(realm, arrayListOf(filter)) Assert.assertEquals(2, sessions.size) sessions.map { diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt index 72cb5304..2fd4b380 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/DateFilterInstrumentedUnitTest.kt @@ -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(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(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(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(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) - Session.testInstance(100.0, true, cal.time) + realm.commitTransaction() - val sessions = Filter.queryOn( - realm, - Session, - arrayListOf(QueryType.WEEK_DAY) - ) + + val sessions = Filter.queryOn(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(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(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(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(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(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(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(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(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(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(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(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(realm, arrayListOf(filter)) Assert.assertEquals(1, sessions.size) sessions[0]?.run { diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt index 2dac694c..fd756ada 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/ExceptionFilterInstrumentedTest.kt @@ -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(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 } } \ No newline at end of file diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt index ac9cc2fe..5ad36cbe 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/RealmFilterInstrumentedUnitTest.kt @@ -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() Assert.assertEquals(1, sessions.size) sessions[0]?.run { diff --git a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt index 66e119d1..b4348fa2 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/SessionFilterInstrumentedUnitTest.kt @@ -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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(realm, arrayListOf(filterMore, filterLess)) Assert.assertEquals(1, sessions.size) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7065380d..f304e967 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,10 @@ android:supportsRtl="true" android:theme="@style/PokerAnalyticsTheme"> + + @@ -26,39 +30,44 @@ + android:launchMode="singleTop" + android:screenOrientation="portrait" /> + android:launchMode="singleTop" + android:screenOrientation="portrait" /> + android:launchMode="singleTop" + android:screenOrientation="portrait" /> + android:launchMode="singleTop" + android:screenOrientation="portrait" /> + android:launchMode="singleTop" + android:screenOrientation="portrait" /> + android:launchMode="singleTop" + android:screenOrientation="portrait" /> + + , options: Options): List { -// Timber.d("sets = ${sets.size}") -// return listOf() -// } + fun computeStatsWithFilters(realm: Realm, filters: List, options: Options): List { + + var computableGroups: MutableList = 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, options: Options): List { + fun computeGroups(realm: Realm, groups: List, options: Options): List { val computedResults = mutableListOf() 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))) } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt b/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt index d0653c92..b1fdf74f 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt @@ -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, sets: RealmResults, stats: List? = null) { - - /** - * The display name of the group - */ - var name: String = name - - /** - * The list of endedSessions to compute - */ - var computables: RealmResults = computables - - /** - * The list of endedSessions to compute - */ - var sets: RealmResults = sets - - /** - * The list of stats to display - */ - var stats: List? = 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, stats: List? = null) { + + /** + * The display name of the group + */ + var name: String = name + + /** + * A list of conditions to get + */ + var conditions: List = conditions + + /** + * The list of endedSessions to compute + */ + private var _computables: RealmResults? = null + + /** + * Retrieves the computables on the relative [realm] filtered with the provided [conditions] + */ + fun computables(realm: Realm): RealmResults { + + // if computables exists and is valid (previous realm not closed) + this._computables?.let { + if (it.isValid) { + return it + } + } + + val computables: RealmResults = Filter.queryOn(realm, this.conditions) + this._computables = computables + return computables + } + + /** + * The list of sets to compute + */ + private var _sessionSets: RealmResults? = null + + /** + * Retrieves the session sets on the relative [realm] filtered with the provided [conditions] + */ + fun sessionSets(realm: Realm): RealmResults { + // if computables exists and is valid (previous realm not closed) + this._sessionSets?.let { + if (it.isValid) { + return it + } + } + + val sets: RealmResults = Filter.queryOn(realm, this.conditions) + this._sessionSets = sets + return sets + } + + /** + * The list of stats to display + */ + var stats: List? = 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 = mutableMapOf() - - // The map containing all evolution numericValues for all stats - private var _evolutionValues: MutableMap> = mutableMapOf() - - fun allStats() : Collection { - 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 = mutableListOf(point) - this._evolutionValues[stat] = values - } - } - - fun addStats(computedStats: Set) { - 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 = mutableMapOf() + + // The map containing all evolution numericValues for all stats + private var _evolutionValues: MutableMap> = mutableMapOf() + + fun allStats(): Collection { + 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 = mutableListOf(point) + this._evolutionValues[stat] = values + } + } + + fun addStats(computedStats: Set) { + 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 { + return when (stat) { + Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES -> this.barEntries(stat) + else -> this.singleLineEntries(stat) + } + } + + fun singleLineEntries(stat: Stat): List { + val entries = mutableListOf() + 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 { + val entries = mutableListOf() + 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 { + + val entries = mutableListOf() + 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) } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt index 7051683f..01b652b1 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt @@ -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? = 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}") + } } diff --git a/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt b/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt index a7242b27..f1ff2e51 100644 --- a/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt +++ b/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt @@ -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) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys") + data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no filter type for $filterElementRow") +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/StatRepresentable.kt b/app/src/main/java/net/pokeranalytics/android/model/StatRepresentable.kt index a7f325e7..25829c3a 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/StatRepresentable.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/StatRepresentable.kt @@ -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 diff --git a/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt b/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt index 8d19eb7e..a7a7f467 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt @@ -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 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 + } - /** - * return the path of the parameter used in the [QueryType] related to this entity - */ - fun fieldNameForQueryType(queryType: QueryType) : String? } // -//fun MutableList.filter(filter: FilterElement) : List { +//fun MutableList.filter(filter: FilterCondition) : List { // // return this.filter { f -> // return@filter true diff --git a/app/src/main/java/net/pokeranalytics/android/model/filter/QueryType.kt b/app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt similarity index 50% rename from app/src/main/java/net/pokeranalytics/android/model/filter/QueryType.kt rename to app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt index 34641b73..077c866c 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/filter/QueryType.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt @@ -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? = 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? 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, filterable: Filterable): RealmQuery { + inline fun filter(realmQuery: RealmQuery): RealmQuery { 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(SMALL_BLIND) + val bigBlindFieldName = FilterHelper.fieldNameForQueryType(BIG_BLIND) + val currencyCodeFieldName = FilterHelper.fieldNameForQueryType(CURRENCY_CODE) + smallBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown + bigBlindFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown + currencyCodeFieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown val blinds: RealmList 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(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 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? = 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 - } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt index 0bb7fc80..01431a6e 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt @@ -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? diff --git a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt index 48ac738f..4588fd95 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt @@ -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 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++ } - */ } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt index b5ce8238..c5a3df93 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt @@ -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? { - 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) { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt index 36e33f9c..fd3aa17a 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt @@ -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) + } + + } + } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt index 37481aaf..dbbba9bc 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt @@ -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): RealmResults<*> { - var realmQuery : RealmQuery = 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 = RealmList() - private set - - fun createOrUpdateFilterElements(filterElementRows: ArrayList) { - 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() - 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 = 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().equalTo("id", filterId).findFirst() + } - return realmQuery.findAll() + @TestOnly + inline fun queryOn(realm: Realm, queries: List): RealmResults { + var realmQuery = realm.where() + 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 = RealmList() + private set + + fun createOrUpdateFilterConditions(filterConditionRows: ArrayList) { + filterConditions.clear() + filterConditionRows + .map { + it.filterName + } + .distinct() + .forEach { filterName-> + filterConditionRows + .filter { + it.filterName == filterName + } + .apply { + val casted = arrayListOf() + 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 results(): RealmResults { + var realmQuery = realm.where() + this.filterConditions.map { + it.queryCondition + }.forEach { + realmQuery = it.filter(realmQuery) + } + + return realmQuery.findAll() + } + } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt new file mode 100644 index 00000000..afada902 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt @@ -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) : 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().apply { + this.addAll(filterElementRows.map { + (it as StringFilterElementRow).stringValue + }) + } + } + is NumericFilterElementRow -> { + this.numericValues = RealmList().apply { + this.addAll(filterElementRows.map { + (it as NumericFilterElementRow).doubleValue + }) + } + } + is FilterElementBlind -> { + this.blindValues = RealmList().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? = null + private var dateValue: Date? = null + private var stringValues: RealmList? = null + private var blindValues: RealmList? = null + + val ids: Array + get() = stringValues?.toTypedArray() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing + + val blinds: RealmList + 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 + 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) + } + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElement.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElement.kt deleted file mode 100644 index be590588..00000000 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElement.kt +++ /dev/null @@ -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) : 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().apply { - this.addAll(filterElementRows.map { - (it as DataFilterElementRow).id - }) - } - } - else -> null - } - - this.numericValues = when (QueryType.valueOf(filterName)) { - QueryType.LIMIT -> { - RealmList().apply { - this.addAll(filterElementRows.map { - (it as FilterElementRow.Limit).limit.ordinal.toDouble() - }) - } - } - QueryType.TABLE_SIZE -> { - RealmList().apply { - this.addAll(filterElementRows.map { - (it as FilterElementRow.TableSize).tableSize.numberOfPlayer.toDouble() - }) - } - } - QueryType.YEAR, QueryType.MONTH, QueryType.DAY_OF_WEEK -> { - RealmList().apply { - this.addAll(filterElementRows.map { - (it as SingleValueFilterElementRow).value.toDouble() - }) - } - } - QueryType.LESS_THAN_NET_RESULT -> { - RealmList().apply { - this.addAll(filterElementRows.map { - (it as ResultLessThan).value - }) - } - } - QueryType.MORE_THAN_NET_RESULT -> { - RealmList().apply { - this.addAll(filterElementRows.map { - (it as ResultMoreThan).value - }) - } - } - else -> null - } - - this.blindValues = when (QueryType.valueOf(filterName)) { - QueryType.BLINDS -> { - RealmList().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? = null - private var dateValue : Date? = null - private var stringValues : RealmList? = null - private var blindValues : RealmList? = null - - val ids : Array - get() = stringValues?.toTypedArray()?: throw FilterValueMapException("filter type not handled") - - val blinds : RealmList - 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 - 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") - -} diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElementBlind.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElementBlind.kt index fb615cbb..ebde6cad 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElementBlind.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/FilterElementBlind.kt @@ -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() \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index bf104448..8bf81de2 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -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 = 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)) } } - 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)) + + 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() + } + + } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt index 1a52cf08..9774e1b7 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt @@ -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) + } + + } + } diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt index d78d1d81..f4b3d9bc 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt @@ -26,22 +26,22 @@ fun Session.parameterRepresentation(context: Context): String { */ private fun Session.significantFields(): List { 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 } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt index 611aa32c..c62b84c3 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt @@ -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!! diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt index 54119d48..bc712d81 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt @@ -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 { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt index 0698614f..fc9e305b 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt @@ -72,4 +72,5 @@ class EditableDataActivity : PokerAnalyticsActivity() { fragmentTransaction.commit() fragment.setData(dataType, primaryKey) } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/FilterDetailsActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/FilterDetailsActivity.kt index 62259941..cdedb52f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/FilterDetailsActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/FilterDetailsActivity.kt @@ -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) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt index 61003c35..afd9f49f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt @@ -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) } - } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt new file mode 100644 index 00000000..8f815c9c --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt @@ -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) { + var stat: Stat = stat + var entries: List = 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) { + + 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") + } + + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt index 204eb4e0..071e2a81 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt @@ -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") } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt index 5a580ac2..49b51960 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt @@ -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) {} } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt index ca6b308e..c43f02d4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt @@ -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 { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt index 90831d09..586888cb 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDataFragment.kt @@ -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) } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt index 2127315e..87c86d33 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt @@ -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 { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt index 9a07a96d..44e8b118 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt @@ -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() + } } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt index 1542a281..042ecb95 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt @@ -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 = ArrayList() private var rowsForFilterSubcategoryRow: HashMap> = HashMap() + private var primaryKey: String? = null private var filterMenu: Menu? = null private var filterCategoryRow: FilterCategoryRow? = null - - - val selectedRows = ArrayList() - - - var isUpdating = false - var shouldOpenKeyboard = true + private val selectedRows = ArrayList() + 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() - 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? { @@ -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] } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt index cab37b3d..8a9915a4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt @@ -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 = 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().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() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt new file mode 100644 index 00000000..a126610a --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt @@ -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 + + companion object { + + + } + + fun setData(stat: Stat, entries: List) { + 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 = "" + + + + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt index 83ba73b6..78b61b06 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt @@ -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().findAll().sort("startDate", Sort.DESCENDING) - this.realmSessions.addChangeListener { t, changeSet -> + this.realmSessions.addChangeListener { _, changeSet -> this.historyAdapter.refreshData() this.historyAdapter.notifyDataSetChanged() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt index ddf62652..a0f6893a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt @@ -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) } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt index 97d8e597..4f112698 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt @@ -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? = 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 { - 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 = 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 = 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 = 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() + 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) + } + } + } + + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt index 01226ad5..d742fbd0 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt @@ -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 } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt index 82d5060e..57c17392 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt @@ -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?) { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt index 160e0cc1..919acd05 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt @@ -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) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt new file mode 100644 index 00000000..56cd4dda --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt @@ -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 + + } + + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt index 4671f50b..c887ff71 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt @@ -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) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt index f24f7979..e9ac2482 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterCategoryRow.kt @@ -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 get() { return filterSectionRows.flatMap { it.filterElements } } - val filterSectionRows : List < FilterSectionRow > + val filterSectionRows: List 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() } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt index 7a899662..d1c6a567 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterElementRow.kt @@ -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) : Boolean { + fun contains(filterConditions: List): 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): ArrayList? { + 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? get() { val excluded = arrayListOf() if (!this.filterSectionRow.allowMultiSelection) { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt index d51b7959..1c2006be 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/FilterSectionRow.kt @@ -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 by lazy { - arrayListOf(this).apply { - this.addAll( - when (this@FilterSectionRow) { - CASH_TOURNAMENT -> arrayListOf(Cash, Tournament) - LIVE_ONLINE -> arrayListOf(Live, Online) - LIMIT_TYPE -> { - val limits = arrayListOf() - net.pokeranalytics.android.model.Limit.values().forEach { - limits.add(Limit(it)) + val filterElements: List + get() { + + val data = arrayListOf().apply { + this.addAll( + when (this@FilterSectionRow) { + + // General + CASH_TOURNAMENT -> arrayListOf(Cash, Tournament) + LIVE_ONLINE -> arrayListOf(Live, Online) + GAME -> { + val games = arrayListOf() + 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() - net.pokeranalytics.android.model.TableSize.all.forEach { - tableSizes.add(TableSize(it)) + LIMIT_TYPE -> { + val limits = arrayListOf() + 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() - 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() + val realm = Realm.getDefaultInstance() + val distinctTableSizes = realm.where().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() + val realm = Realm.getDefaultInstance() + val distinctYears = realm.where().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() + DateFormatSymbols.getInstance(Locale.getDefault()).weekdays.forEachIndexed { index, day -> + if (day.isNotEmpty()) { + daysOfWeek.add(Day(index)) + } + } + daysOfWeek + } + MONTH_OF_YEAR -> { + val months = arrayListOf() + 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() + val realm = Realm.getDefaultInstance() + val sessions = realm.where().findAll().sort("cgSmallBlind", Sort.ASCENDING) + + val distinctBlinds: ArrayList = ArrayList() + val blindsHashMap: ArrayList = 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 { - val rows = ArrayList() - 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) - rows.addAll(games) - } - LIMIT_TYPE -> rows.addAll(Limit.numericValues()) - TABLE_SIZE -> { - val sessions = realm.where().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().sort("endDate", Sort.DESCENDING).findAll() - val years = ArrayList() - - 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 - } - -} - - */ \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt index 0363c09c..7eb0f035 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt @@ -62,7 +62,6 @@ enum class SessionRow : RowRepresentable { START_DATE, END_DATE, BREAK_TIME, COMMENT ) } - else -> arrayListOf() } } Session.Type.CASH_GAME.ordinal -> { diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt index 6ef27c60..6a5a6d10 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt index 77287988..88330925 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/NumbersExtension.kt @@ -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 - } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_graph.xml b/app/src/main/res/layout/activity_graph.xml new file mode 100644 index 00000000..70454fb9 --- /dev/null +++ b/app/src/main/res/layout/activity_graph.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_game_list.xml b/app/src/main/res/layout/bottom_sheet_game_list.xml index 7f5a61b1..ec47ed64 100644 --- a/app/src/main/res/layout/bottom_sheet_game_list.xml +++ b/app/src/main/res/layout/bottom_sheet_game_list.xml @@ -8,9 +8,9 @@ @@ -22,41 +22,8 @@ android:layout_gravity="center" android:orientation="horizontal" app:chipSpacingHorizontal="16dp" - app:singleSelection="true"> - - - - + app:singleSelection="true" /> + diff --git a/app/src/main/res/layout/fragment_graph.xml b/app/src/main/res/layout/fragment_graph.xml new file mode 100644 index 00000000..cf58c3d3 --- /dev/null +++ b/app/src/main/res/layout/fragment_graph.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_session.xml b/app/src/main/res/layout/fragment_session.xml index bbe0d2f0..53457fd3 100644 --- a/app/src/main/res/layout/fragment_session.xml +++ b/app/src/main/res/layout/fragment_session.xml @@ -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" /> \ No newline at end of file diff --git a/app/src/main/res/layout/row_title_value_check.xml b/app/src/main/res/layout/row_title_value_check.xml index 683e9169..a498f549 100644 --- a/app/src/main/res/layout/row_title_value_check.xml +++ b/app/src/main/res/layout/row_title_value_check.xml @@ -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" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8da8384e..5e551563 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,6 +2,12 @@ Poker Analytics + Please set a start date for the session + Hour + Minute + + + Address Suggestions %s deleted diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f6691da0..43246d76 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -6,9 +6,10 @@ @color/colorPrimary @color/colorPrimaryDark @color/colorAccent + @color/green_transparent @color/gray_dark @color/colorPrimary - @color/green_transparent + @color/white @style/PokerAnalyticsTheme.BottomNavigationView @style/PokerAnalyticsTheme.Toolbar @@ -61,7 +62,6 @@ @color/white @color/white @style/ThemeOverlay.AppCompat.Dark.ActionBar - @color/white