Compare commits
1 Commits
master
...
dev_raz_wi
| Author | SHA1 | Date |
|---|---|---|
|
|
757da5623a | 7 years ago |
@ -1,139 +0,0 @@ |
|||||||
# CLAUDE.md |
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
|
||||||
|
|
||||||
## Project Overview |
|
||||||
|
|
||||||
This is **Poker Analytics**, a comprehensive Android application for tracking and analyzing poker sessions. The app supports both cash games and tournaments, providing detailed statistics, reporting, and data visualization capabilities. |
|
||||||
|
|
||||||
### Key Features |
|
||||||
- Session tracking (cash games and tournaments) |
|
||||||
- Advanced filtering and reporting |
|
||||||
- Bankroll management |
|
||||||
- Hand history import and replay |
|
||||||
- Statistical analysis and charting |
|
||||||
- Data backup and export functionality |
|
||||||
- Multi-currency support |
|
||||||
|
|
||||||
## Development Commands |
|
||||||
|
|
||||||
### Building the Project |
|
||||||
```bash |
|
||||||
./gradlew assembleStandardRelease # Build release APK |
|
||||||
./gradlew assembleStandardDebug # Build debug APK |
|
||||||
./gradlew build # Build all variants |
|
||||||
``` |
|
||||||
|
|
||||||
### Running Tests |
|
||||||
```bash |
|
||||||
./gradlew test # Run unit tests |
|
||||||
./gradlew connectedAndroidTest # Run instrumented tests (requires device/emulator) |
|
||||||
./gradlew testStandardDebugUnitTest # Run specific unit tests |
|
||||||
``` |
|
||||||
|
|
||||||
### Cleaning |
|
||||||
```bash |
|
||||||
./gradlew clean # Clean build artifacts |
|
||||||
``` |
|
||||||
|
|
||||||
### Build Configuration |
|
||||||
- **Target SDK**: 35 (Android 15) |
|
||||||
- **Min SDK**: 23 (Android 6.0) |
|
||||||
- **Build Tools**: 30.0.3 |
|
||||||
- **Kotlin Version**: 1.9.24 |
|
||||||
- **Realm Schema Version**: 14 |
|
||||||
|
|
||||||
## Architecture Overview |
|
||||||
|
|
||||||
### Package Structure |
|
||||||
The main source code is organized under `app/src/main/java/net/pokeranalytics/android/`: |
|
||||||
|
|
||||||
#### Core Components |
|
||||||
- **`model/`** - Data models and business logic |
|
||||||
- `realm/` - Realm database models (Session, Bankroll, Result, etc.) |
|
||||||
- `filter/` - Query system for filtering sessions |
|
||||||
- `migrations/` - Database migration handling |
|
||||||
- `handhistory/` - Hand history data structures |
|
||||||
|
|
||||||
- **`ui/`** - User interface components |
|
||||||
- `activity/` - Main activities (HomeActivity, SessionActivity, etc.) |
|
||||||
- `fragment/` - UI fragments organized by feature |
|
||||||
- `adapter/` - RecyclerView adapters and data sources |
|
||||||
- `modules/` - Feature-specific UI modules |
|
||||||
|
|
||||||
- **`calculus/`** - Statistics and calculation engine |
|
||||||
- Core calculation logic for poker statistics |
|
||||||
- Report generation system |
|
||||||
- Performance tracking |
|
||||||
|
|
||||||
- **`util/`** - Utility classes |
|
||||||
- `csv/` - CSV import/export functionality |
|
||||||
- `billing/` - In-app purchase handling |
|
||||||
- `extensions/` - Kotlin extension functions |
|
||||||
|
|
||||||
#### Key Classes |
|
||||||
- **`Session`** (`model/realm/Session.kt`): Core session data model |
|
||||||
- **`HomeActivity`** (`ui/activity/HomeActivity.kt`): Main app entry point with tab navigation |
|
||||||
- **`PokerAnalyticsApplication`**: Application class handling initialization |
|
||||||
|
|
||||||
### Database Architecture |
|
||||||
The app uses **Realm** database with these key entities: |
|
||||||
- `Session` - Individual poker sessions |
|
||||||
- `Bankroll` - Bankroll management |
|
||||||
- `Result` - Session results and statistics |
|
||||||
- `ComputableResult` - Pre-computed statistics for performance |
|
||||||
- `Filter` - Saved filter configurations |
|
||||||
- `HandHistory` - Hand-by-hand game data |
|
||||||
|
|
||||||
### UI Architecture |
|
||||||
- **MVVM pattern** with Android Architecture Components |
|
||||||
- **Fragment-based navigation** with bottom navigation tabs |
|
||||||
- **Custom RecyclerView adapters** for data presentation |
|
||||||
- **Material Design** components |
|
||||||
|
|
||||||
## Key Technologies |
|
||||||
|
|
||||||
### Core Dependencies |
|
||||||
- **Realm Database** (10.15.1) - Local data storage |
|
||||||
- **Kotlin Coroutines** - Asynchronous programming |
|
||||||
- **Firebase Crashlytics** - Crash reporting and analytics |
|
||||||
- **Material Design Components** - UI framework |
|
||||||
- **MPAndroidChart** - Data visualization |
|
||||||
- **CameraX** - Image capture functionality |
|
||||||
|
|
||||||
### Testing |
|
||||||
- **JUnit** for unit testing |
|
||||||
- **Android Instrumented Tests** for integration testing |
|
||||||
- Test files located in `app/src/androidTest/` and `app/src/test/` |
|
||||||
|
|
||||||
## Development Guidelines |
|
||||||
|
|
||||||
### Working with Sessions |
|
||||||
- Sessions are the core data model representing individual poker games |
|
||||||
- Use `Session.newInstance()` to create new sessions properly |
|
||||||
- Always call `computeStats()` after modifying session data |
|
||||||
- Sessions can be in various states: PENDING, STARTED, PAUSED, ENDED |
|
||||||
|
|
||||||
### Database Operations |
|
||||||
- Use Realm transactions for data modifications |
|
||||||
- The app uses schema version 14 - increment when making schema changes |
|
||||||
- Migration logic is in `PokerAnalyticsMigration.kt` |
|
||||||
|
|
||||||
### Testing Data |
|
||||||
- Use `FakeDataManager.createFakeSessions()` for generating test data |
|
||||||
- Seed data is available through the `Seed` class |
|
||||||
|
|
||||||
### Build Variants |
|
||||||
- **standard** - Main production flavor |
|
||||||
- Release builds are optimized and obfuscated with ProGuard |
|
||||||
|
|
||||||
## Performance Considerations |
|
||||||
- Sessions use `ComputableResult` for pre-computed statistics |
|
||||||
- Large datasets are handled with Realm's lazy loading |
|
||||||
- Chart rendering is optimized for large data sets |
|
||||||
- Background processing uses Kotlin Coroutines |
|
||||||
|
|
||||||
## Security & Privacy |
|
||||||
- Sensitive data is encrypted in Realm database |
|
||||||
- Crash logging excludes personal information |
|
||||||
- Backup functionality includes data encryption |
|
||||||
@ -1,183 +1,84 @@ |
|||||||
apply plugin: 'com.android.application' |
apply plugin: 'com.android.application' |
||||||
apply plugin: 'kotlin-android' |
apply plugin: 'kotlin-android' |
||||||
//apply plugin: 'kotlin-android-extensions' |
apply plugin: 'kotlin-android-extensions' |
||||||
apply plugin: 'kotlin-kapt' |
apply plugin: 'kotlin-kapt' |
||||||
apply plugin: 'realm-android' |
apply plugin: 'realm-android' |
||||||
// Crashlytics |
//apply plugin: 'io.fabric' |
||||||
apply plugin: 'com.google.gms.google-services' |
|
||||||
apply plugin: 'com.google.firebase.crashlytics' |
|
||||||
// Serialization |
|
||||||
apply plugin: "kotlinx-serialization" |
|
||||||
|
|
||||||
|
|
||||||
repositories { |
repositories { |
||||||
maven { url 'https://jitpack.io' } // required for MPAndroidChart |
maven { url 'https://maven.fabric.io/public' } |
||||||
jcenter() // for kotlin serialization |
|
||||||
} |
} |
||||||
|
|
||||||
android { |
android { |
||||||
|
|
||||||
compileSdkVersion 35 |
compileSdkVersion 28 |
||||||
buildToolsVersion "30.0.3" |
|
||||||
|
|
||||||
compileOptions { |
compileOptions { |
||||||
sourceCompatibility JavaVersion.VERSION_1_8 |
sourceCompatibility JavaVersion.VERSION_1_8 |
||||||
targetCompatibility JavaVersion.VERSION_1_8 |
targetCompatibility JavaVersion.VERSION_1_8 |
||||||
} |
} |
||||||
|
|
||||||
kotlinOptions { |
|
||||||
jvmTarget = JavaVersion.VERSION_1_8 |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
defaultConfig { |
defaultConfig { |
||||||
applicationId "net.pokeranalytics.android" |
applicationId "net.pokeranalytics.android" |
||||||
minSdkVersion 23 |
minSdkVersion 23 |
||||||
targetSdkVersion 35 |
targetSdkVersion 28 |
||||||
versionCode 180 |
versionCode 1 |
||||||
versionName "6.0.38" |
versionName "1.0" |
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
||||||
} |
} |
||||||
|
|
||||||
buildTypes { |
buildTypes { |
||||||
debug { |
|
||||||
ext.enableCrashlytics = false |
|
||||||
firebaseCrashlytics { |
|
||||||
mappingFileUploadEnabled false // should help speed up build times: https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?hl=en&platform=android |
|
||||||
} |
|
||||||
} |
|
||||||
release { |
release { |
||||||
minifyEnabled true |
minifyEnabled false |
||||||
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
||||||
applicationVariants.all { variant -> |
|
||||||
variant.outputs.all { output -> |
|
||||||
def date = new Date() |
|
||||||
def formattedDate = date.format('yyMMdd_HHmm') |
|
||||||
def appName = "PokerAnalytics" |
|
||||||
def buildType = variant.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 |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
flavorDimensions 'endOfUse' |
|
||||||
productFlavors { // already used: 50000, 51000, 52000, 52130, 52110, 52120 |
|
||||||
standard { |
|
||||||
dimension = 'endOfUse' |
|
||||||
} |
|
||||||
// oct2021 { |
|
||||||
// dimension = 'endOfUse' |
|
||||||
// versionNameSuffix = '_oct2021' |
|
||||||
// versionCode = 52120 + android.defaultConfig.versionCode |
|
||||||
// } |
|
||||||
} |
|
||||||
|
|
||||||
configurations { |
|
||||||
release { |
|
||||||
all*.exclude group: 'com.google.guava', module: 'listenablefuture' |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
buildFeatures { |
|
||||||
viewBinding true |
|
||||||
} |
|
||||||
namespace 'net.pokeranalytics.android' |
|
||||||
lint { |
|
||||||
disable 'MissingTranslation' |
|
||||||
} |
|
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
dependencies { |
dependencies { |
||||||
implementation fileTree(dir: 'libs', include: ['*.jar']) |
implementation fileTree(dir: 'libs', include: ['*.jar']) |
||||||
|
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" |
||||||
// Kotlin |
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6' |
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6" |
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" |
|
||||||
// implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0") // JVM dependency |
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" |
|
||||||
|
|
||||||
// Android |
// Android |
||||||
implementation 'androidx.appcompat:appcompat:1.1.0' |
implementation 'androidx.appcompat:appcompat:1.0.2' |
||||||
implementation 'androidx.core:core-ktx:1.3.1' |
implementation 'androidx.core:core-ktx:1.1.0-alpha04' |
||||||
implementation 'com.google.android.material:material:1.3.0' |
implementation 'com.google.android.material:material:1.0.0' |
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' |
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' |
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' |
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' |
||||||
implementation 'androidx.work:work-runtime-ktx:2.7.1' |
|
||||||
// implementation 'com.google.android.play:core-ktx:1.8.1' // In-app Reviews |
|
||||||
|
|
||||||
implementation 'com.google.android.play:review:2.0.1' |
|
||||||
implementation 'com.google.android.play:review-ktx:2.0.1' |
|
||||||
|
|
||||||
// Places |
// Firebase |
||||||
implementation 'com.google.android.libraries.places:places:2.3.0' |
implementation 'com.google.firebase:firebase-core:16.0.7' |
||||||
|
|
||||||
// Billing / Subscriptions |
// Crashlytics |
||||||
implementation 'com.android.billingclient:billing:7.0.0' |
//implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9' |
||||||
|
|
||||||
// Import the BoM for the Firebase platform |
// Kotlin |
||||||
implementation platform('com.google.firebase:firebase-bom:26.1.0') |
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1' |
||||||
// Declare the dependencies for the Crashlytics and Analytics libraries |
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1" |
||||||
// When using the BoM, you don't specify versions in Firebase library dependencies |
|
||||||
implementation 'com.google.firebase:firebase-crashlytics-ktx' |
|
||||||
implementation 'com.google.firebase:firebase-analytics-ktx' |
|
||||||
|
|
||||||
// Logs |
// Logs |
||||||
implementation 'com.jakewharton.timber:timber:4.7.1' |
implementation 'com.jakewharton.timber:timber:4.7.1' |
||||||
|
|
||||||
// MPAndroidChart |
// Test |
||||||
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' |
androidTestImplementation 'androidx.test:core:1.0.0' |
||||||
|
androidTestImplementation 'androidx.test:runner:1.1.0' |
||||||
// CSV Parser: https://mvnrepository.com/artifact/org.apache.commons/commons-csv |
androidTestImplementation 'androidx.test:rules:1.1.0' |
||||||
implementation 'org.apache.commons:commons-csv:1.7' |
androidTestImplementation 'androidx.test.ext:junit:1.1.0' |
||||||
|
|
||||||
// Polynomial Regression |
|
||||||
implementation 'org.apache.commons:commons-math3:3.6.1' |
|
||||||
|
|
||||||
// ffmpeg for encoding video (HH export) |
|
||||||
// implementation 'com.arthenica:ffmpeg-kit-min-gpl:4.4.LTS' |
|
||||||
|
|
||||||
// Camera |
|
||||||
def camerax_version = "1.1.0" |
|
||||||
implementation "androidx.camera:camera-core:${camerax_version}" |
|
||||||
implementation "androidx.camera:camera-camera2:${camerax_version}" |
|
||||||
implementation "androidx.camera:camera-lifecycle:${camerax_version}" |
|
||||||
implementation "androidx.camera:camera-video:${camerax_version}" |
|
||||||
|
|
||||||
implementation "androidx.camera:camera-view:${camerax_version}" |
|
||||||
implementation "androidx.camera:camera-extensions:${camerax_version}" |
|
||||||
|
|
||||||
// Image picking and registerForActivityResult |
|
||||||
implementation 'androidx.activity:activity-ktx:1.6.1' |
|
||||||
implementation "androidx.fragment:fragment-ktx:1.4.1" |
|
||||||
|
|
||||||
// Retrofit |
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0' |
|
||||||
|
|
||||||
// Volley |
|
||||||
implementation 'com.android.volley:volley:1.2.1' |
|
||||||
|
|
||||||
// Instrumented Tests |
// Required -- JUnit 4 framework |
||||||
androidTestImplementation 'androidx.test:core:1.6.1' |
testImplementation 'junit:junit:4.12' |
||||||
androidTestImplementation 'androidx.test:runner:1.6.2' |
// Optional -- Robolectric environment |
||||||
androidTestImplementation 'androidx.test:rules:1.6.1' |
// testImplementation 'androidx.test:core:1.1.0' |
||||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1' |
// Optional -- Mockito framework |
||||||
|
testImplementation 'com.android.support.test:runner:1.0.1' |
||||||
|
testImplementation 'com.android.support.test:rules:1.0.1' |
||||||
|
|
||||||
// Test |
// testImplementation 'androidx.test.espresso:espresso-core:3.1.0' |
||||||
testImplementation 'junit:junit:4.13.2' |
|
||||||
testImplementation 'com.android.support.test:runner:1.0.2' |
|
||||||
testImplementation 'com.android.support.test:rules:1.0.2' |
|
||||||
|
|
||||||
// gross, somehow needed to make the stop notif work |
|
||||||
implementation 'com.google.guava:guava:27.0.1-android' |
|
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
|
apply plugin: 'com.google.gms.google-services' |
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,420 @@ |
|||||||
|
package net.pokeranalytics.android |
||||||
|
|
||||||
|
import android.os.Looper |
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||||
|
import io.realm.RealmResults |
||||||
|
import net.pokeranalytics.android.calculus.Calculator |
||||||
|
import net.pokeranalytics.android.calculus.ComputedResults |
||||||
|
import net.pokeranalytics.android.calculus.SessionGroup |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.model.realm.Session |
||||||
|
import net.pokeranalytics.android.model.realm.TimeFrame |
||||||
|
import org.junit.Assert |
||||||
|
import org.junit.Assert.assertEquals |
||||||
|
import org.junit.Test |
||||||
|
import org.junit.runner.RunWith |
||||||
|
import java.text.SimpleDateFormat |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
/** |
||||||
|
* Instrumented test, which will execute on an Android device. |
||||||
|
* |
||||||
|
* See [testing documentation](http://d.android.com/tools/testing). |
||||||
|
*/ |
||||||
|
@RunWith(AndroidJUnit4::class) |
||||||
|
|
||||||
|
class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() { |
||||||
|
|
||||||
|
// convenience extension |
||||||
|
fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session { |
||||||
|
var session: Session = Session.newInstance() |
||||||
|
session.result?.netResult = netResult |
||||||
|
session.timeFrame?.setDate(startDate, endDate) |
||||||
|
return session |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testSessionStats() { |
||||||
|
|
||||||
|
val realm = this.mockRealm |
||||||
|
realm.beginTransaction() |
||||||
|
|
||||||
|
var s1 = realm.createObject(Session::class.java, "1") |
||||||
|
var s2 = realm.createObject(Session::class.java, "2") |
||||||
|
|
||||||
|
s1.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
s2.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
|
||||||
|
s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) |
||||||
|
s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) |
||||||
|
|
||||||
|
s1.result?.buyin = 100.0 // net result = -100 |
||||||
|
s2.result?.buyin = 200.0 |
||||||
|
s2.result?.cashout = 500.0 // net result = 300 |
||||||
|
|
||||||
|
s1.cgBigBlind = 0.5 // bb net result = -200bb |
||||||
|
s2.cgBigBlind = 2.0 // bb net result = 150bb |
||||||
|
|
||||||
|
realm.insert(s1) |
||||||
|
realm.insert(s2) |
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
||||||
|
|
||||||
|
val sd1 = sdf.parse("01/1/2019 10:00") |
||||||
|
val ed1 = sdf.parse("01/1/2019 11:00") |
||||||
|
val sd2 = sdf.parse("02/1/2019 08:00") |
||||||
|
val ed2 = sdf.parse("02/1/2019 11:00") |
||||||
|
|
||||||
|
realm.beginTransaction() |
||||||
|
|
||||||
|
s1.timeFrame?.setDate(sd1, ed1) // duration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800 |
||||||
|
s2.timeFrame?.setDate(sd2, ed2) // duration = 3h, hourly = 100, bb100 = 150 / 75 * 100 = +200 |
||||||
|
|
||||||
|
realm.copyToRealmOrUpdate(s1) |
||||||
|
realm.copyToRealmOrUpdate(s2) |
||||||
|
|
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val sessions = realm.where(Session::class.java).findAll() |
||||||
|
val group = SessionGroup(name = "test", sessions = sessions) |
||||||
|
|
||||||
|
var options = Calculator.Options() |
||||||
|
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) |
||||||
|
|
||||||
|
val results: ComputedResults = Calculator.compute(group, options) |
||||||
|
val delta = 0.01 |
||||||
|
|
||||||
|
val sum = results.computedStat(Stat.NETRESULT) |
||||||
|
if (sum != null) { |
||||||
|
assertEquals(200.0, sum.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No Net result stat") |
||||||
|
} |
||||||
|
|
||||||
|
val average = results.computedStat(Stat.AVERAGE) |
||||||
|
if (average != null) { |
||||||
|
assertEquals(100.0, average.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No AVERAGE stat") |
||||||
|
} |
||||||
|
|
||||||
|
val duration = results.computedStat(Stat.DURATION) |
||||||
|
if (duration != null) { |
||||||
|
assertEquals(4.0, duration.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No duration stat") |
||||||
|
} |
||||||
|
|
||||||
|
val hourlyRate = results.computedStat(Stat.HOURLY_RATE) |
||||||
|
if (hourlyRate != null) { |
||||||
|
assertEquals(50.0, hourlyRate.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No houry rate stat") |
||||||
|
} |
||||||
|
val handsPlayed = results.computedStat(Stat.HANDS_PLAYED) |
||||||
|
if (handsPlayed != null) { |
||||||
|
assertEquals(100.0, handsPlayed.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No hands played stat") |
||||||
|
} |
||||||
|
val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES) |
||||||
|
if (numberOfGames != null) { |
||||||
|
assertEquals(2, numberOfGames.value.toInt()) |
||||||
|
} else { |
||||||
|
Assert.fail("No numberOfGames stat") |
||||||
|
} |
||||||
|
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) |
||||||
|
if (numberOfSets != null) { |
||||||
|
assertEquals(2, numberOfSets.value.toInt()) |
||||||
|
} else { |
||||||
|
Assert.fail("No numberOfSets stat") |
||||||
|
} |
||||||
|
val avgBuyin = results.computedStat(Stat.AVERAGE_BUYIN) |
||||||
|
if (avgBuyin != null) { |
||||||
|
assertEquals(150.0, avgBuyin.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No avgBuyin stat") |
||||||
|
} |
||||||
|
val avgDuration = results.computedStat(Stat.AVERAGE_DURATION) |
||||||
|
if (avgDuration != null) { |
||||||
|
assertEquals(2.0, avgDuration.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No avgDuration stat") |
||||||
|
} |
||||||
|
val roi = results.computedStat(Stat.ROI) |
||||||
|
if (roi != null) { |
||||||
|
assertEquals(200 / 300.0, roi.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No roi stat") |
||||||
|
} |
||||||
|
|
||||||
|
val avgBBNet = results.computedStat(Stat.AVERAGE_NET_BB) |
||||||
|
if (avgBBNet != null) { |
||||||
|
assertEquals(-25.0, avgBBNet.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No avgBBNet stat") |
||||||
|
} |
||||||
|
val bbHourlyRate = results.computedStat(Stat.HOURLY_RATE_BB) |
||||||
|
if (bbHourlyRate != null) { |
||||||
|
assertEquals(-12.5, bbHourlyRate.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No bbHourlyRate stat") |
||||||
|
} |
||||||
|
val netbbPer100Hands = results.computedStat(Stat.NET_BB_PER_100_HANDS) |
||||||
|
if (netbbPer100Hands != null) { |
||||||
|
assertEquals(-50.0, netbbPer100Hands.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No netbbPer100Hands stat") |
||||||
|
} |
||||||
|
|
||||||
|
val stdHourly = results.computedStat(Stat.STANDARD_DEVIATION_HOURLY) |
||||||
|
if (stdHourly != null) { |
||||||
|
assertEquals(141.42, stdHourly.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No stdHourly stat") |
||||||
|
} |
||||||
|
|
||||||
|
val std = results.computedStat(Stat.STANDARD_DEVIATION) |
||||||
|
if (std != null) { |
||||||
|
assertEquals(282.84, std.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No std stat") |
||||||
|
} |
||||||
|
|
||||||
|
val std100 = results.computedStat(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS) |
||||||
|
if (std100 != null) { |
||||||
|
assertEquals(707.1, std100.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No std100 stat") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testOverlappingSessions1() { |
||||||
|
|
||||||
|
val realm = this.mockRealm |
||||||
|
realm.beginTransaction() |
||||||
|
|
||||||
|
var s1 = realm.createObject(Session::class.java, "1") |
||||||
|
var s2 = realm.createObject(Session::class.java, "2") |
||||||
|
|
||||||
|
s1.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
s2.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
|
||||||
|
s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) |
||||||
|
s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) |
||||||
|
|
||||||
|
realm.insert(s1) |
||||||
|
realm.insert(s2) |
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
||||||
|
|
||||||
|
val sd1 = sdf.parse("01/1/2019 09:00") |
||||||
|
val ed1 = sdf.parse("01/1/2019 10:00") |
||||||
|
val sd2 = sdf.parse("01/1/2019 08:00") |
||||||
|
val ed2 = sdf.parse("01/1/2019 11:00") |
||||||
|
|
||||||
|
realm.beginTransaction() |
||||||
|
|
||||||
|
s1.timeFrame?.setDate(sd1, ed1) // duration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800 |
||||||
|
s2.timeFrame?.setDate(sd2, ed2) // duration = 4h, hourly = 100, bb100 = 150 / 75 * 100 = +200 |
||||||
|
|
||||||
|
realm.copyToRealmOrUpdate(s1) |
||||||
|
realm.copyToRealmOrUpdate(s2) |
||||||
|
|
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val sessions = realm.where(Session::class.java).findAll() |
||||||
|
val group = SessionGroup(name = "test", sessions = sessions) |
||||||
|
|
||||||
|
var options = Calculator.Options() |
||||||
|
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) |
||||||
|
|
||||||
|
val results: ComputedResults = Calculator.compute(group, options) |
||||||
|
val delta = 0.01 |
||||||
|
|
||||||
|
val duration = results.computedStat(Stat.DURATION) |
||||||
|
if (duration != null) { |
||||||
|
assertEquals(3.0, duration.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No Net result stat") |
||||||
|
} |
||||||
|
|
||||||
|
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) |
||||||
|
if (numberOfSets != null) { |
||||||
|
assertEquals(1, numberOfSets.value.toInt()) |
||||||
|
} else { |
||||||
|
Assert.fail("No numberOfSets stat") |
||||||
|
} |
||||||
|
|
||||||
|
val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES) |
||||||
|
if (numberOfGames != null) { |
||||||
|
assertEquals(2, numberOfGames.value.toInt()) |
||||||
|
} else { |
||||||
|
Assert.fail("No numberOfSets stat") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testOverlappingSessions2() { |
||||||
|
|
||||||
|
val realm = this.mockRealm |
||||||
|
realm.beginTransaction() |
||||||
|
|
||||||
|
var s1 = realm.createObject(Session::class.java, "1") |
||||||
|
var s2 = realm.createObject(Session::class.java, "2") |
||||||
|
var s3 = realm.createObject(Session::class.java, "3") |
||||||
|
|
||||||
|
s1.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
s2.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
s3.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
|
||||||
|
realm.insert(s1) |
||||||
|
realm.insert(s2) |
||||||
|
realm.insert(s3) |
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
||||||
|
|
||||||
|
val sd1 = sdf.parse("01/1/2019 05:00") |
||||||
|
val ed1 = sdf.parse("01/1/2019 09:00") |
||||||
|
val sd2 = sdf.parse("01/1/2019 07:00") |
||||||
|
val ed2 = sdf.parse("01/1/2019 11:00") |
||||||
|
val sd3 = sdf.parse("01/1/2019 03:00") |
||||||
|
val ed3 = sdf.parse("01/1/2019 06:00") |
||||||
|
|
||||||
|
realm.beginTransaction() |
||||||
|
|
||||||
|
s1.timeFrame?.setDate(sd1, ed1) // duration = 4h |
||||||
|
s2.timeFrame?.setDate(sd2, ed2) // duration = 4h |
||||||
|
s3.timeFrame?.setDate(sd3, ed3) // duration = 3h |
||||||
|
|
||||||
|
realm.copyToRealmOrUpdate(s1) |
||||||
|
realm.copyToRealmOrUpdate(s2) |
||||||
|
realm.copyToRealmOrUpdate(s3) |
||||||
|
|
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val sessions = realm.where(Session::class.java).findAll() |
||||||
|
val group = SessionGroup(name = "test", sessions = sessions) |
||||||
|
|
||||||
|
var options = Calculator.Options() |
||||||
|
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) |
||||||
|
|
||||||
|
val results: ComputedResults = Calculator.compute(group, options) |
||||||
|
val delta = 0.01 |
||||||
|
|
||||||
|
val duration = results.computedStat(Stat.DURATION) |
||||||
|
if (duration != null) { |
||||||
|
assertEquals(8.0, duration.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No Net result stat") |
||||||
|
} |
||||||
|
|
||||||
|
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) |
||||||
|
if (numberOfSets != null) { |
||||||
|
assertEquals(1, numberOfSets.value.toInt()) |
||||||
|
} else { |
||||||
|
Assert.fail("No numberOfSets stat") |
||||||
|
} |
||||||
|
|
||||||
|
val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES) |
||||||
|
if (numberOfGames != null) { |
||||||
|
assertEquals(3, numberOfGames.value.toInt()) |
||||||
|
} else { |
||||||
|
Assert.fail("No numberOfSets stat") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
var sessions: RealmResults<Session>? = null |
||||||
|
|
||||||
|
@Test |
||||||
|
fun testOverlappingSessionDeletion() { |
||||||
|
|
||||||
|
val realm = this.mockRealm |
||||||
|
this.sessions = realm.where(Session::class.java).findAll() // monitor session deletions |
||||||
|
|
||||||
|
Looper.prepare() |
||||||
|
this.sessions?.addChangeListener { t, changeSet -> |
||||||
|
|
||||||
|
val deletedSessions = realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll() |
||||||
|
deletedSessions.forEach { it.cleanup() } |
||||||
|
|
||||||
|
} |
||||||
|
Looper.loop() |
||||||
|
|
||||||
|
realm.beginTransaction() |
||||||
|
|
||||||
|
var s1 = realm.createObject(Session::class.java, "1") |
||||||
|
var s2 = realm.createObject(Session::class.java, "2") |
||||||
|
var s3 = realm.createObject(Session::class.java, "3") |
||||||
|
|
||||||
|
s1.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
s2.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
s3.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
|
||||||
|
realm.insert(s1) |
||||||
|
realm.insert(s2) |
||||||
|
realm.insert(s3) |
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
||||||
|
|
||||||
|
val sd1 = sdf.parse("01/1/2019 05:00") |
||||||
|
val ed1 = sdf.parse("01/1/2019 09:00") |
||||||
|
val sd2 = sdf.parse("01/1/2019 07:00") |
||||||
|
val ed2 = sdf.parse("01/1/2019 11:00") |
||||||
|
val sd3 = sdf.parse("01/1/2019 03:00") |
||||||
|
val ed3 = sdf.parse("01/1/2019 06:00") |
||||||
|
|
||||||
|
realm.beginTransaction() |
||||||
|
|
||||||
|
s1.timeFrame?.setDate(sd1, ed1) // duration = 4h |
||||||
|
s2.timeFrame?.setDate(sd2, ed2) // duration = 4h |
||||||
|
s3.timeFrame?.setDate(sd3, ed3) // duration = 3h |
||||||
|
|
||||||
|
realm.copyToRealmOrUpdate(s1) |
||||||
|
realm.copyToRealmOrUpdate(s2) |
||||||
|
realm.copyToRealmOrUpdate(s3) |
||||||
|
|
||||||
|
realm.commitTransaction() |
||||||
|
|
||||||
|
val sessions = realm.where(Session::class.java).findAll() |
||||||
|
val group = SessionGroup(name = "test", sessions = sessions) |
||||||
|
|
||||||
|
var options = Calculator.Options() |
||||||
|
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) |
||||||
|
|
||||||
|
val results: ComputedResults = Calculator.compute(group, options) |
||||||
|
val delta = 0.01 |
||||||
|
|
||||||
|
val duration = results.computedStat(Stat.DURATION) |
||||||
|
if (duration != null) { |
||||||
|
assertEquals(8.0, duration.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No duration stat") |
||||||
|
} |
||||||
|
|
||||||
|
realm.beginTransaction() |
||||||
|
s1.deleteFromRealm() |
||||||
|
realm.commitTransaction() |
||||||
|
// realm.executeTransaction { |
||||||
|
// s1.deleteFromRealm() |
||||||
|
// } |
||||||
|
|
||||||
|
val group2 = SessionGroup(name = "test", sessions = sessions) |
||||||
|
val results2: ComputedResults = Calculator.compute(group2, options) |
||||||
|
|
||||||
|
val duration2 = results2.computedStat(Stat.DURATION) |
||||||
|
if (duration2 != null) { |
||||||
|
assertEquals(7.0, duration2.value, delta) |
||||||
|
} else { |
||||||
|
Assert.fail("No duration2 stat") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
package net.pokeranalytics.android |
||||||
|
|
||||||
|
import io.realm.Realm |
||||||
|
import io.realm.RealmConfiguration |
||||||
|
import org.junit.After |
||||||
|
import org.junit.Before |
||||||
|
|
||||||
|
open class RealmInstrumentedUnitTest { |
||||||
|
|
||||||
|
lateinit var mockRealm: Realm |
||||||
|
|
||||||
|
@Before |
||||||
|
fun setup() { |
||||||
|
|
||||||
|
val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build() |
||||||
|
Realm.setDefaultConfiguration(testConfig) |
||||||
|
mockRealm = Realm.getDefaultInstance() |
||||||
|
} |
||||||
|
|
||||||
|
@After |
||||||
|
@Throws(Exception::class) |
||||||
|
public fun tearDown() { |
||||||
|
mockRealm.close() |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,40 +0,0 @@ |
|||||||
package net.pokeranalytics.android.components |
|
||||||
|
|
||||||
import io.realm.RealmList |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
open class BaseFilterInstrumentedUnitTest : RealmInstrumentedUnitTest() { |
|
||||||
|
|
||||||
// convenience extension |
|
||||||
fun Session.Companion.testInstance( |
|
||||||
netResult: Double = 0.0, |
|
||||||
isTournament: Boolean = false, |
|
||||||
startDate: Date = Date(), |
|
||||||
endDate: Int = 1, |
|
||||||
bankroll: Bankroll? = null, |
|
||||||
game: Game? = null, |
|
||||||
location : Location? = null, |
|
||||||
tournamentName: TournamentName? = null, |
|
||||||
tournamentFeatures: RealmList<TournamentFeature> = RealmList(), |
|
||||||
numberOfTable: Int = 1, |
|
||||||
limit: Int? = null, |
|
||||||
tableSize: Int? = null |
|
||||||
): Session { |
|
||||||
val session: Session = Session.newInstance(super.mockRealm, isTournament, bankroll) |
|
||||||
session.game = game |
|
||||||
session.location = location |
|
||||||
session.tournamentFeatures = tournamentFeatures |
|
||||||
session.tournamentName = tournamentName |
|
||||||
session.limit = limit |
|
||||||
session.numberOfTables = numberOfTable |
|
||||||
session.tableSize = tableSize |
|
||||||
session.startDate = startDate |
|
||||||
session.result?.cashout = netResult |
|
||||||
val cal = Calendar.getInstance() // creates calendar |
|
||||||
cal.time = startDate // sets calendar time/date |
|
||||||
cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour |
|
||||||
session.endDate = cal.time // returns new date object, one hour in the future |
|
||||||
return session |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,105 +0,0 @@ |
|||||||
package net.pokeranalytics.android.components |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmConfiguration |
|
||||||
import net.pokeranalytics.android.model.realm.Result |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import org.junit.After |
|
||||||
import org.junit.Before |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
|
|
||||||
open class RealmInstrumentedUnitTest { |
|
||||||
|
|
||||||
val EPSILON = 0.0001 |
|
||||||
|
|
||||||
lateinit var mockRealm: Realm |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
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 |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Before |
|
||||||
fun setup() { |
|
||||||
|
|
||||||
val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build() |
|
||||||
Realm.setDefaultConfiguration(testConfig) |
|
||||||
this.mockRealm = Realm.getDefaultInstance() |
|
||||||
this.mockRealm.beginTransaction() |
|
||||||
this.mockRealm.deleteAll() |
|
||||||
this.mockRealm.commitTransaction() |
|
||||||
} |
|
||||||
|
|
||||||
@After |
|
||||||
@Throws(Exception::class) |
|
||||||
fun tearDown() { |
|
||||||
this.mockRealm.close() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
|
|
||||||
package net.pokeranalytics.android.components |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmConfiguration |
|
||||||
import net.pokeranalytics.android.model.realm.ComputableResult |
|
||||||
import net.pokeranalytics.android.model.realm.Result |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import org.junit.AfterClass |
|
||||||
import org.junit.Before |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
interface RealmTestDataSource { |
|
||||||
val realmName : String |
|
||||||
} |
|
||||||
|
|
||||||
open class RealmInstrumentedUnitTest { |
|
||||||
|
|
||||||
companion object : RealmTestDataSource { |
|
||||||
lateinit var mockRealm: Realm |
|
||||||
override val realmName: String |
|
||||||
get() = "RealmInstrumentedUnitTest" |
|
||||||
|
|
||||||
init { |
|
||||||
val testConfig = RealmConfiguration.Builder().inMemory().name(realmName).build() |
|
||||||
Realm.setDefaultConfiguration(testConfig) |
|
||||||
mockRealm = Realm.getDefaultInstance() |
|
||||||
} |
|
||||||
|
|
||||||
fun newSessionInstance(realm: Realm) : Session { |
|
||||||
val session = realm.createObject(Session::class.java, UUID.randomUUID().toString()) |
|
||||||
val computableResult = realm.createObject(ComputableResult::class.java) |
|
||||||
computableResult.session = session |
|
||||||
session.result = realm.createObject(Result::class.java) |
|
||||||
return session |
|
||||||
} |
|
||||||
|
|
||||||
@AfterClass |
|
||||||
@Throws(Exception::class) |
|
||||||
fun tearDown() { |
|
||||||
mockRealm.close() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
var mockRealm: Realm = Companion.mockRealm |
|
||||||
|
|
||||||
@Before |
|
||||||
fun setup() { |
|
||||||
this.mockRealm.beginTransaction() |
|
||||||
this.mockRealm.deleteAll() |
|
||||||
this.mockRealm.commitTransaction() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
*/ |
|
||||||
@ -1,41 +0,0 @@ |
|||||||
package net.pokeranalytics.android.components |
|
||||||
|
|
||||||
import io.realm.RealmList |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
open class SessionInstrumentedUnitTest : RealmInstrumentedUnitTest() { |
|
||||||
|
|
||||||
// convenience extension |
|
||||||
fun Session.Companion.testInstance( |
|
||||||
netResult: Double = 0.0, |
|
||||||
isTournament: Boolean = false, |
|
||||||
startDate: Date = Date(), |
|
||||||
endDate: Int = 1, |
|
||||||
bankroll: Bankroll? = null, |
|
||||||
game: Game? = null, |
|
||||||
location: Location? = null, |
|
||||||
tournamentName: TournamentName? = null, |
|
||||||
tournamentFeatures: RealmList<TournamentFeature> = RealmList(), |
|
||||||
numberOfTable: Int = 1, |
|
||||||
limit: Int? = null, |
|
||||||
tableSize: Int? = null |
|
||||||
): Session { |
|
||||||
val session: Session = Session.newInstance(super.mockRealm, isTournament, bankroll) |
|
||||||
session.game = game |
|
||||||
session.location = location |
|
||||||
session.tournamentFeatures = tournamentFeatures |
|
||||||
session.tournamentName = tournamentName |
|
||||||
session.limit = limit |
|
||||||
session.numberOfTables = numberOfTable |
|
||||||
session.tableSize = tableSize |
|
||||||
session.startDate = startDate |
|
||||||
session.result?.netResult = netResult |
|
||||||
val cal = Calendar.getInstance() // creates calendar |
|
||||||
cal.time = startDate // sets calendar time/date |
|
||||||
cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour |
|
||||||
session.endDate = cal.time // returns new date object, one hour in the future |
|
||||||
return session |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,113 +0,0 @@ |
|||||||
package net.pokeranalytics.android.migrations |
|
||||||
|
|
||||||
import android.os.Environment |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import androidx.test.platform.app.InstrumentationRegistry |
|
||||||
import androidx.test.rule.GrantPermissionRule |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmConfiguration |
|
||||||
import io.realm.kotlin.where |
|
||||||
import net.pokeranalytics.android.model.migrations.PokerAnalyticsMigration |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import org.junit.Before |
|
||||||
import org.junit.Rule |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
import timber.log.Timber |
|
||||||
import java.io.File |
|
||||||
import java.io.IOException |
|
||||||
import java.io.InputStream |
|
||||||
|
|
||||||
/* |
|
||||||
class MigrationsInstrumentedUnitTest { |
|
||||||
|
|
||||||
@get:Rule |
|
||||||
var permissionRule = GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) |
|
||||||
|
|
||||||
private lateinit var mockRealm: Realm |
|
||||||
private lateinit var directory: File |
|
||||||
private lateinit var dbFileName: String |
|
||||||
|
|
||||||
@Before |
|
||||||
fun setup() { |
|
||||||
|
|
||||||
Timber.d("*** Setup realm migration") |
|
||||||
|
|
||||||
directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) |
|
||||||
dbFileName = "schema_0.realm" |
|
||||||
|
|
||||||
val context = InstrumentationRegistry.getInstrumentation().context |
|
||||||
val inputStream = context.assets.open(dbFileName) |
|
||||||
copyBundledRealmFile(inputStream, dbFileName) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
// TODO: Uncomment this when we'll have migrations to execute (should crash if we don't apply the migration) |
|
||||||
//@Test(expected = io.realm.exceptions.RealmMigrationNeededException::class) |
|
||||||
fun testMigrationNeeded() { |
|
||||||
|
|
||||||
Timber.d("*** testMigrationNeeded") |
|
||||||
|
|
||||||
val config0 = RealmConfiguration.Builder() |
|
||||||
.directory(directory) |
|
||||||
.name(dbFileName) |
|
||||||
.schemaVersion(0) |
|
||||||
.build() |
|
||||||
|
|
||||||
Realm.migrateRealm(config0, PokerAnalyticsMigration()) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testMigrationSuccess() { |
|
||||||
|
|
||||||
Timber.d("*** testMigrationSuccess") |
|
||||||
|
|
||||||
val config1 = RealmConfiguration.Builder() |
|
||||||
.directory(directory) |
|
||||||
.name(dbFileName) |
|
||||||
// TODO: Set the correct schema version when we'll have migration to avoid crash |
|
||||||
.schemaVersion(0) |
|
||||||
.build() |
|
||||||
|
|
||||||
Realm.migrateRealm(config1, PokerAnalyticsMigration()) |
|
||||||
|
|
||||||
this.mockRealm = Realm.getInstance(config1) |
|
||||||
|
|
||||||
val sessions = this.mockRealm.where<Session>().findAll() |
|
||||||
|
|
||||||
Timber.d("*** Sessions: ${sessions.size}") |
|
||||||
this.mockRealm.close() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Copy [inputStream] to [outFileName] |
|
||||||
*/ |
|
||||||
private fun copyBundledRealmFile(inputStream: InputStream, outFileName: String): String? { |
|
||||||
try { |
|
||||||
|
|
||||||
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), outFileName) |
|
||||||
val outputStream = file.outputStream() |
|
||||||
|
|
||||||
inputStream.use { input -> |
|
||||||
outputStream.use { fileOut -> |
|
||||||
input.copyTo(fileOut) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
inputStream.close() |
|
||||||
outputStream.close() |
|
||||||
|
|
||||||
Timber.d("File: ${file.length()}") |
|
||||||
|
|
||||||
return file.absolutePath |
|
||||||
} catch (e: IOException) { |
|
||||||
e.printStackTrace() |
|
||||||
} |
|
||||||
|
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
*/ |
|
||||||
@ -1,103 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import org.junit.Assert.assertEquals |
|
||||||
import org.junit.Test |
|
||||||
import java.util.* |
|
||||||
import androidx.test.platform.app.InstrumentationRegistry |
|
||||||
|
|
||||||
class CriteriaTest : BaseFilterInstrumentedUnitTest() { |
|
||||||
@Test |
|
||||||
fun getQueryConditions() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time) |
|
||||||
val firstValue = cal.get(Calendar.YEAR) |
|
||||||
cal.add(Calendar.YEAR, 1) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
cal.add(Calendar.YEAR, -11) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
cal.add(Calendar.YEAR, 7) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
cal.add(Calendar.YEAR, -2) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
cal.add(Calendar.YEAR, 10) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
|
|
||||||
val lastValue = firstValue + 5 |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val yearQueries = Criteria.Years.queryConditions |
|
||||||
|
|
||||||
assertEquals(16, yearQueries.size) |
|
||||||
|
|
||||||
val firstYearCondition = yearQueries.first().conditions.first() as QueryCondition.AnyYear |
|
||||||
assertEquals(firstValue - 10, firstYearCondition.listOfValues.first()) |
|
||||||
|
|
||||||
val lastYearCondition = yearQueries.last().conditions.first() as QueryCondition.AnyYear |
|
||||||
assertEquals(lastValue, lastYearCondition.listOfValues.first()) |
|
||||||
|
|
||||||
|
|
||||||
// val years = Criteria.Years.queryConditions as List<QueryCondition.AnyYear> |
|
||||||
// println("years = ${years.map { it.getDisplayName() }}") |
|
||||||
// |
|
||||||
// assertEquals(16, years.size) |
|
||||||
// assertEquals(firstValue-10, years.first().listOfValues.first()) |
|
||||||
// assertEquals(lastValue, years.last().listOfValues.first()) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun combined() { |
|
||||||
|
|
||||||
val criterias = listOf(Criteria.MonthsOfYear, Criteria.DaysOfWeek) |
|
||||||
val combined = criterias.combined() |
|
||||||
val context = InstrumentationRegistry.getInstrumentation().context |
|
||||||
|
|
||||||
combined.forEach { |
|
||||||
it.conditions.forEach {qc-> |
|
||||||
|
|
||||||
println(qc.getDisplayName(context)) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun upToNow() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time) |
|
||||||
cal.add(Calendar.YEAR, 1) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
cal.add(Calendar.YEAR, -11) |
|
||||||
val firstValue = cal.get(Calendar.YEAR) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
cal.add(Calendar.YEAR, 7) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
cal.add(Calendar.YEAR, -2) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
cal.add(Calendar.YEAR, 10) |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
|
|
||||||
val lastValue = firstValue + 10 |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
val context = InstrumentationRegistry.getInstrumentation().context |
|
||||||
|
|
||||||
val allMonths = Criteria.AllMonthsUpToNow.queries |
|
||||||
allMonths.forEach { |
|
||||||
it.conditions.forEach { qc-> |
|
||||||
println("<<<<< ${qc.getDisplayName(context)}") |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,79 +0,0 @@ |
|||||||
package net.pokeranalytics.android.performanceTests |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import androidx.test.core.app.ApplicationProvider |
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import net.pokeranalytics.android.calculus.Calculator |
|
||||||
import net.pokeranalytics.android.calculus.ComputableGroup |
|
||||||
import net.pokeranalytics.android.calculus.ComputedResults |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.components.RealmInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
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.utils.Seed |
|
||||||
import net.pokeranalytics.android.util.FakeDataManager |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
/** |
|
||||||
* Instrumented test, which will execute on an Android device. |
|
||||||
* |
|
||||||
* See [testing documentation](http://d.android.com/tools/testing). |
|
||||||
*/ |
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class PerfsInstrumentedUnitTest : RealmInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testGlobalPerfs() { |
|
||||||
|
|
||||||
Timber.d("*** start global perfs ") |
|
||||||
|
|
||||||
|
|
||||||
val realm = mockRealm |
|
||||||
val app: Context = ApplicationProvider.getApplicationContext() |
|
||||||
|
|
||||||
realm.beginTransaction() |
|
||||||
Seed(app).execute(realm) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
|
|
||||||
FakeDataManager.createFakeSessions(5000) {success -> |
|
||||||
|
|
||||||
if (success) { |
|
||||||
|
|
||||||
val start = System.currentTimeMillis() |
|
||||||
|
|
||||||
val sessions = realm.where(Session::class.java).findAll() |
|
||||||
val computableResults = realm.where(ComputableResult::class.java).findAll() |
|
||||||
val sets = realm.where(SessionSet::class.java).findAll() |
|
||||||
|
|
||||||
Timber.d("sessions: ${sessions.size}") |
|
||||||
Timber.d("computableResults: ${computableResults.size}") |
|
||||||
Timber.d("sets: ${sets.size}") |
|
||||||
|
|
||||||
val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE) |
|
||||||
val group = ComputableGroup(Query(), stats) |
|
||||||
|
|
||||||
val options = Calculator.Options() |
|
||||||
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) |
|
||||||
|
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
Timber.d("*** ended in ${System.currentTimeMillis() - start} milliseconds") |
|
||||||
|
|
||||||
val sum = results.computedStat(Stat.NET_RESULT) |
|
||||||
Timber.d("*** NET RESULT: ${sum?.value}") |
|
||||||
|
|
||||||
val average = results.computedStat(Stat.AVERAGE) |
|
||||||
Timber.d("*** AVERAGE: ${average?.value}") |
|
||||||
|
|
||||||
val duration = results.computedStat(Stat.HOURLY_DURATION) |
|
||||||
Timber.d("*** HOURLY_DURATION: ${duration?.value}") |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,128 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import io.realm.Realm |
|
||||||
import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator |
|
||||||
import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup |
|
||||||
import net.pokeranalytics.android.components.SessionInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.model.realm.Currency |
|
||||||
import org.junit.Assert |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() { |
|
||||||
|
|
||||||
private fun createDefaultTransactionTypes(realm: Realm) { |
|
||||||
TransactionType.Value.values().forEachIndexed { index, value -> |
|
||||||
val type = TransactionType() |
|
||||||
type.additive = value.additive |
|
||||||
type.kind = value.uniqueIdentifier |
|
||||||
type.lock = true |
|
||||||
realm.insertOrUpdate(type) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// convenience extension |
|
||||||
fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session { |
|
||||||
val session: Session = newInstance(super.mockRealm, false) |
|
||||||
session.result?.netResult = netResult |
|
||||||
session.startDate = startDate |
|
||||||
session.endDate = endDate |
|
||||||
return session |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testReport() { |
|
||||||
|
|
||||||
val realm = mockRealm |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
this.createDefaultTransactionTypes(realm) |
|
||||||
|
|
||||||
val br1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
br1.name = "br1" |
|
||||||
val br2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
br2.name = "br2" |
|
||||||
|
|
||||||
br1.initialValue = 100.0 |
|
||||||
br2.initialValue = 1000.0 |
|
||||||
|
|
||||||
val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) |
|
||||||
t1.amount = 100.0 |
|
||||||
t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) |
|
||||||
t1.bankroll = br1 |
|
||||||
|
|
||||||
val t2 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) |
|
||||||
t2.amount = 500.0 |
|
||||||
t2.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) |
|
||||||
t2.bankroll = br2 |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
s1.bankroll = br1 |
|
||||||
s1.result?.cashout = 200.0 |
|
||||||
|
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
s2.bankroll = br2 |
|
||||||
s2.result?.cashout = 500.0 |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val br1 = realm.where(Bankroll::class.java).equalTo("name", "br1").findFirst() |
|
||||||
val brSetup1 = BankrollReportSetup(br1?.id) |
|
||||||
val report1 = BankrollCalculator.computeReport(realm, brSetup1) |
|
||||||
Assert.assertEquals(400.0, report1.total, EPSILON) |
|
||||||
|
|
||||||
val br2 = realm.where(Bankroll::class.java).equalTo("name", "br2").findFirst() |
|
||||||
val brSetup2 = BankrollReportSetup(br2?.id) |
|
||||||
val report2 = BankrollCalculator.computeReport(realm, brSetup2) |
|
||||||
Assert.assertEquals(2000.0, report2.total, EPSILON) |
|
||||||
|
|
||||||
val brSetupAll = BankrollReportSetup() |
|
||||||
val reportAll = BankrollCalculator.computeReport(realm, brSetupAll) |
|
||||||
Assert.assertEquals(2400.0, reportAll.total, EPSILON) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
fun testReportWithRate() { |
|
||||||
|
|
||||||
val realm = mockRealm |
|
||||||
|
|
||||||
var br1: Bankroll? = null |
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
this.createDefaultTransactionTypes(realm) |
|
||||||
|
|
||||||
val c1 = realm.createObject(Currency::class.java, UUID.randomUUID().toString()) |
|
||||||
c1.rate = 10.0 |
|
||||||
|
|
||||||
br1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
br1?.currency = c1 |
|
||||||
br1?.initialValue = 100.0 |
|
||||||
|
|
||||||
val t1 = realm.createObject(Transaction::class.java, UUID.randomUUID().toString()) |
|
||||||
t1.amount = 100.0 |
|
||||||
t1.type = TransactionType.getByValue(TransactionType.Value.BONUS, realm) |
|
||||||
t1.bankroll = br1 |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
s1.bankroll = br1 |
|
||||||
s1.result?.cashout = 200.0 |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val brSetup1 = BankrollReportSetup(br1?.id) |
|
||||||
val report1 = BankrollCalculator.computeReport(realm, brSetup1) |
|
||||||
Assert.assertEquals(400.0, report1.total, EPSILON) |
|
||||||
|
|
||||||
val brSetupAll = BankrollReportSetup() |
|
||||||
val reportAll = BankrollCalculator.computeReport(realm, brSetupAll) |
|
||||||
Assert.assertEquals(4000.0, reportAll.total, EPSILON) |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,53 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests |
|
||||||
|
|
||||||
import net.pokeranalytics.android.components.RealmInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import net.pokeranalytics.android.model.realm.Currency |
|
||||||
import net.pokeranalytics.android.util.extensions.findById |
|
||||||
import org.junit.Assert |
|
||||||
import org.junit.Test |
|
||||||
|
|
||||||
class DeleteInstrumentedUnitTest : RealmInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testDeleteValidation() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
|
|
||||||
val br1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
br1.live = false |
|
||||||
val br2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
br2.live = false |
|
||||||
|
|
||||||
val c1 = realm.createObject(Currency::class.java, "1") |
|
||||||
val c2 = realm.createObject(Currency::class.java, "2") |
|
||||||
c1.rate = 0.1 |
|
||||||
c2.rate = 2.0 |
|
||||||
br1.currency = c1 |
|
||||||
br2.currency = c2 |
|
||||||
|
|
||||||
s1.bankroll = br1 |
|
||||||
s2.bankroll = br2 |
|
||||||
|
|
||||||
s1.result?.netResult = 100.0 |
|
||||||
s2.result?.netResult = 200.0 |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
var isValidForDelete = br1.isValidForDelete(realm) |
|
||||||
Assert.assertEquals(false, isValidForDelete) |
|
||||||
|
|
||||||
realm.findById(Bankroll::class.java, "1")?.let { |
|
||||||
isValidForDelete = it.isValidForDelete(realm) |
|
||||||
Assert.assertEquals(false, isValidForDelete) |
|
||||||
|
|
||||||
isValidForDelete = realm.copyFromRealm(it).isValidForDelete(realm) |
|
||||||
Assert.assertEquals(false, isValidForDelete) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,87 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import androidx.test.platform.app.InstrumentationRegistry |
|
||||||
import net.pokeranalytics.android.components.RealmInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.realm.Location |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
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) |
|
||||||
|
|
||||||
class FavoriteSessionUnitTest : RealmInstrumentedUnitTest() { |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
fun testFavoriteWithoutLocation() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
val s3 = newSessionInstance(realm) |
|
||||||
|
|
||||||
s1.endDate = Date() |
|
||||||
s2.endDate = Date() |
|
||||||
s3.endDate = Date() |
|
||||||
|
|
||||||
s1.cgBlinds = "2/4" |
|
||||||
s2.cgBlinds = "2/4" |
|
||||||
s3.cgBlinds = "1/2" |
|
||||||
|
|
||||||
realm.insert(s1) |
|
||||||
realm.insert(s2) |
|
||||||
realm.insert(s3) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
val favSession = FavoriteSessionFinder.favoriteSession(Session.Type.CASH_GAME.ordinal, null, realm, InstrumentationRegistry.getInstrumentation().targetContext) |
|
||||||
|
|
||||||
if (favSession != null) { |
|
||||||
Assert.assertEquals(4.0, favSession.cgBiggestBet) |
|
||||||
} else { |
|
||||||
Assert.fail("session shouldn't be null") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testFavoriteWithLocation() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
val s3 = newSessionInstance(realm) |
|
||||||
|
|
||||||
val loc1 = realm.createObject(Location::class.java, "1") |
|
||||||
val loc2 = realm.createObject(Location::class.java, "2") |
|
||||||
|
|
||||||
s1.cgBiggestBet = 4.0 |
|
||||||
s2.cgBiggestBet = 4.0 |
|
||||||
s3.cgBiggestBet = 1.0 |
|
||||||
|
|
||||||
s1.location = loc1 |
|
||||||
s2.location = loc1 |
|
||||||
s3.location = loc2 |
|
||||||
|
|
||||||
realm.insert(s1) |
|
||||||
realm.insert(s2) |
|
||||||
realm.insert(s3) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
val favSession = FavoriteSessionFinder.favoriteSession(Session.Type.CASH_GAME.ordinal, loc2, realm, InstrumentationRegistry.getInstrumentation().targetContext) |
|
||||||
|
|
||||||
if (favSession != null) { |
|
||||||
Assert.assertEquals(1.0, favSession.cgBiggestBet) |
|
||||||
} else { |
|
||||||
Assert.fail("session shouldn't be null") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,38 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import net.pokeranalytics.android.components.SessionInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import net.pokeranalytics.android.model.realm.Result |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class StatPerformanceUnitTest : SessionInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSessionNetResultOnLoad() { |
|
||||||
val realm = mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val bankroll = realm.createObject(Bankroll::class.java, "1") |
|
||||||
bankroll.live = false |
|
||||||
|
|
||||||
for (index in 0..100) { |
|
||||||
Session.testInstance((-2000..2000).random().toDouble(), bankroll = bankroll) |
|
||||||
println("*** creating $index") |
|
||||||
} |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val d1 = Date() |
|
||||||
realm.where(Result::class.java).sum("netResult") |
|
||||||
|
|
||||||
val d2 = Date() |
|
||||||
val duration = (d2.time - d1.time) |
|
||||||
println("*** ended in $duration milliseconds") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,838 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import io.realm.RealmResults |
|
||||||
import net.pokeranalytics.android.calculus.Calculator |
|
||||||
import net.pokeranalytics.android.calculus.ComputableGroup |
|
||||||
import net.pokeranalytics.android.calculus.ComputedResults |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.components.SessionInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.model.realm.Currency |
|
||||||
import org.junit.Assert |
|
||||||
import org.junit.Assert.assertEquals |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
import java.text.SimpleDateFormat |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
/** |
|
||||||
* Instrumented test, which will execute on an Android device. |
|
||||||
* |
|
||||||
* See [testing documentation](http://d.android.com/tools/testing). |
|
||||||
*/ |
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testAllSessionStats() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
val computableResults = realm.where(ComputableResult::class.java).findAll() |
|
||||||
|
|
||||||
assertEquals(0, computableResults.size) |
|
||||||
|
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
|
|
||||||
s1.result?.buyin = 100.0 // net result = -100 |
|
||||||
s2.result?.buyin = 200.0 |
|
||||||
s2.result?.cashout = 500.0 // net result = 300 |
|
||||||
|
|
||||||
s1.cgBlinds = "0.5" // bb net result = -200bb |
|
||||||
s2.cgBlinds = "2.0" // bb net result = 150bb |
|
||||||
|
|
||||||
s2.tableSize = 5 |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 10:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 11:00") // 1 hour |
|
||||||
val sd2 = sdf.parse("02/1/2019 08:00") |
|
||||||
val ed2 = sdf.parse("02/1/2019 11:00") // 3 hours |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
|
|
||||||
val l1 = realm.createObject(Location::class.java, UUID.randomUUID().toString()) |
|
||||||
s1.location = l1 |
|
||||||
s2.location = l1 |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
assertEquals(2, computableResults.size) |
|
||||||
|
|
||||||
computableResults.forEach { |
|
||||||
println(">>>>>> rated net = ${it.ratedNet} ") |
|
||||||
} |
|
||||||
|
|
||||||
val group = ComputableGroup(Query()) |
|
||||||
|
|
||||||
val options = Calculator.Options() |
|
||||||
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION, |
|
||||||
Stat.LONGEST_STREAKS, Stat.LOCATIONS_PLAYED, Stat.DAYS_PLAYED) |
|
||||||
|
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
val delta = 0.01 |
|
||||||
|
|
||||||
val sum = results.computedStat(Stat.NET_RESULT) |
|
||||||
if (sum != null) { |
|
||||||
assertEquals(200.0, sum.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No Net result stat") |
|
||||||
} |
|
||||||
|
|
||||||
val average = results.computedStat(Stat.AVERAGE) |
|
||||||
if (average != null) { |
|
||||||
assertEquals(100.0, average.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No AVERAGE stat") |
|
||||||
} |
|
||||||
|
|
||||||
val duration = results.computedStat(Stat.HOURLY_DURATION) |
|
||||||
if (duration != null) { |
|
||||||
assertEquals(4.0, duration.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No netDuration stat") |
|
||||||
} |
|
||||||
|
|
||||||
val hourlyRate = results.computedStat(Stat.HOURLY_RATE) |
|
||||||
if (hourlyRate != null) { |
|
||||||
assertEquals(50.0, hourlyRate.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No houry rate stat") |
|
||||||
} |
|
||||||
val handsPlayed = results.computedStat(Stat.HANDS_PLAYED) |
|
||||||
if (handsPlayed != null) { |
|
||||||
assertEquals(177.77, handsPlayed.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No hands played stat") |
|
||||||
} |
|
||||||
val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES) |
|
||||||
if (numberOfGames != null) { |
|
||||||
assertEquals(2, numberOfGames.value.toInt()) |
|
||||||
} else { |
|
||||||
Assert.fail("No numberOfGames stat") |
|
||||||
} |
|
||||||
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) |
|
||||||
if (numberOfSets != null) { |
|
||||||
assertEquals(2, numberOfSets.value.toInt()) |
|
||||||
} else { |
|
||||||
Assert.fail("No numberOfSets stat") |
|
||||||
} |
|
||||||
val avgBuyin = results.computedStat(Stat.AVERAGE_BUYIN) |
|
||||||
if (avgBuyin != null) { |
|
||||||
assertEquals(150.0, avgBuyin.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No avgBuyin stat") |
|
||||||
} |
|
||||||
val avgDuration = results.computedStat(Stat.AVERAGE_HOURLY_DURATION) |
|
||||||
if (avgDuration != null) { |
|
||||||
assertEquals(2.0, avgDuration.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No avgDuration stat") |
|
||||||
} |
|
||||||
val roi = results.computedStat(Stat.ROI) |
|
||||||
if (roi != null) { |
|
||||||
assertEquals(200 / 300.0, roi.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No roi stat") |
|
||||||
} |
|
||||||
|
|
||||||
val avgBBNet = results.computedStat(Stat.AVERAGE_NET_BB) |
|
||||||
if (avgBBNet != null) { |
|
||||||
assertEquals(-25.0, avgBBNet.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No avgBBNet stat") |
|
||||||
} |
|
||||||
val bbHourlyRate = results.computedStat(Stat.HOURLY_RATE_BB) |
|
||||||
if (bbHourlyRate != null) { |
|
||||||
assertEquals(-12.5, bbHourlyRate.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No bbHourlyRate stat") |
|
||||||
} |
|
||||||
val netbbPer100Hands = results.computedStat(Stat.NET_BB_PER_100_HANDS) |
|
||||||
if (netbbPer100Hands != null) { |
|
||||||
assertEquals(-28.12, netbbPer100Hands.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No netbbPer100Hands stat") |
|
||||||
} |
|
||||||
|
|
||||||
val std = results.computedStat(Stat.STANDARD_DEVIATION) |
|
||||||
if (std != null) { |
|
||||||
assertEquals(200.0, std.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No std stat") |
|
||||||
} |
|
||||||
|
|
||||||
val stdHourly = results.computedStat(Stat.STANDARD_DEVIATION_HOURLY) |
|
||||||
if (stdHourly != null) { |
|
||||||
assertEquals(111.8, stdHourly.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No stdHourly stat") |
|
||||||
} |
|
||||||
|
|
||||||
val std100 = results.computedStat(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS) |
|
||||||
if (std100 != null) { |
|
||||||
assertEquals(497.54, std100.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No std100 stat") |
|
||||||
} |
|
||||||
|
|
||||||
results.computedStat(Stat.MAXIMUM_NET_RESULT)?.let { |
|
||||||
assertEquals(300.0, it.value, delta) |
|
||||||
} ?: run { |
|
||||||
Assert.fail("No MAXIMUM_NETRESULT") |
|
||||||
} |
|
||||||
|
|
||||||
results.computedStat(Stat.MINIMUM_NET_RESULT)?.let { |
|
||||||
assertEquals(-100.0, it.value, delta) |
|
||||||
} ?: run { |
|
||||||
Assert.fail("No MINIMUM_NETRESULT") |
|
||||||
} |
|
||||||
|
|
||||||
results.computedStat(Stat.MAXIMUM_DURATION)?.let { |
|
||||||
assertEquals(3.0, it.value, delta) |
|
||||||
} ?: run { |
|
||||||
Assert.fail("No MAXIMUM_DURATION") |
|
||||||
} |
|
||||||
|
|
||||||
results.computedStat(Stat.DAYS_PLAYED)?.let { |
|
||||||
assertEquals(2.0, it.value, delta) |
|
||||||
} ?: run { |
|
||||||
Assert.fail("No DAYS_PLAYED") |
|
||||||
} |
|
||||||
|
|
||||||
results.computedStat(Stat.LOCATIONS_PLAYED)?.let { |
|
||||||
assertEquals(1.0, it.value, delta) |
|
||||||
} ?: run { |
|
||||||
Assert.fail("No LOCATIONS_PLAYED") |
|
||||||
} |
|
||||||
|
|
||||||
results.computedStat(Stat.LONGEST_STREAKS)?.let { |
|
||||||
assertEquals(1.0, it.value, delta) |
|
||||||
assertEquals(1.0, it.secondValue!!, delta) |
|
||||||
} ?: run { |
|
||||||
Assert.fail("No LOCATIONS_PLAYED") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testOverlappingSessions1() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 09:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 10:00") |
|
||||||
val sd2 = sdf.parse("01/1/2019 08:00") |
|
||||||
val ed2 = sdf.parse("01/1/2019 11:00") |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
|
|
||||||
// netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800 |
|
||||||
// netDuration = 4h, hourly = 100, bb100 = 150 / 75 * 100 = +200 |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE) |
|
||||||
val group = ComputableGroup(Query(), stats) |
|
||||||
|
|
||||||
val options = Calculator.Options() |
|
||||||
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) |
|
||||||
|
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
val delta = 0.01 |
|
||||||
|
|
||||||
val duration = results.computedStat(Stat.HOURLY_DURATION) |
|
||||||
if (duration != null) { |
|
||||||
assertEquals(3.0, duration.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No Net result stat") |
|
||||||
} |
|
||||||
|
|
||||||
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) |
|
||||||
if (numberOfSets != null) { |
|
||||||
assertEquals(1, numberOfSets.value.toInt()) |
|
||||||
} else { |
|
||||||
Assert.fail("No numberOfSets stat") |
|
||||||
} |
|
||||||
|
|
||||||
val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES) |
|
||||||
if (numberOfGames != null) { |
|
||||||
assertEquals(2, numberOfGames.value.toInt()) |
|
||||||
} else { |
|
||||||
Assert.fail("No numberOfSets stat") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testOverlappingSessions2() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
val s3 = newSessionInstance(realm) |
|
||||||
|
|
||||||
realm.insert(s1) |
|
||||||
realm.insert(s2) |
|
||||||
realm.insert(s3) |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 05:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 09:00") // 4h |
|
||||||
val sd2 = sdf.parse("01/1/2019 07:00") |
|
||||||
val ed2 = sdf.parse("01/1/2019 11:00") // 4h |
|
||||||
val sd3 = sdf.parse("01/1/2019 03:00") |
|
||||||
val ed3 = sdf.parse("01/1/2019 06:00") // 3h |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
s3.startDate = sd3 |
|
||||||
s3.endDate = ed3 |
|
||||||
|
|
||||||
realm.copyToRealmOrUpdate(s1) |
|
||||||
realm.copyToRealmOrUpdate(s2) |
|
||||||
realm.copyToRealmOrUpdate(s3) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE) |
|
||||||
val group = ComputableGroup(Query(), stats) |
|
||||||
|
|
||||||
val options = Calculator.Options() |
|
||||||
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) |
|
||||||
|
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
val delta = 0.01 |
|
||||||
|
|
||||||
val duration = results.computedStat(Stat.HOURLY_DURATION) |
|
||||||
if (duration != null) { |
|
||||||
assertEquals(8.0, duration.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No Net result stat") |
|
||||||
} |
|
||||||
|
|
||||||
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) |
|
||||||
if (numberOfSets != null) { |
|
||||||
assertEquals(1, numberOfSets.value.toInt()) |
|
||||||
} else { |
|
||||||
Assert.fail("No numberOfSets stat") |
|
||||||
} |
|
||||||
|
|
||||||
val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES) |
|
||||||
if (numberOfGames != null) { |
|
||||||
assertEquals(3, numberOfGames.value.toInt()) |
|
||||||
} else { |
|
||||||
Assert.fail("No numberOfSets stat") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private var sessions: RealmResults<Session>? = null |
|
||||||
|
|
||||||
// @Test |
|
||||||
fun testOverlappingSessionDeletion() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
this.sessions = realm.where(Session::class.java).findAll() // monitor session deletions |
|
||||||
|
|
||||||
this.sessions?.addChangeListener { _, changeSet -> |
|
||||||
|
|
||||||
val deletedSessions = |
|
||||||
realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll() |
|
||||||
deletedSessions.forEach { it.cleanup() } |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val s1 = realm.createObject(Session::class.java, "1") |
|
||||||
val s2 = realm.createObject(Session::class.java, "2") |
|
||||||
val s3 = realm.createObject(Session::class.java, "3") |
|
||||||
|
|
||||||
realm.insert(s1) |
|
||||||
realm.insert(s2) |
|
||||||
realm.insert(s3) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 05:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 09:00") |
|
||||||
val sd2 = sdf.parse("01/1/2019 07:00") |
|
||||||
val ed2 = sdf.parse("01/1/2019 11:00") |
|
||||||
val sd3 = sdf.parse("01/1/2019 03:00") |
|
||||||
val ed3 = sdf.parse("01/1/2019 06:00") |
|
||||||
|
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
s3.startDate = sd3 |
|
||||||
s3.endDate = ed3 |
|
||||||
|
|
||||||
realm.copyToRealmOrUpdate(s1) |
|
||||||
realm.copyToRealmOrUpdate(s2) |
|
||||||
realm.copyToRealmOrUpdate(s3) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE) |
|
||||||
val group = ComputableGroup(Query(), stats) |
|
||||||
|
|
||||||
val options = Calculator.Options() |
|
||||||
options.stats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) |
|
||||||
|
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
val delta = 0.01 |
|
||||||
|
|
||||||
val duration = results.computedStat(Stat.HOURLY_DURATION) |
|
||||||
if (duration != null) { |
|
||||||
assertEquals(8.0, duration.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No netDuration stat") |
|
||||||
} |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
s1.deleteFromRealm() |
|
||||||
} |
|
||||||
|
|
||||||
val stats2: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE) |
|
||||||
val group2 = ComputableGroup(Query(), stats2) |
|
||||||
|
|
||||||
val results2: ComputedResults = Calculator.compute(realm, group2, options) |
|
||||||
|
|
||||||
val duration2 = results2.computedStat(Stat.HOURLY_DURATION) |
|
||||||
if (duration2 != null) { |
|
||||||
assertEquals(7.0, duration2.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No duration2 stat") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSessionSetCount() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
realm.insert(s1) |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 09:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 10:00") |
|
||||||
|
|
||||||
s1.startDate = sd1 //netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800 |
|
||||||
s1.endDate = ed1 |
|
||||||
|
|
||||||
realm.copyToRealmOrUpdate(s1) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val sets = realm.where(SessionSet::class.java).findAll() |
|
||||||
|
|
||||||
assertEquals(1, sets.size) |
|
||||||
|
|
||||||
val set = sets.first() |
|
||||||
if (set != null) { |
|
||||||
assertEquals(sd1.time, set.startDate.time) |
|
||||||
assertEquals(ed1.time, set.endDate.time) |
|
||||||
} else { |
|
||||||
Assert.fail("No set") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSessionSetCount2() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
|
|
||||||
realm.insert(s1) |
|
||||||
realm.insert(s2) |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 09:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 10:00") |
|
||||||
val sd2 = sdf.parse("01/2/2018 09:00") |
|
||||||
val ed2 = sdf.parse("01/2/2018 10:00") |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
|
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
|
|
||||||
realm.copyToRealmOrUpdate(s1) |
|
||||||
realm.copyToRealmOrUpdate(s2) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val sets = realm.where(SessionSet::class.java).findAll() |
|
||||||
|
|
||||||
assertEquals(2, sets.size) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSessionSetCount3() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("02/1/2019 09:00") |
|
||||||
val ed1 = sdf.parse("02/1/2019 10:00") |
|
||||||
val sd2 = sdf.parse("01/1/2019 09:00") |
|
||||||
val ed2 = sdf.parse("03/1/2019 10:00") |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
|
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
|
|
||||||
val ed22 = sdf.parse("01/1/2019 10:00") |
|
||||||
s2.endDate = ed22 |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val sets = realm.where(SessionSet::class.java).findAll() |
|
||||||
assertEquals(2, sets.size) |
|
||||||
|
|
||||||
val group = ComputableGroup(Query(), listOf(Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS, Stat.HOURLY_DURATION)) |
|
||||||
val options = Calculator.Options() |
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
|
|
||||||
results.computedStat(Stat.NUMBER_OF_GAMES)?.let { |
|
||||||
assertEquals(2, it.value.toInt()) |
|
||||||
} |
|
||||||
results.computedStat(Stat.NUMBER_OF_SETS)?.let { |
|
||||||
assertEquals(2, it.value.toInt()) |
|
||||||
} |
|
||||||
results.computedStat(Stat.HOURLY_DURATION)?.let { |
|
||||||
assertEquals(2.0, it.value, 0.001) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSessionRestartInOverlappingSessions() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
val s3 = newSessionInstance(realm) |
|
||||||
|
|
||||||
realm.insert(s1) |
|
||||||
realm.insert(s2) |
|
||||||
realm.insert(s3) |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 05:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 09:00") // 4h |
|
||||||
val sd2 = sdf.parse("01/1/2019 07:00") |
|
||||||
val ed2 = sdf.parse("01/1/2019 11:00") // 4h |
|
||||||
val sd3 = sdf.parse("01/1/2019 03:00") |
|
||||||
val ed3 = sdf.parse("01/1/2019 06:00") // 3h |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
s3.startDate = sd3 |
|
||||||
s3.endDate = ed3 |
|
||||||
|
|
||||||
realm.copyToRealmOrUpdate(s1) |
|
||||||
realm.copyToRealmOrUpdate(s2) |
|
||||||
realm.copyToRealmOrUpdate(s3) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
s1.endDate = null |
|
||||||
} |
|
||||||
|
|
||||||
val stats: List<Stat> = listOf(Stat.NET_RESULT, Stat.AVERAGE) |
|
||||||
val group = ComputableGroup(Query(), stats) |
|
||||||
|
|
||||||
val options = Calculator.Options() |
|
||||||
// options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) |
|
||||||
|
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
val delta = 0.01 |
|
||||||
|
|
||||||
val duration = results.computedStat(Stat.HOURLY_DURATION) |
|
||||||
if (duration != null) { |
|
||||||
assertEquals(7.0, duration.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No Net result stat") |
|
||||||
} |
|
||||||
|
|
||||||
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) |
|
||||||
if (numberOfSets != null) { |
|
||||||
assertEquals(2, numberOfSets.value.toInt()) |
|
||||||
} else { |
|
||||||
Assert.fail("No numberOfSets stat") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// @Test |
|
||||||
fun testRatedNetResultSessions() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
|
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
|
|
||||||
val c1 = realm.createObject(Currency::class.java, "1") |
|
||||||
val c2 = realm.createObject(Currency::class.java, "2") |
|
||||||
|
|
||||||
c1.rate = 0.5 |
|
||||||
c2.rate = 1.0 |
|
||||||
b1.currency = c1 |
|
||||||
b2.currency = c2 |
|
||||||
s1.bankroll = b1 |
|
||||||
s2.bankroll = b2 |
|
||||||
s1.result?.netResult = 100.0 |
|
||||||
s2.result?.netResult = 200.0 |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val stats: List<Stat> = listOf(Stat.NET_RESULT) |
|
||||||
val group = ComputableGroup(Query(), stats) |
|
||||||
val options = Calculator.Options() |
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
|
|
||||||
val netResult = results.computedStat(Stat.NET_RESULT) |
|
||||||
assertEquals(250.0, netResult?.value) |
|
||||||
} |
|
||||||
|
|
||||||
// @Test |
|
||||||
fun testUpdateRatedNetResultSessions() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
|
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
|
|
||||||
val c1 = realm.createObject(Currency::class.java, "1") |
|
||||||
val c2 = realm.createObject(Currency::class.java, "2") |
|
||||||
|
|
||||||
c1.rate = 0.5 |
|
||||||
c2.rate = 1.0 |
|
||||||
b1.currency = c1 |
|
||||||
b2.currency = c2 |
|
||||||
s1.bankroll = b1 |
|
||||||
s2.bankroll = b2 |
|
||||||
s1.result?.netResult = 100.0 |
|
||||||
s2.result?.netResult = 200.0 |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val stats: List<Stat> = listOf(Stat.NET_RESULT) |
|
||||||
val group = ComputableGroup(Query(), stats) |
|
||||||
val options = Calculator.Options() |
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
|
|
||||||
val netResult = results.computedStat(Stat.NET_RESULT) |
|
||||||
assertEquals(250.0, netResult?.value) |
|
||||||
|
|
||||||
println("currency set rate real test starts here") |
|
||||||
|
|
||||||
val updatedC2 = realm.where(Currency::class.java).equalTo("id", "2").findFirst() |
|
||||||
updatedC2?.let { currency -> |
|
||||||
val newC2 = realm.copyFromRealm(currency) |
|
||||||
newC2.rate = 3.0 |
|
||||||
realm.executeTransaction { |
|
||||||
it.copyToRealmOrUpdate(newC2) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val updatedGroup = ComputableGroup(Query(), stats) |
|
||||||
val updatedResults: ComputedResults = Calculator.compute(realm, updatedGroup, options) |
|
||||||
val updatedNetResult = updatedResults.computedStat(Stat.NET_RESULT) |
|
||||||
assertEquals(650.0, updatedNetResult?.value) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testDaysPlayed() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm) |
|
||||||
val s2 = newSessionInstance(realm) |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 09:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 10:00") |
|
||||||
val sd2 = sdf.parse("01/1/2019 19:00") |
|
||||||
val ed2 = sdf.parse("01/1/2019 20:00") |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
|
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
} |
|
||||||
|
|
||||||
val group = ComputableGroup(Query(), listOf()) |
|
||||||
val options = Calculator.Options(stats = listOf(Stat.DAYS_PLAYED)) |
|
||||||
val report = Calculator.computeGroups(realm, listOf(group), options) |
|
||||||
|
|
||||||
report.results.firstOrNull()?.computedStat(Stat.DAYS_PLAYED)?.let { |
|
||||||
assertEquals(1, it.value.toInt()) |
|
||||||
} ?: run { |
|
||||||
Assert.fail("Missing DAYS_PLAYED") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testFilteredHourlyRate() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm, true) |
|
||||||
val s2 = newSessionInstance(realm, true) |
|
||||||
val s3 = newSessionInstance(realm, false) |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 09:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 10:00") |
|
||||||
val sd2 = sdf.parse("01/1/2019 04:00") |
|
||||||
val ed2 = sdf.parse("01/1/2019 05:00") |
|
||||||
val sd3 = sdf.parse("01/1/2019 03:00") |
|
||||||
val ed3 = sdf.parse("01/1/2019 11:00") |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
s3.startDate = sd3 |
|
||||||
s3.endDate = ed3 |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val group = ComputableGroup(Query(QueryCondition.IsCash)) |
|
||||||
|
|
||||||
val options = Calculator.Options() |
|
||||||
options.stats = listOf(Stat.HOURLY_DURATION) |
|
||||||
|
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
val delta = 0.01 |
|
||||||
|
|
||||||
val duration = results.computedStat(Stat.HOURLY_DURATION) |
|
||||||
if (duration != null) { |
|
||||||
assertEquals(2.0, duration.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No Net result stat") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testFilteredHourlyRate2() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
val s1 = newSessionInstance(realm, true) |
|
||||||
val s2 = newSessionInstance(realm, true) |
|
||||||
val s3 = newSessionInstance(realm, false) |
|
||||||
|
|
||||||
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") |
|
||||||
|
|
||||||
val sd1 = sdf.parse("01/1/2019 06:00") |
|
||||||
val ed1 = sdf.parse("01/1/2019 09:00") |
|
||||||
val sd2 = sdf.parse("01/1/2019 07:00") |
|
||||||
val ed2 = sdf.parse("01/1/2019 10:00") |
|
||||||
val sd3 = sdf.parse("01/1/2019 03:00") |
|
||||||
val ed3 = sdf.parse("01/1/2019 11:00") |
|
||||||
|
|
||||||
s1.startDate = sd1 |
|
||||||
s1.endDate = ed1 |
|
||||||
s2.startDate = sd2 |
|
||||||
s2.endDate = ed2 |
|
||||||
s3.startDate = sd3 |
|
||||||
s3.endDate = ed3 |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val group = ComputableGroup(Query(QueryCondition.IsCash)) |
|
||||||
|
|
||||||
val options = Calculator.Options() |
|
||||||
options.stats = listOf(Stat.HOURLY_DURATION) |
|
||||||
|
|
||||||
val results: ComputedResults = Calculator.compute(realm, group, options) |
|
||||||
val delta = 0.01 |
|
||||||
|
|
||||||
val duration = results.computedStat(Stat.HOURLY_DURATION) |
|
||||||
if (duration != null) { |
|
||||||
assertEquals(4.0, duration.value, delta) |
|
||||||
} else { |
|
||||||
Assert.fail("No Net result stat") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,190 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests.filter |
|
||||||
|
|
||||||
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.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.rows.FilterSectionRow |
|
||||||
import org.junit.Assert |
|
||||||
import org.junit.Test |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
class BlindFilterInstrumentedTest : BaseFilterInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSingleBlindNoCurrencyFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val currency = realm.createObject(net.pokeranalytics.android.model.realm.Currency::class.java, "1") |
|
||||||
currency.code = "USD" |
|
||||||
|
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
b2.currency = currency |
|
||||||
|
|
||||||
val s1 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s1.cgBlinds = "0.5/1" |
|
||||||
|
|
||||||
val s2 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s2.cgBlinds = "0.5/1" |
|
||||||
|
|
||||||
val s3 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s3.cgBlinds = "1/2" |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyStake() |
|
||||||
|
|
||||||
val blind = QueryCondition.AnyStake().apply { |
|
||||||
listOfValues = arrayListOf(s1.blinds!!) |
|
||||||
} |
|
||||||
|
|
||||||
// blind.filterSectionRow = FilterSectionRow.Blind |
|
||||||
|
|
||||||
val filterElement = FilterCondition(arrayListOf(blind), FilterSectionRow.Stakes) |
|
||||||
filter.updateValueBy(filterElement) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(2, sessions.size) |
|
||||||
sessions.map { |
|
||||||
Assert.assertTrue(arrayListOf(s1.id, s2.id).contains((it as Session).id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSingleBlindNoSmallBlindNoCurrencyFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val currency = realm.createObject(net.pokeranalytics.android.model.realm.Currency::class.java, "1") |
|
||||||
currency.code = "AUD" |
|
||||||
|
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
b2.currency = currency |
|
||||||
|
|
||||||
val s1 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s1.cgBlinds = "0.5/1" |
|
||||||
|
|
||||||
val s2 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s2.cgBlinds = "0.5/1" |
|
||||||
|
|
||||||
val s3 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s3.cgBlinds = "1/2" |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
|
|
||||||
val filter = QueryCondition.AnyStake() |
|
||||||
|
|
||||||
val blind1 = QueryCondition.AnyStake().apply { |
|
||||||
listOfValues = arrayListOf(s1.blinds!!) |
|
||||||
} |
|
||||||
val blind2 = QueryCondition.AnyStake().apply { |
|
||||||
listOfValues = arrayListOf(s2.blinds!!) |
|
||||||
} |
|
||||||
|
|
||||||
val filterElements = FilterCondition(arrayListOf(blind1, blind2), FilterSectionRow.Stakes) |
|
||||||
filter.updateValueBy(filterElements) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(2, sessions.size) |
|
||||||
sessions.map { |
|
||||||
Assert.assertTrue(arrayListOf(s1.id, s2.id).contains((it as Session).id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSingleBlindCurrencyFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val currency = realm.createObject(net.pokeranalytics.android.model.realm.Currency::class.java, "1") |
|
||||||
currency.code = "USD" |
|
||||||
|
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
b2.currency = currency |
|
||||||
|
|
||||||
val s1 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s1.cgBlinds = "0.5/1" |
|
||||||
|
|
||||||
val s2 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s2.cgBlinds = "0.5/1" |
|
||||||
|
|
||||||
val s3 = Session.testInstance(100.0, false, Date(), 1, b2) |
|
||||||
s3.cgBlinds = "1/2" |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
|
|
||||||
val filter = QueryCondition.AnyStake() |
|
||||||
|
|
||||||
val blind = QueryCondition.AnyStake().apply { |
|
||||||
listOfValues = arrayListOf(s3.blinds!!) |
|
||||||
} |
|
||||||
|
|
||||||
val filterElement = FilterCondition(arrayListOf(blind), FilterSectionRow.Stakes) |
|
||||||
filter.updateValueBy(filterElement) |
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions.map { |
|
||||||
Assert.assertEquals(s3.id, (it as Session).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testMultiBlindNoCurrencyFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val currency = realm.createObject(net.pokeranalytics.android.model.realm.Currency::class.java, "1") |
|
||||||
currency.code = "AUD" |
|
||||||
|
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
b2.currency = currency |
|
||||||
|
|
||||||
val s1 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s1.cgBlinds = "0.5/1" |
|
||||||
|
|
||||||
val s2 = Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
s2.cgBlinds = "0.5/1" |
|
||||||
|
|
||||||
val s3 = Session.testInstance(100.0, false, Date(), 1, b2) |
|
||||||
s3.cgBlinds = "1/2" |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
|
|
||||||
val filter = QueryCondition.AnyStake() |
|
||||||
|
|
||||||
val stake1 = QueryCondition.AnyStake().apply { |
|
||||||
listOfValues = arrayListOf(s1.cgStakes!!) |
|
||||||
} |
|
||||||
val stake2 = QueryCondition.AnyStake().apply { |
|
||||||
listOfValues = arrayListOf(s2.cgStakes!!) |
|
||||||
} |
|
||||||
|
|
||||||
val filterElement = FilterCondition(arrayListOf(stake1, stake2), FilterSectionRow.Stakes) |
|
||||||
filter.updateValueBy(filterElement) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(2, sessions.size) |
|
||||||
sessions.map { |
|
||||||
Assert.assertTrue(arrayListOf(s1.id, s2.id).contains((it as Session).id)) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,87 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests.filter |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.CustomField |
|
||||||
import net.pokeranalytics.android.model.realm.CustomFieldEntry |
|
||||||
import net.pokeranalytics.android.model.realm.Filter |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import org.junit.Assert |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class CustomFieldFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testCustomFieldListFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val cf1 = realm.createObject(CustomField::class.java, "1") |
|
||||||
cf1.type = CustomField.Type.LIST.ordinal |
|
||||||
|
|
||||||
val cfe1 = realm.createObject(CustomFieldEntry::class.java, "9") |
|
||||||
val cfe2 = realm.createObject(CustomFieldEntry::class.java, "8") |
|
||||||
cfe1.value = "super" |
|
||||||
cfe2.value = "nul" |
|
||||||
|
|
||||||
cf1.entries.add(cfe1) |
|
||||||
cf1.entries.add(cfe2) |
|
||||||
|
|
||||||
val s1 = Session.testInstance(100.0, false, Date(), 1) |
|
||||||
s1.customFieldEntries.add(cfe1) |
|
||||||
val s2 = Session.testInstance(100.0, true, Date(), 1) |
|
||||||
s2.customFieldEntries.add(cfe2) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.CustomFieldListQuery(cfe2))) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s2.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testCustomFieldAmountFilter() { |
|
||||||
|
|
||||||
val cfId = "1234" |
|
||||||
var s2Id = "" |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.executeTransaction { |
|
||||||
|
|
||||||
val cf = realm.createObject(CustomField::class.java, cfId) |
|
||||||
cf.type = CustomField.Type.AMOUNT.ordinal |
|
||||||
|
|
||||||
val cfe1 = realm.createObject(CustomFieldEntry::class.java, "999") |
|
||||||
cf.entries.add(cfe1) |
|
||||||
cfe1.numericValue = 30.0 |
|
||||||
|
|
||||||
val cfe2 = realm.createObject(CustomFieldEntry::class.java, "888") |
|
||||||
cf.entries.add(cfe2) |
|
||||||
cfe2.numericValue = 100.0 |
|
||||||
|
|
||||||
val s1 = Session.testInstance(100.0, false, Date(), 1) |
|
||||||
s1.customFieldEntries.add(cfe1) |
|
||||||
val s2 = Session.testInstance(100.0, true, Date(), 1) |
|
||||||
s2.customFieldEntries.add(cfe2) |
|
||||||
s2Id = s2.id |
|
||||||
} |
|
||||||
|
|
||||||
val condition = QueryCondition.CustomFieldNumberQuery(cfId, 100.0) |
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(condition)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions.first()?.run { |
|
||||||
Assert.assertEquals(s2Id, this.id) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,513 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests.filter |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.Filter |
|
||||||
import net.pokeranalytics.android.model.realm.FilterCondition |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import net.pokeranalytics.android.ui.view.rows.FilterSectionRow |
|
||||||
import net.pokeranalytics.android.util.extensions.hourMinute |
|
||||||
import net.pokeranalytics.android.util.extensions.startOfDay |
|
||||||
import org.junit.Assert |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class DateFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testDayOfWeekFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() // creates calendar |
|
||||||
cal.time = Date() // sets calendar time/date |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time) |
|
||||||
cal.add(Calendar.DAY_OF_MONTH, 1) // adds one hour |
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyDayOfWeek() |
|
||||||
cal.time = s1.startDate |
|
||||||
|
|
||||||
val filterElementRow = QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(cal.get(Calendar.DAY_OF_WEEK)) } |
|
||||||
val filterElement = FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.DynamicDate) |
|
||||||
filter.updateValueBy(filterElement) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testMonthFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time) |
|
||||||
cal.add(Calendar.MONTH, 1) |
|
||||||
|
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyMonthOfYear() |
|
||||||
cal.time = s1.startDate |
|
||||||
val filterElementRow = QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(cal.get(Calendar.MONTH)) } |
|
||||||
|
|
||||||
val filterElement = FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.DynamicDate) |
|
||||||
filter.updateValueBy(filterElement) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testYearFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time) |
|
||||||
cal.add(Calendar.YEAR, 1) |
|
||||||
|
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyYear() |
|
||||||
cal.time = s1.startDate |
|
||||||
val filterElementRow = QueryCondition.AnyYear().apply { listOfValues = arrayListOf(cal.get(Calendar.YEAR)) } |
|
||||||
val filterElement = FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.DynamicDate) |
|
||||||
filter.updateValueBy(filterElement) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
fun testWeekEndFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
cal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY) |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time) |
|
||||||
cal.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY) |
|
||||||
|
|
||||||
Session.testInstance(100.0, true, cal.time) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsWeekEnd)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testWeekDayFilter() { |
|
||||||
|
|
||||||
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<Session>(realm, Query(QueryCondition.IsWeekDay)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testTodayFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val s1 = Session.testInstance(100.0, false) |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -72) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsToday)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testTodayNoonFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date().startOfDay() |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -72) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsToday)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
fun testYesterdayFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -24) |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
Session.testInstance(100.0, false) |
|
||||||
|
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -72) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.WasYesterday)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testYesterdayNoonFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -24) |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time.startOfDay()) |
|
||||||
|
|
||||||
Session.testInstance(100.0, false) |
|
||||||
|
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -72) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.WasYesterday)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testTodayAndYesterdayFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -24) |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
val s2 = Session.testInstance(100.0, false) |
|
||||||
|
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -72) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 3) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.WasTodayAndYesterday)) |
|
||||||
|
|
||||||
Assert.assertEquals(2, sessions.size) |
|
||||||
Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2))) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testThisYear() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = Session.testInstance(100.0, false) |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -24) |
|
||||||
val s2 = Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -72) |
|
||||||
val s3 = Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
cal.add(Calendar.YEAR, -4) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
cal.add(Calendar.YEAR, -1) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 5) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.DuringThisYear)) |
|
||||||
|
|
||||||
Assert.assertEquals(3, sessions.size) |
|
||||||
Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2, s3))) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testThisMonth() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = Session.testInstance(100.0, false) |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
cal.set(Calendar.DAY_OF_MONTH, 1) |
|
||||||
cal.time = cal.time.startOfDay() |
|
||||||
val s2 = Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -1) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
cal.add(Calendar.MONTH, -1) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
cal.add(Calendar.YEAR, -1) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 5) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.DuringThisMonth)) |
|
||||||
|
|
||||||
Assert.assertEquals(2, sessions.size) |
|
||||||
Assert.assertTrue(sessions.containsAll(arrayListOf(s1,s2))) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testThisWeek() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = Session.testInstance(100.0, false) |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = Date() |
|
||||||
cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, 1) |
|
||||||
cal.time = cal.time.startOfDay() |
|
||||||
cal.add(Calendar.HOUR_OF_DAY, -1) |
|
||||||
Session.testInstance(100.0, false, cal.time) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
Assert.assertTrue(realm.where(Session::class.java).findAll().count() == 2) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.DuringThisWeek)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
Assert.assertTrue(sessions.containsAll(arrayListOf(s1))) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testStartedFomDateFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() // creates calendar |
|
||||||
cal.time = Date() // sets calendar time/date |
|
||||||
|
|
||||||
Session.testInstance(100.0, false, cal.time, 1) |
|
||||||
|
|
||||||
cal.add(Calendar.DAY_OF_YEAR, 2) // adds one hour |
|
||||||
val s2 = Session.testInstance(100.0, true, cal.time, 1) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.StartedFromDate(Date()) |
|
||||||
val filterElementRow = QueryCondition.StartedFromDate(Date()).apply { singleValue = s2.startDate!!} |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.FixedDate)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s2.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testStartedToDateFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() // creates calendar |
|
||||||
cal.time = Date() // sets calendar time/date |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time, 1) |
|
||||||
|
|
||||||
cal.add(Calendar.DAY_OF_YEAR, 2) // adds one hour |
|
||||||
Session.testInstance(100.0, true, cal.time, 1) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.StartedToDate(Date()) |
|
||||||
val filterElementRow = QueryCondition.StartedToDate(Date()).apply { singleValue = s1.startDate!! } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.FixedDate)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testEndedFomDateFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() // creates calendar |
|
||||||
cal.time = Date() // sets calendar time/date |
|
||||||
|
|
||||||
Session.testInstance(100.0, false, cal.time, 1) |
|
||||||
|
|
||||||
cal.add(Calendar.DAY_OF_YEAR, 2) // adds one hour |
|
||||||
val s2 = Session.testInstance(100.0, true, cal.time, 1) |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.EndedFromDate(Date()) |
|
||||||
val filterElementRow = QueryCondition.EndedFromDate(Date()).apply { singleValue = s2.endDate() } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.FixedDate)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s2.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testEndedToDateFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() // creates calendar |
|
||||||
cal.time = Date() // sets calendar time/date |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time, 1) |
|
||||||
|
|
||||||
cal.add(Calendar.DAY_OF_YEAR, 2) // adds one hour |
|
||||||
|
|
||||||
Session.testInstance(100.0, true, cal.time, 1) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
|
|
||||||
val filter = QueryCondition.EndedToDate(Date()) |
|
||||||
val filterElementRow = QueryCondition.EndedToDate(Date()).apply { singleValue = s1.endDate() } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.FixedDate)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s1.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testFomTimeToTime() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
val cal = Calendar.getInstance() // creates calendar |
|
||||||
cal.time = Date() // sets calendar time/date |
|
||||||
cal.set(Calendar.HOUR_OF_DAY, 14) // adds one hour |
|
||||||
|
|
||||||
println("<<<<< s1 ${cal.hourMinute()}") |
|
||||||
val s1 = Session.testInstance(100.0, false, cal.time, 1) |
|
||||||
println("<<<<< s1 ${cal.hourMinute()}") |
|
||||||
cal.add(Calendar.HOUR_OF_DAY, 2) // adds one hour |
|
||||||
println("<<<<< s2 ${cal.hourMinute()}") |
|
||||||
val s2 = Session.testInstance(100.0, true, cal.time, 1) |
|
||||||
println("<<<<< s2 ${cal.hourMinute()}") |
|
||||||
|
|
||||||
cal.set(Calendar.HOUR_OF_DAY, 23) // adds one hour |
|
||||||
println("<<<<< s3 ${cal.hourMinute()}") |
|
||||||
val s3 = Session.testInstance(100.0, true, cal.time, 2) |
|
||||||
println("<<<<< s3 ${cal.hourMinute()}") |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.StartedFromTime(s2.startDate!!) |
|
||||||
val filter2 = QueryCondition.EndedToTime(s2.endDate!!) |
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter, filter2)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(s2.id, (this).id) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,20 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests.filter |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
|
||||||
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.FilterCondition |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class ExceptionFilterInstrumentedTest: BaseFilterInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test(expected = PokerAnalyticsException.FilterElementUnknownName::class) |
|
||||||
fun testFilterException() { |
|
||||||
FilterCondition().queryCondition |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,66 +0,0 @@ |
|||||||
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.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.Filter |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import net.pokeranalytics.android.ui.view.rows.FixedValueFilterItemRow |
|
||||||
import net.pokeranalytics.android.ui.view.rows.FilterCategoryRow |
|
||||||
import net.pokeranalytics.android.ui.view.rows.FilterSectionRow |
|
||||||
import org.junit.Assert |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class RealmFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSaveLoadCashFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val filter = Filter() |
|
||||||
filter.name = "testSaveLoadCashFilter" |
|
||||||
|
|
||||||
val filterElement = QueryCondition.IsCash |
|
||||||
val filterItemRow = FixedValueFilterItemRow(filterElement, FilterSectionRow.CashOrTournament) |
|
||||||
|
|
||||||
filter.createOrUpdateFilterConditions(arrayListOf(filterItemRow)) |
|
||||||
|
|
||||||
val useCount = filter.countBy(FilterCategoryRow.GENERAL) |
|
||||||
Assert.assertEquals(1, useCount) |
|
||||||
|
|
||||||
val isCash = filter.contains(filterElement) |
|
||||||
Assert.assertEquals(true, isCash) |
|
||||||
|
|
||||||
val filterComponent = filter.filterConditions.first() |
|
||||||
|
|
||||||
filterComponent?.let { |
|
||||||
Assert.assertTrue(it.queryCondition is QueryCondition.IsCash) |
|
||||||
} ?: run { |
|
||||||
Assert.fail() |
|
||||||
} |
|
||||||
|
|
||||||
Session.testInstance(100.0, false, Date(), 1) |
|
||||||
Session.testInstance(100.0, true, Date(), 1) |
|
||||||
|
|
||||||
realm.copyToRealm(filter) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val newRealm = this.mockRealm |
|
||||||
newRealm.where(Filter::class.java).equalTo("name", "testSaveLoadCashFilter").findFirst()?.let { foundFilter -> |
|
||||||
val sessions = foundFilter.results<Session>() |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(Session.Type.CASH_GAME.ordinal, this.type) |
|
||||||
} ?: run { |
|
||||||
Assert.fail() |
|
||||||
} |
|
||||||
} ?: run { |
|
||||||
Assert.fail() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,597 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests.filter |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import io.realm.RealmList |
|
||||||
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.ui.view.rows.FilterSectionRow |
|
||||||
import org.junit.Assert |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testCashFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
Session.testInstance(100.0, false, Date(), 1) |
|
||||||
Session.testInstance(100.0, true, Date(), 1) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsCash)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(Session.Type.CASH_GAME.ordinal, (this).type) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testTournamentFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
|
|
||||||
Session.testInstance(100.0, false, Date(), 1) |
|
||||||
Session.testInstance(100.0, true, Date(), 1) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsTournament)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
sessions[0]?.run { |
|
||||||
Assert.assertEquals(Session.Type.TOURNAMENT.ordinal, (this).type) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testLiveFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
|
|
||||||
realm.beginTransaction() |
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
b1.live = true |
|
||||||
b2.live = false |
|
||||||
|
|
||||||
Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
Session.testInstance(100.0, true, Date(), 1, b2) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsLive)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
(sessions[0] as Session).bankroll?.run { |
|
||||||
Assert.assertEquals(true, this.live) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testOnlineFilter() { |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
b1.live = false |
|
||||||
b2.live = true |
|
||||||
|
|
||||||
Session.testInstance(100.0, false, Date(), 1, b1) |
|
||||||
Session.testInstance(100.0, true, Date(), 1, b2) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsOnline)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
(sessions[0] as Session).bankroll?.run { |
|
||||||
Assert.assertEquals(false, this.live) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testBankrollFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
b1.live = false |
|
||||||
b2.live = true |
|
||||||
Session.testInstance(bankroll = b1) |
|
||||||
Session.testInstance(bankroll = b2) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyBankroll() |
|
||||||
val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.Bankroll)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
(sessions[0] as Session).bankroll?.run { |
|
||||||
Assert.assertEquals(b1.id, this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testMultipleBankrollFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
val b3 = realm.createObject(Bankroll::class.java, "3") |
|
||||||
Session.testInstance(bankroll = b1) |
|
||||||
Session.testInstance(bankroll = b2) |
|
||||||
Session.testInstance(bankroll = b3) |
|
||||||
Session.testInstance(bankroll = b1) |
|
||||||
Session.testInstance(bankroll = b2) |
|
||||||
Session.testInstance(bankroll = b3) |
|
||||||
Session.testInstance(bankroll = b1) |
|
||||||
Session.testInstance(bankroll = b2) |
|
||||||
Session.testInstance(bankroll = b3) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyBankroll() |
|
||||||
val filterElementRow = QueryCondition.AnyBankroll().apply { setObject(b1) } |
|
||||||
val filterElementRow2 = QueryCondition.AnyBankroll().apply { setObject(b2) } |
|
||||||
|
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow, filterElementRow2), FilterSectionRow.Bankroll)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(6, sessions.size) |
|
||||||
|
|
||||||
val result = arrayListOf(b1.id, b2.id) |
|
||||||
|
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).bankroll?.id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testGameFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val g1 = realm.createObject(Game::class.java, "1") |
|
||||||
val g2 = realm.createObject(Game::class.java, "2") |
|
||||||
Session.testInstance(game = g1) |
|
||||||
Session.testInstance(game = g2) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val anyGame = QueryCondition.AnyGame(g2) |
|
||||||
val fc = FilterCondition(arrayListOf(anyGame), FilterSectionRow.Game) |
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(fc.queryCondition)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
(sessions[0] as Session).game?.run { |
|
||||||
Assert.assertEquals(g2.id, this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testMultipleGameFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val g1 = realm.createObject(Game::class.java, "1") |
|
||||||
val g2 = realm.createObject(Game::class.java, "2") |
|
||||||
val g3 = realm.createObject(Game::class.java, "3") |
|
||||||
Session.testInstance(game = g1) |
|
||||||
Session.testInstance(game = g2) |
|
||||||
Session.testInstance(game = g3) |
|
||||||
Session.testInstance(game = g1) |
|
||||||
Session.testInstance(game = g2) |
|
||||||
Session.testInstance(game = g3) |
|
||||||
Session.testInstance(game = g1) |
|
||||||
Session.testInstance(game = g2) |
|
||||||
Session.testInstance(game = g3) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filterElementRow = QueryCondition.AnyGame().apply { setObject(g2) } |
|
||||||
val filterElementRow2 = QueryCondition.AnyGame().apply { setObject(g3) } |
|
||||||
|
|
||||||
val filterCondition = FilterCondition(arrayListOf(filterElementRow, filterElementRow2), FilterSectionRow.Game) |
|
||||||
|
|
||||||
val queryCondition = filterCondition.queryCondition |
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(queryCondition)) |
|
||||||
|
|
||||||
Assert.assertEquals(6, sessions.size) |
|
||||||
|
|
||||||
val result = arrayListOf(g2.id, g3.id) |
|
||||||
|
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).game?.id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testLocationFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val l1 = realm.createObject(Location::class.java, "1") |
|
||||||
val l2 = realm.createObject(Location::class.java, "2") |
|
||||||
Session.testInstance(location = l1) |
|
||||||
Session.testInstance(location = l2) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyLocation() |
|
||||||
val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.Location)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
(sessions[0] as Session).location?.run { |
|
||||||
Assert.assertEquals(l1.id, this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testMultipleLocationFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val l1 = realm.createObject(Location::class.java, "1") |
|
||||||
val l2 = realm.createObject(Location::class.java, "2") |
|
||||||
val l3 = realm.createObject(Location::class.java, "3") |
|
||||||
Session.testInstance(location = l1) |
|
||||||
Session.testInstance(location = l2) |
|
||||||
Session.testInstance(location = l3) |
|
||||||
Session.testInstance(location = l1) |
|
||||||
Session.testInstance(location = l2) |
|
||||||
Session.testInstance(location = l3) |
|
||||||
Session.testInstance(location = l1) |
|
||||||
Session.testInstance(location = l2) |
|
||||||
Session.testInstance(location = l3) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyLocation() |
|
||||||
|
|
||||||
val filterElementRow = QueryCondition.AnyLocation().apply { setObject(l1) } |
|
||||||
val filterElementRow2 = QueryCondition.AnyLocation().apply { setObject(l3) } |
|
||||||
|
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow, filterElementRow2), FilterSectionRow.Location)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(6, sessions.size) |
|
||||||
|
|
||||||
val result = arrayListOf(l1.id, l3.id) |
|
||||||
|
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).location?.id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testTournamentNameFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val t1 = realm.createObject(TournamentName::class.java, "1") |
|
||||||
val t2 = realm.createObject(TournamentName::class.java, "2") |
|
||||||
Session.testInstance(tournamentName = t1) |
|
||||||
Session.testInstance(tournamentName = t2) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyTournamentName() |
|
||||||
|
|
||||||
val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.TournamentName)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
(sessions[0] as Session).tournamentName?.run { |
|
||||||
Assert.assertEquals(t1.id, this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testMultipleTournamentNameFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val t1 = realm.createObject(TournamentName::class.java, "1") |
|
||||||
val t2 = realm.createObject(TournamentName::class.java, "2") |
|
||||||
val t3 = realm.createObject(TournamentName::class.java, "3") |
|
||||||
Session.testInstance(tournamentName = t1) |
|
||||||
Session.testInstance(tournamentName = t2) |
|
||||||
Session.testInstance(tournamentName = t3) |
|
||||||
Session.testInstance(tournamentName = t1) |
|
||||||
Session.testInstance(tournamentName = t2) |
|
||||||
Session.testInstance(tournamentName = t3) |
|
||||||
Session.testInstance(tournamentName = t1) |
|
||||||
Session.testInstance(tournamentName = t2) |
|
||||||
Session.testInstance(tournamentName = t3) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyTournamentName() |
|
||||||
val filterElementRow = QueryCondition.AnyTournamentName().apply { setObject(t1) } |
|
||||||
val filterElementRow2 = QueryCondition.AnyTournamentName().apply { setObject(t2) } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow, filterElementRow2), FilterSectionRow.TournamentName)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(6, sessions.size) |
|
||||||
|
|
||||||
val result = arrayListOf(t1.id, t2.id) |
|
||||||
|
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).tournamentName?.id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testAllTournamentFeatureFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val t1 = realm.createObject(TournamentFeature::class.java, "1") |
|
||||||
val t2 = realm.createObject(TournamentFeature::class.java, "2") |
|
||||||
val t3 = realm.createObject(TournamentFeature::class.java, "3") |
|
||||||
val t4 = realm.createObject(TournamentFeature::class.java, "4") |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1,t2)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t2,t3)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t3,t4)) |
|
||||||
val s = Session.testInstance(tournamentFeatures = RealmList(t1,t2,t3,t4)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1,t4)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1,t3)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t2,t4, t3)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1)) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AllTournamentFeature() |
|
||||||
val filterElementRow = QueryCondition.AllTournamentFeature().apply { setObject(t1) } |
|
||||||
val filterElementRow2 = QueryCondition.AllTournamentFeature().apply { setObject(t2) } |
|
||||||
val filterElementRow3 = QueryCondition.AllTournamentFeature().apply { setObject(t4) } |
|
||||||
|
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow, filterElementRow2, filterElementRow3), FilterSectionRow.TournamentFeature)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
(sessions[0] as Session).run { |
|
||||||
Assert.assertEquals(s.id, this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testAnyTournamentFeatureFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val t1 = realm.createObject(TournamentFeature::class.java, "1") |
|
||||||
val t2 = realm.createObject(TournamentFeature::class.java, "2") |
|
||||||
val t3 = realm.createObject(TournamentFeature::class.java, "3") |
|
||||||
val t4 = realm.createObject(TournamentFeature::class.java, "4") |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1,t2)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t2,t3)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t3,t4)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1,t2,t3,t4)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1,t4)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1,t3)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t2,t4, t3)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1)) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyTournamentFeature() |
|
||||||
val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t1) } |
|
||||||
val filterElementRow2 = QueryCondition.AnyTournamentFeature().apply { setObject(t2) } |
|
||||||
val filterElementRow3 = QueryCondition.AnyTournamentFeature().apply { setObject(t3) } |
|
||||||
val filterElementRow4 = QueryCondition.AnyTournamentFeature().apply { setObject(t4) } |
|
||||||
|
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow, filterElementRow2, filterElementRow3, filterElementRow4), FilterSectionRow.TournamentFeature)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(8, sessions.size) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testSingleAnyTournamentFeatureFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val t1 = realm.createObject(TournamentFeature::class.java, "1") |
|
||||||
val t2 = realm.createObject(TournamentFeature::class.java, "2") |
|
||||||
val t3 = realm.createObject(TournamentFeature::class.java, "3") |
|
||||||
val t4 = realm.createObject(TournamentFeature::class.java, "4") |
|
||||||
val s1 = Session.testInstance(tournamentFeatures = RealmList(t1,t2)) |
|
||||||
val s2 = Session.testInstance(tournamentFeatures = RealmList(t2,t3)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t3,t4)) |
|
||||||
val s3 = Session.testInstance(tournamentFeatures = RealmList(t1,t2,t3,t4)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1,t4)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1,t3)) |
|
||||||
val s4 = Session.testInstance(tournamentFeatures = RealmList(t2,t4, t3)) |
|
||||||
Session.testInstance(tournamentFeatures = RealmList(t1)) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyTournamentFeature() |
|
||||||
val filterElementRow = QueryCondition.AnyTournamentFeature().apply { setObject(t2) } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.TournamentFeature)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
val result = arrayListOf(s1.id, s2.id, s3.id, s4.id) |
|
||||||
|
|
||||||
Assert.assertEquals(4, sessions.size) |
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testTableSizeFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = Session.testInstance(tableSize = 2) |
|
||||||
val s2 = Session.testInstance(tableSize = 4) |
|
||||||
Session.testInstance(tableSize = 9) |
|
||||||
Session.testInstance(tableSize = 10) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.AnyTableSize() |
|
||||||
val filterElementRow = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(2) } |
|
||||||
val filterElementRow2 = QueryCondition.AnyTableSize().apply { listOfValues = arrayListOf(4) } |
|
||||||
|
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow, filterElementRow2), FilterSectionRow.TableSize)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(2, sessions.size) |
|
||||||
|
|
||||||
val result = arrayListOf(s1.id, s2.id) |
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testMoreThanNetResultFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
Session.testInstance(netResult = 200.0) |
|
||||||
val s1 = Session.testInstance(netResult = 500.0) |
|
||||||
Session.testInstance(netResult = 50.0) |
|
||||||
val s2 = Session.testInstance(netResult = 570.0) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.NetAmountWon() |
|
||||||
val filterElementRow = QueryCondition.moreOrEqual<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(204.0) } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.Value)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filterElementRow)) |
|
||||||
|
|
||||||
Assert.assertEquals(2, sessions.size) |
|
||||||
|
|
||||||
val result = arrayListOf(s1.id, s2.id) |
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testLessThanNetResultFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = Session.testInstance(netResult = 200.0) |
|
||||||
val s2 = Session.testInstance(netResult = 500.0) |
|
||||||
val s3 = Session.testInstance(netResult = 50.0) |
|
||||||
Session.testInstance(netResult = 570.0) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filter = QueryCondition.NetAmountWon() |
|
||||||
val filterElementRow = QueryCondition.lessOrEqual<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(540.0) } |
|
||||||
filter.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.Value)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filter)) |
|
||||||
|
|
||||||
Assert.assertEquals(3, sessions.size) |
|
||||||
|
|
||||||
val result = arrayListOf(s1.id, s2.id, s3.id) |
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testNetResultFilter() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = Session.testInstance(netResult = 200.0) |
|
||||||
Session.testInstance(netResult = 500.0) |
|
||||||
Session.testInstance(netResult = 50.0) |
|
||||||
Session.testInstance(netResult = 570.0) |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filterMore = QueryCondition.NetAmountWon() |
|
||||||
val filterElementRow = QueryCondition.moreOrEqual<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(199.0) } |
|
||||||
filterMore.updateValueBy(FilterCondition(arrayListOf(filterElementRow), FilterSectionRow.Value)) |
|
||||||
|
|
||||||
val filterLess = QueryCondition.NetAmountWon() |
|
||||||
val filterElementRow2 = QueryCondition.lessOrEqual<QueryCondition.NetAmountWon>().apply { listOfValues = arrayListOf(400.0) } |
|
||||||
filterLess.updateValueBy(FilterCondition(arrayListOf(filterElementRow2), FilterSectionRow.Value)) |
|
||||||
|
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filterMore, filterLess)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
|
|
||||||
val result = arrayListOf(s1.id) |
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testNumberOfRebuys() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = Session.testInstance(netResult = 200.0, isTournament = true) |
|
||||||
s1.result!!.buyin = 50.0 |
|
||||||
s1.tournamentEntryFee = 10.0 |
|
||||||
val s2 = Session.testInstance(netResult = 50.0, isTournament = true) |
|
||||||
s2.result!!.buyin = 10.0 |
|
||||||
s2.tournamentEntryFee = 10.0 |
|
||||||
|
|
||||||
|
|
||||||
val s3 = Session.testInstance(netResult = 500.0) |
|
||||||
s3.cgBlinds = "2.0" |
|
||||||
s3.result!!.buyin = 1000.0 |
|
||||||
|
|
||||||
val s4 = Session.testInstance(netResult = 570.0) |
|
||||||
s4.cgBlinds = "5.0" |
|
||||||
s4.result!!.buyin = 200.0 |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filterMore = QueryCondition.NumberOfRebuy(QueryCondition.Operator.MORE, 4.0) |
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(filterMore)) |
|
||||||
|
|
||||||
Assert.assertEquals(2, sessions.size) |
|
||||||
|
|
||||||
val result = arrayListOf(s1.id, s3.id) |
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).id)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testTournamentFinalPosition() { |
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
val s1 = Session.testInstance(netResult = 200.0, isTournament = true) |
|
||||||
s1.result!!.tournamentFinalPosition = 5 |
|
||||||
|
|
||||||
val s2 = Session.testInstance(netResult = 50.0, isTournament = true) |
|
||||||
s2.result!!.tournamentFinalPosition = 15 |
|
||||||
|
|
||||||
val s3 = Session.testInstance(netResult = 500.0, isTournament = true) |
|
||||||
s3.result!!.tournamentFinalPosition = 2 |
|
||||||
|
|
||||||
val s4 = Session.testInstance(netResult = 570.0, isTournament = true) |
|
||||||
s4.result!!.tournamentFinalPosition = 1 |
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val filterLess = QueryCondition.TournamentFinalPosition(QueryCondition.Operator.LESS, finalPosition = 1) |
|
||||||
var sessions = Filter.queryOn<Session>(realm, Query(filterLess)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
|
|
||||||
var result = arrayListOf(s4.id) |
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).id)) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
val filterMore = QueryCondition.TournamentFinalPosition(QueryCondition.Operator.MORE, finalPosition = 10) |
|
||||||
sessions = Filter.queryOn(realm, Query(filterMore)) |
|
||||||
|
|
||||||
Assert.assertEquals(1, sessions.size) |
|
||||||
|
|
||||||
result = arrayListOf(s2.id) |
|
||||||
sessions.forEach { |
|
||||||
Assert.assertTrue(result.contains((it as Session).id)) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,55 +0,0 @@ |
|||||||
package net.pokeranalytics.android.unitTests.filter |
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|
||||||
import androidx.test.platform.app.InstrumentationRegistry |
|
||||||
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import net.pokeranalytics.android.model.realm.Filter |
|
||||||
import net.pokeranalytics.android.model.realm.Transaction |
|
||||||
import net.pokeranalytics.android.model.realm.TransactionType |
|
||||||
import org.junit.Assert |
|
||||||
import org.junit.Test |
|
||||||
import org.junit.runner.RunWith |
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class) |
|
||||||
class TransactionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() { |
|
||||||
|
|
||||||
@Test |
|
||||||
fun testTransactionTypeFilter() { |
|
||||||
val context = InstrumentationRegistry.getInstrumentation().context |
|
||||||
|
|
||||||
val realm = this.mockRealm |
|
||||||
realm.beginTransaction() |
|
||||||
TransactionType.Value.values().forEachIndexed { index, value -> |
|
||||||
val type = TransactionType() |
|
||||||
val name = "test" |
|
||||||
type.name = name |
|
||||||
type.additive = value.additive |
|
||||||
type.kind = value.uniqueIdentifier |
|
||||||
type.lock = true |
|
||||||
realm.insertOrUpdate(type) |
|
||||||
} |
|
||||||
|
|
||||||
val t1: Transaction = realm.createObject(Transaction::class.java, "1") |
|
||||||
t1.type = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm) |
|
||||||
val t2: Transaction = realm.createObject(Transaction::class.java, "2") |
|
||||||
t2.type = TransactionType.getByValue(TransactionType.Value.WITHDRAWAL, realm) |
|
||||||
|
|
||||||
val b1 = realm.createObject(Bankroll::class.java, "1") |
|
||||||
t1.bankroll = b1 |
|
||||||
|
|
||||||
val b2 = realm.createObject(Bankroll::class.java, "2") |
|
||||||
t2.bankroll = b2 |
|
||||||
|
|
||||||
realm.commitTransaction() |
|
||||||
|
|
||||||
val transactions = Filter.queryOn<Transaction>(realm, Query(QueryCondition.AnyTransactionType(t1.type!!))) |
|
||||||
|
|
||||||
Assert.assertEquals(1, transactions.size) |
|
||||||
transactions[0]?.run { |
|
||||||
Assert.assertEquals(t1.type!!.id, (this).type!!.id) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,7 +0,0 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||||
|
|
||||||
<!-- Add WRITE_EXTERNAL_STORAGE only for debug / test --> |
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
|
||||||
|
|
||||||
</manifest> |
|
||||||
@ -1,262 +1,36 @@ |
|||||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
xmlns:tools="http://schemas.android.com/tools"> |
package="net.pokeranalytics.android"> |
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" /> |
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" /> |
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> |
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> |
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> |
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera.any" android:required="false" /> |
|
||||||
<!-- <uses-feature android:name="android.hardware.camera" android:required="false" />--> |
|
||||||
|
|
||||||
<application |
<application |
||||||
android:name=".PokerAnalyticsApplication" |
|
||||||
android:allowBackup="true" |
android:allowBackup="true" |
||||||
android:icon="@mipmap/ic_launcher" |
android:icon="@mipmap/ic_launcher" |
||||||
android:label="@string/app_name" |
android:label="@string/app_name" |
||||||
android:roundIcon="@mipmap/ic_launcher_round" |
android:roundIcon="@mipmap/ic_launcher_round" |
||||||
android:supportsRtl="true" |
android:supportsRtl="true" |
||||||
android:largeHeap="true" |
android:name=".PokerAnalyticsApplication" |
||||||
android:theme="@style/PokerAnalyticsTheme"> |
android:theme="@style/PokerAnalyticsTheme"> |
||||||
|
|
||||||
<meta-data |
|
||||||
android:name="firebase_crashlytics_collection_enabled" |
|
||||||
android:value="true" /> |
|
||||||
|
|
||||||
<activity |
<activity |
||||||
android:name="net.pokeranalytics.android.ui.activity.HomeActivity" |
android:name=".ui.activity.HomeActivity" |
||||||
android:label="@string/app_name" |
android:label="@string/app_name"> |
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true"> |
|
||||||
<intent-filter> |
<intent-filter> |
||||||
<action android:name="android.intent.action.MAIN" /> |
<action android:name="android.intent.action.MAIN"/> |
||||||
<action android:name="android.intent.action.VIEW" /> |
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" /> |
<category android:name="android.intent.category.LAUNCHER"/> |
||||||
</intent-filter> |
</intent-filter> |
||||||
|
|
||||||
</activity> |
</activity> |
||||||
|
<activity android:name=".ui.activity.DataListActivity" /> |
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.ImportActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true"> |
|
||||||
|
|
||||||
<intent-filter tools:ignore="AppLinkUrlError"> |
|
||||||
<action android:name="android.intent.action.VIEW" /> |
|
||||||
<category android:name="android.intent.category.DEFAULT" /> |
|
||||||
|
|
||||||
<data android:scheme="file" /> |
|
||||||
<data android:scheme="content" /> |
|
||||||
<data android:mimeType="text/comma-separated-values" /> |
|
||||||
<data android:mimeType="text/csv" /> |
|
||||||
|
|
||||||
</intent-filter> |
|
||||||
|
|
||||||
</activity> |
|
||||||
|
|
||||||
<!-- DatabaseCopyActivity is only used in development for now --> |
|
||||||
|
|
||||||
<!-- <activity android:name=".ui.activity.DatabaseCopyActivity"--> |
|
||||||
<!-- android:launchMode="singleTop"--> |
|
||||||
<!-- android:screenOrientation="portrait"--> |
|
||||||
<!-- android:exported="true">--> |
|
||||||
|
|
||||||
<!-- <intent-filter>--> |
|
||||||
<!-- <action android:name="android.intent.action.VIEW" />--> |
|
||||||
<!-- <category android:name="android.intent.category.DEFAULT" />--> |
|
||||||
|
|
||||||
<!-- <data android:scheme="content" />--> |
|
||||||
<!-- <data android:scheme="file" />--> |
|
||||||
<!-- <data android:mimeType="*/*" />--> |
|
||||||
<!-- </intent-filter>--> |
|
||||||
|
|
||||||
<!-- </activity>--> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.session.SessionActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<!-- No screenOrientation="portrait" to fix Oreo crash --> |
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.feed.NewDataMenuActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:theme="@style/PokerAnalyticsTheme.MenuDialog" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.bankroll.BankrollActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.handhistory.HandHistoryActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:windowSoftInputMode="stateAlwaysHidden" |
|
||||||
android:exported="true"/> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.bankroll.BankrollDetailsActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.Top10Activity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.SettingsActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
<activity |
||||||
android:name="net.pokeranalytics.android.ui.activity.GraphActivity" |
android:name=".ui.activity.SessionActivity" |
||||||
android:launchMode="singleTop" |
android:launchMode="singleTop"/> |
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
<activity android:name=".ui.activity.EditableDataActivity"/> |
||||||
android:name="net.pokeranalytics.android.ui.activity.ProgressReportActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.ComparisonReportActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.calendar.CalendarDetailsActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.ComparisonChartActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.datalist.DataListActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.filter.FiltersListActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.data.EditableDataActivity" |
|
||||||
android:launchMode="standard" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.CurrenciesActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.filter.FiltersActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.GDPRActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.BillingActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.ReportCreationActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.TableReportActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.settings.DealtHandsPerHourActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.calendar.GridCalendarActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.settings.TransactionFilterActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<!-- No screenOrientation="portrait" to fix Oreo crash --> |
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.ColorPickerActivity" |
|
||||||
android:theme="@style/PokerAnalyticsTheme.AlertDialog" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:exported="true"/> |
|
||||||
|
|
||||||
<activity |
|
||||||
android:name="net.pokeranalytics.android.ui.activity.components.CameraActivity" |
|
||||||
android:launchMode="singleTop" |
|
||||||
android:screenOrientation="portrait" |
|
||||||
android:exported="true" /> |
|
||||||
|
|
||||||
<service |
|
||||||
android:name="net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayExportService" |
|
||||||
android:exported="false"/> |
|
||||||
|
|
||||||
<meta-data |
<meta-data |
||||||
android:name="preloaded_fonts" |
android:name="preloaded_fonts" |
||||||
android:resource="@array/preloaded_fonts" /> |
android:resource="@array/preloaded_fonts" /> |
||||||
|
|
||||||
<provider |
|
||||||
android:name="androidx.core.content.FileProvider" |
|
||||||
android:authorities="${applicationId}.fileprovider" |
|
||||||
android:exported="false" |
|
||||||
android:grantUriPermissions="true"> |
|
||||||
<meta-data |
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS" |
|
||||||
android:resource="@xml/provider_paths" /> |
|
||||||
</provider> |
|
||||||
|
|
||||||
</application> |
</application> |
||||||
|
|
||||||
</manifest> |
</manifest> |
||||||
|
Before Width: | Height: | Size: 161 KiB |
@ -1,75 +0,0 @@ |
|||||||
package net.pokeranalytics.android.api |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import kotlinx.coroutines.CoroutineScope |
|
||||||
import kotlinx.coroutines.Dispatchers |
|
||||||
import kotlinx.coroutines.async |
|
||||||
import net.pokeranalytics.android.util.CrashLogging |
|
||||||
import net.pokeranalytics.android.util.extensions.isNetworkAvailable |
|
||||||
import okhttp3.MediaType |
|
||||||
import okhttp3.MultipartBody |
|
||||||
import okhttp3.RequestBody |
|
||||||
import retrofit2.Call |
|
||||||
import retrofit2.Retrofit |
|
||||||
import retrofit2.http.Multipart |
|
||||||
import retrofit2.http.POST |
|
||||||
import retrofit2.http.Part |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
object RetrofitClient { |
|
||||||
private const val BASE_URL = "https://www.pokeranalytics.net/backup/" |
|
||||||
fun getClient(): Retrofit = |
|
||||||
Retrofit.Builder() |
|
||||||
.baseUrl(BASE_URL) |
|
||||||
.build() |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
class BackupService { |
|
||||||
private val retrofit = RetrofitClient.getClient() |
|
||||||
val backupApi: MyBackupApi = retrofit.create(MyBackupApi::class.java) |
|
||||||
} |
|
||||||
|
|
||||||
interface MyBackupApi { |
|
||||||
@Multipart |
|
||||||
@POST("send") |
|
||||||
fun postFile(@Part mail: MultipartBody.Part, @Part fileBody: MultipartBody.Part): Call<Void> |
|
||||||
} |
|
||||||
|
|
||||||
object BackupApi { |
|
||||||
|
|
||||||
private val service = BackupService() |
|
||||||
|
|
||||||
// curl -F recipient=laurent@staxriver.com -F file=@test.txt https://www.pokeranalytics.net/backup/send |
|
||||||
suspend fun backupFile(context: Context, mail: String, fileName: String, fileContent: String): Boolean { |
|
||||||
|
|
||||||
val filePart = MultipartBody.Part.createFormData( |
|
||||||
"file", |
|
||||||
fileName, |
|
||||||
RequestBody.create(MediaType.parse("text/csv"), fileContent) |
|
||||||
) |
|
||||||
|
|
||||||
val mailPart = MultipartBody.Part.createFormData("recipient", mail) |
|
||||||
|
|
||||||
return if (context.isNetworkAvailable()) { |
|
||||||
var success = false |
|
||||||
val job = CoroutineScope(context = Dispatchers.IO).async { |
|
||||||
success = try { |
|
||||||
val response = service.backupApi.postFile(mailPart, filePart).execute() |
|
||||||
Timber.d("response code = ${response.code()}") |
|
||||||
Timber.d("success = ${response.isSuccessful}") |
|
||||||
true |
|
||||||
} catch (e: Exception) { |
|
||||||
Timber.d("!!! backup failed: ${e.message}") |
|
||||||
CrashLogging.logException(e) |
|
||||||
false |
|
||||||
} |
|
||||||
} |
|
||||||
job.await() |
|
||||||
return success |
|
||||||
} else { |
|
||||||
false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,49 +0,0 @@ |
|||||||
package net.pokeranalytics.android.api |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import com.android.volley.Request |
|
||||||
import com.android.volley.toolbox.JsonArrayRequest |
|
||||||
import com.android.volley.toolbox.Volley |
|
||||||
import org.json.JSONArray |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
data class BlogPost(var id: Int, var content: String) |
|
||||||
|
|
||||||
private fun JSONArray.toBlogPosts(): List<BlogPost> { |
|
||||||
|
|
||||||
val posts = mutableListOf<BlogPost>() |
|
||||||
(0 until this.length()).forEach { index -> |
|
||||||
val jo = this.getJSONObject(index) |
|
||||||
val post = BlogPost(jo.getInt("id"), jo.getJSONObject("content").getString("rendered")) |
|
||||||
posts.add(post) |
|
||||||
} |
|
||||||
return posts |
|
||||||
} |
|
||||||
|
|
||||||
class BlogPostApi { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
private const val tipsLastPostsURL = "https://www.poker-analytics.net/blog/wp-json/wp/v2/posts/?categories=109\n" |
|
||||||
|
|
||||||
fun getLatestPosts(context: Context, callback: (List<BlogPost>) -> (Unit)) { |
|
||||||
|
|
||||||
val queue = Volley.newRequestQueue(context) |
|
||||||
|
|
||||||
val jsonObjectRequest = JsonArrayRequest( |
|
||||||
Request.Method.GET, tipsLastPostsURL, null, |
|
||||||
{ response -> |
|
||||||
// Timber.d("posts = $response") |
|
||||||
callback(response.toBlogPosts()) |
|
||||||
}, |
|
||||||
{ error -> |
|
||||||
Timber.w("Error while retrieving blog posts: $error") |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
queue.add(jsonObjectRequest) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,60 +0,0 @@ |
|||||||
package net.pokeranalytics.android.api |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import androidx.annotation.Keep |
|
||||||
import com.android.volley.VolleyError |
|
||||||
import com.android.volley.toolbox.StringRequest |
|
||||||
import com.android.volley.toolbox.Volley |
|
||||||
import kotlinx.serialization.Serializable |
|
||||||
import kotlinx.serialization.decodeFromString |
|
||||||
import kotlinx.serialization.json.Json |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
@Keep |
|
||||||
@Serializable |
|
||||||
data class RateResponse(var info: RateInfo) |
|
||||||
|
|
||||||
@Keep |
|
||||||
@Serializable |
|
||||||
data class RateInfo(var rate: Double) |
|
||||||
|
|
||||||
class CurrencyConverterApi { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
val json = Json { ignoreUnknownKeys = true } |
|
||||||
|
|
||||||
fun currencyRate(fromCurrency: String, toCurrency: String, context: Context, callback: (Double?, VolleyError?) -> (Unit)) { |
|
||||||
|
|
||||||
val queue = Volley.newRequestQueue(context) |
|
||||||
val url = "https://api.apilayer.com/exchangerates_data/convert?to=$toCurrency&from=$fromCurrency&amount=1" |
|
||||||
|
|
||||||
Timber.d("Api call = $url") |
|
||||||
|
|
||||||
val stringRequest = object : StringRequest( |
|
||||||
Method.GET, url, |
|
||||||
{ response -> |
|
||||||
|
|
||||||
val o = json.decodeFromString<RateResponse>(response) |
|
||||||
Timber.d("rate = ${o.info.rate}") |
|
||||||
callback(o.info.rate, null) |
|
||||||
}, |
|
||||||
{ |
|
||||||
Timber.d("Api call failed: ${it.message}") |
|
||||||
callback(null, it) |
|
||||||
}) { |
|
||||||
|
|
||||||
override fun getHeaders(): MutableMap<String, String> { |
|
||||||
val headers = HashMap<String, String>() |
|
||||||
headers["apikey"] = "XnfeyID3PMKd3k4zTPW0XmZAbcZlZgqH" |
|
||||||
return headers |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
queue.add(stringRequest) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,242 +0,0 @@ |
|||||||
package net.pokeranalytics.android.api |
|
||||||
|
|
||||||
import com.android.volley.* |
|
||||||
import com.android.volley.toolbox.HttpHeaderParser |
|
||||||
import java.io.* |
|
||||||
|
|
||||||
|
|
||||||
open class VolleyMultipartRequest : Request<NetworkResponse?> { |
|
||||||
|
|
||||||
private val twoHyphens = "--" |
|
||||||
private val lineEnd = "\r\n" |
|
||||||
private val boundary = "apiclient-" + System.currentTimeMillis() |
|
||||||
private var mListener: Response.Listener<NetworkResponse> |
|
||||||
private var mErrorListener: Response.ErrorListener |
|
||||||
private var mHeaders: Map<String, String>? = null |
|
||||||
private var byteData: Map<String, DataPart>? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Default constructor with predefined header and post method. |
|
||||||
* |
|
||||||
* @param url request destination |
|
||||||
* @param headers predefined custom header |
|
||||||
* @param listener on success achieved 200 code from request |
|
||||||
* @param errorListener on error http or library timeout |
|
||||||
*/ |
|
||||||
constructor( |
|
||||||
url: String?, headers: Map<String, String>?, |
|
||||||
byteData: Map<String, DataPart>, |
|
||||||
listener: Response.Listener<NetworkResponse>, |
|
||||||
errorListener: Response.ErrorListener |
|
||||||
) : super(Method.POST, url, errorListener) { |
|
||||||
mListener = listener |
|
||||||
this.mErrorListener = errorListener |
|
||||||
mHeaders = headers |
|
||||||
this.byteData = byteData |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor with option method and default header configuration. |
|
||||||
* |
|
||||||
* @param method method for now accept POST and GET only |
|
||||||
* @param url request destination |
|
||||||
* @param listener on success event handler |
|
||||||
* @param errorListener on error event handler |
|
||||||
*/ |
|
||||||
constructor( |
|
||||||
method: Int, url: String?, |
|
||||||
listener: Response.Listener<NetworkResponse>, |
|
||||||
errorListener: Response.ErrorListener |
|
||||||
) : super(method, url, errorListener) { |
|
||||||
mListener = listener |
|
||||||
this.mErrorListener = errorListener |
|
||||||
} |
|
||||||
|
|
||||||
@Throws(AuthFailureError::class) |
|
||||||
override fun getHeaders(): Map<String, String> { |
|
||||||
return if (mHeaders != null) mHeaders!! else super.getHeaders() |
|
||||||
} |
|
||||||
|
|
||||||
override fun getBodyContentType(): String { |
|
||||||
return "multipart/form-data;boundary=$boundary" |
|
||||||
} |
|
||||||
|
|
||||||
@Throws(AuthFailureError::class) |
|
||||||
override fun getBody(): ByteArray? { |
|
||||||
val bos = ByteArrayOutputStream() |
|
||||||
val dos = DataOutputStream(bos) |
|
||||||
try { |
|
||||||
// populate text payload |
|
||||||
val params = params |
|
||||||
if (params != null && params.isNotEmpty()) { |
|
||||||
textParse(dos, params, paramsEncoding) |
|
||||||
} |
|
||||||
|
|
||||||
// populate data byte payload |
|
||||||
val data = |
|
||||||
byteData |
|
||||||
if (data != null && data.isNotEmpty()) { |
|
||||||
dataParse(dos, data) |
|
||||||
} |
|
||||||
|
|
||||||
// close multipart form data after text and file data |
|
||||||
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd) |
|
||||||
return bos.toByteArray() |
|
||||||
} catch (e: IOException) { |
|
||||||
e.printStackTrace() |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
override fun parseNetworkResponse(response: NetworkResponse?): Response<NetworkResponse?> { |
|
||||||
return try { |
|
||||||
Response.success( |
|
||||||
response, |
|
||||||
HttpHeaderParser.parseCacheHeaders(response) |
|
||||||
) |
|
||||||
} catch (e: Exception) { |
|
||||||
Response.error(ParseError(e)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun deliverResponse(response: NetworkResponse?) { |
|
||||||
mListener.onResponse(response) |
|
||||||
} |
|
||||||
|
|
||||||
override fun deliverError(error: VolleyError?) { |
|
||||||
mErrorListener.onErrorResponse(error) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse string map into data output stream by key and value. |
|
||||||
* |
|
||||||
* @param dataOutputStream data output stream handle string parsing |
|
||||||
* @param params string inputs collection |
|
||||||
* @param encoding encode the inputs, default UTF-8 |
|
||||||
* @throws IOException |
|
||||||
*/ |
|
||||||
@Throws(IOException::class) |
|
||||||
private fun textParse( |
|
||||||
dataOutputStream: DataOutputStream, |
|
||||||
params: Map<String, String>, |
|
||||||
encoding: String |
|
||||||
) { |
|
||||||
try { |
|
||||||
for ((key, value) in params) { |
|
||||||
buildTextPart(dataOutputStream, key, value) |
|
||||||
} |
|
||||||
} catch (uee: UnsupportedEncodingException) { |
|
||||||
throw RuntimeException("Encoding not supported: $encoding", uee) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Parse data into data output stream. |
|
||||||
* |
|
||||||
* @param dataOutputStream data output stream handle file attachment |
|
||||||
* @param data loop through data |
|
||||||
* @throws IOException |
|
||||||
*/ |
|
||||||
@Throws(IOException::class) |
|
||||||
private fun dataParse(dataOutputStream: DataOutputStream, data: Map<String, DataPart>) { |
|
||||||
for ((key, value) in data) { |
|
||||||
buildDataPart(dataOutputStream, value, key) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Write string data into header and data output stream. |
|
||||||
* |
|
||||||
* @param dataOutputStream data output stream handle string parsing |
|
||||||
* @param parameterName name of input |
|
||||||
* @param parameterValue value of input |
|
||||||
* @throws IOException |
|
||||||
*/ |
|
||||||
@Throws(IOException::class) |
|
||||||
private fun buildTextPart( |
|
||||||
dataOutputStream: DataOutputStream, |
|
||||||
parameterName: String, |
|
||||||
parameterValue: String |
|
||||||
) { |
|
||||||
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd) |
|
||||||
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"$parameterName\"$lineEnd") |
|
||||||
//dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd); |
|
||||||
dataOutputStream.writeBytes(lineEnd) |
|
||||||
dataOutputStream.writeBytes(parameterValue + lineEnd) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Write data file into header and data output stream. |
|
||||||
* |
|
||||||
* @param dataOutputStream data output stream handle data parsing |
|
||||||
* @param dataFile data byte as DataPart from collection |
|
||||||
* @param inputName name of data input |
|
||||||
* @throws IOException |
|
||||||
*/ |
|
||||||
@Throws(IOException::class) |
|
||||||
private fun buildDataPart( |
|
||||||
dataOutputStream: DataOutputStream, |
|
||||||
dataFile: DataPart, |
|
||||||
inputName: String |
|
||||||
) { |
|
||||||
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd) |
|
||||||
dataOutputStream.writeBytes( |
|
||||||
"Content-Disposition: form-data; name=\"" + |
|
||||||
inputName + "\"; filename=\"" + dataFile.fileName + "\"" + lineEnd |
|
||||||
) |
|
||||||
if (dataFile.type != null && !dataFile.type!!.trim { it <= ' ' }.isEmpty()) { |
|
||||||
dataOutputStream.writeBytes("Content-Type: " + dataFile.type + lineEnd) |
|
||||||
} |
|
||||||
dataOutputStream.writeBytes(lineEnd) |
|
||||||
val fileInputStream = ByteArrayInputStream(dataFile.content) |
|
||||||
var bytesAvailable: Int = fileInputStream.available() |
|
||||||
val maxBufferSize = 1024 * 1024 |
|
||||||
var bufferSize = Math.min(bytesAvailable, maxBufferSize) |
|
||||||
val buffer = ByteArray(bufferSize) |
|
||||||
var bytesRead: Int = fileInputStream.read(buffer, 0, bufferSize) |
|
||||||
while (bytesRead > 0) { |
|
||||||
dataOutputStream.write(buffer, 0, bufferSize) |
|
||||||
bytesAvailable = fileInputStream.available() |
|
||||||
bufferSize = Math.min(bytesAvailable, maxBufferSize) |
|
||||||
bytesRead = fileInputStream.read(buffer, 0, bufferSize) |
|
||||||
} |
|
||||||
dataOutputStream.writeBytes(lineEnd) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Simple data container use for passing byte file |
|
||||||
*/ |
|
||||||
class DataPart { |
|
||||||
|
|
||||||
var fileName: String? = null |
|
||||||
|
|
||||||
var content: ByteArray? = null |
|
||||||
|
|
||||||
var type: String? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor with data. |
|
||||||
* |
|
||||||
* @param name label of data |
|
||||||
* @param data byte data |
|
||||||
*/ |
|
||||||
constructor(name: String?, data: ByteArray) { |
|
||||||
fileName = name |
|
||||||
content = data |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor with mime data type. |
|
||||||
* |
|
||||||
* @param name label of data |
|
||||||
* @param data byte data |
|
||||||
* @param mimeType mime data like "image/jpeg" |
|
||||||
*/ |
|
||||||
constructor(name: String?, data: ByteArray, mimeType: String?) { |
|
||||||
fileName = name |
|
||||||
content = data |
|
||||||
type = mimeType |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,32 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus |
|
||||||
|
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.model.Criteria |
|
||||||
import net.pokeranalytics.android.ui.graph.Graph |
|
||||||
|
|
||||||
enum class AggregationType { |
|
||||||
SESSION, |
|
||||||
MONTH, |
|
||||||
YEAR, |
|
||||||
DURATION; |
|
||||||
|
|
||||||
val resId: Int |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
SESSION -> R.string.session |
|
||||||
MONTH -> R.string.month |
|
||||||
YEAR -> R.string.year |
|
||||||
DURATION -> R.string.duration |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val criterias: List<Criteria> |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
MONTH -> listOf(Criteria.AllMonthsUpToNow) |
|
||||||
YEAR -> listOf(Criteria.Years) |
|
||||||
else -> listOf() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,12 @@ |
|||||||
|
package net.pokeranalytics.android.calculus |
||||||
|
|
||||||
|
class AggregationParameter<T> { |
||||||
|
var values: List<T>? = null |
||||||
|
} |
||||||
|
|
||||||
|
class Aggregator { |
||||||
|
|
||||||
|
var parameters: List<AggregationParameter<*>> = listOf() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
@ -1,637 +1,207 @@ |
|||||||
package net.pokeranalytics.android.calculus |
package net.pokeranalytics.android.calculus |
||||||
|
|
||||||
import android.content.Context |
|
||||||
import io.realm.Realm |
|
||||||
import net.pokeranalytics.android.calculus.Stat.* |
import net.pokeranalytics.android.calculus.Stat.* |
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
import net.pokeranalytics.android.model.realm.SessionSet |
||||||
import net.pokeranalytics.android.model.Criteria |
|
||||||
import net.pokeranalytics.android.model.combined |
|
||||||
import net.pokeranalytics.android.model.extensions.hourlyDuration |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.filter |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.util.extensions.startOfDay |
|
||||||
import java.util.* |
|
||||||
import kotlin.math.max |
|
||||||
import kotlin.math.min |
|
||||||
import kotlin.math.pow |
|
||||||
import kotlin.math.sqrt |
|
||||||
|
|
||||||
/** |
/** |
||||||
* The class performing statIds computation |
* The class performing stats computation |
||||||
*/ |
*/ |
||||||
class Calculator { |
class Calculator { |
||||||
|
|
||||||
/** |
/** |
||||||
* The options used for calculations and display |
* The options used for calculations or display |
||||||
*/ |
*/ |
||||||
class Options( |
class Options { |
||||||
var progressValues: ProgressValues = ProgressValues.NONE, |
|
||||||
var stats: List<Stat> = listOf(), |
|
||||||
var criterias: List<Criteria> = listOf(), |
|
||||||
var query: Query = Query(), |
|
||||||
var filterId: String? = null, |
|
||||||
private var aggregationType: AggregationType? = null, |
|
||||||
var userGenerated: Boolean = false, |
|
||||||
var reportSetupId: String? = null, |
|
||||||
var includedTransactions: List<TransactionType> = listOf() |
|
||||||
) { |
|
||||||
|
|
||||||
constructor( |
|
||||||
progressValues: ProgressValues = ProgressValues.NONE, |
|
||||||
stats: List<Stat> = listOf(), |
|
||||||
criterias: List<Criteria> = listOf(), |
|
||||||
filter: Filter? = null, |
|
||||||
aggregationType: AggregationType? = null, |
|
||||||
userGenerated: Boolean = false, |
|
||||||
reportSetupId: String? = null) : |
|
||||||
this(progressValues, stats, criterias, filter?.query ?: Query(), filter?.id, aggregationType, userGenerated, reportSetupId) |
|
||||||
|
|
||||||
/** |
/** |
||||||
* Specifies whether progress values should be added and their kind |
* The way the stats are going to be displayed |
||||||
*/ |
*/ |
||||||
// var progressValues: ProgressValues = progressValues |
enum class Display { |
||||||
// get() { |
TABLE, |
||||||
// if (field == ProgressValues.NONE && this.display.requireProgressValues) { |
EVOLUTION, |
||||||
// return ProgressValues.STANDARD |
COMPARISON, |
||||||
// } |
MAP, |
||||||
// return field |
POLYNOMIAL |
||||||
// } |
|
||||||
|
|
||||||
init { |
|
||||||
this.aggregationType?.let { |
|
||||||
this.criterias = it.criterias |
|
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
/** |
||||||
* The type of evolution numericValues |
* The type of evolution values |
||||||
*/ |
*/ |
||||||
enum class ProgressValues { |
enum class EvolutionValues { |
||||||
NONE, |
NONE, |
||||||
STANDARD, |
STANDARD, |
||||||
TIMED |
DATED |
||||||
} |
} |
||||||
|
|
||||||
|
var display: Display = Display.TABLE |
||||||
|
var evolutionValues: EvolutionValues = EvolutionValues.NONE |
||||||
|
var displayedStats: List<Stat> = listOf() |
||||||
|
|
||||||
/** |
/** |
||||||
* This function determines whether the standard deviation should be computed |
* This function determines whether the standard deviation should be computed |
||||||
*/ |
*/ |
||||||
val computeStandardDeviation: Boolean |
fun shouldComputeStandardDeviation() : Boolean { |
||||||
get() { |
this.displayedStats.forEach { stat -> |
||||||
this.stats.forEach { |
return when (stat) { |
||||||
if (it.isStandardDeviation) { |
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true |
||||||
return true |
else -> false |
||||||
} |
} |
||||||
} |
} |
||||||
return false |
return false |
||||||
} |
} |
||||||
|
|
||||||
/** |
// var aggregation: Aggregation? = null |
||||||
* Whether the longest streaks should be computed |
|
||||||
*/ |
|
||||||
val computeLongestStreak: Boolean |
|
||||||
get() { |
|
||||||
return this.stats.contains(LONGEST_STREAKS) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Whether the values should be sorted |
|
||||||
*/ |
|
||||||
val shouldSortValues: Boolean |
|
||||||
get() { |
|
||||||
return this.progressValues != ProgressValues.NONE || this.computeLongestStreak |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Whether the number of locations played should be computed |
|
||||||
*/ |
|
||||||
val computeLocationsPlayed: Boolean |
|
||||||
get() { |
|
||||||
return this.stats.contains(LOCATIONS_PLAYED) |
|
||||||
} |
|
||||||
/** |
|
||||||
* Whether the number of days played should be computed |
|
||||||
*/ |
|
||||||
val computeDaysPlayed: Boolean |
|
||||||
get() { |
|
||||||
return this.stats.contains(DAYS_PLAYED) |
|
||||||
} |
|
||||||
/** |
|
||||||
* Whether progress values should be managed at the group level |
|
||||||
*/ |
|
||||||
val shouldManageMultiGroupProgressValues: Boolean |
|
||||||
get() { |
|
||||||
return if (this.aggregationType != null) { |
|
||||||
this.aggregationType == AggregationType.MONTH || this.aggregationType == AggregationType.YEAR |
|
||||||
} else { |
|
||||||
false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns some default name |
|
||||||
*/ |
|
||||||
fun getName(context: Context): String { |
|
||||||
return when (this.stats.size) { |
|
||||||
1 -> this.stats.first().localizedTitle(context) |
|
||||||
else -> this.query.getName(context) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
companion object { |
companion object { |
||||||
|
|
||||||
fun computeStatsWithEvolutionByAggregationType( |
fun computePreAggregation(sets: List<SessionSet>, options: Options): List<ComputedResults> { |
||||||
realm: Realm, |
return listOf() |
||||||
stat: Stat, |
|
||||||
group: ComputableGroup, |
|
||||||
aggregationType: AggregationType, |
|
||||||
stats: List<Stat>? = null |
|
||||||
): Report { |
|
||||||
|
|
||||||
val options = Options( |
|
||||||
progressValues = Options.ProgressValues.STANDARD, |
|
||||||
stats = listOf(stat), |
|
||||||
aggregationType = aggregationType |
|
||||||
) |
|
||||||
|
|
||||||
if (aggregationType == AggregationType.DURATION) { |
|
||||||
options.progressValues = Options.ProgressValues.TIMED |
|
||||||
} |
|
||||||
|
|
||||||
stats?.let { |
|
||||||
options.stats = stats |
|
||||||
} |
|
||||||
|
|
||||||
return when (aggregationType) { |
|
||||||
AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(realm, listOf(group), options) |
|
||||||
AggregationType.MONTH, AggregationType.YEAR -> { |
|
||||||
this.computeStats(realm, options) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun computeStats(realm: Realm, options: Options = Options()): Report { |
|
||||||
|
|
||||||
val computableGroups: MutableList<ComputableGroup> = mutableListOf() |
|
||||||
|
|
||||||
val combinations = options.criterias.combined() |
|
||||||
// Timber.d("Combinations: ${ combinations.map { it.defaultName }}") |
|
||||||
|
|
||||||
for (comparatorQuery in combinations) { |
|
||||||
comparatorQuery.merge(options.query) |
|
||||||
val group = ComputableGroup(comparatorQuery) |
|
||||||
computableGroups.add(group) |
|
||||||
} |
|
||||||
|
|
||||||
if (computableGroups.size == 0) { |
|
||||||
val group = ComputableGroup(options.query) |
|
||||||
computableGroups.add(group) |
|
||||||
} |
|
||||||
|
|
||||||
return this.computeGroups(realm, computableGroups, options) |
|
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Computes all statIds for list of Session sessionGroup |
* Computes all stats for list of Session sessionGroup |
||||||
*/ |
*/ |
||||||
fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options = Options()): Report { |
fun computeGroups(groups: List<SessionGroup>, options: Options): List<ComputedResults> { |
||||||
|
|
||||||
val report = Report(options) |
var computedResults: MutableList<ComputedResults> = mutableListOf() |
||||||
groups.forEach { group -> |
groups.forEach { group -> |
||||||
|
// Computes actual sessionGroup stats |
||||||
// Clean existing computables / sessionSets if group is reused |
val results: ComputedResults = Calculator.compute(group, options = options) |
||||||
group.cleanup() |
|
||||||
|
|
||||||
// Computes actual sessionGroup statIds |
|
||||||
val results: ComputedResults = this.compute(realm, group, options) |
|
||||||
|
|
||||||
// Computes the compared sessionGroup if existing |
// Computes the compared sessionGroup if existing |
||||||
val comparedGroup = group.comparedGroup |
val comparedGroup = group.comparedSessions |
||||||
if (comparedGroup != null) { |
if (comparedGroup != null) { |
||||||
val comparedResults = this.compute(realm, comparedGroup, options) |
val comparedResults = Calculator.compute(comparedGroup, options = options) |
||||||
group.comparedComputedResults = comparedResults |
group.comparedComputedResults = comparedResults |
||||||
results.computeStatVariations(comparedResults) |
results.computeStatVariations(comparedResults) |
||||||
} |
} |
||||||
|
|
||||||
if (options.shouldManageMultiGroupProgressValues) { |
results.finalize(options) // later treatment, such as evolution values sorting |
||||||
group.comparedComputedResults = report.results.lastOrNull() |
computedResults.add(results) |
||||||
} |
} |
||||||
|
|
||||||
results.finalize() // later treatment, such as evolution numericValues sorting |
return computedResults |
||||||
report.addResults(results) |
|
||||||
|
|
||||||
// val e = Date() |
|
||||||
// val duration = (e.time - s.time) / 1000.0 |
|
||||||
// Timber.d(">>> group ${group.name} in $duration seconds") |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
return report |
|
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Computes statIds for a SessionSet |
* Computes stats for a SessionSet |
||||||
*/ |
*/ |
||||||
fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options = Options()): ComputedResults { |
fun compute(sessionGroup: SessionGroup, options: Options) : ComputedResults { |
||||||
|
|
||||||
val results = ComputedResults(computableGroup, options.shouldManageMultiGroupProgressValues) |
|
||||||
|
|
||||||
val computables = computableGroup.computables(realm, options.shouldSortValues) |
|
||||||
|
|
||||||
if (computables.size == 0) { // we don't want to return stats with 0 as a value when comparing best performances |
|
||||||
return results |
|
||||||
} |
|
||||||
|
|
||||||
// Timber.d("#### Start computing group, ${computables.size} computables") |
|
||||||
results.addStat(NUMBER_OF_GAMES, computables.size.toDouble()) |
|
||||||
// computables.forEach { |
|
||||||
// Timber.d("$$$ buyin = ${it.ratedBuyin} $$$ net result = ${it.ratedNet}") |
|
||||||
// } |
|
||||||
|
|
||||||
var ratedNet = computables.sum(ComputableResult.Field.RATED_NET.identifier).toDouble() |
|
||||||
if (options.includedTransactions.isNotEmpty()) { |
|
||||||
for (transactionType in options.includedTransactions) { |
|
||||||
val transactions = computableGroup.transactions(realm, transactionType, options.shouldSortValues) |
|
||||||
val transactionRatedAmount = transactions.sum(Transaction.Field.RATED_AMOUNT.identifier).toDouble() |
|
||||||
ratedNet += transactionRatedAmount |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
results.addStat(NET_RESULT, ratedNet) |
|
||||||
|
|
||||||
val totalHands = computables.sum(ComputableResult.Field.ESTIMATED_HANDS.identifier).toDouble() |
|
||||||
results.addStat(HANDS_PLAYED, totalHands) |
|
||||||
|
|
||||||
val bbSum = computables.sum(ComputableResult.Field.BB_NET.identifier).toDouble() |
|
||||||
results.addStat(BB_NET_RESULT, bbSum) |
|
||||||
|
|
||||||
val bbSessionCount = computables.sum(ComputableResult.Field.HAS_BIG_BLIND.identifier).toInt() |
|
||||||
results.addStat(BB_SESSION_COUNT, bbSessionCount.toDouble()) |
|
||||||
|
|
||||||
val winningSessionCount = computables.sum(ComputableResult.Field.IS_POSITIVE.identifier).toInt() |
|
||||||
results.addStat(WINNING_SESSION_COUNT, winningSessionCount.toDouble()) |
|
||||||
|
|
||||||
val totalBuyin = computables.sum(ComputableResult.Field.RATED_BUYIN.identifier).toDouble() |
|
||||||
results.addStat(TOTAL_BUYIN, totalBuyin) |
|
||||||
|
|
||||||
val totalTips = computables.sum(ComputableResult.Field.RATED_TIPS.identifier).toDouble() |
val sessions: List<SessionInterface> = sessionGroup.sessions |
||||||
results.addStat(TOTAL_TIPS, totalTips) |
var sessionSets = sessionGroup.sessions.mapNotNull { it.sessionSet }.toHashSet() |
||||||
|
|
||||||
// Timber.d("########## totalBuyin = ${totalBuyin} ### sum = ${sum}") |
var results: ComputedResults = ComputedResults() |
||||||
|
|
||||||
val maxNetResult = computables.max(ComputableResult.Field.RATED_NET.identifier)?.toDouble() |
var sum: Double = 0.0 |
||||||
maxNetResult?.let { |
var totalHands: Double = 0.0 |
||||||
results.addStat(MAXIMUM_NET_RESULT, it) |
var bbSum: Double = 0.0 |
||||||
} |
var bbSessionCount: Int = 0 |
||||||
|
var winningSessionCount: Int = 0 |
||||||
val minNetResult = computables.min(ComputableResult.Field.RATED_NET.identifier)?.toDouble() |
var totalBuyin: Double = 0.0 |
||||||
minNetResult?.let { |
|
||||||
results.addStat(MINIMUM_NET_RESULT, it) |
|
||||||
} |
|
||||||
|
|
||||||
Stat.netBBPer100Hands(bbSum, totalHands)?.let { netBB100 -> |
|
||||||
results.addStat(NET_BB_PER_100_HANDS, netBB100) |
|
||||||
} |
|
||||||
Stat.returnOnInvestment(ratedNet, totalBuyin)?.let { roi -> |
|
||||||
results.addStat(ROI, roi) |
|
||||||
} |
|
||||||
|
|
||||||
// val shouldComputeITMRatio = options.stats.contains(TOURNAMENT_ITM_RATIO) || computableGroup.displayedStats?.contains(TOURNAMENT_ITM_RATIO) == true |
|
||||||
// if (shouldComputeITMRatio) { |
|
||||||
// val itmCount = computables.count { it.session?.result?.cashout ?: 0.0 > 0.0 } // should we add a property inside ComputableResult for better performance? |
|
||||||
// val itmRatio = itmCount.toDouble() / computables.size.toDouble() |
|
||||||
// results.addStat(TOURNAMENT_ITM_RATIO, itmRatio) |
|
||||||
// } |
|
||||||
|
|
||||||
if (options.computeLocationsPlayed) { |
|
||||||
results.addStat(LOCATIONS_PLAYED, computables.distinctBy { it.session?.location?.id }.size.toDouble()) |
|
||||||
} |
|
||||||
|
|
||||||
var average = 0.0 // also used for standard deviation later |
|
||||||
if (computables.size > 0) { |
|
||||||
average = ratedNet / computables.size.toDouble() |
|
||||||
val winRatio = winningSessionCount.toDouble() / computables.size.toDouble() |
|
||||||
val itmRatio = winningSessionCount.toDouble() / computables.size.toDouble() |
|
||||||
val avgBuyin = totalBuyin / computables.size.toDouble() |
|
||||||
|
|
||||||
results.addStats( |
|
||||||
setOf( |
|
||||||
ComputedStat(AVERAGE, average), |
|
||||||
ComputedStat(WIN_RATIO, winRatio), |
|
||||||
ComputedStat(TOURNAMENT_ITM_RATIO, itmRatio), |
|
||||||
ComputedStat(AVERAGE_BUYIN, avgBuyin) |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
var averageBB = 0.0 |
|
||||||
if (bbSessionCount > 0) { |
|
||||||
averageBB = bbSum / bbSessionCount |
|
||||||
results.addStat(AVERAGE_NET_BB, averageBB) |
|
||||||
} |
|
||||||
|
|
||||||
val shouldIterateOverComputables = |
|
||||||
(options.progressValues == Options.ProgressValues.STANDARD || options.computeLongestStreak) |
|
||||||
|
|
||||||
// Computable Result |
|
||||||
if (shouldIterateOverComputables) { |
|
||||||
|
|
||||||
var index = 0 |
|
||||||
var tSum = 0.0 |
|
||||||
var tBBSum = 0.0 |
|
||||||
var tBBSessionCount = 0 |
|
||||||
var tWinningSessionCount = 0 |
|
||||||
var tBuyinSum = 0.0 |
|
||||||
var tHands = 0.0 |
|
||||||
var longestWinStreak = 0 |
|
||||||
var longestLoseStreak = 0 |
|
||||||
var currentStreak = 0 |
|
||||||
var tITMCount = 0 |
|
||||||
|
|
||||||
computables.forEach { computable -> |
|
||||||
index++ |
|
||||||
tSum += computable.ratedNet |
|
||||||
tBBSum += computable.bbNet |
|
||||||
tBBSessionCount += computable.hasBigBlind |
|
||||||
tWinningSessionCount += computable.isPositive |
|
||||||
tITMCount += computable.isPositive |
|
||||||
tBuyinSum += computable.ratedBuyin |
|
||||||
tHands += computable.estimatedHands |
|
||||||
|
|
||||||
if (computable.isPositive == 1) { // positive result |
|
||||||
if (currentStreak >= 0) { // currently positive streak |
|
||||||
currentStreak++ |
|
||||||
} else { // currently negative streak |
|
||||||
longestLoseStreak = min(longestLoseStreak, currentStreak) |
|
||||||
currentStreak = 1 |
|
||||||
} |
|
||||||
} else { // negative result |
|
||||||
if (currentStreak <= 0) { // currently negative streak |
|
||||||
currentStreak-- |
|
||||||
} else { // currently positive streak |
|
||||||
longestWinStreak = max(longestWinStreak, currentStreak) |
|
||||||
currentStreak = -1 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val session = |
// Compute for each session |
||||||
computable.session ?: throw PAIllegalStateException("Computing lone ComputableResult") |
var index: Int = 0 |
||||||
results.addEvolutionValue(tSum, stat = NET_RESULT, data = session) |
sessions.forEach { s -> |
||||||
results.addEvolutionValue(tSum / index, stat = AVERAGE, data = session) |
index++; |
||||||
results.addEvolutionValue(index.toDouble(), stat = NUMBER_OF_GAMES, data = session) |
|
||||||
results.addEvolutionValue(tBBSum / tBBSessionCount, stat = AVERAGE_NET_BB, data = session) |
|
||||||
results.addEvolutionValue(tBBSum, stat = BB_NET_RESULT, data = session) |
|
||||||
results.addEvolutionValue( |
|
||||||
(tWinningSessionCount.toDouble() / index.toDouble()), |
|
||||||
stat = WIN_RATIO, |
|
||||||
data = session |
|
||||||
) |
|
||||||
results.addEvolutionValue( |
|
||||||
tITMCount.toDouble() / index.toDouble(), |
|
||||||
stat = TOURNAMENT_ITM_RATIO, |
|
||||||
data = session) |
|
||||||
results.addEvolutionValue(tBuyinSum / index, stat = AVERAGE_BUYIN, data = session) |
|
||||||
results.addEvolutionValue(computable.ratedNet, stat = STANDARD_DEVIATION, data = session) |
|
||||||
|
|
||||||
Stat.netBBPer100Hands(tBBSum, tHands)?.let { netBB100 -> |
|
||||||
results.addEvolutionValue(netBB100, stat = NET_BB_PER_100_HANDS, data = session) |
|
||||||
} |
|
||||||
|
|
||||||
Stat.returnOnInvestment(tSum, tBuyinSum)?.let { roi -> |
sum += s.value |
||||||
results.addEvolutionValue(roi, stat = ROI, data = session) |
bbSum += s.bbNetResult |
||||||
|
bbSessionCount += s.bigBlindSessionCount |
||||||
|
if (s.value >= 0) { |
||||||
|
winningSessionCount++ |
||||||
} |
} |
||||||
|
totalBuyin += s.buyin |
||||||
|
totalHands += s.estimatedHands |
||||||
|
|
||||||
} |
if (options.evolutionValues == Options.EvolutionValues.STANDARD) { |
||||||
|
|
||||||
if (currentStreak >= 0) { |
results.addEvolutionValue(sum, NETRESULT) |
||||||
longestWinStreak = max(longestWinStreak, currentStreak) |
results.addEvolutionValue(sum / index, AVERAGE) |
||||||
} else { |
results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES) |
||||||
longestLoseStreak = min(longestLoseStreak, currentStreak) |
results.addEvolutionValue(bbSum / bbSessionCount, AVERAGE_NET_BB) |
||||||
|
results.addEvolutionValue(Stat.netBBPer100Hands(bbSum, totalHands), NET_BB_PER_100_HANDS) |
||||||
|
results.addEvolutionValue((winningSessionCount / index).toDouble(), WIN_RATIO) |
||||||
|
results.addEvolutionValue(totalBuyin / index, AVERAGE_BUYIN) |
||||||
|
results.addEvolutionValue(Stat.returnOnInvestment(sum, totalBuyin), ROI) |
||||||
} |
} |
||||||
|
|
||||||
// loseStreak is negative and we want it positive |
|
||||||
results.addStat(LONGEST_STREAKS, longestWinStreak.toDouble(), -longestLoseStreak.toDouble()) |
|
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
val sessionSets = computableGroup.sessionSets(realm, options.shouldSortValues) |
// Compute for each serie |
||||||
results.addStat(NUMBER_OF_SETS, sessionSets.size.toDouble()) |
var duration: Double = 0.0 |
||||||
|
var hourlyRate: Double = 0.0; var hourlyRateBB: Double = 0.0 |
||||||
var gHourlyDuration: Double? = null |
|
||||||
var gBBSum: Double? = null |
|
||||||
var maxDuration: Double? = null |
|
||||||
|
|
||||||
if (computableGroup.conditions.isEmpty()) { // SessionSets are fine |
|
||||||
gHourlyDuration = |
|
||||||
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours) |
|
||||||
gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble() |
|
||||||
|
|
||||||
sessionSets.max(SessionSet.Field.NET_DURATION.identifier)?.let { |
|
||||||
maxDuration = it.toDouble() / 3600000 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val shouldIterateOverSets = computableGroup.conditions.isNotEmpty() |
|
||||||
|| options.progressValues != Options.ProgressValues.NONE |
|
||||||
|| options.computeDaysPlayed |
|
||||||
|
|
||||||
// Session Set |
|
||||||
if (shouldIterateOverSets) { |
|
||||||
|
|
||||||
var tHourlyDuration = 0.0 |
|
||||||
var tIndex = 0 |
|
||||||
var tRatedNetSum = 0.0 |
|
||||||
var tBBSum = 0.0 |
|
||||||
var tTotalHands = 0.0 |
|
||||||
var tHourlyRate: Double |
|
||||||
var tHourlyRateBB: Double |
|
||||||
val daysSet = mutableSetOf<Date>() |
|
||||||
var tMaxDuration = 0.0 |
|
||||||
|
|
||||||
|
var gIndex = 0; var gSum = 0.0; var gTotalHands = 0.0; var gBBSum = 0.0; |
||||||
sessionSets.forEach { sessionSet -> |
sessionSets.forEach { sessionSet -> |
||||||
tIndex++ |
gIndex++ |
||||||
|
duration += sessionSet.hourlyDuration |
||||||
val setStats = SSStats(sessionSet, computableGroup.query) |
gSum += sessionSet.netResult |
||||||
|
gTotalHands += sessionSet.estimatedHands |
||||||
tRatedNetSum += setStats.ratedNet |
gBBSum += sessionSet.bbNetResult |
||||||
tBBSum += setStats.bbSum |
|
||||||
tHourlyDuration += setStats.hourlyDuration |
|
||||||
tTotalHands += setStats.estimatedHands |
|
||||||
tMaxDuration = max(tMaxDuration, setStats.hourlyDuration) |
|
||||||
|
|
||||||
tHourlyRate = tRatedNetSum / tHourlyDuration |
|
||||||
tHourlyRateBB = tBBSum / tHourlyDuration |
|
||||||
daysSet.add(sessionSet.startDate.startOfDay()) |
|
||||||
|
|
||||||
when (options.progressValues) { |
|
||||||
Options.ProgressValues.STANDARD -> { |
|
||||||
results.addEvolutionValue(tHourlyRate, stat = HOURLY_RATE, data = sessionSet) |
|
||||||
results.addEvolutionValue(tIndex.toDouble(), stat = NUMBER_OF_SETS, data = sessionSet) |
|
||||||
results.addEvolutionValue( |
|
||||||
sessionSet.hourlyDuration, |
|
||||||
tHourlyDuration, |
|
||||||
HOURLY_DURATION, |
|
||||||
sessionSet |
|
||||||
) |
|
||||||
results.addEvolutionValue( |
|
||||||
tHourlyDuration / tIndex, |
|
||||||
stat = AVERAGE_HOURLY_DURATION, |
|
||||||
data = sessionSet |
|
||||||
) |
|
||||||
results.addEvolutionValue(tHourlyRateBB, stat = HOURLY_RATE_BB, data = sessionSet) |
|
||||||
|
|
||||||
} |
hourlyRate = gSum / duration |
||||||
Options.ProgressValues.TIMED -> { |
hourlyRateBB = gBBSum / duration |
||||||
results.addEvolutionValue(tRatedNetSum, tHourlyDuration, NET_RESULT, sessionSet) |
|
||||||
results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet) |
|
||||||
results.addEvolutionValue( |
|
||||||
tIndex.toDouble(), |
|
||||||
tHourlyDuration, |
|
||||||
NUMBER_OF_SETS, |
|
||||||
sessionSet |
|
||||||
) |
|
||||||
results.addEvolutionValue( |
|
||||||
sessionSet.hourlyDuration, |
|
||||||
tHourlyDuration, |
|
||||||
HOURLY_DURATION, |
|
||||||
sessionSet |
|
||||||
) |
|
||||||
results.addEvolutionValue( |
|
||||||
tHourlyDuration / tIndex, |
|
||||||
tHourlyDuration, |
|
||||||
AVERAGE_HOURLY_DURATION, |
|
||||||
sessionSet |
|
||||||
) |
|
||||||
results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet) |
|
||||||
|
|
||||||
Stat.netBBPer100Hands(tBBSum, tTotalHands)?.let { netBB100 -> |
|
||||||
results.addEvolutionValue( |
|
||||||
netBB100, |
|
||||||
tHourlyDuration, |
|
||||||
NET_BB_PER_100_HANDS, |
|
||||||
sessionSet |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
else -> { |
|
||||||
// nothing |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
results.addStat(DAYS_PLAYED, daysSet.size.toDouble()) |
|
||||||
|
|
||||||
|
if (options.evolutionValues == Options.EvolutionValues.DATED) { |
||||||
|
results.addEvolutionValue(gSum, duration, NETRESULT) |
||||||
|
results.addEvolutionValue(gSum / duration, duration, HOURLY_RATE) |
||||||
|
results.addEvolutionValue(Stat.netBBPer100Hands(gBBSum, gTotalHands), duration, NET_BB_PER_100_HANDS) |
||||||
|
results.addEvolutionValue(hourlyRate, duration, HOURLY_RATE) |
||||||
|
results.addEvolutionValue(gIndex.toDouble(), duration, NUMBER_OF_SETS) |
||||||
|
results.addEvolutionValue(sessionSet.duration.toDouble(), duration, DURATION) |
||||||
|
results.addEvolutionValue(duration / gIndex, duration, AVERAGE_DURATION) |
||||||
|
results.addEvolutionValue(hourlyRateBB, duration, HOURLY_RATE_BB) |
||||||
} |
} |
||||||
|
|
||||||
gHourlyDuration = tHourlyDuration |
|
||||||
gBBSum = tBBSum |
|
||||||
maxDuration = tMaxDuration |
|
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
var hourlyRate = 0.0 |
val average: Double = sum / sessions.size.toDouble() |
||||||
if (gHourlyDuration != null) { |
|
||||||
|
|
||||||
hourlyRate = ratedNet / gHourlyDuration |
|
||||||
if (sessionSets.size > 0) { |
|
||||||
val avgDuration = gHourlyDuration / sessionSets.size |
|
||||||
results.addStat(HOURLY_RATE, hourlyRate) |
|
||||||
results.addStat(AVERAGE_HOURLY_DURATION, avgDuration) |
|
||||||
} |
|
||||||
results.addStat(HOURLY_DURATION, gHourlyDuration) |
|
||||||
} |
|
||||||
|
|
||||||
if (gBBSum != null) { |
|
||||||
if (gHourlyDuration != null) { |
|
||||||
results.addStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration) |
|
||||||
} |
|
||||||
results.addStat(AVERAGE_NET_BB, gBBSum / bbSessionCount) |
|
||||||
} |
|
||||||
|
|
||||||
maxDuration?.let { maxd -> |
|
||||||
results.addStat(MAXIMUM_DURATION, maxd) // (milliseconds to hours) |
|
||||||
} |
|
||||||
|
|
||||||
val bbPer100Hands = bbSum / totalHands * 100 |
// Create stats |
||||||
|
results.addStats(setOf( |
||||||
|
ComputedStat(NETRESULT, sum), |
||||||
|
ComputedStat(HOURLY_RATE, hourlyRate), |
||||||
|
ComputedStat(AVERAGE, average), |
||||||
|
ComputedStat(DURATION, duration), |
||||||
|
ComputedStat(NUMBER_OF_SETS, sessionSets.size.toDouble()), |
||||||
|
ComputedStat(NUMBER_OF_GAMES, sessions.size.toDouble()), |
||||||
|
ComputedStat(AVERAGE_DURATION, duration / sessions.size), |
||||||
|
ComputedStat(NET_BB_PER_100_HANDS, Stat.netBBPer100Hands(bbSum, totalHands)), |
||||||
|
ComputedStat(HOURLY_RATE_BB, bbSum / duration), |
||||||
|
ComputedStat(AVERAGE_NET_BB, bbSum / bbSessionCount), |
||||||
|
ComputedStat(WIN_RATIO, (winningSessionCount / sessions.size).toDouble()), |
||||||
|
ComputedStat(AVERAGE_BUYIN, totalBuyin / sessions.size), |
||||||
|
ComputedStat(ROI, Stat.returnOnInvestment(sum, totalBuyin)), |
||||||
|
ComputedStat(HANDS_PLAYED, totalHands) |
||||||
|
|
||||||
|
)) |
||||||
|
|
||||||
// Standard Deviation |
// Standard Deviation |
||||||
if (options.computeStandardDeviation) { |
if (options.shouldComputeStandardDeviation()) { |
||||||
|
|
||||||
// Session |
|
||||||
var stdSum = 0.0 |
|
||||||
var stdBBSum = 0.0 |
|
||||||
var stdBBper100HandsSum = 0.0 |
|
||||||
computables.forEach { session -> |
|
||||||
stdSum += (session.ratedNet - average).pow(2.0) |
|
||||||
stdBBSum += (session.bbNet - averageBB).pow(2.0) |
|
||||||
stdBBper100HandsSum += (session.bbPer100Hands - bbPer100Hands).pow(2.0) |
|
||||||
} |
|
||||||
val standardDeviation = sqrt(stdSum / computables.size) |
|
||||||
val standardDeviationBB = sqrt(stdBBSum / computables.size) |
|
||||||
val standardDeviationBBper100Hands = sqrt(stdBBper100HandsSum / computables.size) |
|
||||||
|
|
||||||
results.addStat(STANDARD_DEVIATION, standardDeviation) |
|
||||||
results.addStat(STANDARD_DEVIATION_BB, standardDeviationBB) |
|
||||||
results.addStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands) |
|
||||||
|
|
||||||
// Session Set |
|
||||||
if (gHourlyDuration != null) { |
|
||||||
var hourlyStdSum = 0.0 |
|
||||||
sessionSets.forEach { set -> |
|
||||||
val ssStats = SSStats(set, computableGroup.query) |
|
||||||
val sHourlyRate = ssStats.hourlyRate |
|
||||||
hourlyStdSum += (sHourlyRate - hourlyRate).pow(2.0) |
|
||||||
} |
|
||||||
val hourlyStandardDeviation = sqrt(hourlyStdSum / sessionSets.size) |
|
||||||
|
|
||||||
results.addStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) |
|
||||||
} |
|
||||||
|
|
||||||
|
var stdSum: Double = 0.0 |
||||||
|
sessions.forEach { s -> |
||||||
|
stdSum += Math.pow(s.value - average, 2.0) |
||||||
} |
} |
||||||
|
val standardDeviation: Double = Math.sqrt(stdSum / sessions.size) |
||||||
|
|
||||||
return results |
var hourlyStdSum: Double = 0.0 |
||||||
|
sessionSets.forEach { sg -> |
||||||
|
hourlyStdSum += Math.pow(sg.hourlyRate - hourlyRate, 2.0) |
||||||
} |
} |
||||||
|
val hourlyStandardDeviation: Double = Math.sqrt(hourlyStdSum / sessionSets.size) |
||||||
|
|
||||||
|
results.addStats(setOf( |
||||||
|
ComputedStat(STANDARD_DEVIATION, standardDeviation), |
||||||
|
ComputedStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) |
||||||
|
)) |
||||||
} |
} |
||||||
|
|
||||||
} |
return results |
||||||
|
|
||||||
class SSStats(sessionSet: SessionSet, query: Query) { // Session Set Stats |
|
||||||
|
|
||||||
var hourlyDuration: Double = 0.0 |
|
||||||
var estimatedHands: Double = 0.0 |
|
||||||
var bbSum: Double = 0.0 |
|
||||||
var ratedNet: Double = 0.0 |
|
||||||
|
|
||||||
val hourlyRate: Double |
|
||||||
get() { |
|
||||||
return this.ratedNet / this.hourlyDuration |
|
||||||
} |
} |
||||||
|
|
||||||
init { |
|
||||||
|
|
||||||
if (sessionSet.sessions?.size == 1) { // use precomputed values |
|
||||||
this.initStatsWithSet(sessionSet) |
|
||||||
} else { // dynamically filter and compute subset |
|
||||||
val setSessions = sessionSet.sessions!! |
|
||||||
val filteredSessions = setSessions.filter(query) |
|
||||||
if (setSessions.size == filteredSessions.size) { |
|
||||||
this.initStatsWithSet(sessionSet) |
|
||||||
} else { |
|
||||||
ratedNet = filteredSessions.sumOf { it.computableResult?.ratedNet ?: 0.0 } |
|
||||||
bbSum = filteredSessions.sumOf { it.bbNet } |
|
||||||
hourlyDuration = filteredSessions.hourlyDuration |
|
||||||
estimatedHands = filteredSessions.sumOf { it.estimatedHands } |
|
||||||
} |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
private fun initStatsWithSet(sessionSet: SessionSet) { |
|
||||||
ratedNet = sessionSet.ratedNet |
|
||||||
bbSum = sessionSet.bbNet |
|
||||||
hourlyDuration = sessionSet.hourlyDuration |
|
||||||
estimatedHands = sessionSet.estimatedHands |
|
||||||
} |
|
||||||
|
|
||||||
} |
} |
||||||
@ -0,0 +1,106 @@ |
|||||||
|
package net.pokeranalytics.android.calculus |
||||||
|
|
||||||
|
import net.pokeranalytics.android.model.realm.SessionSet |
||||||
|
|
||||||
|
/** |
||||||
|
* An interface to describe objects that can be summed |
||||||
|
*/ |
||||||
|
interface Summable { |
||||||
|
var value: Double |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* An interface describing some class that can be computed |
||||||
|
*/ |
||||||
|
interface SessionInterface : Summable { |
||||||
|
var sessionSet: SessionSet? |
||||||
|
var estimatedHands: Double |
||||||
|
var bbNetResult: Double |
||||||
|
var bigBlindSessionCount: Int // 0 or 1 |
||||||
|
var buyin: Double |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* A sessionGroup of computable items identified by a name |
||||||
|
*/ |
||||||
|
class SessionGroup(name: String, sessions: List<SessionInterface>) { |
||||||
|
|
||||||
|
var name: String = name |
||||||
|
var sessions: List<SessionInterface> = sessions |
||||||
|
|
||||||
|
// A subgroup used to compute stat variation |
||||||
|
var comparedSessions: SessionGroup? = null |
||||||
|
|
||||||
|
// The computed stats of the comparable sessionGroup |
||||||
|
var comparedComputedResults: ComputedResults? = null |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class ComputedResults() { |
||||||
|
|
||||||
|
// The computed stats of the sessionGroup |
||||||
|
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() |
||||||
|
|
||||||
|
// The map containing all evolution values for all stats |
||||||
|
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf() |
||||||
|
|
||||||
|
fun addEvolutionValue(value: Double, stat: Stat) { |
||||||
|
this._addEvolutionValue(Point(value), stat = stat) |
||||||
|
} |
||||||
|
|
||||||
|
fun addEvolutionValue(value: Double, duration: Double, stat: Stat) { |
||||||
|
this._addEvolutionValue(Point(value, y = duration), stat = stat) |
||||||
|
} |
||||||
|
|
||||||
|
private fun _addEvolutionValue(point: Point, stat: Stat) { |
||||||
|
var evolutionValues = this._evolutionValues[stat] |
||||||
|
if (evolutionValues != null) { |
||||||
|
evolutionValues.add(point) |
||||||
|
} else { |
||||||
|
var values: MutableList<Point> = mutableListOf(point) |
||||||
|
this._evolutionValues[stat] = values |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun addStats(computedStats: Set<ComputedStat>) { |
||||||
|
computedStats.forEach { |
||||||
|
this._computedStats[it.stat] = it |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun computedStat(stat: Stat) : ComputedStat? { |
||||||
|
return this._computedStats[stat] |
||||||
|
} |
||||||
|
|
||||||
|
fun computeStatVariations(resultsToCompare: ComputedResults) { |
||||||
|
this._computedStats.keys.forEach { stat -> |
||||||
|
var computedStat = this.computedStat(stat) |
||||||
|
val comparedStat = resultsToCompare.computedStat(stat) |
||||||
|
if (computedStat != null && comparedStat != null) { |
||||||
|
computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun finalize(options: Calculator.Options) { |
||||||
|
if (options.evolutionValues != Calculator.Options.EvolutionValues.NONE) { |
||||||
|
|
||||||
|
// Sort points as a distribution |
||||||
|
this._computedStats.keys.filter { it.hasDistributionSorting() }.forEach { stat -> |
||||||
|
// @todo sort |
||||||
|
// var evolutionValues = this._evolutionValues[stat] |
||||||
|
// evolutionValues.so |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class Point(x: Double, y: Double) { |
||||||
|
val x: Double = x |
||||||
|
val y: Double = y |
||||||
|
|
||||||
|
constructor(x: Double) : this(x, 1.0) |
||||||
|
|
||||||
|
} |
||||||
@ -1,111 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmResults |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* A sessionGroup of computable items identified by a name |
|
||||||
*/ |
|
||||||
class ComputableGroup(val query: Query, var displayedStats: List<Stat>? = null) { |
|
||||||
|
|
||||||
/** |
|
||||||
* A subgroup used to compute stat variation |
|
||||||
*/ |
|
||||||
var comparedGroup: ComputableGroup? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* The computed statIds of the comparable sessionGroup |
|
||||||
*/ |
|
||||||
var comparedComputedResults: ComputedResults? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* A list of _conditions to get |
|
||||||
*/ |
|
||||||
val conditions: List<QueryCondition> |
|
||||||
get() { |
|
||||||
return this.query.conditions |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The size of the retrieved computables list |
|
||||||
*/ |
|
||||||
var size: Int = 0 |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of endedSessions to compute |
|
||||||
*/ |
|
||||||
private var _computables: RealmResults<ComputableResult>? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Retrieves the computables on the relative [realm] filtered with the provided [conditions] |
|
||||||
*/ |
|
||||||
fun computables(realm: Realm, sorted: Boolean = false): RealmResults<ComputableResult> { |
|
||||||
|
|
||||||
// if computables exists and is valid (previous realm not closed) |
|
||||||
this._computables?.let { |
|
||||||
if (it.isValid) { |
|
||||||
return it |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val sortedField = if (sorted) "session.startDate" else null |
|
||||||
val computables = Filter.queryOn<ComputableResult>(realm, this.query, sortedField) |
|
||||||
|
|
||||||
this.size = computables.size |
|
||||||
this._computables = computables |
|
||||||
|
|
||||||
return computables |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of sets to compute |
|
||||||
*/ |
|
||||||
private var _sessionSets: RealmResults<SessionSet>? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Retrieves the session sets on the relative [realm] filtered with the provided [conditions] |
|
||||||
*/ |
|
||||||
fun sessionSets(realm: Realm, sorted: Boolean = false): RealmResults<SessionSet> { |
|
||||||
// if computables exists and is valid (previous realm not closed) |
|
||||||
this._sessionSets?.let { |
|
||||||
if (it.isValid) { |
|
||||||
return it |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val sortedField = if (sorted) SessionSet.Field.START_DATE.identifier else null |
|
||||||
val sets = Filter.queryOn<SessionSet>(realm, this.query, sortedField) |
|
||||||
this._sessionSets = sets |
|
||||||
return sets |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Retrieves the transactions on the relative [realm] filtered with the provided [conditions] |
|
||||||
*/ |
|
||||||
fun transactions(realm: Realm, transactionType: TransactionType, sorted: Boolean = false): RealmResults<Transaction> { |
|
||||||
val query = this.query.copy() |
|
||||||
query.add(QueryCondition.AnyTransactionType(transactionType)) |
|
||||||
val sortedField = if (sorted) "date" else null |
|
||||||
Timber.d("query = ${query.defaultName}") |
|
||||||
return Filter.queryOn(realm, query, sortedField) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Nullifies used Realm results |
|
||||||
*/ |
|
||||||
fun cleanup() { |
|
||||||
this._computables = null |
|
||||||
this._sessionSets = null |
|
||||||
} |
|
||||||
|
|
||||||
val isEmpty: Boolean |
|
||||||
get() { |
|
||||||
return this._computables?.isEmpty() ?: true |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,35 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus |
|
||||||
|
|
||||||
import net.pokeranalytics.android.util.TextFormat |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
/** |
|
||||||
* ComputedStat contains a [stat] and their associated [value] |
|
||||||
*/ |
|
||||||
class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? = null, var currency: Currency? = null) { |
|
||||||
|
|
||||||
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { |
|
||||||
if (previousValue != null) { |
|
||||||
this.variation = (value - previousValue) / previousValue |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The value used to get evolution dataset |
|
||||||
*/ |
|
||||||
var progressValue: Double? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* The variation of the stat |
|
||||||
*/ |
|
||||||
var variation: Double? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* Formats the value of the stat to be suitable for display |
|
||||||
*/ |
|
||||||
val textFormat: TextFormat |
|
||||||
get() { |
|
||||||
return this.stat.textFormat(this.value, this.secondValue, this.currency) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,8 @@ |
|||||||
|
package net.pokeranalytics.android.calculus |
||||||
|
|
||||||
|
import android.graphics.Color |
||||||
|
|
||||||
|
class StatFormat { |
||||||
|
var text: String = "" |
||||||
|
var color: Int = Color.BLUE |
||||||
|
} |
||||||
@ -1,300 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.model.interfaces.GraphIdentifiableEntry |
|
||||||
import net.pokeranalytics.android.ui.graph.Graph |
|
||||||
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry |
|
||||||
import net.pokeranalytics.android.ui.view.DefaultLegendValues |
|
||||||
import net.pokeranalytics.android.ui.view.LegendContent |
|
||||||
import net.pokeranalytics.android.util.TextFormat |
|
||||||
|
|
||||||
/** |
|
||||||
* The class returned after performing calculation in the Calculator object |
|
||||||
*/ |
|
||||||
class Report(var options: Calculator.Options) { |
|
||||||
|
|
||||||
/** |
|
||||||
* The mutable list of ComputedResults, one for each group of data |
|
||||||
*/ |
|
||||||
private var _results: MutableList<ComputedResults> = mutableListOf() |
|
||||||
|
|
||||||
val results: List<ComputedResults> |
|
||||||
get() { |
|
||||||
return this._results |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a new result to the list of ComputedResults |
|
||||||
*/ |
|
||||||
fun addResults(result: ComputedResults) { |
|
||||||
this._results.add(result) |
|
||||||
} |
|
||||||
|
|
||||||
fun max(stat: Stat): ComputedResults? { |
|
||||||
|
|
||||||
var computedResults: ComputedResults? = null |
|
||||||
var count = 0 |
|
||||||
var max = Double.MIN_VALUE |
|
||||||
for (cr in this._results) { |
|
||||||
cr.computedStat(stat)?.value?.let { value -> |
|
||||||
count += 1 |
|
||||||
if (value > max) { |
|
||||||
computedResults = cr |
|
||||||
max = value |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return if (count >= 2) { computedResults } else { null } |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
class Point(val x: Double, val y: Double, val data: Any) { |
|
||||||
constructor(y: Double, data: Any) : this(0.0, y, data) |
|
||||||
} |
|
||||||
|
|
||||||
class ComputedResults(group: ComputableGroup, |
|
||||||
shouldManageMultiGroupProgressValues: Boolean = false) : GraphUnderlyingEntry { |
|
||||||
|
|
||||||
/** |
|
||||||
* The session group used to computed the statIds |
|
||||||
*/ |
|
||||||
var group: ComputableGroup = group |
|
||||||
|
|
||||||
// The computed statIds of the sessionGroup |
|
||||||
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() |
|
||||||
|
|
||||||
// The map containing all evolution numericValues for all statIds |
|
||||||
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf() |
|
||||||
|
|
||||||
private var shouldManageMultiGroupProgressValues = shouldManageMultiGroupProgressValues |
|
||||||
|
|
||||||
private val allStats: Collection<ComputedStat> |
|
||||||
get() { return this._computedStats.values } |
|
||||||
|
|
||||||
val evolutionValues: Map<Stat, MutableList<Point>> |
|
||||||
get() { |
|
||||||
return this._evolutionValues |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a value to the evolution values |
|
||||||
*/ |
|
||||||
fun addEvolutionValue(value: Double, duration: Double? = null, stat: Stat, data: GraphIdentifiableEntry) { |
|
||||||
|
|
||||||
val point = if (duration != null) { |
|
||||||
Point(duration, y = value, data = data.objectIdentifier) |
|
||||||
} else { |
|
||||||
Point(value, data = data.objectIdentifier) |
|
||||||
} |
|
||||||
this.addEvolutionValue(point, stat = stat) |
|
||||||
} |
|
||||||
|
|
||||||
private fun addEvolutionValue(point: Point, stat: Stat) { |
|
||||||
val evolutionValues = this._evolutionValues[stat] |
|
||||||
if (evolutionValues != null) { |
|
||||||
evolutionValues.add(point) |
|
||||||
} else { |
|
||||||
val values: MutableList<Point> = mutableListOf(point) |
|
||||||
this._evolutionValues[stat] = values |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun addStat(stat: Stat, value: Double, secondValue: Double? = null) { |
|
||||||
val computedStat = ComputedStat(stat, value, secondValue = secondValue) |
|
||||||
this.addComputedStat(computedStat) |
|
||||||
} |
|
||||||
|
|
||||||
fun addStats(computedStats: Set<ComputedStat>) { |
|
||||||
computedStats.forEach { |
|
||||||
this.addComputedStat(it) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a [computedStat] to the list of statIds |
|
||||||
* Also computes evolution values using the previously computed values |
|
||||||
*/ |
|
||||||
private fun addComputedStat(computedStat: ComputedStat) { |
|
||||||
this._computedStats[computedStat.stat] = computedStat |
|
||||||
} |
|
||||||
|
|
||||||
private fun consolidateProgressStats() { |
|
||||||
|
|
||||||
if (this.shouldManageMultiGroupProgressValues) { |
|
||||||
|
|
||||||
fun computeProgressValues(computedResults: ComputedResults) { |
|
||||||
|
|
||||||
this.allStats.forEach { computedStat -> |
|
||||||
val stat = computedStat.stat |
|
||||||
computedResults.computedStat(stat)?.let { previousComputedStat -> |
|
||||||
when (stat) { |
|
||||||
Stat.NET_RESULT, Stat.HOURLY_DURATION, Stat.BB_NET_RESULT, Stat.BB_SESSION_COUNT, |
|
||||||
Stat.WINNING_SESSION_COUNT, Stat.TOTAL_BUYIN, Stat.HANDS_PLAYED, Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> { |
|
||||||
val previousValue = previousComputedStat.progressValue ?: 0.0 |
|
||||||
computedStat.progressValue = previousValue + computedStat.value |
|
||||||
} |
|
||||||
else -> { |
|
||||||
} |
|
||||||
} |
|
||||||
} ?: run { |
|
||||||
computedStat.progressValue = computedStat.value |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
this.group.comparedComputedResults?.let { previousResult -> |
|
||||||
computeProgressValues(previousResult) |
|
||||||
} ?: run { |
|
||||||
computeProgressValues(this) |
|
||||||
} |
|
||||||
|
|
||||||
val netResult = this.computedStat(Stat.NET_RESULT)?.progressValue |
|
||||||
val bbNetResult = this.computedStat(Stat.BB_NET_RESULT)?.progressValue |
|
||||||
val duration = this.computedStat(Stat.HOURLY_DURATION)?.progressValue |
|
||||||
val numberOfGames = this.computedStat(Stat.NUMBER_OF_GAMES)?.progressValue |
|
||||||
val numberOfSets = this.computedStat(Stat.NUMBER_OF_SETS)?.progressValue |
|
||||||
val handsPlayed = this.computedStat(Stat.HANDS_PLAYED)?.progressValue |
|
||||||
val winningCount = this.computedStat(Stat.WINNING_SESSION_COUNT)?.progressValue |
|
||||||
val bbSessionCount = this.computedStat(Stat.BB_SESSION_COUNT)?.progressValue |
|
||||||
val totalBuyin = this.computedStat(Stat.TOTAL_BUYIN)?.progressValue |
|
||||||
|
|
||||||
this.allStats.forEach { computedStat -> |
|
||||||
when (computedStat.stat) { |
|
||||||
Stat.HOURLY_RATE -> { |
|
||||||
if (netResult != null && duration != null) { |
|
||||||
computedStat.progressValue = netResult / duration |
|
||||||
} |
|
||||||
} |
|
||||||
Stat.AVERAGE -> { |
|
||||||
if (netResult != null && numberOfGames != null) { |
|
||||||
computedStat.progressValue = netResult / numberOfGames |
|
||||||
} |
|
||||||
} |
|
||||||
Stat.AVERAGE_HOURLY_DURATION -> { |
|
||||||
if (duration != null && numberOfSets != null) { |
|
||||||
computedStat.progressValue = duration / numberOfSets |
|
||||||
} |
|
||||||
} |
|
||||||
Stat.NET_BB_PER_100_HANDS -> { |
|
||||||
if (bbNetResult != null && handsPlayed != null) { |
|
||||||
computedStat.progressValue = Stat.netBBPer100Hands(bbNetResult, handsPlayed) |
|
||||||
} |
|
||||||
} |
|
||||||
Stat.HOURLY_RATE_BB -> { |
|
||||||
if (bbNetResult != null && duration != null) { |
|
||||||
computedStat.progressValue = bbNetResult / duration |
|
||||||
} |
|
||||||
} |
|
||||||
Stat.AVERAGE_NET_BB -> { |
|
||||||
if (bbNetResult != null && bbSessionCount != null) { |
|
||||||
computedStat.progressValue = bbNetResult / bbSessionCount |
|
||||||
} |
|
||||||
} |
|
||||||
Stat.WIN_RATIO -> { |
|
||||||
if (winningCount != null && numberOfGames != null) { |
|
||||||
computedStat.progressValue = winningCount / numberOfGames |
|
||||||
} |
|
||||||
} |
|
||||||
Stat.AVERAGE_BUYIN -> { |
|
||||||
if (totalBuyin != null && numberOfGames != null) { |
|
||||||
computedStat.progressValue = totalBuyin / numberOfGames |
|
||||||
} |
|
||||||
} |
|
||||||
Stat.ROI -> { |
|
||||||
if (totalBuyin != null && netResult != null) { |
|
||||||
computedStat.progressValue = Stat.returnOnInvestment(netResult, totalBuyin) |
|
||||||
} |
|
||||||
} |
|
||||||
else -> { |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
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() { |
|
||||||
this.consolidateProgressStats() |
|
||||||
} |
|
||||||
|
|
||||||
val isEmpty: Boolean |
|
||||||
get() { |
|
||||||
return this.group.isEmpty |
|
||||||
} |
|
||||||
|
|
||||||
// Stat Entry |
|
||||||
|
|
||||||
override fun entryTitle(context: Context): String { |
|
||||||
return this.group.query.getName(context) |
|
||||||
} |
|
||||||
|
|
||||||
override fun formattedValue(stat: Stat): TextFormat { |
|
||||||
this.computedStat(stat)?.let { |
|
||||||
return it.textFormat |
|
||||||
} ?: run { |
|
||||||
throw PAIllegalStateException("Missing stat in results") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun legendValues( |
|
||||||
stat: Stat, |
|
||||||
total: Double, |
|
||||||
style: Graph.Style, |
|
||||||
groupName: String, |
|
||||||
context: Context |
|
||||||
): LegendContent { |
|
||||||
|
|
||||||
when (style) { |
|
||||||
|
|
||||||
Graph.Style.BAR -> { |
|
||||||
return when (stat) { |
|
||||||
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { |
|
||||||
val totalStatValue = stat.textFormat(total, currency = null) |
|
||||||
DefaultLegendValues(this.entryTitle(context), totalStatValue) |
|
||||||
} |
|
||||||
else -> { |
|
||||||
val entryValue = this.formattedValue(stat) |
|
||||||
val countValue = this.computedStat(Stat.NUMBER_OF_GAMES)?.textFormat |
|
||||||
DefaultLegendValues(this.entryTitle(context), entryValue, countValue) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else -> { |
|
||||||
|
|
||||||
return when (stat) { |
|
||||||
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { |
|
||||||
val totalStatValue = stat.textFormat(total, currency = null) |
|
||||||
DefaultLegendValues(this.entryTitle(context), totalStatValue) |
|
||||||
} |
|
||||||
else -> { |
|
||||||
val entryValue = this.formattedValue(stat) |
|
||||||
val totalStatValue = stat.textFormat(total, currency = null) |
|
||||||
DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,338 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import android.os.CountDownTimer |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmQuery |
|
||||||
import io.realm.RealmResults |
|
||||||
import kotlinx.coroutines.CoroutineScope |
|
||||||
import kotlinx.coroutines.Dispatchers |
|
||||||
import kotlinx.coroutines.launch |
|
||||||
import net.pokeranalytics.android.calculus.optimalduration.CashGameOptimalDurationCalculator |
|
||||||
import net.pokeranalytics.android.model.LiveOnline |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.ui.view.rows.StaticReport |
|
||||||
import net.pokeranalytics.android.util.CrashLogging |
|
||||||
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration |
|
||||||
import timber.log.Timber |
|
||||||
import kotlin.coroutines.CoroutineContext |
|
||||||
|
|
||||||
|
|
||||||
interface NewPerformanceListener { |
|
||||||
fun newBestPerformanceHandler() |
|
||||||
} |
|
||||||
|
|
||||||
class ReportWhistleBlower(var context: Context) { |
|
||||||
|
|
||||||
private var sessions: RealmResults<Session>? = null |
|
||||||
private var results: RealmResults<Result>? = null |
|
||||||
|
|
||||||
private var currentTask: ReportTask? = null |
|
||||||
|
|
||||||
private val currentNotifications: MutableList<String> = mutableListOf() // Performance.id |
|
||||||
|
|
||||||
private val listeners: MutableList<NewPerformanceListener> = mutableListOf() |
|
||||||
|
|
||||||
var paused: Boolean = false |
|
||||||
|
|
||||||
private var timer: CountDownTimer? = null |
|
||||||
|
|
||||||
init { |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
|
|
||||||
this.sessions = realm.where(Session::class.java).findAll() |
|
||||||
this.sessions?.addChangeListener { _ -> |
|
||||||
requestReportLaunch() |
|
||||||
} |
|
||||||
|
|
||||||
this.results = realm.where(Result::class.java).findAll() |
|
||||||
this.results?.addChangeListener { _ -> |
|
||||||
requestReportLaunch() |
|
||||||
} |
|
||||||
|
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
fun addListener(newPerformanceListener: NewPerformanceListener) { |
|
||||||
this.listeners.add(newPerformanceListener) |
|
||||||
} |
|
||||||
|
|
||||||
fun removeListener(listener: NewPerformanceListener) { |
|
||||||
this.listeners.remove(listener) |
|
||||||
} |
|
||||||
|
|
||||||
fun requestReportLaunch() { |
|
||||||
// Timber.d(">>> Launch report") |
|
||||||
|
|
||||||
if (paused) { |
|
||||||
CrashLogging.log("can't start reports comparisons because of paused state") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
this.timer?.cancel() |
|
||||||
|
|
||||||
val launchStart = 100L |
|
||||||
val timer = object: CountDownTimer(launchStart, launchStart) { |
|
||||||
override fun onTick(p0: Long) { } |
|
||||||
|
|
||||||
override fun onFinish() { |
|
||||||
launchReportTask() |
|
||||||
} |
|
||||||
} |
|
||||||
this.timer = timer |
|
||||||
timer.start() |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun launchReportTask() { |
|
||||||
|
|
||||||
synchronized(this) { |
|
||||||
this.currentTask?.cancel() |
|
||||||
|
|
||||||
val reportTask = ReportTask(this, this.context) |
|
||||||
this.currentTask = reportTask |
|
||||||
reportTask.start() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Pauses the whistleblower, for example when importing data |
|
||||||
*/ |
|
||||||
fun pause() { |
|
||||||
this.paused = true |
|
||||||
this.currentTask?.cancel() |
|
||||||
this.currentTask = null |
|
||||||
} |
|
||||||
|
|
||||||
fun resume() { |
|
||||||
this.paused = false |
|
||||||
this.requestReportLaunch() |
|
||||||
} |
|
||||||
|
|
||||||
fun has(performanceId: String): Boolean { |
|
||||||
return this.currentNotifications.contains(performanceId) |
|
||||||
} |
|
||||||
|
|
||||||
fun notify(performance: Performance) { |
|
||||||
|
|
||||||
this.currentNotifications.add(performance.id) |
|
||||||
for (listener in this.listeners) { |
|
||||||
listener.newBestPerformanceHandler() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun clearNotifications() { |
|
||||||
this.currentNotifications.clear() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Context) { |
|
||||||
|
|
||||||
private var cancelled = false |
|
||||||
|
|
||||||
var handler: (() -> Unit)? = null |
|
||||||
|
|
||||||
private val coroutineContext: CoroutineContext |
|
||||||
get() = Dispatchers.Default |
|
||||||
|
|
||||||
fun start() { |
|
||||||
messages.add("Starting task...") |
|
||||||
launchReports() |
|
||||||
} |
|
||||||
|
|
||||||
fun cancel() { |
|
||||||
this.cancelled = true |
|
||||||
} |
|
||||||
|
|
||||||
var messages: MutableList<String> = mutableListOf() |
|
||||||
|
|
||||||
private fun launchReports() { |
|
||||||
CoroutineScope(coroutineContext).launch { |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
|
|
||||||
// Basic |
|
||||||
for (basicReport in StaticReport.basicReports) { |
|
||||||
if (cancelled) { |
|
||||||
break |
|
||||||
} |
|
||||||
launchReport(realm, basicReport) |
|
||||||
} |
|
||||||
|
|
||||||
// CustomField |
|
||||||
val customFields = realm.where(CustomField::class.java) |
|
||||||
.equalTo("type", CustomField.Type.LIST.uniqueIdentifier).findAll() |
|
||||||
for (customField in customFields) { |
|
||||||
if (cancelled) { |
|
||||||
break |
|
||||||
} |
|
||||||
launchReport(realm, StaticReport.CustomFieldList(customField)) |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun launchReport(realm: Realm, report: StaticReport) { |
|
||||||
|
|
||||||
// Timber.d(">>> launch report = $report") |
|
||||||
|
|
||||||
when (report) { |
|
||||||
StaticReport.OptimalDuration -> launchOptimalDuration(realm, report) |
|
||||||
else -> launchDefaultReport(realm, report) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun launchDefaultReport(realm: Realm, report: StaticReport) { |
|
||||||
val options = Calculator.Options( |
|
||||||
stats = report.stats, |
|
||||||
criterias = report.criteria |
|
||||||
) |
|
||||||
|
|
||||||
val result = Calculator.computeStats(realm, options = options) |
|
||||||
analyseDefaultReport(realm, report, result) |
|
||||||
} |
|
||||||
|
|
||||||
private fun launchOptimalDuration(realm: Realm, report: StaticReport) { |
|
||||||
LiveOnline.entries.forEach { key -> |
|
||||||
val duration = CashGameOptimalDurationCalculator.start(key.isLive) |
|
||||||
analyseOptimalDuration(realm, report, key, duration) |
|
||||||
} |
|
||||||
|
|
||||||
this.handler?.let { it() } |
|
||||||
} |
|
||||||
|
|
||||||
private fun analyseDefaultReport(realm: Realm, staticReport: StaticReport, result: Report) { |
|
||||||
|
|
||||||
messages.add("Analyse report $staticReport...") |
|
||||||
|
|
||||||
val nameSeparator = " " |
|
||||||
|
|
||||||
for (stat in result.options.stats) { |
|
||||||
|
|
||||||
// Timber.d("analyse stat: $stat for report: $staticReport") |
|
||||||
|
|
||||||
// Get current performance |
|
||||||
var query = performancesQuery(realm, staticReport, stat) |
|
||||||
|
|
||||||
val customField: CustomField? = |
|
||||||
(staticReport as? StaticReport.CustomFieldList)?.customField |
|
||||||
customField?.let { |
|
||||||
query = query.equalTo("customFieldId", it.id) |
|
||||||
} |
|
||||||
val currentPerf = query.findFirst() |
|
||||||
|
|
||||||
// Store if necessary, delete if necessary |
|
||||||
val bestComputedResults = result.max(stat) |
|
||||||
bestComputedResults?.let { computedResults -> |
|
||||||
messages.add("found new perf...") |
|
||||||
|
|
||||||
val performanceQuery = computedResults.group.query |
|
||||||
val performanceName = performanceQuery.getName(this.context, nameSeparator) |
|
||||||
|
|
||||||
Timber.d("Best computed = $performanceName, ${computedResults.computedStat(Stat.NET_RESULT)?.value}") |
|
||||||
|
|
||||||
var storePerf = true |
|
||||||
currentPerf?.let { |
|
||||||
messages.add("has current perf...") |
|
||||||
|
|
||||||
currentPerf.name?.let { name -> |
|
||||||
if (computedResults.group.query.getName(this.context, nameSeparator) == name) { |
|
||||||
storePerf = false |
|
||||||
} |
|
||||||
} |
|
||||||
currentPerf.objectId?.let { objectId -> |
|
||||||
if (computedResults.group.query.objectId == objectId) { |
|
||||||
storePerf = false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (storePerf) { |
|
||||||
realm.executeTransaction { |
|
||||||
currentPerf.name = performanceName |
|
||||||
currentPerf.objectId = performanceQuery.objectId |
|
||||||
currentPerf.customFieldId = customField?.id |
|
||||||
} |
|
||||||
this.whistleBlower.notify(currentPerf) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
messages.add("storePerf = $storePerf...") |
|
||||||
|
|
||||||
if (currentPerf == null && storePerf) { |
|
||||||
val performance = Performance( |
|
||||||
staticReport, |
|
||||||
stat, |
|
||||||
performanceName, |
|
||||||
performanceQuery.objectId, |
|
||||||
customField?.id, |
|
||||||
null |
|
||||||
) |
|
||||||
realm.executeTransaction { it.copyToRealm(performance) } |
|
||||||
this.whistleBlower.notify(performance) |
|
||||||
} |
|
||||||
|
|
||||||
} ?: run { // if there is no max but a now irrelevant Performance, we delete it |
|
||||||
|
|
||||||
messages.add("deletes current perf if necessary: $currentPerf...") |
|
||||||
|
|
||||||
// Timber.d("NO best computed value, current perf = $currentPerf ") |
|
||||||
currentPerf?.let { perf -> |
|
||||||
realm.executeTransaction { |
|
||||||
// Timber.d("Delete perf: stat = ${perf.stat}, report = ${perf.reportId}") |
|
||||||
perf.deleteFromRealm() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun analyseOptimalDuration(realm: Realm, staticReport: StaticReport, key: PerformanceKey, duration: Double?) { |
|
||||||
|
|
||||||
val performance = performancesQuery(realm, staticReport, key).findFirst() |
|
||||||
|
|
||||||
duration?.let { |
|
||||||
var storePerf = true |
|
||||||
|
|
||||||
val formattedDuration = (duration / 3600 / 1000).formattedHourlyDuration() |
|
||||||
performance?.let { perf -> |
|
||||||
if (perf.value == duration) { |
|
||||||
storePerf = false |
|
||||||
} |
|
||||||
|
|
||||||
if (storePerf) { |
|
||||||
realm.executeTransaction { |
|
||||||
perf.name = formattedDuration |
|
||||||
perf.value = duration |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (storePerf) { |
|
||||||
val perf = Performance(staticReport, key, name = formattedDuration, value = duration) |
|
||||||
realm.executeTransaction { it.copyToRealm(perf) } |
|
||||||
this.whistleBlower.notify(perf) |
|
||||||
} |
|
||||||
|
|
||||||
} ?: run { // no duration |
|
||||||
performance?.let { perf -> |
|
||||||
realm.executeTransaction { |
|
||||||
perf.deleteFromRealm() // delete if the perf exists |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun performancesQuery(realm: Realm, staticReport: StaticReport, key: PerformanceKey): RealmQuery<Performance> { |
|
||||||
return realm.where(Performance::class.java) |
|
||||||
.equalTo("reportId", staticReport.uniqueIdentifier) |
|
||||||
.equalTo("key", key.value) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,335 +1,118 @@ |
|||||||
package net.pokeranalytics.android.calculus |
package net.pokeranalytics.android.calculus |
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.R |
import net.pokeranalytics.android.R |
||||||
import net.pokeranalytics.android.exceptions.FormattingException |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.model.realm.PerformanceKey |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
import net.pokeranalytics.android.ui.view.RowViewType |
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import net.pokeranalytics.android.util.TextFormat |
|
||||||
import net.pokeranalytics.android.util.enumerations.IntIdentifiable |
|
||||||
import net.pokeranalytics.android.util.enumerations.IntSearchable |
|
||||||
import net.pokeranalytics.android.util.extensions.formatted |
|
||||||
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration |
|
||||||
import net.pokeranalytics.android.util.extensions.toCurrency |
|
||||||
import java.util.* |
|
||||||
import kotlin.math.exp |
|
||||||
import kotlin.math.pow |
|
||||||
|
|
||||||
class StatFormattingException(message: String) : Exception(message) |
|
||||||
|
|
||||||
/** |
/** |
||||||
* An enum representing all the types of Session statistics |
* An enum representing all the types of Session statistics |
||||||
*/ |
*/ |
||||||
enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable, PerformanceKey { |
enum class Stat : RowRepresentable { |
||||||
|
|
||||||
NET_RESULT(1), |
NETRESULT, |
||||||
BB_NET_RESULT(2), |
HOURLY_RATE, |
||||||
HOURLY_RATE(3), |
AVERAGE, |
||||||
AVERAGE(4), |
NUMBER_OF_SETS, |
||||||
NUMBER_OF_SETS(5), |
NUMBER_OF_GAMES, |
||||||
NUMBER_OF_GAMES(6), |
DURATION, |
||||||
HOURLY_DURATION(7), |
AVERAGE_DURATION, |
||||||
AVERAGE_HOURLY_DURATION(8), |
NET_BB_PER_100_HANDS, |
||||||
NET_BB_PER_100_HANDS(9), |
HOURLY_RATE_BB, |
||||||
HOURLY_RATE_BB(10), |
AVERAGE_NET_BB, |
||||||
AVERAGE_NET_BB(11), |
WIN_RATIO, |
||||||
WIN_RATIO(12), |
AVERAGE_BUYIN, |
||||||
AVERAGE_BUYIN(13), |
ROI, |
||||||
ROI(14), |
STANDARD_DEVIATION, |
||||||
STANDARD_DEVIATION(15), |
STANDARD_DEVIATION_HOURLY, |
||||||
STANDARD_DEVIATION_HOURLY(16), |
STANDARD_DEVIATION_BB_PER_100_HANDS, |
||||||
STANDARD_DEVIATION_BB_PER_100_HANDS(17), |
HANDS_PLAYED; |
||||||
HANDS_PLAYED(18), |
|
||||||
LOCATIONS_PLAYED(19), |
|
||||||
LONGEST_STREAKS(20), |
|
||||||
MAXIMUM_NET_RESULT(21), |
|
||||||
MINIMUM_NET_RESULT(22), |
|
||||||
MAXIMUM_DURATION(23), |
|
||||||
DAYS_PLAYED(24), |
|
||||||
WINNING_SESSION_COUNT(25), |
|
||||||
BB_SESSION_COUNT(26), |
|
||||||
TOTAL_BUYIN(27), |
|
||||||
RISK_OF_RUIN(28), |
|
||||||
STANDARD_DEVIATION_BB(29), |
|
||||||
TOURNAMENT_ITM_RATIO(30), |
|
||||||
TOTAL_TIPS(31) |
|
||||||
; |
|
||||||
|
|
||||||
companion object : IntSearchable<Stat> { |
|
||||||
|
|
||||||
override fun valuesInternal(): Array<Stat> { |
/** |
||||||
return values() |
* Returns whether the stat evolution values requires a distribution sorting |
||||||
|
*/ |
||||||
|
fun hasDistributionSorting() : Boolean { |
||||||
|
when (this) { |
||||||
|
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> return true |
||||||
|
else -> return false |
||||||
} |
} |
||||||
|
|
||||||
val userSelectableList: List<Stat> |
|
||||||
get() { |
|
||||||
return values().filter { it.canBeUserSelected } |
|
||||||
} |
} |
||||||
|
|
||||||
val evolutionValuesList: List<Stat> |
companion object { |
||||||
get() { |
|
||||||
return values().filter { it.hasProgressValues } |
|
||||||
} |
|
||||||
|
|
||||||
fun returnOnInvestment(netResult: Double, buyin: Double): Double? { |
fun returnOnInvestment(netResult: Double, buyin: Double) : Double { |
||||||
if (buyin == 0.0) { |
|
||||||
return null |
|
||||||
} |
|
||||||
return netResult / buyin |
return netResult / buyin |
||||||
} |
} |
||||||
|
|
||||||
fun netBBPer100Hands(netBB: Double, numberOfHands: Double): Double? { |
fun netBBPer100Hands(netBB: Double, numberOfHands: Double) : Double { |
||||||
if (numberOfHands == 0.0) { |
|
||||||
return null |
|
||||||
} |
|
||||||
return netBB / numberOfHands * 100 |
return netBB / numberOfHands * 100 |
||||||
} |
} |
||||||
|
|
||||||
fun riskOfRuin(hourlyRate: Double, hourlyStandardDeviation: Double, bankrollValue: Double): Double? { |
|
||||||
|
|
||||||
if (bankrollValue <= 0.0) { |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
val numerator = -2 * hourlyRate * bankrollValue |
|
||||||
val denominator = hourlyStandardDeviation.pow(2.0) |
|
||||||
val ratio = numerator / denominator |
|
||||||
return exp(ratio) |
|
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
override val value: Int = this.uniqueIdentifier |
|
||||||
|
|
||||||
override val resId: Int? |
override val resId: Int? |
||||||
get() { |
get() { |
||||||
return when (this) { |
return when (this) { |
||||||
NET_RESULT -> R.string.net_result |
NETRESULT -> R.string.net_result |
||||||
BB_NET_RESULT -> R.string.total_net_result_bb_ |
|
||||||
HOURLY_RATE -> R.string.average_hour_rate |
HOURLY_RATE -> R.string.average_hour_rate |
||||||
AVERAGE -> R.string.average |
AVERAGE -> R.string.average |
||||||
NUMBER_OF_SETS -> R.string.number_of_sessions |
NUMBER_OF_SETS -> R.string.number_of_groups |
||||||
NUMBER_OF_GAMES -> R.string.number_of_records |
NUMBER_OF_GAMES -> R.string.number_of_games |
||||||
HOURLY_DURATION -> R.string.duration |
DURATION -> R.string.duration |
||||||
AVERAGE_HOURLY_DURATION -> R.string.average_hours_played |
AVERAGE_DURATION -> R.string.average_duration |
||||||
NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands |
NET_BB_PER_100_HANDS -> R.string.net_bb_per_100_hands |
||||||
HOURLY_RATE_BB -> R.string.average_hour_rate_bb_ |
HOURLY_RATE_BB -> R.string.hourly_rate_bb |
||||||
AVERAGE_NET_BB -> R.string.average_net_result_bb_ |
AVERAGE_NET_BB -> R.string.average_net_bb |
||||||
WIN_RATIO -> R.string.win_ratio |
WIN_RATIO -> R.string.win_ratio |
||||||
AVERAGE_BUYIN -> R.string.average_buyin |
AVERAGE_BUYIN -> R.string.average_buyin |
||||||
ROI -> R.string.tournament_roi |
ROI -> R.string.roi |
||||||
STANDARD_DEVIATION -> R.string.standard_deviation |
STANDARD_DEVIATION -> R.string.standard_deviation |
||||||
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour |
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_hourly |
||||||
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands |
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands |
||||||
STANDARD_DEVIATION_BB -> R.string.bb_standard_deviation |
HANDS_PLAYED -> R.string.hands_played |
||||||
HANDS_PLAYED -> R.string.number_of_hands |
|
||||||
LOCATIONS_PLAYED -> R.string.locations_played |
|
||||||
LONGEST_STREAKS -> R.string.longest_streaks |
|
||||||
MAXIMUM_NET_RESULT -> R.string.max_net_result |
|
||||||
MINIMUM_NET_RESULT -> R.string.min_net_result |
|
||||||
MAXIMUM_DURATION -> R.string.longest_session |
|
||||||
DAYS_PLAYED -> R.string.days_played |
|
||||||
TOTAL_BUYIN -> R.string.total_buyin |
|
||||||
TOURNAMENT_ITM_RATIO -> R.string.itm_ratio |
|
||||||
TOTAL_TIPS -> R.string.total_tips |
|
||||||
else -> throw PAIllegalStateException("Stat ${this.name} name required but undefined") |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
|
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal |
||||||
|
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Formats the value of the stat to be suitable for display |
* ComputedStat contains a [stat] and their associated [value] |
||||||
*/ |
*/ |
||||||
fun textFormat(value: Double, secondValue: Double? = null, currency: Currency? = null): TextFormat { |
class ComputedStat(stat: Stat, value: Double) { |
||||||
|
|
||||||
if (value.isNaN()) { |
|
||||||
return TextFormat(NULL_TEXT, R.color.white) |
|
||||||
} |
|
||||||
|
|
||||||
when (this) { |
|
||||||
// Amounts + red/green |
|
||||||
NET_RESULT, HOURLY_RATE, AVERAGE, MAXIMUM_NET_RESULT, MINIMUM_NET_RESULT -> { |
|
||||||
val color = if (value >= this.threshold) R.color.green else R.color.red |
|
||||||
return TextFormat(value.toCurrency(currency), color) |
|
||||||
} |
|
||||||
// Red/green numericValues |
|
||||||
HOURLY_RATE_BB, AVERAGE_NET_BB, NET_BB_PER_100_HANDS, BB_NET_RESULT -> { |
|
||||||
val color = if (value >= this.threshold) R.color.green else R.color.red |
|
||||||
return TextFormat(value.formatted, color) |
|
||||||
} |
|
||||||
// white integers |
|
||||||
NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED -> { |
|
||||||
return TextFormat("${value.toInt()}") |
|
||||||
} // white durations |
|
||||||
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> { |
|
||||||
return TextFormat(value.formattedHourlyDuration()) |
|
||||||
} // red/green percentages |
|
||||||
WIN_RATIO, ROI, TOURNAMENT_ITM_RATIO -> { |
|
||||||
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red |
|
||||||
return TextFormat("${(value * 100).formatted}%", color) |
|
||||||
} |
|
||||||
RISK_OF_RUIN -> { |
|
||||||
val color = if (value * 100 <= this.threshold) R.color.green else R.color.red |
|
||||||
return TextFormat("${(value * 100).formatted}%", color) |
|
||||||
} |
|
||||||
// white amounts |
|
||||||
AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, |
|
||||||
STANDARD_DEVIATION_BB_PER_100_HANDS, STANDARD_DEVIATION_BB, TOTAL_BUYIN, TOTAL_TIPS -> { |
|
||||||
return TextFormat(value.toCurrency(currency)) |
|
||||||
} |
|
||||||
LONGEST_STREAKS -> { |
|
||||||
return TextFormat("${value.toInt()}W / ${secondValue!!.toInt()}L") |
|
||||||
} |
|
||||||
else -> throw FormattingException("Stat formatting of ${this.name} not handled") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private val threshold: Double |
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { |
||||||
get() { |
if (previousValue != null) { |
||||||
return when (this) { |
this.variation = (value - previousValue) / previousValue |
||||||
RISK_OF_RUIN -> 5.0 |
|
||||||
TOURNAMENT_ITM_RATIO -> 10.0 |
|
||||||
WIN_RATIO -> 50.0 |
|
||||||
else -> 0.0 |
|
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Returns a label used to display the legend right value, typically a total or an average |
* The statistic type |
||||||
*/ |
*/ |
||||||
fun cumulativeLabelResId(context: Context): String { |
var stat: Stat = stat |
||||||
val resId = when (this) { |
|
||||||
AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS, |
|
||||||
HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, HOURLY_RATE -> R.string.average |
|
||||||
NUMBER_OF_SETS -> R.string.number_of_sessions |
|
||||||
NUMBER_OF_GAMES -> R.string.number_of_records |
|
||||||
NET_RESULT, BB_NET_RESULT -> R.string.total |
|
||||||
STANDARD_DEVIATION -> R.string.net_result |
|
||||||
STANDARD_DEVIATION_BB -> R.string.average_net_result_bb_ |
|
||||||
STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses |
|
||||||
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands |
|
||||||
WIN_RATIO, HOURLY_DURATION, TOURNAMENT_ITM_RATIO -> return this.localizedTitle(context) |
|
||||||
else -> null |
|
||||||
} |
|
||||||
resId?.let { |
|
||||||
return context.getString(it) |
|
||||||
} ?: run { |
|
||||||
return NULL_TEXT |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
/** |
||||||
* Returns the different available aggregation type for each statistic |
* The stat value |
||||||
*/ |
*/ |
||||||
val aggregationTypes: List<AggregationType> |
var value: Double = value |
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
NET_RESULT -> listOf( |
|
||||||
AggregationType.SESSION, |
|
||||||
AggregationType.MONTH, |
|
||||||
AggregationType.YEAR, |
|
||||||
AggregationType.DURATION |
|
||||||
) |
|
||||||
NUMBER_OF_GAMES, NUMBER_OF_SETS -> listOf(AggregationType.MONTH, AggregationType.YEAR) |
|
||||||
else -> listOf(AggregationType.SESSION, AggregationType.MONTH, AggregationType.YEAR) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
/** |
||||||
* Returns if the stat has an evolution graph |
* The variation of the stat |
||||||
*/ |
*/ |
||||||
val hasProgressGraph: Boolean |
var variation: Double? = null |
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, STANDARD_DEVIATION_BB, |
|
||||||
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, HANDS_PLAYED, |
|
||||||
STANDARD_DEVIATION_BB_PER_100_HANDS, TOTAL_TIPS -> false |
|
||||||
else -> true |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val isStandardDeviation: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
STANDARD_DEVIATION, STANDARD_DEVIATION_BB, |
|
||||||
STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val legendHideRightValue: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
AVERAGE, NUMBER_OF_SETS, NUMBER_OF_GAMES, WIN_RATIO, TOURNAMENT_ITM_RATIO, |
|
||||||
HOURLY_DURATION, AVERAGE_HOURLY_DURATION -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
/** |
||||||
* Returns if the stat has a significant value to display in a progress graph |
* Formats the value of the stat to be suitable for display |
||||||
*/ |
*/ |
||||||
val graphSignificantIndividualValue: Boolean |
fun format() : StatFormat { |
||||||
get() { |
return StatFormat() |
||||||
return when (this) { |
|
||||||
AVERAGE, WIN_RATIO, TOURNAMENT_ITM_RATIO, |
|
||||||
NUMBER_OF_SETS, NUMBER_OF_GAMES, |
|
||||||
STANDARD_DEVIATION, HOURLY_DURATION -> false |
|
||||||
else -> true |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Returns if the stat graph should show the number of sessions |
* Returns a StatFormat instance for an evolution value located at the specified [index] |
||||||
*/ |
*/ |
||||||
val graphShouldShowNumberOfSessions: Boolean |
fun evolutionValueFormat(index: Int) : StatFormat { |
||||||
get() { |
return StatFormat() |
||||||
return when (this) { |
|
||||||
NUMBER_OF_GAMES, NUMBER_OF_SETS -> false |
|
||||||
else -> true |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val graphShowsXAxisZero: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
HOURLY_DURATION -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val graphShowsYAxisZero: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
HOURLY_DURATION -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
private val canBeUserSelected: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
WINNING_SESSION_COUNT, BB_SESSION_COUNT, RISK_OF_RUIN -> false |
|
||||||
else -> true |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private val hasProgressValues: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
NET_RESULT, NET_BB_PER_100_HANDS, HOURLY_RATE_BB, |
|
||||||
AVERAGE_HOURLY_DURATION, HOURLY_DURATION, |
|
||||||
NUMBER_OF_SETS, ROI, AVERAGE_BUYIN, |
|
||||||
WIN_RATIO, TOURNAMENT_ITM_RATIO, |
|
||||||
AVERAGE_NET_BB, NUMBER_OF_GAMES, AVERAGE -> true |
|
||||||
else -> false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal |
|
||||||
} |
} |
||||||
|
|||||||
@ -1,128 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.bankroll |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import net.pokeranalytics.android.calculus.Calculator |
|
||||||
import net.pokeranalytics.android.calculus.ComputableGroup |
|
||||||
import net.pokeranalytics.android.calculus.ComputedResults |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.util.extensions.findById |
|
||||||
|
|
||||||
class BankrollCalculator { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
fun computeReport(realm: Realm, setup: BankrollReportSetup) : BankrollReport { |
|
||||||
|
|
||||||
val report = BankrollReport(setup) |
|
||||||
|
|
||||||
realm.refresh() // fixes an issue where a newly created bankroll is not found, throwing an exception |
|
||||||
val bankrolls: List<Bankroll> = |
|
||||||
if (setup.bankrollId != null) { |
|
||||||
val bankroll = realm.findById<Bankroll>(setup.bankrollId) ?: throw PAIllegalStateException("Bankroll not found with id=${setup.bankrollId}") |
|
||||||
report.currency = bankroll.utilCurrency |
|
||||||
listOf(bankroll) |
|
||||||
} |
|
||||||
else realm.where(Bankroll::class.java).findAll() |
|
||||||
|
|
||||||
var initialValue = 0.0 |
|
||||||
var transactionNet = 0.0 |
|
||||||
|
|
||||||
bankrolls.forEach { bankroll -> |
|
||||||
|
|
||||||
val rate = if (setup.virtualBankroll) bankroll.rate else 1.0 |
|
||||||
|
|
||||||
if (setup.shouldAddInitialValue) { |
|
||||||
initialValue += bankroll.initialValue * rate |
|
||||||
} |
|
||||||
|
|
||||||
if (setup.virtualBankroll) { |
|
||||||
val sum = realm.where(Transaction::class.java) |
|
||||||
.equalTo("bankroll.id", bankroll.id) |
|
||||||
.notEqualTo("type.kind", TransactionType.Value.TRANSFER.uniqueIdentifier) |
|
||||||
.sum("amount") // we remove transfer as they don't impact the overall bankroll |
|
||||||
transactionNet += rate * sum.toDouble() |
|
||||||
} else { |
|
||||||
|
|
||||||
bankroll.transactions?.let { transactions -> |
|
||||||
val sum = transactions.sum("amount") |
|
||||||
transactionNet += rate * sum.toDouble() |
|
||||||
} |
|
||||||
|
|
||||||
bankroll.destinationTransactions?.let { transactions -> |
|
||||||
for (transaction in transactions) { |
|
||||||
val transferRate = transaction.transferRate ?: 1.0 |
|
||||||
transactionNet += transferRate * transaction.amount * -1 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
report.transactionsNet = transactionNet |
|
||||||
report.initial = initialValue |
|
||||||
|
|
||||||
val baseQuery = setup.query(realm) |
|
||||||
val transactions = Filter.queryOn<Transaction>(realm, baseQuery) |
|
||||||
report.addDatedItems(transactions) |
|
||||||
|
|
||||||
transactions.forEach { |
|
||||||
report.addTransaction(it) |
|
||||||
} |
|
||||||
|
|
||||||
val sessionQuery = Query(QueryCondition.DateNotNull).merge(baseQuery) |
|
||||||
val sessions = Filter.queryOn<Session>(realm, sessionQuery) |
|
||||||
report.addDatedItems(sessions) |
|
||||||
|
|
||||||
if (setup.virtualBankroll) { |
|
||||||
|
|
||||||
val options = Calculator.Options(stats = listOf(Stat.NET_RESULT, Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY)) |
|
||||||
val group = ComputableGroup(baseQuery) |
|
||||||
val result = Calculator.compute(realm, group, options) |
|
||||||
result.computedStat(Stat.NET_RESULT)?.let { |
|
||||||
report.netResult = it.value |
|
||||||
} |
|
||||||
this.computeRiskOfRuin(report, result) |
|
||||||
|
|
||||||
} else { |
|
||||||
val results = Filter.queryOn<Result>(realm, baseQuery) |
|
||||||
report.netResult = results.sum("net").toDouble() |
|
||||||
} |
|
||||||
|
|
||||||
val depositType = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm) |
|
||||||
report.transactionBuckets[depositType.id]?.let { bucket -> |
|
||||||
report.depositTotal = bucket.transactions.sumOf { it.amount } |
|
||||||
} |
|
||||||
|
|
||||||
val withdrawalType = TransactionType.getByValue(TransactionType.Value.WITHDRAWAL, realm) |
|
||||||
report.transactionBuckets[withdrawalType.id]?.let { bucket -> |
|
||||||
report.withdrawalTotal = bucket.transactions.sumOf { it.amount } |
|
||||||
} |
|
||||||
|
|
||||||
report.generateGraphPointsIfNecessary() |
|
||||||
|
|
||||||
//realm.close() |
|
||||||
|
|
||||||
return report |
|
||||||
} |
|
||||||
|
|
||||||
private fun computeRiskOfRuin(report: BankrollReport, results: ComputedResults) { |
|
||||||
|
|
||||||
val hourlyRate = results.computedStat(Stat.HOURLY_RATE)?.value |
|
||||||
val hourlyStandardDeviation = results.computedStat(Stat.STANDARD_DEVIATION_HOURLY)?.value |
|
||||||
|
|
||||||
if (hourlyRate != null && hourlyStandardDeviation != null) { |
|
||||||
|
|
||||||
report.riskOfRuin = Stat.riskOfRuin(hourlyRate, hourlyStandardDeviation, report.total) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,287 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.bankroll |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import com.github.mikephil.charting.data.Entry |
|
||||||
import com.github.mikephil.charting.data.LineDataSet |
|
||||||
import io.realm.Realm |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.interfaces.DatedBankrollGraphEntry |
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import net.pokeranalytics.android.model.realm.Transaction |
|
||||||
import net.pokeranalytics.android.ui.graph.DataSetFactory |
|
||||||
import net.pokeranalytics.android.util.extensions.findById |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
/** |
|
||||||
* This class holds the results from the BankrollCalculator computations |
|
||||||
* It has all the information required for the Bankroll various displays |
|
||||||
*/ |
|
||||||
class BankrollReport(var setup: BankrollReportSetup) { |
|
||||||
|
|
||||||
/** |
|
||||||
* The java.util.Currency |
|
||||||
*/ |
|
||||||
var currency: Currency? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* The value of the bankroll |
|
||||||
*/ |
|
||||||
var total: Double = 0.0 |
|
||||||
private set |
|
||||||
|
|
||||||
/** |
|
||||||
* The initial value of the bankroll, or of all bankrolls if virtual is computed |
|
||||||
*/ |
|
||||||
var initial: Double = 0.0 |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.computeBankrollTotal() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The net result from poker computables |
|
||||||
*/ |
|
||||||
var netResult: Double = 0.0 |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.computeBankrollTotal() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The net result from transactions |
|
||||||
*/ |
|
||||||
var transactionsNet: Double = 0.0 |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.computeBankrollTotal() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Computes the bankroll total |
|
||||||
*/ |
|
||||||
private fun computeBankrollTotal() { |
|
||||||
this.total = this.initial + this.netResult + this.transactionsNet |
|
||||||
// Timber.d("init = $initial, net = $netResult, trans = $transactionsNet") |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The sum of all deposits |
|
||||||
*/ |
|
||||||
var depositTotal: Double = 0.0 |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.computeNetBanked() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The sum of all withdrawals |
|
||||||
*/ |
|
||||||
var withdrawalTotal: Double = 0.0 |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.computeNetBanked() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Computes the net banked amount |
|
||||||
*/ |
|
||||||
private fun computeNetBanked() { |
|
||||||
this.netBanked = -this.withdrawalTotal - this.depositTotal |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The difference between withdrawals and deposits |
|
||||||
*/ |
|
||||||
var netBanked: Double = 0.0 |
|
||||||
private set |
|
||||||
|
|
||||||
/** |
|
||||||
* The risk of ruin |
|
||||||
*/ |
|
||||||
var riskOfRuin: Double? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of transactions held by the bankroll |
|
||||||
*/ |
|
||||||
var transactions: List<Transaction> = mutableListOf() |
|
||||||
private set |
|
||||||
|
|
||||||
/** |
|
||||||
* A map containing TransactionBuckets by transaction types |
|
||||||
*/ |
|
||||||
var transactionBuckets: HashMap<String, TransactionBucket> = HashMap() |
|
||||||
private set |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of bankroll graph points |
|
||||||
*/ |
|
||||||
private var evolutionPoints: MutableList<BRGraphPoint> = mutableListOf() |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of dated items used for the graph |
|
||||||
*/ |
|
||||||
private var evolutionItems: MutableList<DatedBankrollGraphEntry> = mutableListOf() |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a list of dated items to the evolution items used to get the bankroll graph |
|
||||||
*/ |
|
||||||
fun addDatedItems(items: Collection<DatedBankrollGraphEntry>) { |
|
||||||
this.evolutionItems.addAll(items) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a transaction to its type bucket |
|
||||||
*/ |
|
||||||
fun addTransaction(transaction: Transaction) { |
|
||||||
|
|
||||||
transaction.type?.let { type -> |
|
||||||
|
|
||||||
var bucket = this.transactionBuckets[type.id] |
|
||||||
|
|
||||||
if (bucket == null) { |
|
||||||
val b = TransactionBucket(type.name, this.setup.virtualBankroll) |
|
||||||
this.transactionBuckets[type.id] = b |
|
||||||
bucket = b |
|
||||||
} |
|
||||||
|
|
||||||
bucket.addTransaction(transaction) |
|
||||||
|
|
||||||
} ?: run { |
|
||||||
throw PAIllegalStateException("Transaction has no type") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Generates the graph points used for the virtual bankroll |
|
||||||
*/ |
|
||||||
fun generateGraphPointsIfNecessary() { |
|
||||||
|
|
||||||
if (!this.setup.virtualBankroll) { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
this.evolutionItems.sortBy { it.date } |
|
||||||
|
|
||||||
var total = this.initial |
|
||||||
this.evolutionItems.forEach { |
|
||||||
val rate = it.bankroll?.rate ?: 1.0 |
|
||||||
|
|
||||||
// Timber.d("rate = $rate, amount = ${it.amount}") |
|
||||||
total += it.amount * rate |
|
||||||
// Timber.d("total = $total") |
|
||||||
val point = BRGraphPoint(total, it.date, it.objectIdentifier) |
|
||||||
this.evolutionPoints.add(point) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns a data set used for the bankroll graph |
|
||||||
*/ |
|
||||||
fun lineDataSet(context: Context): LineDataSet { |
|
||||||
|
|
||||||
val entries = mutableListOf<Entry>() |
|
||||||
this.evolutionPoints.forEachIndexed { index, point -> |
|
||||||
val entry = Entry(index.toFloat(), point.value.toFloat(), point.data) |
|
||||||
entries.add(entry) |
|
||||||
} |
|
||||||
return DataSetFactory.lineDataSetInstance(entries, "", context) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* A class describing the parameters required to launch a bankroll report |
|
||||||
* |
|
||||||
*/ |
|
||||||
class BankrollReportSetup(val bankrollId: String? = null, val from: Date? = null, val to: Date? = null) { |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns whether the setup concerns the virtual bankroll, |
|
||||||
* i.e. the bankroll summing all concrete bankrolls |
|
||||||
*/ |
|
||||||
val virtualBankroll: Boolean |
|
||||||
get() { |
|
||||||
return this.bankrollId == null |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* the query used to get bankroll transactions |
|
||||||
*/ |
|
||||||
fun query(realm: Realm): Query { |
|
||||||
val query = Query() |
|
||||||
|
|
||||||
this.bankrollId?.let { |
|
||||||
val bankroll = realm.findById<Bankroll>(it) ?: throw PAIllegalStateException("Bankroll not found with id $it") |
|
||||||
val bankrollCondition = QueryCondition.AnyBankroll(bankroll) |
|
||||||
query.add(bankrollCondition) |
|
||||||
} |
|
||||||
this.from?.let { |
|
||||||
query.add(QueryCondition.StartedFromDate(it)) |
|
||||||
} |
|
||||||
this.to?.let { |
|
||||||
query.add(QueryCondition.StartedToDate(it)) |
|
||||||
} |
|
||||||
return query |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns whether or not the initial value should be added for the bankroll total |
|
||||||
*/ |
|
||||||
val shouldAddInitialValue: Boolean |
|
||||||
get() { |
|
||||||
return this.from == null |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* A TransactionBucket holds a list of _transactions and computes its amount sum |
|
||||||
*/ |
|
||||||
class TransactionBucket(var name: String, useRate: Boolean = false) { |
|
||||||
|
|
||||||
/** |
|
||||||
* Whether the bankroll rate should be used |
|
||||||
*/ |
|
||||||
private var useRate: Boolean = useRate |
|
||||||
|
|
||||||
/** |
|
||||||
* A list of _transactions |
|
||||||
*/ |
|
||||||
private var _transactions: MutableList<Transaction> = mutableListOf() |
|
||||||
|
|
||||||
val transactions: List<Transaction> |
|
||||||
get() { |
|
||||||
return this._transactions |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The sum of all _transactions |
|
||||||
*/ |
|
||||||
var total: Double = 0.0 |
|
||||||
private set |
|
||||||
|
|
||||||
|
|
||||||
fun addTransaction(transaction: Transaction) { |
|
||||||
|
|
||||||
this._transactions.add(transaction) |
|
||||||
var rate = 1.0 |
|
||||||
if (this.useRate) { |
|
||||||
rate = transaction.bankroll?.currency?.rate ?: 1.0 |
|
||||||
} |
|
||||||
|
|
||||||
val ratedAmount = rate * transaction.amount |
|
||||||
this.total += ratedAmount |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
data class BRGraphPoint(var value: Double, var date: Date, var data: Any? = null) { |
|
||||||
|
|
||||||
// var variation: Double = 0.0 |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,118 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.bankroll |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmResults |
|
||||||
import kotlinx.coroutines.Dispatchers |
|
||||||
import kotlinx.coroutines.GlobalScope |
|
||||||
import kotlinx.coroutines.async |
|
||||||
import kotlinx.coroutines.launch |
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import net.pokeranalytics.android.model.realm.ComputableResult |
|
||||||
import net.pokeranalytics.android.model.realm.Transaction |
|
||||||
import timber.log.Timber |
|
||||||
import java.util.* |
|
||||||
import kotlin.coroutines.CoroutineContext |
|
||||||
|
|
||||||
object BankrollReportManager { |
|
||||||
|
|
||||||
private val coroutineContext: CoroutineContext |
|
||||||
get() = Dispatchers.Main |
|
||||||
|
|
||||||
private var reports: MutableMap<String?, BankrollReport> = mutableMapOf() |
|
||||||
|
|
||||||
private var computableResults: RealmResults<ComputableResult> |
|
||||||
private var bankrolls: RealmResults<Bankroll> |
|
||||||
private var transactions: RealmResults<Transaction> |
|
||||||
|
|
||||||
init { |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
computableResults = realm.where(ComputableResult::class.java).findAll() |
|
||||||
bankrolls = realm.where(Bankroll::class.java).findAll() |
|
||||||
transactions = realm.where(Transaction::class.java).findAll() |
|
||||||
|
|
||||||
initializeListeners() |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Listens to all objects that might have an impact on any bankroll report |
|
||||||
*/ |
|
||||||
private fun initializeListeners() { |
|
||||||
|
|
||||||
this.computableResults.addChangeListener { t, changeSet -> |
|
||||||
val indexes = changeSet.changes.plus(changeSet.insertions).toList() |
|
||||||
val bankrolls = indexes.mapNotNull { t[it]?.session?.bankroll }.toSet() |
|
||||||
this.updateBankrolls(bankrolls) |
|
||||||
} |
|
||||||
this.bankrolls.addChangeListener { t, changeSet -> |
|
||||||
val indexes = changeSet.changes.plus(changeSet.insertions).toList() |
|
||||||
val bankrolls = indexes.mapNotNull { t[it] }.toSet() |
|
||||||
this.updateBankrolls(bankrolls) |
|
||||||
} |
|
||||||
this.transactions.addChangeListener { t, changeSet -> |
|
||||||
val indexes = changeSet.changes.plus(changeSet.insertions).toList() |
|
||||||
val bankrolls = indexes.mapNotNull { t[it]?.bankroll }.toSet() |
|
||||||
this.updateBankrolls(bankrolls) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun reportForBankroll(bankrollId: String?, handler: (BankrollReport) -> Unit) { |
|
||||||
|
|
||||||
Timber.d("Request bankroll report for bankrollId = $bankrollId") |
|
||||||
// if the report exists, return it |
|
||||||
val existingReport: BankrollReport? = this.reports[bankrollId] |
|
||||||
if (existingReport != null) { |
|
||||||
handler(existingReport) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// otherwise compute it |
|
||||||
GlobalScope.launch(coroutineContext) { |
|
||||||
|
|
||||||
var report: BankrollReport? = null |
|
||||||
val coroutine = GlobalScope.async { |
|
||||||
val s = Date() |
|
||||||
Timber.d(">>>>> start computing bankroll...") |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
|
|
||||||
val setup = BankrollReportSetup(bankrollId) |
|
||||||
report = BankrollCalculator.computeReport(realm, setup) |
|
||||||
|
|
||||||
realm.close() |
|
||||||
|
|
||||||
val e = Date() |
|
||||||
val duration = (e.time - s.time) / 1000.0 |
|
||||||
Timber.d(">>>>> ended in $duration seconds") |
|
||||||
|
|
||||||
} |
|
||||||
coroutine.await() |
|
||||||
|
|
||||||
report?.let { |
|
||||||
handler(it) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Notifies the manager of cases not managed by RealmResults listener, such as deletions |
|
||||||
*/ |
|
||||||
fun notifyBankrollReportImpact(bankrollId: String) { |
|
||||||
this.reports.remove(bankrollId) |
|
||||||
this.reports.remove(null) |
|
||||||
} |
|
||||||
|
|
||||||
private fun updateBankrolls(bankrolls: Set<Bankroll>) { |
|
||||||
this.invalidateReport(bankrolls) |
|
||||||
} |
|
||||||
|
|
||||||
private fun invalidateReport(bankrolls: Set<Bankroll>) { |
|
||||||
this.reports.remove(null) |
|
||||||
bankrolls.forEach { br -> |
|
||||||
this.reports.remove(br.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,12 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import net.pokeranalytics.android.calculus.AggregationType |
|
||||||
import net.pokeranalytics.android.ui.graph.Graph |
|
||||||
|
|
||||||
val AggregationType.axisFormatting: Graph.AxisFormatting |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
AggregationType.DURATION -> Graph.AxisFormatting.X_DURATION |
|
||||||
else -> Graph.AxisFormatting.DEFAULT |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,84 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import com.github.mikephil.charting.data.* |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.calculus.ComputedResults |
|
||||||
import net.pokeranalytics.android.calculus.Point |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.ui.graph.DataSetFactory |
|
||||||
import kotlin.math.abs |
|
||||||
|
|
||||||
|
|
||||||
// MPAndroidChart |
|
||||||
|
|
||||||
fun ComputedResults.defaultStatEntries(stat: Stat, context: Context): DataSet<out Entry> { |
|
||||||
return when (stat) { |
|
||||||
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HOURLY_DURATION -> this.barEntries(stat, context = context) |
|
||||||
Stat.STANDARD_DEVIATION -> this.distributionEntries(stat, context) |
|
||||||
else -> this.singleLineEntries(stat, context) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun ComputedResults.singleLineEntries(stat: Stat, context: Context): LineDataSet { |
|
||||||
val entries = mutableListOf<Entry>() |
|
||||||
this.evolutionValues[stat]?.let { points -> |
|
||||||
points.forEachIndexed { index, p -> |
|
||||||
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) |
|
||||||
} |
|
||||||
} |
|
||||||
return DataSetFactory.lineDataSetInstance(entries, this.group.query.getName(context), context) |
|
||||||
} |
|
||||||
|
|
||||||
fun ComputedResults.durationEntries(stat: Stat, context: Context): LineDataSet { |
|
||||||
val entries = mutableListOf<Entry>() |
|
||||||
this.evolutionValues[stat]?.let { points -> |
|
||||||
points.forEach { p -> |
|
||||||
entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) |
|
||||||
} |
|
||||||
} |
|
||||||
return DataSetFactory.lineDataSetInstance(entries, stat.name, context) |
|
||||||
} |
|
||||||
|
|
||||||
private fun ComputedResults.barEntries(stat: Stat, context: Context): BarDataSet { |
|
||||||
|
|
||||||
val entries = mutableListOf<BarEntry>() |
|
||||||
this.evolutionValues[stat]?.let { points -> |
|
||||||
points.forEach { p -> |
|
||||||
entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) |
|
||||||
} |
|
||||||
} |
|
||||||
return DataSetFactory.barDataSetInstance(entries, stat.name, context) |
|
||||||
} |
|
||||||
|
|
||||||
private fun ComputedResults.distributionEntries(stat: Stat, context: Context): BarDataSet { |
|
||||||
|
|
||||||
val colors = mutableListOf<Int>() |
|
||||||
val entries = mutableListOf<BarEntry>() |
|
||||||
this.evolutionValues[stat]?.let { points -> |
|
||||||
|
|
||||||
val negative = mutableListOf<Point>() |
|
||||||
val positive = mutableListOf<Point>() |
|
||||||
|
|
||||||
points.sortByDescending { it.y } |
|
||||||
points.forEach { |
|
||||||
if (it.y < 0) { |
|
||||||
negative.add(it) |
|
||||||
} else { |
|
||||||
positive.add(it) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
negative.forEachIndexed { index, p -> |
|
||||||
entries.add(BarEntry(index.toFloat(), abs(p.y.toFloat()), p.data)) |
|
||||||
colors.add(context.getColor(R.color.red)) |
|
||||||
} |
|
||||||
positive.forEachIndexed { index, p -> |
|
||||||
val x = negative.size + index.toFloat() |
|
||||||
entries.add(BarEntry(x, p.y.toFloat(), p.data)) |
|
||||||
colors.add(context.getColor(R.color.green)) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
return DataSetFactory.barDataSetInstance(entries, stat.name, context, colors) |
|
||||||
} |
|
||||||
@ -1,64 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.calculus.Calculator |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.ui.activity.ComparisonReportActivity |
|
||||||
import net.pokeranalytics.android.ui.activity.ProgressReportActivity |
|
||||||
import net.pokeranalytics.android.ui.activity.TableReportActivity |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
|
|
||||||
/** |
|
||||||
* The way the computed stats are going to be displayed |
|
||||||
*/ |
|
||||||
enum class ReportDisplay : RowRepresentable { |
|
||||||
TABLE, |
|
||||||
PROGRESS, |
|
||||||
COMPARISON, |
|
||||||
MAP; |
|
||||||
|
|
||||||
override val resId: Int? |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
TABLE -> R.string.table |
|
||||||
PROGRESS -> R.string.progress |
|
||||||
COMPARISON -> R.string.comparison |
|
||||||
MAP -> R.string.map |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val activityClass: Class<*> |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
TABLE -> TableReportActivity::class.java |
|
||||||
PROGRESS -> ProgressReportActivity::class.java |
|
||||||
COMPARISON -> ComparisonReportActivity::class.java |
|
||||||
else -> throw PAIllegalStateException("undefined activity for report display") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val progressValues: Calculator.Options.ProgressValues |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PROGRESS, COMPARISON -> Calculator.Options.ProgressValues.STANDARD |
|
||||||
else -> Calculator.Options.ProgressValues.NONE |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// val requireProgressValues: Boolean |
|
||||||
// get() { |
|
||||||
// return when (this) { |
|
||||||
// PROGRESS, COMPARISON -> true |
|
||||||
// else -> false |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
val multipleStatSelection: Boolean |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PROGRESS -> false |
|
||||||
else -> true |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,64 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import com.github.mikephil.charting.data.BarDataSet |
|
||||||
import com.github.mikephil.charting.data.BarEntry |
|
||||||
import com.github.mikephil.charting.data.Entry |
|
||||||
import com.github.mikephil.charting.data.LineDataSet |
|
||||||
import net.pokeranalytics.android.calculus.Report |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.ui.graph.DataSetFactory |
|
||||||
import net.pokeranalytics.android.util.ColorUtils |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the list of entries corresponding to the provided [stat] |
|
||||||
* One value will be returned by result |
|
||||||
*/ |
|
||||||
fun Report.lineEntries(stat: Stat? = null, context: Context): LineDataSet { |
|
||||||
val entries = mutableListOf<Entry>() |
|
||||||
val statToUse = stat ?: this.options.stats.firstOrNull() |
|
||||||
val statName = statToUse?.name ?: "" |
|
||||||
|
|
||||||
statToUse?.let { |
|
||||||
this.results.forEachIndexed { index, results -> |
|
||||||
results.computedStat(it)?.progressValue?.let { progressValue -> |
|
||||||
entries.add(Entry(index.toFloat(), progressValue.toFloat(), results)) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return DataSetFactory.lineDataSetInstance(entries, statName, context) |
|
||||||
} |
|
||||||
|
|
||||||
fun Report.barEntries(stat: Stat? = null, context: Context): BarDataSet { |
|
||||||
val entries = mutableListOf<BarEntry>() |
|
||||||
val statToUse = stat ?: options.stats.firstOrNull() |
|
||||||
|
|
||||||
statToUse?.let { |
|
||||||
this.results.forEachIndexed { index, results -> |
|
||||||
val cs = results.computedStat(it) |
|
||||||
cs?.let { computedStat -> |
|
||||||
val barEntry = BarEntry(index.toFloat(), computedStat.value.toFloat(), results) |
|
||||||
entries.add(barEntry) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val label = statToUse?.name ?: "" |
|
||||||
return DataSetFactory.barDataSetInstance(entries, label, context) |
|
||||||
} |
|
||||||
|
|
||||||
fun Report.multiLineEntries(context: Context): List<LineDataSet> { |
|
||||||
val dataSets = mutableListOf<LineDataSet>() |
|
||||||
|
|
||||||
options.stats.forEach { stat -> |
|
||||||
this.results.forEachIndexed { index, result -> |
|
||||||
val ds = result.singleLineEntries(stat, context) |
|
||||||
ds.color = ColorUtils.almostRandomColor(index, context) |
|
||||||
dataSets.add(ds) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return dataSets |
|
||||||
} |
|
||||||
@ -1,81 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.calcul |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
|
|
||||||
class StatRepresentable(var stat: Stat) : RowRepresentable { |
|
||||||
|
|
||||||
companion object { |
|
||||||
fun resId(stat: Stat): Int { |
|
||||||
return when (stat) { |
|
||||||
Stat.NET_RESULT -> R.string.net_result |
|
||||||
Stat.BB_NET_RESULT -> R.string.total_net_result_bb_ |
|
||||||
Stat.HOURLY_RATE -> R.string.average_hour_rate |
|
||||||
Stat.AVERAGE -> R.string.average |
|
||||||
Stat.NUMBER_OF_SETS -> R.string.number_of_sessions |
|
||||||
Stat.NUMBER_OF_GAMES -> R.string.number_of_records |
|
||||||
Stat.HOURLY_DURATION -> R.string.duration |
|
||||||
Stat.AVERAGE_HOURLY_DURATION -> R.string.average_hours_played |
|
||||||
Stat.NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands |
|
||||||
Stat.HOURLY_RATE_BB -> R.string.average_hour_rate_bb_ |
|
||||||
Stat.AVERAGE_NET_BB -> R.string.average_net_result_bb_ |
|
||||||
Stat.WIN_RATIO -> R.string.win_ratio |
|
||||||
Stat.AVERAGE_BUYIN -> R.string.average_buyin |
|
||||||
Stat.ROI -> R.string.tournament_roi |
|
||||||
Stat.STANDARD_DEVIATION -> R.string.standard_deviation |
|
||||||
Stat.STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour |
|
||||||
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands |
|
||||||
Stat.STANDARD_DEVIATION_BB -> R.string.bb_standard_deviation |
|
||||||
Stat.HANDS_PLAYED -> R.string.number_of_hands |
|
||||||
Stat.LOCATIONS_PLAYED -> R.string.locations_played |
|
||||||
Stat.LONGEST_STREAKS -> R.string.longest_streaks |
|
||||||
Stat.MAXIMUM_NET_RESULT -> R.string.max_net_result |
|
||||||
Stat.MINIMUM_NET_RESULT -> R.string.min_net_result |
|
||||||
Stat.MAXIMUM_DURATION -> R.string.longest_session |
|
||||||
Stat.DAYS_PLAYED -> R.string.days_played |
|
||||||
Stat.TOTAL_BUYIN -> R.string.total_buyin |
|
||||||
else -> throw PAIllegalStateException("Stat ${stat.name} name required but undefined") |
|
||||||
} |
|
||||||
} |
|
||||||
fun localizedTitle(stat: Stat, context: Context): String { |
|
||||||
return context.getString(resId(stat)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val resId: Int |
|
||||||
get() { |
|
||||||
return resId(this.stat) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns a label used to display the legend right value, typically a total or an average |
|
||||||
*/ |
|
||||||
fun cumulativeLabelResId(context: Context): String { |
|
||||||
val resId = when (this.stat) { |
|
||||||
Stat.AVERAGE, Stat.AVERAGE_HOURLY_DURATION, Stat.NET_BB_PER_100_HANDS, |
|
||||||
Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.ROI, Stat.HOURLY_RATE -> R.string.average |
|
||||||
Stat.NUMBER_OF_SETS -> R.string.number_of_sessions |
|
||||||
Stat.NUMBER_OF_GAMES -> R.string.number_of_records |
|
||||||
Stat.NET_RESULT -> R.string.total |
|
||||||
Stat.STANDARD_DEVIATION -> R.string.net_result |
|
||||||
Stat.STANDARD_DEVIATION_BB -> R.string.average_net_result_bb_ |
|
||||||
Stat.STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses |
|
||||||
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands |
|
||||||
Stat.WIN_RATIO, Stat.HOURLY_DURATION -> return this.localizedTitle(context) |
|
||||||
else -> null |
|
||||||
} |
|
||||||
resId?.let { |
|
||||||
return context.getString(it) |
|
||||||
} ?: run { |
|
||||||
return NULL_TEXT |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,183 +0,0 @@ |
|||||||
package net.pokeranalytics.android.calculus.optimalduration |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import net.pokeranalytics.android.calculus.Calculator |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import org.apache.commons.math3.fitting.PolynomialCurveFitter |
|
||||||
import org.apache.commons.math3.fitting.WeightedObservedPoints |
|
||||||
import timber.log.Timber |
|
||||||
import java.util.* |
|
||||||
import kotlin.math.pow |
|
||||||
import kotlin.math.round |
|
||||||
|
|
||||||
/*** |
|
||||||
* This class attempts to find the optimal game duration, |
|
||||||
* meaning the duration where the player will maximize its results, based on his history. |
|
||||||
* The results stands for cash game, and are separated between live and online. |
|
||||||
* Various reasons can prevent the algorithm to find a duration, see below. |
|
||||||
*/ |
|
||||||
class CashGameOptimalDurationCalculator { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
private const val bucket = 60 * 60 * 1000L // the duration of bucket |
|
||||||
private const val bucketInterval = |
|
||||||
4 // number of duration tests inside the bucket to find the best duration |
|
||||||
private const val minimumValidityCount = |
|
||||||
10 // the number of sessions inside a bucket to start having a reasonable average |
|
||||||
private const val intervalValidity = |
|
||||||
3 // the minimum number of unit between the shortest & longest valid buckets |
|
||||||
private const val polynomialDegree = 7 // the degree of the computed polynomial |
|
||||||
|
|
||||||
/*** |
|
||||||
* Starts the calculation |
|
||||||
* [isLive] is a boolean to indicate if we're looking at live or online games |
|
||||||
* return a duration or null if it could not be computed |
|
||||||
*/ |
|
||||||
fun start(isLive: Boolean): Double? { |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
|
|
||||||
val query = Query().add(QueryCondition.IsCash) // cash game |
|
||||||
query.add( |
|
||||||
if (isLive) { |
|
||||||
QueryCondition.IsLive |
|
||||||
} else { |
|
||||||
QueryCondition.IsOnline |
|
||||||
} |
|
||||||
) // live / online |
|
||||||
query.add(QueryCondition.EndDateNotNull) // ended |
|
||||||
query.add(QueryCondition.BiggestBetNotNull) // has BB value |
|
||||||
|
|
||||||
val sessions = query.queryWith(realm.where(Session::class.java)).findAll() |
|
||||||
val sessionsByDuration = sessions.groupBy { |
|
||||||
val dur = round((it.netDuration / bucket).toDouble()) * bucket |
|
||||||
// Timber.d("Stop notif > key: $dur") |
|
||||||
dur |
|
||||||
} |
|
||||||
|
|
||||||
// define validity interval |
|
||||||
var start: Double? = null |
|
||||||
var end: Double? = null |
|
||||||
var validBuckets = 0 |
|
||||||
|
|
||||||
val hkeys = sessionsByDuration.keys.map { it / 3600 / 1000.0 }.sorted() |
|
||||||
// Timber.d("Stop notif > keys: $hkeys ") |
|
||||||
for (key in sessionsByDuration.keys.sorted()) { |
|
||||||
val sessionCount = sessionsByDuration[key]?.size ?: 0 |
|
||||||
if (start == null && sessionCount >= minimumValidityCount) { |
|
||||||
start = key |
|
||||||
} |
|
||||||
if (sessionCount >= minimumValidityCount) { |
|
||||||
end = key |
|
||||||
validBuckets++ |
|
||||||
} |
|
||||||
} |
|
||||||
// Timber.d("Stop notif > validBuckets: $validBuckets ") |
|
||||||
if (!(start != null && end != null && (end - start) >= intervalValidity)) { |
|
||||||
// Timber.d("Stop notif > invalid setup: $start / $end ") |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
// define if we have enough sessions |
|
||||||
if (sessions.size < 50) { |
|
||||||
// Timber.d("Stop notif > not enough sessions: ${sessions.size} ") |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
val options = Calculator.Options() |
|
||||||
options.query = query |
|
||||||
val report = Calculator.computeStats(realm, options) |
|
||||||
val stdBB = |
|
||||||
report.results.firstOrNull()?.computedStat(Stat.STANDARD_DEVIATION_BB)?.value |
|
||||||
|
|
||||||
val p = polynomialRegression(sessions, stdBB) |
|
||||||
|
|
||||||
var bestAverage = 0.0 |
|
||||||
var bestHourlyRate = 0.0 |
|
||||||
var bestDuration = 0.0 |
|
||||||
var maxDuration = 0.0 |
|
||||||
|
|
||||||
val keys = sessionsByDuration.keys.filter { it >= start && it <= end }.sorted() |
|
||||||
|
|
||||||
for (key in keys) { |
|
||||||
|
|
||||||
val sessionCount = sessionsByDuration[key]?.size ?: 0 |
|
||||||
|
|
||||||
if (sessionCount < minimumValidityCount / 2) continue // if too few sessions we don't consider the duration valid |
|
||||||
|
|
||||||
for (i in 0 until bucketInterval) { |
|
||||||
|
|
||||||
val duration = key + i * bucket / bucketInterval |
|
||||||
|
|
||||||
val averageResult = getBB(duration, p) |
|
||||||
val hourly = averageResult / duration |
|
||||||
if (averageResult > bestAverage && hourly > 2 / 3 * bestHourlyRate) { |
|
||||||
bestAverage = averageResult |
|
||||||
bestDuration = duration |
|
||||||
} |
|
||||||
|
|
||||||
if (duration > 0 && hourly > bestHourlyRate) { |
|
||||||
bestHourlyRate = hourly |
|
||||||
} |
|
||||||
if (duration > maxDuration) { |
|
||||||
maxDuration = duration |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
if (bestDuration > 0.0) { |
|
||||||
return bestDuration |
|
||||||
} |
|
||||||
|
|
||||||
// Timber.d("Stop notif > not found, best duration: $bestDuration") |
|
||||||
realm.close() |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
private fun getBB(netDuration: Double, polynomial: DoubleArray): Double { |
|
||||||
var y = 0.0 |
|
||||||
for (i in polynomial.indices) { |
|
||||||
y += polynomial[i] * netDuration.pow(i) |
|
||||||
} |
|
||||||
return y |
|
||||||
} |
|
||||||
|
|
||||||
private fun polynomialRegression( |
|
||||||
sessions: List<Session>, |
|
||||||
bbStandardDeviation: Double? |
|
||||||
): DoubleArray { |
|
||||||
|
|
||||||
val stdBB = bbStandardDeviation ?: Double.MAX_VALUE |
|
||||||
|
|
||||||
val points = WeightedObservedPoints() |
|
||||||
val now = Date().time |
|
||||||
|
|
||||||
sessions.forEach { |
|
||||||
var weight = 5.0 |
|
||||||
|
|
||||||
val endTime = it.endDate?.time ?: 0L |
|
||||||
|
|
||||||
val age = now - endTime |
|
||||||
if (age > 2 * 365 * 24 * 3600 * 1000L) { // if more than 2 years loses 1 point |
|
||||||
weight -= 1.0 |
|
||||||
} |
|
||||||
if (it.bbNet > 2 * stdBB) { // if very big result loses 3 points |
|
||||||
weight -= 3.0 |
|
||||||
} |
|
||||||
|
|
||||||
points.add(weight, it.netDuration.toDouble(), it.bbNet) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
// polynomial of 7 degree, same as iOS |
|
||||||
return PolynomialCurveFitter.create(polynomialDegree).fit(points.toList()) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,32 +0,0 @@ |
|||||||
package net.pokeranalytics.android.exceptions |
|
||||||
|
|
||||||
import net.pokeranalytics.android.model.Criteria |
|
||||||
|
|
||||||
class ModelException(message: String) : Exception(message) |
|
||||||
class FormattingException(message: String) : Exception(message) |
|
||||||
class RowRepresentableEditDescriptorException(message: String) : Exception(message) |
|
||||||
|
|
||||||
class ConfigurationException(message: String) : Exception(message) |
|
||||||
|
|
||||||
class EnumIdentifierNotFoundException(message: String) : Exception(message) |
|
||||||
class MisconfiguredSavableEnumException(message: String) : Exception(message) |
|
||||||
class PAIllegalStateException(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 queryWith has no entity initialized") |
|
||||||
// object FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable") |
|
||||||
class QueryValueMapUnknown(message: String = "fieldName is missing"): PokerAnalyticsException(message) |
|
||||||
class QueryTypeUnhandled(clazz: String) : |
|
||||||
PokerAnalyticsException(message = "queryWith type not handled: $clazz") |
|
||||||
class ComparisonCriteriaUnhandled(criteria: Criteria) : |
|
||||||
PokerAnalyticsException(message = "Criteria type not handled: ${criteria.uniqueIdentifier}") |
|
||||||
object QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected") |
|
||||||
object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "queryWith is empty or null") |
|
||||||
// data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "queryWith element '$filterElementRow' type is missing") |
|
||||||
// data class QueryValueMapMissingKeys(val missingKeys: List<String>) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys") |
|
||||||
// data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow") |
|
||||||
// data class MissingFieldNameForQueryCondition(val name: String) : PokerAnalyticsException(message = "Missing fieldname for QueryCondition ${name}") |
|
||||||
// object InputFragmentException : PokerAnalyticsException(message = "RowEditableDelegate must be a Fragment") |
|
||||||
} |
|
||||||
@ -0,0 +1,5 @@ |
|||||||
|
package net.pokeranalytics.android.exceptions |
||||||
|
|
||||||
|
class ModelException(message: String) : Exception(message) { |
||||||
|
|
||||||
|
} |
||||||
@ -1,356 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.Sort |
|
||||||
import io.realm.kotlin.where |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.exceptions.PokerAnalyticsException |
|
||||||
import net.pokeranalytics.android.model.Criteria.Bankrolls.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.Games.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.Limits.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.Locations.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.Stakes.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.TableSizes.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.TournamentFeatures.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.TournamentFees.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.TournamentNames.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.TournamentTypes.comparison |
|
||||||
import net.pokeranalytics.android.model.Criteria.TransactionTypes.comparison |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.interfaces.NameManageable |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.util.enumerations.IntIdentifiable |
|
||||||
import net.pokeranalytics.android.util.enumerations.IntSearchable |
|
||||||
import net.pokeranalytics.android.util.extensions.findById |
|
||||||
|
|
||||||
fun List<Criteria>.combined(): List<Query> { |
|
||||||
val comparatorList = ArrayList<List<Query>>() |
|
||||||
this.forEach { criteria -> |
|
||||||
comparatorList.add(criteria.queries) |
|
||||||
} |
|
||||||
return getCombinations(comparatorList) |
|
||||||
} |
|
||||||
|
|
||||||
fun getCombinations(queries: List<List<Query>>): List<Query> { |
|
||||||
|
|
||||||
if (queries.isEmpty()) { |
|
||||||
return listOf() |
|
||||||
} |
|
||||||
|
|
||||||
val mutableQueries = queries.toMutableList() |
|
||||||
var combinations = mutableQueries.removeAt(0) |
|
||||||
|
|
||||||
for (queryList in mutableQueries) { |
|
||||||
|
|
||||||
val newCombinations = mutableListOf<Query>() |
|
||||||
combinations.forEach { combinedQuery -> |
|
||||||
queryList.forEach { queryToAdd -> |
|
||||||
val nq = Query().merge(combinedQuery).merge(queryToAdd) |
|
||||||
newCombinations.add(nq) |
|
||||||
} |
|
||||||
} |
|
||||||
combinations = newCombinations |
|
||||||
} |
|
||||||
|
|
||||||
return combinations |
|
||||||
} |
|
||||||
|
|
||||||
sealed class Criteria(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable { |
|
||||||
|
|
||||||
abstract class RealmCriteria(uniqueIdentifier: Int) : Criteria(uniqueIdentifier) { |
|
||||||
inline fun <reified T : NameManageable> comparison(): List<Query> { |
|
||||||
if (this is ListCustomFields) { |
|
||||||
val objects = mutableListOf<QueryCondition.CustomFieldListQuery>() |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
realm.findById(CustomField::class.java, this.customFieldId)?.entries?.forEach { |
|
||||||
objects.add(QueryCondition.CustomFieldListQuery(it)) |
|
||||||
} |
|
||||||
objects.sort() |
|
||||||
realm.close() |
|
||||||
return objects.map { Query(it) } |
|
||||||
} |
|
||||||
return compare<QueryCondition.QueryDataCondition<NameManageable>, T>() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
abstract class SimpleCriteria(private val conditions: List<QueryCondition>, uniqueIdentifier: Int) : Criteria(uniqueIdentifier) { |
|
||||||
fun comparison(): List<Query> { |
|
||||||
return conditions.map { Query(it) } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
abstract class ListCriteria(uniqueIdentifier: Int) : Criteria(uniqueIdentifier) { |
|
||||||
inline fun <reified T : QueryCondition.ListOfValues<S>, reified S : Comparable<S>> comparison(): List<Query> { |
|
||||||
|
|
||||||
if (this is ValueCustomFields) { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
val distincts = realm.where<CustomFieldEntry>().equalTo("customFields.id", this.customFieldId).findAll().sort("numericValue", Sort.ASCENDING) |
|
||||||
realm.close() |
|
||||||
|
|
||||||
val objects = mutableListOf<QueryCondition.CustomFieldNumberQuery>() |
|
||||||
distincts.mapNotNull { |
|
||||||
it.numericValue |
|
||||||
}.distinct().forEach {value -> |
|
||||||
val condition: QueryCondition.CustomFieldNumberQuery = when (this.customFieldType(realm)) { |
|
||||||
CustomField.Type.AMOUNT.uniqueIdentifier -> QueryCondition.CustomFieldAmountQuery() |
|
||||||
CustomField.Type.NUMBER.uniqueIdentifier -> QueryCondition.CustomFieldNumberQuery() |
|
||||||
else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
}.apply { |
|
||||||
this.customFieldId = this@ListCriteria.customFieldId |
|
||||||
listOfValues = arrayListOf(value) |
|
||||||
} |
|
||||||
objects.add(condition) |
|
||||||
} |
|
||||||
objects.sort() |
|
||||||
return objects.map { Query(it) } |
|
||||||
} |
|
||||||
|
|
||||||
QueryCondition.distinct<Session, T, S>()?.let { |
|
||||||
val values = it.mapNotNull { session -> |
|
||||||
when (this) { |
|
||||||
is Limits -> if (session.limit is S) { |
|
||||||
session.limit as S |
|
||||||
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
is TournamentTypes -> if (session.tournamentType is S) { |
|
||||||
session.tournamentType as S |
|
||||||
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
is TableSizes -> if (session.tableSize is S) { |
|
||||||
session.tableSize as S |
|
||||||
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
is TournamentFees -> if (session.tournamentEntryFee is S) { |
|
||||||
session.tournamentEntryFee as S |
|
||||||
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
is Stakes -> if (session.cgStakes is S) { |
|
||||||
session.cgStakes as S |
|
||||||
} else throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
else -> null |
|
||||||
} |
|
||||||
}.distinct() |
|
||||||
return compareList<T, S>(values = values) |
|
||||||
} |
|
||||||
return listOf() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
object Bankrolls : RealmCriteria(1) |
|
||||||
object Games : RealmCriteria(2) |
|
||||||
object TournamentNames : RealmCriteria(3) |
|
||||||
object Locations : RealmCriteria(4) |
|
||||||
object TournamentFeatures : RealmCriteria(5) |
|
||||||
object TransactionTypes : RealmCriteria(6) |
|
||||||
object Limits : ListCriteria(7) |
|
||||||
object TableSizes : ListCriteria(8) |
|
||||||
object TournamentTypes : ListCriteria(9) |
|
||||||
object MonthsOfYear : SimpleCriteria(List(12) { index -> |
|
||||||
QueryCondition.AnyMonthOfYear().apply { listOfValues = arrayListOf(index) } |
|
||||||
}, 10) |
|
||||||
|
|
||||||
object DaysOfWeek : SimpleCriteria(List(7) { index -> |
|
||||||
QueryCondition.AnyDayOfWeek().apply { listOfValues = arrayListOf(index + 1) } |
|
||||||
}, 11) |
|
||||||
|
|
||||||
object SessionTypes : SimpleCriteria(listOf(QueryCondition.IsCash, QueryCondition.IsTournament), 12) |
|
||||||
object BankrollTypes : SimpleCriteria(listOf(QueryCondition.IsLive, QueryCondition.IsOnline), 13) |
|
||||||
object DayPeriods : SimpleCriteria(listOf(QueryCondition.IsWeekDay, QueryCondition.IsWeekEnd), 14) |
|
||||||
object Years : ListCriteria(15) |
|
||||||
object AllMonthsUpToNow : ListCriteria(16) |
|
||||||
object Stakes : ListCriteria(17) |
|
||||||
object TournamentFees : ListCriteria(18) |
|
||||||
object Cash : SimpleCriteria(listOf(QueryCondition.IsCash), 19) |
|
||||||
object Tournament : SimpleCriteria(listOf(QueryCondition.IsTournament), 20) |
|
||||||
data class ListCustomFields(override var customFieldId: String) : RealmCriteria(21), CustomFieldCriteria |
|
||||||
data class ValueCustomFields(override var customFieldId: String) : ListCriteria(22), CustomFieldCriteria |
|
||||||
object Duration : ListCriteria(23) |
|
||||||
|
|
||||||
val queries: List<Query> |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
is AllMonthsUpToNow -> { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
val firstSession = realm.where<Session>().isNotNull("startDate").sort("startDate", Sort.ASCENDING).findFirst() |
|
||||||
val lastSession = realm.where<Session>().isNotNull("startDate").sort("startDate", Sort.DESCENDING).findFirst() |
|
||||||
realm.close() |
|
||||||
|
|
||||||
val years: ArrayList<Query> = arrayListOf() |
|
||||||
|
|
||||||
val firstYear = firstSession?.year ?: return years |
|
||||||
val firstMonth = firstSession.month ?: return years |
|
||||||
val lastYear = lastSession?.year ?: return years |
|
||||||
val lastMonth = lastSession.month ?: return years |
|
||||||
|
|
||||||
for (year in firstYear..lastYear) { |
|
||||||
val currentYear = QueryCondition.AnyYear(year) |
|
||||||
for (month in 0..11) { |
|
||||||
|
|
||||||
if (year == firstYear && month < firstMonth) { |
|
||||||
continue |
|
||||||
} |
|
||||||
if (year == lastYear && month > lastMonth) { |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
val currentMonth = QueryCondition.AnyMonthOfYear(month) |
|
||||||
val query = Query(currentMonth, currentYear) |
|
||||||
years.add(query) |
|
||||||
} |
|
||||||
} |
|
||||||
years |
|
||||||
} |
|
||||||
else -> { |
|
||||||
return this.queryConditions |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val queryConditions: List<Query> |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
is Bankrolls -> comparison<Bankroll>() |
|
||||||
is Games -> comparison<Game>() |
|
||||||
is TournamentFeatures -> comparison<TournamentFeature>() |
|
||||||
is TournamentNames -> comparison<TournamentName>() |
|
||||||
is Locations -> comparison<Location>() |
|
||||||
is TransactionTypes -> comparison<TransactionType>() |
|
||||||
is SimpleCriteria -> comparison() |
|
||||||
is Limits -> comparison<QueryCondition.AnyLimit, Int>() |
|
||||||
is TournamentTypes -> comparison<QueryCondition.AnyTournamentType, Int>() |
|
||||||
is TableSizes -> comparison<QueryCondition.AnyTableSize, Int>() |
|
||||||
is TournamentFees -> comparison<QueryCondition.TournamentFee, Double>() |
|
||||||
is Years -> { |
|
||||||
val years = arrayListOf<Query>() |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
val lastSession = realm.where<Session>().isNotNull("startDate").sort("startDate", Sort.DESCENDING).findFirst() |
|
||||||
val yearNow = lastSession?.year ?: return years |
|
||||||
|
|
||||||
realm.where<Session>().isNotNull("startDate").sort("year", Sort.ASCENDING).findFirst()?.year?.let { |
|
||||||
for (index in 0..(yearNow - it)) { |
|
||||||
val yearCondition = QueryCondition.AnyYear().apply { |
|
||||||
listOfValues = arrayListOf(it + index) |
|
||||||
} |
|
||||||
years.add(Query(yearCondition)) |
|
||||||
} |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
years |
|
||||||
} |
|
||||||
is Stakes -> comparison<QueryCondition.AnyStake, String>() |
|
||||||
is ListCustomFields -> comparison<CustomFieldEntry>() |
|
||||||
is ValueCustomFields -> { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
val queries = when (this.customFieldType(realm)) { |
|
||||||
CustomField.Type.AMOUNT.uniqueIdentifier -> comparison<QueryCondition.CustomFieldAmountQuery, Double >() |
|
||||||
CustomField.Type.NUMBER.uniqueIdentifier -> comparison<QueryCondition.CustomFieldNumberQuery, Double >() |
|
||||||
else -> throw PokerAnalyticsException.ComparisonCriteriaUnhandled(this) |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
queries |
|
||||||
} |
|
||||||
is Duration -> { |
|
||||||
val hourlyQueries = (0..12).map { i -> |
|
||||||
val more = QueryCondition.Duration(i * 60) |
|
||||||
more.operator = QueryCondition.Operator.MORE_OR_EQUAL |
|
||||||
val less = QueryCondition.Duration((i + 1) * 60) |
|
||||||
less.operator = QueryCondition.Operator.LESS |
|
||||||
Query(more, less) |
|
||||||
}.toMutableList() |
|
||||||
|
|
||||||
val moreThan12Hours = QueryCondition.Duration(12 * 60) |
|
||||||
moreThan12Hours.operator = QueryCondition.Operator.MORE_OR_EQUAL |
|
||||||
hourlyQueries.add(Query(moreThan12Hours)) |
|
||||||
|
|
||||||
hourlyQueries |
|
||||||
} |
|
||||||
else -> throw PokerAnalyticsException.ComparisonCriteriaUnhandled(this) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val resId: Int? |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
Bankrolls -> R.string.bankroll |
|
||||||
Games -> R.string.game |
|
||||||
TournamentNames -> R.string.tournament_name |
|
||||||
Locations -> R.string.location |
|
||||||
TournamentFeatures -> R.string.tournament_feature |
|
||||||
Limits -> R.string.limit |
|
||||||
TableSizes -> R.string.table_size |
|
||||||
TournamentTypes -> R.string.tournament_type |
|
||||||
MonthsOfYear -> R.string.month_of_the_year |
|
||||||
DaysOfWeek -> R.string.day_of_the_week |
|
||||||
SessionTypes -> R.string.cash_or_tournament |
|
||||||
BankrollTypes -> R.string.live_or_online |
|
||||||
DayPeriods -> R.string.weekdays_or_weekend |
|
||||||
Years -> R.string.year |
|
||||||
AllMonthsUpToNow -> R.string.month |
|
||||||
Stakes -> R.string.blind |
|
||||||
TournamentFees -> R.string.entry_fees |
|
||||||
// is ListCustomFields -> this.customField.resId |
|
||||||
// is ValueCustomFields -> this.customField.resId |
|
||||||
else -> null |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
companion object : IntSearchable<Criteria> { |
|
||||||
|
|
||||||
inline fun <reified S : QueryCondition.QueryDataCondition<NameManageable>, reified T : NameManageable> compare(): List<Query> { |
|
||||||
val objects = mutableListOf<S>() |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
realm.where<T>().sort("name").findAll().forEach { |
|
||||||
val condition = (QueryCondition.getInstance<T>() as S).apply { |
|
||||||
setObject(it) |
|
||||||
} |
|
||||||
objects.add(condition) |
|
||||||
} |
|
||||||
// objects.sort() |
|
||||||
realm.close() |
|
||||||
return objects.map { Query(it) } |
|
||||||
} |
|
||||||
|
|
||||||
inline fun <reified S : QueryCondition.ListOfValues<T>, T : Any> compareList(values: List<T>): List<Query> { |
|
||||||
val objects = mutableListOf<S>() |
|
||||||
values.forEach { |
|
||||||
val condition = (S::class.java.newInstance()).apply { |
|
||||||
listOfValues = arrayListOf(it) |
|
||||||
} |
|
||||||
objects.add(condition) |
|
||||||
} |
|
||||||
objects.sort() |
|
||||||
return objects.map { Query(it) } |
|
||||||
} |
|
||||||
|
|
||||||
// SavableEnum |
|
||||||
override fun valuesInternal(): Array<Criteria> { |
|
||||||
return all.toTypedArray() |
|
||||||
} |
|
||||||
|
|
||||||
val all: List<Criteria> |
|
||||||
get() { |
|
||||||
return listOf( |
|
||||||
Bankrolls, Games, TournamentNames, Locations, |
|
||||||
TournamentFeatures, Limits, TableSizes, TournamentTypes, |
|
||||||
MonthsOfYear, DaysOfWeek, SessionTypes, |
|
||||||
BankrollTypes, DayPeriods, Years, |
|
||||||
AllMonthsUpToNow, |
|
||||||
Stakes, TournamentFees |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
interface CustomFieldCriteria { |
|
||||||
var customFieldId: String |
|
||||||
|
|
||||||
fun customField(realm: Realm) : CustomField { |
|
||||||
return realm.findById(this.customFieldId) ?: throw PAIllegalStateException("Custom field not found") |
|
||||||
} |
|
||||||
|
|
||||||
fun customFieldType(realm: Realm): Int { |
|
||||||
return this.customField(realm).type |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,54 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
|
|
||||||
enum class Limit : RowRepresentable { |
|
||||||
NO, |
|
||||||
POT, |
|
||||||
FIXED, |
|
||||||
SPREAD, |
|
||||||
MIXED; |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
fun getInstance(value: String) : Limit? { |
|
||||||
return when (value) { |
|
||||||
"NL", "No Limit" -> NO |
|
||||||
"PL", "Pot Limit" -> POT |
|
||||||
"FL", "Fixed Limit", "Limit" -> FIXED |
|
||||||
"ML", "Mixed Limit" -> MIXED |
|
||||||
"SL", "Spread Limit" -> SPREAD |
|
||||||
else -> null |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val shortName: String |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
NO -> "NL" |
|
||||||
POT -> "PL" |
|
||||||
FIXED -> "FL" |
|
||||||
MIXED -> "ML" |
|
||||||
SPREAD -> "SL" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val longName: String |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
NO -> "No Limit" |
|
||||||
POT -> "Pot Limit" |
|
||||||
FIXED -> "Limit" |
|
||||||
MIXED -> "Mixed Limit" |
|
||||||
SPREAD -> "Spread Limit" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return this.longName |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,38 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.model.realm.PerformanceKey |
|
||||||
import net.pokeranalytics.android.util.enumerations.IntIdentifiable |
|
||||||
import net.pokeranalytics.android.util.enumerations.IntSearchable |
|
||||||
|
|
||||||
enum class LiveOnline(override var uniqueIdentifier: Int) : PerformanceKey, IntIdentifiable { |
|
||||||
LIVE(0), |
|
||||||
ONLINE(1); |
|
||||||
|
|
||||||
companion object : IntSearchable<LiveOnline> { |
|
||||||
|
|
||||||
override fun valuesInternal(): Array<LiveOnline> { |
|
||||||
return values() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
override val resId: Int? |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
LIVE -> R.string.live |
|
||||||
ONLINE -> R.string.online |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val value: Int |
|
||||||
get() { |
|
||||||
return this.uniqueIdentifier |
|
||||||
} |
|
||||||
|
|
||||||
val isLive: Boolean |
|
||||||
get() { |
|
||||||
return (this == LIVE) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
data class Stakes(var blinds: String?, var ante: Double?) { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
@ -1,77 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.util.Parser |
|
||||||
|
|
||||||
class TableSize( |
|
||||||
var numberOfPlayer: Int, |
|
||||||
var rowViewType: Int = RowViewType.TITLE_GRID.ordinal, |
|
||||||
var alternativeLabels: Boolean = false |
|
||||||
) : RowRepresentable { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
fun all(alternativeLabels: Boolean): List<TableSize> { |
|
||||||
return Array(9, init = { index -> |
|
||||||
TableSize(index + 2, alternativeLabels = alternativeLabels) |
|
||||||
}).toList() |
|
||||||
} |
|
||||||
|
|
||||||
fun valueForLabel(label: String) : Int? { |
|
||||||
|
|
||||||
Parser.parseNumber(label)?.let { |
|
||||||
return it.toInt() |
|
||||||
} |
|
||||||
|
|
||||||
return when (label) { |
|
||||||
"Full Ring", "Full-Ring" -> 10 |
|
||||||
"Short-Handed", "Short Handed" -> 6 |
|
||||||
"Heads-Up", "Heads Up" -> 2 |
|
||||||
else -> null |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
if (this.alternativeLabels) { |
|
||||||
return this.numberOfPlayer.toString() |
|
||||||
} |
|
||||||
|
|
||||||
return if (this.numberOfPlayer == 2) { |
|
||||||
return "HU" |
|
||||||
} else { |
|
||||||
"${this.numberOfPlayer}-max" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val resId: Int? |
|
||||||
get() { |
|
||||||
return if (this.numberOfPlayer == 2) { |
|
||||||
R.string.heads_up |
|
||||||
} else { |
|
||||||
R.string.max |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun localizedTitle(context: Context): String { |
|
||||||
if (this.alternativeLabels) { |
|
||||||
return this.numberOfPlayer.toString() |
|
||||||
} |
|
||||||
|
|
||||||
this.resId?.let { |
|
||||||
return if (this.numberOfPlayer == 2) { |
|
||||||
context.getString(it) |
|
||||||
} else { |
|
||||||
"${this.numberOfPlayer}-${context.getString(it)}" |
|
||||||
} |
|
||||||
} |
|
||||||
return super.localizedTitle(context) |
|
||||||
} |
|
||||||
|
|
||||||
override val viewType: Int |
|
||||||
get() = rowViewType |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,43 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
|
|
||||||
enum class TournamentType(val label: String) : RowRepresentable { |
|
||||||
MTT("MTT"), |
|
||||||
SNG("SNG"); |
|
||||||
|
|
||||||
companion object { |
|
||||||
val all : List<TournamentType> |
|
||||||
get() { |
|
||||||
return values().toList() |
|
||||||
} |
|
||||||
|
|
||||||
fun getValueForLabel(label: String) : TournamentType? { |
|
||||||
return when (label) { |
|
||||||
SNG.label, "Single-Table" -> SNG |
|
||||||
MTT.label, "Multi-Table" -> MTT |
|
||||||
else -> null |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val resId: Int? |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
MTT -> R.string.mtt |
|
||||||
SNG -> R.string.sng |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return when (this) { |
|
||||||
MTT -> "MTT" |
|
||||||
SNG -> "SNG" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val viewType: Int = RowViewType.TITLE.ordinal |
|
||||||
} |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.blogpost |
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName |
|
||||||
|
|
||||||
class BlogPost { |
|
||||||
|
|
||||||
@SerializedName("id") |
|
||||||
var id: Int = 0 |
|
||||||
|
|
||||||
@SerializedName("level") |
|
||||||
var content: String = "" |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,103 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.filter |
|
||||||
|
|
||||||
import io.realm.RealmModel |
|
||||||
import io.realm.RealmResults |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.util.CrashLogging |
|
||||||
|
|
||||||
/** |
|
||||||
* We want to be able to store filters in the database: |
|
||||||
* - filters can be a combination of sub filters |
|
||||||
* - filters can be applied to different type of objects: Sessions, Hands, Transactions... |
|
||||||
* - filters can be applied to a list of different type of objects (feed) |
|
||||||
* |
|
||||||
* A queryWith is described by the following: |
|
||||||
* - a data type: Session, Hands... |
|
||||||
* - a field: table size of a Session |
|
||||||
* - an operator: equal, >=, <... |
|
||||||
* - a value: an id, a number, a date... |
|
||||||
* |
|
||||||
* We can decide to have a collection of [operator, value] for a field |
|
||||||
* |
|
||||||
* Combination: |
|
||||||
* - multiple datatype filters should be handled as 'OR' |
|
||||||
* - multiple field filters should be handled as 'AND' |
|
||||||
* - multiple '=' filters as 'OR' |
|
||||||
* - multiple 'Greater than', 'less than' as 'AND' |
|
||||||
* - multiple numericValues as 'OR' |
|
||||||
* |
|
||||||
* Also: |
|
||||||
* A queryWith should be able to be converted into a Realm query |
|
||||||
* |
|
||||||
*/ |
|
||||||
|
|
||||||
class UnmanagedFilterField(message: String) : Exception(message) { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 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 : RealmModel { |
|
||||||
|
|
||||||
/** |
|
||||||
* return the path of the parameter used in the [QueryCondition] related to this entity |
|
||||||
*/ |
|
||||||
// fun fieldNameForQueryType(queryCondition: QueryCondition) : String? |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
inline fun <reified T : Filterable> RealmResults<T>.filter(query: Query) : RealmResults<T> { |
|
||||||
return query.queryWith(this.where()).findAll() |
|
||||||
} |
|
||||||
|
|
||||||
class FilterHelper { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
inline fun <reified T : Filterable > fieldNameForQueryType(queryCondition: Class< out 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) |
|
||||||
Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition) |
|
||||||
Result::class.java -> Result.fieldNameForQueryType(queryCondition) |
|
||||||
else -> { |
|
||||||
CrashLogging.logException(PAIllegalStateException("Filterable type fields are not defined for condition ${queryCondition::class}, class ${T::class}")) |
|
||||||
null |
|
||||||
// throw UnmanagedFilterField("Filterable type fields are not defined for class ${T::class}") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
fieldName?.let { |
|
||||||
return fieldName |
|
||||||
} ?: run { |
|
||||||
throw PokerAnalyticsException.MissingFieldNameForQueryCondition(queryCondition.name) |
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// |
|
||||||
//fun MutableList<Filterable>.queryWith(queryWith: FilterCondition) : List<Filterable> { |
|
||||||
// |
|
||||||
// return this.queryWith { f -> |
|
||||||
// return@queryWith true |
|
||||||
// } |
|
||||||
//} |
|
||||||
|
|
||||||
|
|
||||||
// doesnt compile: Class "FilterableRealmObject" must contain at least 1 persistable field. |
|
||||||
|
|
||||||
//class FilterableRealmObject : RealmObject(), Filterable { |
|
||||||
// |
|
||||||
//} |
|
||||||
@ -1,128 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.filter |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import io.realm.RealmQuery |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
|
|
||||||
fun List<Query>.mapFirstCondition() : List<QueryCondition> { |
|
||||||
return this.map { it.conditions.first() } |
|
||||||
} |
|
||||||
|
|
||||||
class Query { |
|
||||||
|
|
||||||
constructor(query: Query) { |
|
||||||
this._conditions.addAll(query.conditions) |
|
||||||
} |
|
||||||
|
|
||||||
constructor(vararg elements: QueryCondition?) { |
|
||||||
if (elements.isNotEmpty()) { |
|
||||||
this.add(elements.filterNotNull()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private val _conditions: MutableList<QueryCondition> = mutableListOf() |
|
||||||
val conditions: List<QueryCondition> |
|
||||||
get() { |
|
||||||
return this._conditions |
|
||||||
} |
|
||||||
|
|
||||||
fun add(vararg elements: QueryCondition): Query { |
|
||||||
if (elements.isNotEmpty()) { |
|
||||||
this.add(elements.asList()) |
|
||||||
} |
|
||||||
return this |
|
||||||
} |
|
||||||
|
|
||||||
fun add(queryCondition: QueryCondition): Query { |
|
||||||
this._conditions.add(queryCondition) |
|
||||||
return this |
|
||||||
} |
|
||||||
|
|
||||||
fun add(queryConditions: List<QueryCondition>): Query { |
|
||||||
this._conditions.addAll(queryConditions) |
|
||||||
return this |
|
||||||
} |
|
||||||
|
|
||||||
fun remove(queryCondition: QueryCondition) { |
|
||||||
this._conditions.remove(queryCondition) |
|
||||||
} |
|
||||||
|
|
||||||
val defaultName: String |
|
||||||
get() { |
|
||||||
return when (this._conditions.size) { |
|
||||||
0 -> NULL_TEXT |
|
||||||
else -> this._conditions.joinToString("") { it.id.joinToString("") } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun getName(context: Context, separator: String = " + "): String { |
|
||||||
return when (this._conditions.size) { |
|
||||||
0 -> context.getString(R.string.all_sessions) // @todo should be dependant of the underlying type, ie. Session, Transaction... |
|
||||||
else -> this._conditions.joinToString(separator) { it.getDisplayNameWithValues(context) } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
inline fun <reified T : Filterable> queryWith(query: RealmQuery<T>): RealmQuery<T> { |
|
||||||
var realmQuery = query |
|
||||||
|
|
||||||
val queryFromTime = this.conditions.firstOrNull { |
|
||||||
it is QueryCondition.StartedFromTime |
|
||||||
} |
|
||||||
val queryToTime = this.conditions.firstOrNull { |
|
||||||
it is QueryCondition.EndedToTime |
|
||||||
} |
|
||||||
|
|
||||||
this.conditions.forEach { |
|
||||||
realmQuery = when (it) { |
|
||||||
is QueryCondition.StartedFromTime -> { |
|
||||||
it.queryWith(realmQuery, queryToTime) |
|
||||||
} |
|
||||||
is QueryCondition.EndedToTime -> { |
|
||||||
it.queryWith(realmQuery, queryFromTime) |
|
||||||
} |
|
||||||
else -> { |
|
||||||
it.queryWith(realmQuery) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// println("<<<<<< ${realmQuery.description}") |
|
||||||
// val queryLast = this.conditions.firstOrNull { |
|
||||||
// it is QueryCondition.Last |
|
||||||
// } |
|
||||||
// queryLast?.let {qc -> |
|
||||||
// (qc as QueryCondition.Last).singleValue?.let { |
|
||||||
// return realmQuery.limit(it.toLong()) |
|
||||||
// } |
|
||||||
// } |
|
||||||
return realmQuery |
|
||||||
} |
|
||||||
|
|
||||||
fun merge(query: Query) : Query { |
|
||||||
this.add(query.conditions) |
|
||||||
return this |
|
||||||
} |
|
||||||
|
|
||||||
fun copy(): Query { |
|
||||||
return Query(this) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/* |
|
||||||
Returns the first object Id of any QueryCondition |
|
||||||
*/ |
|
||||||
val objectId: String? |
|
||||||
get() { |
|
||||||
for (c in this._conditions) { |
|
||||||
when (c) { |
|
||||||
is QueryCondition.QueryDataCondition<*> -> { |
|
||||||
c.objectId?.let { return it } |
|
||||||
} |
|
||||||
else -> {} |
|
||||||
} |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,108 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.handhistory |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import net.pokeranalytics.android.model.realm.Game |
|
||||||
import net.pokeranalytics.android.model.realm.Session |
|
||||||
import net.pokeranalytics.android.model.realm.handhistory.HandHistory |
|
||||||
import net.pokeranalytics.android.util.extensions.findById |
|
||||||
import timber.log.Timber |
|
||||||
|
|
||||||
class HandSetup { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
/*** |
|
||||||
* Returns a HandSetup instance using a [configurationId], the id of a Realm object, |
|
||||||
* Session or HandHistory, used to later configure the HandHistory |
|
||||||
*/ |
|
||||||
fun from(configurationId: String?, attached: Boolean, realm: Realm): HandSetup { |
|
||||||
|
|
||||||
return if (configurationId != null) { |
|
||||||
val handSetup = HandSetup() |
|
||||||
val hh = realm.findById(HandHistory::class.java, configurationId) |
|
||||||
if (hh != null) { |
|
||||||
handSetup.configure(hh) |
|
||||||
} |
|
||||||
val session = realm.findById(Session::class.java, configurationId) |
|
||||||
if (session != null) { |
|
||||||
handSetup.configure(session, attached) |
|
||||||
} |
|
||||||
handSetup |
|
||||||
} else { |
|
||||||
HandSetup() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
var type: Session.Type? = null |
|
||||||
|
|
||||||
var blinds: String? = null |
|
||||||
|
|
||||||
// var bigBlind: Double? = null |
|
||||||
|
|
||||||
var ante: Double? = null |
|
||||||
|
|
||||||
var tableSize: Int? = null |
|
||||||
|
|
||||||
var bigBlindAnte: Boolean = false |
|
||||||
|
|
||||||
var game: Game? = null |
|
||||||
|
|
||||||
var session: Session? = null |
|
||||||
|
|
||||||
var straddlePositions: MutableList<Position> = mutableListOf() |
|
||||||
private set |
|
||||||
|
|
||||||
fun clearStraddles() { |
|
||||||
this.straddlePositions.clear() |
|
||||||
} |
|
||||||
|
|
||||||
private fun configure(handHistory: HandHistory) { |
|
||||||
this.blinds = handHistory.blinds |
|
||||||
this.bigBlindAnte = handHistory.bigBlindAnte |
|
||||||
this.ante = handHistory.ante |
|
||||||
this.tableSize = handHistory.numberOfPlayers |
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* Configures the Hand Setup with a [session] |
|
||||||
* [attached] denotes if the HandHistory must be directly linked to the session |
|
||||||
*/ |
|
||||||
private fun configure(session: Session, attached: Boolean) { |
|
||||||
if (attached) { |
|
||||||
this.session = session |
|
||||||
} |
|
||||||
if (session.endDate == null) { |
|
||||||
this.game = session.game // we don't want to force the max number of cards if unsure |
|
||||||
} |
|
||||||
this.type = session.sessionType |
|
||||||
this.blinds = session.cgBlinds |
|
||||||
this.ante = session.cgAnte |
|
||||||
this.tableSize = session.tableSize |
|
||||||
|
|
||||||
val blindValues = session.blindValues |
|
||||||
if (blindValues.size > 2) { |
|
||||||
this.straddlePositions = Position.positionsPerPlayers(10).drop(2).take(blindValues.size - 2).toMutableList() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/*** |
|
||||||
* This method sorts the straddle positions in their natural order |
|
||||||
* If the straddle position contains the button, we're usually in a Mississipi straddle, |
|
||||||
* meaning the BUT straddles, then CO, then HJ... |
|
||||||
* Except if it goes to UTG, in which case we don't know if we're in standard straddle, or Mississipi |
|
||||||
* We use the first straddled position to sort out this case |
|
||||||
*/ |
|
||||||
fun setStraddlePositions(firstStraddlePosition: Position, positions: LinkedHashSet<Position>) { |
|
||||||
var sortedPosition = positions.sortedBy { it.ordinal } |
|
||||||
if (positions.contains(Position.BUT) && firstStraddlePosition != Position.UTG) { |
|
||||||
sortedPosition = sortedPosition.reversed() |
|
||||||
} |
|
||||||
Timber.d("sortedPosition = $sortedPosition") |
|
||||||
this.straddlePositions = sortedPosition.toMutableList() |
|
||||||
Timber.d("this.straddlePositions = ${this.straddlePositions}") |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,53 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.handhistory |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
enum class Position(var value: String) : RowRepresentable { |
|
||||||
SB("SB"), |
|
||||||
BB("BB"), |
|
||||||
UTG("UTG"), |
|
||||||
UTG1("UTG+1"), |
|
||||||
UTG2("UTG+2"), |
|
||||||
UTG3("UTG+3"), |
|
||||||
MP("MP"), |
|
||||||
HJ("HJ"), |
|
||||||
CO("CO"), |
|
||||||
BUT("BUT"); |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
fun positionsPerPlayers(playerCount: Int) : LinkedHashSet<Position> { |
|
||||||
return when(playerCount) { |
|
||||||
2 -> linkedSetOf(SB, BB) |
|
||||||
3 -> linkedSetOf(SB, BB, BUT) |
|
||||||
4 -> linkedSetOf(SB, BB, UTG, BUT) |
|
||||||
5 -> linkedSetOf(SB, BB, UTG, CO, BUT) |
|
||||||
6 -> linkedSetOf(SB, BB, UTG, HJ, CO, BUT) |
|
||||||
7 -> linkedSetOf(SB, BB, UTG, MP, HJ, CO, BUT) |
|
||||||
8 -> linkedSetOf(SB, BB, UTG, UTG1, MP, HJ, CO, BUT) |
|
||||||
9 -> linkedSetOf(SB, BB, UTG, UTG1, UTG2, MP, HJ, CO, BUT) |
|
||||||
10 -> linkedSetOf(SB, BB, UTG, UTG1, UTG2, UTG3, MP, HJ, CO, BUT) |
|
||||||
else -> throw PAIllegalStateException("Unmanaged number of players") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val shortValue: String |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
UTG1 -> "+1" |
|
||||||
UTG2 -> "+2" |
|
||||||
UTG3 -> "+3" |
|
||||||
else -> this.value |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return this.value |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,50 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.handhistory |
|
||||||
|
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.ui.modules.handhistory.replayer.HandStep |
|
||||||
|
|
||||||
enum class Street : HandStep { |
|
||||||
PREFLOP, |
|
||||||
FLOP, |
|
||||||
TURN, |
|
||||||
RIVER, |
|
||||||
SUMMARY; |
|
||||||
|
|
||||||
override val street: Street = this |
|
||||||
|
|
||||||
val totalBoardCards: Int |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PREFLOP -> 0 |
|
||||||
FLOP -> 3 |
|
||||||
TURN -> 4 |
|
||||||
RIVER, SUMMARY -> 5 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val resId: Int |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PREFLOP -> R.string.street_preflop |
|
||||||
FLOP -> R.string.street_flop |
|
||||||
TURN -> R.string.street_turn |
|
||||||
RIVER -> R.string.street_river |
|
||||||
SUMMARY -> R.string.summary |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val next: Street |
|
||||||
get() { |
|
||||||
return values()[this.ordinal + 1] |
|
||||||
} |
|
||||||
|
|
||||||
val previous: Street? |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
PREFLOP -> null |
|
||||||
else -> values()[this.ordinal - 1] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@ -1,22 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.interfaces |
|
||||||
|
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
interface Dated { |
|
||||||
|
|
||||||
var date: Date |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
interface DatedValue : Dated { |
|
||||||
|
|
||||||
var amount: Double |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
interface DatedBankrollGraphEntry : DatedValue, GraphIdentifiableEntry { |
|
||||||
|
|
||||||
var bankroll: Bankroll? |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,129 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.interfaces |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmModel |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.exceptions.ModelException |
|
||||||
|
|
||||||
enum class SaveValidityStatus { |
|
||||||
VALID, |
|
||||||
ALREADY_EXISTS, |
|
||||||
DATA_INVALID; |
|
||||||
} |
|
||||||
|
|
||||||
enum class DeleteValidityStatus { |
|
||||||
VALID, |
|
||||||
INVALID, |
|
||||||
SESSIONS_LINKED, |
|
||||||
TRANSACTIONS_LINKED; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* An interface to group object which are managed by the database |
|
||||||
*/ |
|
||||||
interface Manageable : Savable, Deletable |
|
||||||
|
|
||||||
interface NameManageable : Manageable { |
|
||||||
var name: String |
|
||||||
|
|
||||||
override fun isValidForSave(): Boolean { |
|
||||||
return this.name.isNotEmpty() |
|
||||||
} |
|
||||||
|
|
||||||
override fun alreadyExists(realm: Realm): Boolean { |
|
||||||
return realm.where(this::class.java).equalTo("name", this.name).and().notEqualTo("id", this.id).findAll().isNotEmpty() |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
|
||||||
throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled") |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
|
||||||
return R.string.relationship_error |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
class ObjectIdentifier(var id: String, var clazz: Class<out Identifiable>) |
|
||||||
|
|
||||||
/** |
|
||||||
* An interface associate a unique uniqueIdentifier to an object |
|
||||||
*/ |
|
||||||
interface Identifiable : RealmModel { |
|
||||||
|
|
||||||
/** |
|
||||||
* A unique uniqueIdentifier getter |
|
||||||
*/ |
|
||||||
var id: String |
|
||||||
|
|
||||||
/** |
|
||||||
* required because "this.class" returns the proxy class, making where<class> queries crash |
|
||||||
*/ |
|
||||||
val realmObjectClass: Class<out Identifiable> |
|
||||||
|
|
||||||
val objectIdentifier: ObjectIdentifier |
|
||||||
get() { |
|
||||||
return ObjectIdentifier(this.id, this.realmObjectClass) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* An interface to easily handle the validity of any object we want to save |
|
||||||
*/ |
|
||||||
interface Savable : Identifiable { |
|
||||||
|
|
||||||
fun isValidForSave(): Boolean |
|
||||||
fun alreadyExists(realm: Realm): Boolean |
|
||||||
|
|
||||||
/** |
|
||||||
* A method to define if an object is safe for saving in database |
|
||||||
*/ |
|
||||||
fun getSaveValidityStatus(realm: Realm): SaveValidityStatus { |
|
||||||
if (!isValidForSave()) { |
|
||||||
return SaveValidityStatus.DATA_INVALID |
|
||||||
} |
|
||||||
|
|
||||||
if (alreadyExists(realm)) { |
|
||||||
return SaveValidityStatus.ALREADY_EXISTS |
|
||||||
} |
|
||||||
|
|
||||||
return SaveValidityStatus.VALID |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* A method to get the reason why the object can't be saved |
|
||||||
*/ |
|
||||||
fun getFailedSaveMessage(status: SaveValidityStatus): Int |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* An interface to easily handle the validity of any object we want to delete |
|
||||||
*/ |
|
||||||
interface Deletable : Identifiable { |
|
||||||
|
|
||||||
/** |
|
||||||
* A method to define if an object is safe for deletion in database |
|
||||||
*/ |
|
||||||
fun isValidForDelete(realm: Realm): Boolean |
|
||||||
|
|
||||||
|
|
||||||
fun getDeleteStatus(context: Context, realm: Realm): DeleteValidityStatus { |
|
||||||
if (!isValidForDelete(realm)) { |
|
||||||
return DeleteValidityStatus.INVALID |
|
||||||
} |
|
||||||
return DeleteValidityStatus.VALID |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* A method to get the reason why the object can't be deleted |
|
||||||
*/ |
|
||||||
fun getFailedDeleteMessage(status: DeleteValidityStatus): Int |
|
||||||
|
|
||||||
/** |
|
||||||
* A method to override if we need to delete linked objects or other stuff |
|
||||||
*/ |
|
||||||
fun deleteDependencies(realm: Realm) {} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,189 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.interfaces |
|
||||||
|
|
||||||
import net.pokeranalytics.android.model.realm.Bankroll |
|
||||||
import net.pokeranalytics.android.util.BLIND_SEPARATOR |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import net.pokeranalytics.android.util.UserDefaults |
|
||||||
import net.pokeranalytics.android.util.extensions.formatted |
|
||||||
import net.pokeranalytics.android.util.extensions.toCurrency |
|
||||||
import java.lang.Integer.min |
|
||||||
import java.text.NumberFormat |
|
||||||
import java.text.ParseException |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
data class CodedStake(var stakes: String) : Comparable<CodedStake> { |
|
||||||
|
|
||||||
var ante: Double? = null |
|
||||||
var blinds: String? = null |
|
||||||
var currency: Currency |
|
||||||
|
|
||||||
init { |
|
||||||
|
|
||||||
var currencyCode: String? = null |
|
||||||
|
|
||||||
val parameters = this.stakes.split(StakesHolder.cbSeparator) |
|
||||||
parameters.forEach { param -> |
|
||||||
when { |
|
||||||
param.contains(StakesHolder.cbAnte) -> ante = param.removePrefix(StakesHolder.cbAnte).let { NumberFormat.getInstance().parse(it)?.toDouble() } |
|
||||||
param.contains(StakesHolder.cbBlinds) -> blinds = param.removePrefix(StakesHolder.cbBlinds) |
|
||||||
param.contains(StakesHolder.cbCode) -> currencyCode = param.removePrefix( |
|
||||||
StakesHolder.cbCode |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
this.currency = currencyCode?.let { Currency.getInstance(it) } |
|
||||||
?: run { UserDefaults.currency } |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
override fun compareTo(other: CodedStake): Int { |
|
||||||
|
|
||||||
if (this.currency == other.currency) { |
|
||||||
|
|
||||||
this.blinds?.let { b1 -> |
|
||||||
other.blinds?.let { b2 -> |
|
||||||
if (b1 == b2) { |
|
||||||
return this.compareAnte(other) |
|
||||||
} else { |
|
||||||
val bv1 = this.reversedBlindsArray(b1) |
|
||||||
val bv2 = this.reversedBlindsArray(b2) |
|
||||||
|
|
||||||
for (i in 0 until min(bv1.size, bv2.size)) { |
|
||||||
if (bv1[i] != bv2[i]) { |
|
||||||
return bv1[i].compareTo(bv2[i]) |
|
||||||
} else { |
|
||||||
continue |
|
||||||
} |
|
||||||
} |
|
||||||
return bv1.size.compareTo(bv2.size) |
|
||||||
} |
|
||||||
} ?: run { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
|
|
||||||
} ?: run { |
|
||||||
return this.compareAnte(other) |
|
||||||
} |
|
||||||
} else { |
|
||||||
return this.currency.currencyCode.compareTo(other.currency.currencyCode) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun compareAnte(other: CodedStake): Int { |
|
||||||
this.ante?.let { a1 -> |
|
||||||
other.ante?.let { a2 -> |
|
||||||
return a1.compareTo(a2) |
|
||||||
} ?: run { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
} ?: run { |
|
||||||
return -1 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun reversedBlindsArray(blinds: String): List<Double> { |
|
||||||
return blinds.split(BLIND_SEPARATOR).mapNotNull { NumberFormat.getInstance().parse(it)?.toDouble() }.reversed() |
|
||||||
} |
|
||||||
|
|
||||||
fun formattedStakes(): String { |
|
||||||
val components = arrayListOf<String>() |
|
||||||
this.formattedBlinds()?.let { components.add(it) } |
|
||||||
|
|
||||||
if ((this.ante ?: -1.0) > 0.0) { |
|
||||||
this.formattedAnte()?.let { components.add("($it)") } |
|
||||||
} |
|
||||||
|
|
||||||
return if (components.isNotEmpty()) { |
|
||||||
components.joinToString(" ") |
|
||||||
} else { |
|
||||||
NULL_TEXT |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private fun formattedBlinds(): String? { |
|
||||||
this.blinds?.let { |
|
||||||
val placeholder = 1.0 |
|
||||||
val regex = Regex("-?\\d+(\\.\\d+)?") |
|
||||||
return placeholder.toCurrency(currency).replace(regex, it) |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
private fun formattedAnte(): String? { |
|
||||||
this.ante?.let { |
|
||||||
return it.toCurrency(this.currency) |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
interface StakesHolder { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
const val cbSeparator = ";" |
|
||||||
const val cbAnte = "A=" |
|
||||||
const val cbBlinds = "B=" |
|
||||||
const val cbCode = "C=" |
|
||||||
|
|
||||||
fun readableStakes(value: String): String { |
|
||||||
return CodedStake(value).formattedStakes() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
val ante: Double? |
|
||||||
val blinds: String? |
|
||||||
val biggestBet: Double? |
|
||||||
val stakes: String? |
|
||||||
|
|
||||||
val bankroll: Bankroll? |
|
||||||
|
|
||||||
fun setHolderStakes(stakes: String?) |
|
||||||
fun setHolderBiggestBet(biggestBet: Double?) |
|
||||||
|
|
||||||
val blindValues: List<Double> |
|
||||||
get() { |
|
||||||
this.blinds?.let { blinds -> |
|
||||||
val blindsSplit = blinds.split(BLIND_SEPARATOR) |
|
||||||
return blindsSplit.mapNotNull { |
|
||||||
try { |
|
||||||
NumberFormat.getInstance().parse(it)?.toDouble() |
|
||||||
} catch (e: ParseException) { |
|
||||||
null |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return listOf() |
|
||||||
} |
|
||||||
|
|
||||||
fun generateStakes() { |
|
||||||
|
|
||||||
if (this.ante == null && this.blinds == null) { |
|
||||||
setHolderStakes(null) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
val components = arrayListOf<String>() |
|
||||||
|
|
||||||
this.blinds?.let { components.add("${cbBlinds}${it}") } |
|
||||||
this.ante?.let { components.add("${cbAnte}${it.formatted}") } |
|
||||||
|
|
||||||
val code = this.bankroll?.currency?.code ?: UserDefaults.currency.currencyCode |
|
||||||
components.add("${cbCode}${code}") |
|
||||||
|
|
||||||
setHolderStakes(components.joinToString(cbSeparator)) |
|
||||||
} |
|
||||||
|
|
||||||
fun defineHighestBet() { |
|
||||||
val bets = arrayListOf<Double>() |
|
||||||
this.ante?.let { bets.add(it) } |
|
||||||
bets.addAll(this.blindValues) |
|
||||||
setHolderBiggestBet(bets.maxOrNull()) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,28 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.interfaces |
|
||||||
|
|
||||||
import java.util.* |
|
||||||
|
|
||||||
/** |
|
||||||
* Interface to let an object be filtered through specific time parameters |
|
||||||
*/ |
|
||||||
interface TimeFilterable { |
|
||||||
|
|
||||||
var dayOfWeek : Int? |
|
||||||
var dayOfMonth : Int? |
|
||||||
var month : Int? |
|
||||||
var year : Int? |
|
||||||
|
|
||||||
/** |
|
||||||
* Call this method whenever the date of the object is modified |
|
||||||
*/ |
|
||||||
fun updateTimeParameter(startDate: Date?) { |
|
||||||
startDate?.let { |
|
||||||
val cal = Calendar.getInstance() |
|
||||||
cal.time = it |
|
||||||
this.dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) |
|
||||||
this.dayOfMonth = cal.get(Calendar.DAY_OF_MONTH) |
|
||||||
this.month = cal.get(Calendar.MONTH) |
|
||||||
this.year = cal.get(Calendar.YEAR) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,34 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.interfaces |
|
||||||
|
|
||||||
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
interface GraphIdentifiableEntry : GraphUnderlyingEntry, Identifiable |
|
||||||
|
|
||||||
interface Timed : GraphIdentifiableEntry { |
|
||||||
|
|
||||||
fun startDate() : Date? |
|
||||||
|
|
||||||
fun endDate() : Date |
|
||||||
|
|
||||||
var breakDuration: Long |
|
||||||
|
|
||||||
var pauseDate: Date? |
|
||||||
|
|
||||||
var netDuration: Long |
|
||||||
|
|
||||||
/** |
|
||||||
* Computes the net netDuration of the session |
|
||||||
*/ |
|
||||||
fun computeNetDuration() { |
|
||||||
this.startDate()?.let { start -> |
|
||||||
this.netDuration = this.endDate().time - start.time - this.breakDuration |
|
||||||
} ?: run { |
|
||||||
this.netDuration = 0L |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val hourlyDuration: Double |
|
||||||
get() = this.netDuration / 3600000.0 |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,14 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.interfaces |
|
||||||
|
|
||||||
import io.realm.RealmModel |
|
||||||
|
|
||||||
/** |
|
||||||
* An interface to be able to track the usage of an object |
|
||||||
*/ |
|
||||||
interface UsageCountable : Identifiable { |
|
||||||
var useCount: Int |
|
||||||
get() { return 0 } |
|
||||||
set(_) {} |
|
||||||
|
|
||||||
val ownerClass: Class<out RealmModel> |
|
||||||
} |
|
||||||
@ -1,234 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.migrations |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.kotlin.where |
|
||||||
import net.pokeranalytics.android.PokerAnalyticsApplication |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.realm.* |
|
||||||
import net.pokeranalytics.android.model.realm.handhistory.HandHistory |
|
||||||
import net.pokeranalytics.android.model.utils.Seed |
|
||||||
import net.pokeranalytics.android.model.utils.SessionSetManager |
|
||||||
import net.pokeranalytics.android.util.BLIND_SEPARATOR |
|
||||||
import net.pokeranalytics.android.util.Preferences |
|
||||||
import java.text.NumberFormat |
|
||||||
|
|
||||||
class Patcher { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
fun patchAll(application: PokerAnalyticsApplication) { |
|
||||||
|
|
||||||
val context = application.applicationContext |
|
||||||
|
|
||||||
// NOTE: it's more than possible that at one point many patches become redundant |
|
||||||
// with each other |
|
||||||
|
|
||||||
patchMissingTransactionTypes(context) |
|
||||||
|
|
||||||
Preferences.executeOnce(Preferences.Keys.PATCH_COMPUTABLE_RESULTS, context) { |
|
||||||
patchComputableResults() |
|
||||||
} |
|
||||||
Preferences.executeOnce(Preferences.Keys.PATCH_SESSION_SETS, context) { |
|
||||||
patchSessionSet() |
|
||||||
} |
|
||||||
Preferences.executeOnce(Preferences.Keys.PATCH_BREAK, context) { |
|
||||||
patchBreaks() |
|
||||||
} |
|
||||||
Preferences.executeOnce(Preferences.Keys.PATCH_TRANSACTION_TYPES_NAMES, context) { |
|
||||||
patchDefaultTransactionTypes(context) |
|
||||||
} |
|
||||||
Preferences.executeOnce(Preferences.Keys.PATCH_STAKES, context) { |
|
||||||
patchStakes() |
|
||||||
} |
|
||||||
Preferences.executeOnce(Preferences.Keys.PATCH_NEGATIVE_LIMITS, context) { |
|
||||||
patchNegativeLimits() |
|
||||||
} |
|
||||||
Preferences.executeOnce(Preferences.Keys.CLEAN_BLINDS_FILTERS, context) { |
|
||||||
cleanBlindsFilters() |
|
||||||
} |
|
||||||
|
|
||||||
Preferences.executeOnce(Preferences.Keys.ADD_NEW_TRANSACTION_TYPES, context) { |
|
||||||
patchMissingTransactionTypes(context) |
|
||||||
} |
|
||||||
|
|
||||||
Preferences.executeOnce(Preferences.Keys.PATCH_ZERO_TABLE, context) { |
|
||||||
patchZeroTable() |
|
||||||
} |
|
||||||
|
|
||||||
Preferences.executeOnce(Preferences.Keys.PATCH_RATED_AMOUNT, context) { |
|
||||||
patchRatedAmounts() |
|
||||||
} |
|
||||||
|
|
||||||
patchPerformances(application) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun patchMissingTransactionTypes(context: Context) { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
|
|
||||||
val transactionTypes = TransactionType.Value.values() |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
Seed.createDefaultTransactionTypes(transactionTypes, context, realm) |
|
||||||
} |
|
||||||
|
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
private fun patchBreaks() { |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
val sets = realm.where(SessionSet::class.java).findAll() |
|
||||||
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.IsCash)) |
|
||||||
val results = realm.where(Result::class.java).findAll() |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
sets.forEach { |
|
||||||
it.computeStats() |
|
||||||
} |
|
||||||
sessions.forEach { |
|
||||||
it.generateStakes() |
|
||||||
it.defineHighestBet() |
|
||||||
} |
|
||||||
results.forEach { |
|
||||||
it.computeNumberOfRebuy() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
realm.close() |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
private fun patchDefaultTransactionTypes(context: Context) { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
realm.executeTransaction { |
|
||||||
val tts = realm.where(TransactionType::class.java).findAll() |
|
||||||
tts.forEach { tt -> |
|
||||||
tt.kind?.let { kind -> |
|
||||||
val value = TransactionType.Value.values()[kind] |
|
||||||
tt.name = value.localizedTitle(context) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
private fun patchStakes() { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
realm.executeTransaction { |
|
||||||
val sessions = realm.where(Session::class.java).findAll() |
|
||||||
sessions.forEach { session -> |
|
||||||
val blinds = arrayListOf(session.cgOldSmallBlind, session.cgOldBigBlind).filterNotNull() |
|
||||||
val blindsFormatted = blinds.map { NumberFormat.getInstance().format(it) } |
|
||||||
session.cgAnte = null |
|
||||||
if (blindsFormatted.isNotEmpty()) { |
|
||||||
session.cgBlinds = blindsFormatted.joinToString(BLIND_SEPARATOR) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val handHistories = realm.where(HandHistory::class.java).findAll() |
|
||||||
handHistories.forEach { hh -> |
|
||||||
val blinds = arrayListOf(hh.oldSmallBlind, hh.oldBigBlind).filterNotNull() |
|
||||||
val blindsFormatted = blinds.map { NumberFormat.getInstance().format(it) } |
|
||||||
if (blindsFormatted.isNotEmpty()) { |
|
||||||
hh.blinds = blindsFormatted.joinToString(BLIND_SEPARATOR) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
private fun patchNegativeLimits() { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
realm.executeTransaction { |
|
||||||
val sessions = realm.where(Session::class.java).lessThan("limit", 0).findAll() |
|
||||||
sessions.forEach { session -> |
|
||||||
session.limit = null |
|
||||||
} |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
private fun cleanBlindsFilters() { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
realm.executeTransaction { |
|
||||||
val blindFilterConditions = realm.where(FilterCondition::class.java).equalTo("filterName", "AnyBlind").findAll() |
|
||||||
val filterIds = blindFilterConditions.mapNotNull { it.filters?.firstOrNull() }.map { it.id } |
|
||||||
val filters = realm.where(Filter::class.java).`in`("id", filterIds.toTypedArray()).findAll() |
|
||||||
filters.deleteAllFromRealm() |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
02/09/19: A bug with the session set management made them kept instead of deleted, |
|
||||||
thus making duration calculation wrong |
|
||||||
*/ |
|
||||||
private fun patchSessionSet() { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
|
|
||||||
realm.executeTransaction { |
|
||||||
realm.where(SessionSet::class.java).findAll().deleteAllFromRealm() |
|
||||||
val sessions = realm.where(Session::class.java).isNotNull("startDate").isNotNull("endDate").findAll() |
|
||||||
sessions.forEach { session -> |
|
||||||
SessionSetManager.updateTimeline(session) |
|
||||||
} |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
15/04/21: Two needs: |
|
||||||
- To get better performance for ITM Ratio, a positive session is a net >= 0 for cash game, or a cashedOut > 0 for tournaments |
|
||||||
- The field "ratedTips" is added |
|
||||||
*/ |
|
||||||
private fun patchComputableResults() { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
realm.executeTransaction { |
|
||||||
val crs = realm.where(ComputableResult::class.java).findAll() |
|
||||||
crs.forEach { cr -> |
|
||||||
cr.session?.let { cr.updateWith(it) } |
|
||||||
} |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
private fun patchPerformances(application: PokerAnalyticsApplication) { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
val sessionCount = realm.where<Session>().findAll().size |
|
||||||
val performanceCount = realm.where<Performance>().findAll().size |
|
||||||
|
|
||||||
if (sessionCount > 1 && performanceCount == 0) { |
|
||||||
application.reportWhistleBlower?.requestReportLaunch() |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
private fun patchZeroTable() { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
val zero = 0 |
|
||||||
val sessions = realm.where<Session>().equalTo("numberOfTables", zero).findAll() |
|
||||||
realm.executeTransaction { |
|
||||||
sessions.forEach { s -> |
|
||||||
s.numberOfTables = 1 |
|
||||||
} |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
private fun patchRatedAmounts() { |
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
val transactions = realm.where<Transaction>().findAll() |
|
||||||
realm.executeTransaction { |
|
||||||
transactions.forEach { t -> |
|
||||||
t.computeRatedAmount() |
|
||||||
} |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,348 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.migrations |
|
||||||
|
|
||||||
import io.realm.DynamicRealm |
|
||||||
import io.realm.RealmMigration |
|
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import timber.log.Timber |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
|
|
||||||
class PokerAnalyticsMigration : RealmMigration { |
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { |
|
||||||
|
|
||||||
// DynamicRealm exposes an editable schema |
|
||||||
val schema = realm.schema |
|
||||||
|
|
||||||
var currentVersion = oldVersion.toInt() |
|
||||||
Timber.d("*** migrate from $oldVersion to $newVersion") |
|
||||||
|
|
||||||
// Migrate to version 1 |
|
||||||
if (currentVersion == 0) { |
|
||||||
Timber.d("*** Running migration 1") |
|
||||||
|
|
||||||
schema.get("Filter")?.addField("entityType", Int::class.java) |
|
||||||
?.setNullable("entityType", true) |
|
||||||
schema.get("FilterElement")?.let { |
|
||||||
it.setNullable("filterName", true) |
|
||||||
it.setNullable("sectionName", true) |
|
||||||
} |
|
||||||
schema.get("FilterElementBlind")?.renameField("code", "currencyCode") |
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 2 |
|
||||||
if (currentVersion == 1) { |
|
||||||
Timber.d("*** Running migration ${currentVersion + 1}") |
|
||||||
schema.rename("FilterElement", "FilterCondition") |
|
||||||
|
|
||||||
schema.get("Filter")?.renameField("filterElements", "filterConditions") |
|
||||||
|
|
||||||
schema.get("SessionSet")?.let { |
|
||||||
it.addField("id", String::class.java).setRequired("id", true) |
|
||||||
it.addPrimaryKey("id") |
|
||||||
} |
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 3 |
|
||||||
if (currentVersion == 2) { |
|
||||||
Timber.d("*** Running migration ${currentVersion + 1}") |
|
||||||
schema.rename("Report", "ReportSetup") |
|
||||||
|
|
||||||
schema.get("Filter")?.removeField("entityType") |
|
||||||
|
|
||||||
schema.get("Session")?.let { |
|
||||||
it.addField("blinds", String::class.java).transform { |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
schema.get("FilterCondition")?.let { |
|
||||||
it.removeField("blindValues") |
|
||||||
it.removeField("numericValues") |
|
||||||
|
|
||||||
it.addField("operator", Integer::class.java) |
|
||||||
it.addField("intValue", Integer::class.java) |
|
||||||
it.addRealmListField("intValues", Integer::class.java) |
|
||||||
it.addField("doubleValue", Double::class.java).setNullable("doubleValue", true) |
|
||||||
it.addRealmListField("doubleValues", Double::class.java) |
|
||||||
if (it.isRequired("doubleValues")) { |
|
||||||
it.setRequired("doubleValues", false) |
|
||||||
} |
|
||||||
it.addField("stringValue", String::class.java) |
|
||||||
} |
|
||||||
|
|
||||||
schema.get("ComputableResult")?.removeField("sessionSet") |
|
||||||
|
|
||||||
schema.get("Bankroll")?.addField("initialValue", Double::class.java) |
|
||||||
|
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 4 |
|
||||||
if (currentVersion == 3) { |
|
||||||
Timber.d("*** Running migration ${currentVersion + 1}") |
|
||||||
|
|
||||||
schema.get("Result")?.addField("numberOfRebuy", Double::class.java) |
|
||||||
?.setNullable("numberOfRebuy", true) |
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 5 |
|
||||||
if (currentVersion == 4) { |
|
||||||
Timber.d("*** Running migration ${currentVersion + 1}") |
|
||||||
schema.get("Bankroll")?.removeField("transactions") |
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 6 |
|
||||||
if (currentVersion == 5) { |
|
||||||
Timber.d("*** Running migration ${currentVersion + 1}") |
|
||||||
schema.get("Transaction")?.let { |
|
||||||
it.addField("dayOfWeek", Integer::class.java) |
|
||||||
it.addField("month", Integer::class.java) |
|
||||||
it.addField("year", Integer::class.java) |
|
||||||
it.addField("dayOfMonth", Integer::class.java) |
|
||||||
} |
|
||||||
|
|
||||||
val cfEntry = schema.create("CustomFieldEntry")?.let { |
|
||||||
it.addField("id", String::class.java).setRequired("id", true) |
|
||||||
it.addPrimaryKey("id") |
|
||||||
it.addField("value", String::class.java).setNullable("value", false) |
|
||||||
it.addField("order", Integer::class.java).setNullable("order", false) |
|
||||||
// it.addRealmObjectField("customField", it).setNullable("customField", false) |
|
||||||
it.addField("numericValue", Double::class.java).setNullable("numericValue", true) |
|
||||||
} |
|
||||||
|
|
||||||
cfEntry?.let { customFieldEntrySchema -> |
|
||||||
schema.get("CustomField")?.let { |
|
||||||
it.addField("type", Integer::class.java).setNullable("type", false) |
|
||||||
it.addField("duplicateValue", Boolean::class.java) |
|
||||||
it.addField("sortCondition", Integer::class.java) |
|
||||||
.setRequired("sortCondition", true) |
|
||||||
it.addRealmListField("entries", customFieldEntrySchema) |
|
||||||
} |
|
||||||
|
|
||||||
schema.get("Session")?.let { |
|
||||||
it.addField("startDateHourMinuteComponent", Double::class.java) |
|
||||||
.setNullable("startDateHourMinuteComponent", true) |
|
||||||
it.addField("endDateHourMinuteComponent", Double::class.java) |
|
||||||
.setNullable("endDateHourMinuteComponent", true) |
|
||||||
it.addRealmListField("customFieldEntries", customFieldEntrySchema) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
schema.get("ReportSetup")?.let { |
|
||||||
it.addRealmListField("statIds", Int::class.java).setNullable("statIds", true) |
|
||||||
it.addRealmListField("criteriaCustomFieldIds", String::class.java) |
|
||||||
it.addRealmListField("criteriaIds", Int::class.java) |
|
||||||
.setNullable("criteriaIds", true) |
|
||||||
it.removeField("filters") |
|
||||||
schema.get("Filter")?.let { filterSchema -> |
|
||||||
it.addRealmObjectField("filter", filterSchema) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
schema.get("Filter")?.addField("filterableTypeUniqueIdentifier", Integer::class.java) |
|
||||||
schema.get("Filter")?.addField("useCount", Int::class.java) |
|
||||||
schema.get("Filter")?.removeField("usageCount") |
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 7 |
|
||||||
if (currentVersion == 6) { |
|
||||||
Timber.d("*** Running migration ${currentVersion + 1}") |
|
||||||
schema.get("TransactionType")?.addField("useCount", Int::class.java) |
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 8 |
|
||||||
if (currentVersion == 7) { |
|
||||||
schema.create("Comment")?.let { commentSchema -> |
|
||||||
commentSchema.addField("id", String::class.java).setRequired("id", true) |
|
||||||
commentSchema.addPrimaryKey("id") |
|
||||||
commentSchema.addField("content", String::class.java).setRequired("content", true) |
|
||||||
commentSchema.addField("date", Date::class.java).setRequired("date", true) |
|
||||||
|
|
||||||
schema.get("Player")?.let { |
|
||||||
it.addField("summary", String::class.java).setRequired("summary", true) |
|
||||||
it.addField("color", Int::class.java).setNullable("color", true) |
|
||||||
it.addField("picture", String::class.java) |
|
||||||
it.addRealmListField("comments", commentSchema) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 9 |
|
||||||
if (currentVersion == 8) { |
|
||||||
schema.get("HandHistory")?.let { hhSchema -> |
|
||||||
|
|
||||||
schema.get("Session")?.let { sessionSchema -> |
|
||||||
sessionSchema.removeField("hands") |
|
||||||
hhSchema.addRealmObjectField("session", sessionSchema) |
|
||||||
} ?: throw PAIllegalStateException("Session schema not found") |
|
||||||
hhSchema.addField("smallBlind", Double::class.java).setRequired("smallBlind", false) |
|
||||||
hhSchema.addField("bigBlind", Double::class.java).setRequired("bigBlind", false) |
|
||||||
hhSchema.addField("ante", Double::class.java) |
|
||||||
hhSchema.addField("bigBlindAnte", Boolean::class.java) |
|
||||||
hhSchema.addField("numberOfPlayers", Int::class.java) |
|
||||||
hhSchema.addField("comment", String::class.java) |
|
||||||
hhSchema.addField("heroIndex", Int::class.java).setRequired("heroIndex", false) |
|
||||||
hhSchema.addField("dayOfWeek", Integer::class.java) |
|
||||||
hhSchema.addField("month", Integer::class.java) |
|
||||||
hhSchema.addField("year", Integer::class.java) |
|
||||||
hhSchema.addField("dayOfMonth", Integer::class.java) |
|
||||||
|
|
||||||
val cardSchema = schema.create("Card") |
|
||||||
cardSchema.addField("value", Int::class.java).setRequired("value", false) |
|
||||||
cardSchema.addField("suitIdentifier", Int::class.java) |
|
||||||
.setRequired("suitIdentifier", false) |
|
||||||
cardSchema.addField("index", Int::class.java) |
|
||||||
hhSchema.addRealmListField("board", cardSchema) |
|
||||||
|
|
||||||
val actionSchema = schema.create("Action") |
|
||||||
actionSchema.addField("streetIdentifier", Int::class.java) |
|
||||||
actionSchema.addField("index", Int::class.java) |
|
||||||
actionSchema.addField("position", Int::class.java) |
|
||||||
actionSchema.addField("typeIdentifier", Int::class.java) |
|
||||||
.setRequired("typeIdentifier", false) |
|
||||||
actionSchema.addField("amount", Double::class.java).setRequired("amount", false) |
|
||||||
actionSchema.addField("effectiveAmount", Double::class.java) |
|
||||||
actionSchema.addField("positionRemainingStack", Double::class.java) |
|
||||||
.setRequired("positionRemainingStack", false) |
|
||||||
hhSchema.addRealmListField("actions", actionSchema) |
|
||||||
|
|
||||||
val playerSetupSchema = schema.create("PlayerSetup") |
|
||||||
schema.get("Player")?.let { |
|
||||||
playerSetupSchema.addRealmObjectField("player", it) |
|
||||||
} ?: throw PAIllegalStateException("Session schema not found") |
|
||||||
playerSetupSchema.addField("position", Int::class.java) |
|
||||||
playerSetupSchema.addField("stack", Double::class.java).setRequired("stack", false) |
|
||||||
playerSetupSchema.addRealmListField("cards", cardSchema) |
|
||||||
hhSchema.addRealmListField("playerSetups", playerSetupSchema) |
|
||||||
|
|
||||||
val wonPotSchema = schema.create("WonPot") |
|
||||||
wonPotSchema.addField("position", Int::class.java) |
|
||||||
wonPotSchema.addField("amount", Double::class.java) |
|
||||||
hhSchema.addRealmListField("winnerPots", wonPotSchema) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 10 |
|
||||||
if (currentVersion == 9) { |
|
||||||
schema.get("Session")?.addField("handsCount", Int::class.java) |
|
||||||
?.setRequired("handsCount", false) |
|
||||||
|
|
||||||
schema.get("Session")?.transform { obj -> // otherwise we get a 0 default value |
|
||||||
obj.setNull("handsCount") |
|
||||||
} |
|
||||||
|
|
||||||
val configSchema = schema.create("UserConfig") |
|
||||||
configSchema.addField("id", String::class.java).setRequired("id", true) |
|
||||||
configSchema.addPrimaryKey("id") |
|
||||||
configSchema.addField("liveDealtHandsPerHour", Int::class.java) |
|
||||||
configSchema.addField("onlineDealtHandsPerHour", Int::class.java) |
|
||||||
|
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 11 |
|
||||||
if (currentVersion == 10) { |
|
||||||
schema.get("ComputableResult")?.addField("ratedTips", Double::class.java) |
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 12 |
|
||||||
if (currentVersion == 11) { |
|
||||||
|
|
||||||
schema.get("Session")?.let { ss -> |
|
||||||
|
|
||||||
ss.addField("cgAnte", Double::class.java) |
|
||||||
?.setNullable("cgAnte", true) |
|
||||||
ss.addField("cgBiggestBet", Double::class.java) |
|
||||||
?.setNullable("cgBiggestBet", true) |
|
||||||
ss.addField("cgStakes", String::class.java) |
|
||||||
ss.addField("cgBlinds", String::class.java) |
|
||||||
ss.removeField("blinds") |
|
||||||
|
|
||||||
ss.renameField("cgSmallBlind", "cgOldSmallBlind") |
|
||||||
ss.renameField("cgBigBlind", "cgOldBigBlind") |
|
||||||
} |
|
||||||
|
|
||||||
schema.get("HandHistory")?.let { hs -> |
|
||||||
hs.setNullable("ante", true) |
|
||||||
hs.addField("stakes", String::class.java) |
|
||||||
hs.addField("blinds", String::class.java) |
|
||||||
hs.addField("biggestBet", Double::class.java) |
|
||||||
?.setNullable("biggestBet", true) |
|
||||||
hs.renameField("smallBlind", "oldSmallBlind") |
|
||||||
hs.renameField("bigBlind", "oldBigBlind") |
|
||||||
} |
|
||||||
|
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 13 |
|
||||||
if (currentVersion == 12) { |
|
||||||
Timber.d("*** Running migration ${currentVersion + 1}") |
|
||||||
schema.get("Transaction")?.let { ts -> |
|
||||||
ts.addField("transferRate", Double::class.java) |
|
||||||
.setNullable("transferRate", true) |
|
||||||
schema.get("Bankroll")?.let { bs -> |
|
||||||
ts.addRealmObjectField("destination", bs) |
|
||||||
} ?: throw PAIllegalStateException("Bankroll schema not found") |
|
||||||
} |
|
||||||
|
|
||||||
schema.create("Performance")?.let { ps -> |
|
||||||
ps.addField("id", String::class.java).setRequired("id", true) |
|
||||||
ps.addPrimaryKey("id") |
|
||||||
ps.addField("reportId", Int::class.java) |
|
||||||
ps.addField("key", Int::class.java) |
|
||||||
ps.addField("name", String::class.java) |
|
||||||
ps.addField("objectId", String::class.java)//.setNullable("objectId", true) |
|
||||||
ps.addField("customFieldId", String::class.java)//.setNullable("customFieldId", true) |
|
||||||
ps.addField("value", Double::class.java).setRequired("value", false) //.setNullable("value", true) |
|
||||||
} |
|
||||||
|
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
// Migrate to version 14 |
|
||||||
if (currentVersion == 13) { |
|
||||||
Timber.d("*** Running migration ${currentVersion + 1}") |
|
||||||
schema.get("Transaction")?.let { ts -> |
|
||||||
ts.addField("ratedAmount", Double::class.java) |
|
||||||
} ?: throw PAIllegalStateException("Transaction schema not found") |
|
||||||
|
|
||||||
//transactionTypeIds |
|
||||||
schema.get("UserConfig")?.let { ucs -> |
|
||||||
ucs.addField("transactionTypeIds", String::class.java).setRequired("transactionTypeIds", true) |
|
||||||
} ?: throw PAIllegalStateException("UserConfig schema not found") |
|
||||||
|
|
||||||
schema.get("Performance")?.let { ps -> |
|
||||||
if (!ps.isPrimaryKey("id")) { |
|
||||||
ps.addPrimaryKey("id") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
currentVersion++ |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean { |
|
||||||
return other is RealmMigration |
|
||||||
} |
|
||||||
|
|
||||||
override fun hashCode(): Int { |
|
||||||
return RealmMigration::javaClass.hashCode() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,176 +1,96 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
import android.content.Context |
import android.text.InputType |
||||||
import io.realm.Realm |
import io.realm.Realm |
||||||
|
import io.realm.RealmList |
||||||
import io.realm.RealmObject |
import io.realm.RealmObject |
||||||
import io.realm.RealmResults |
|
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.LinkingObjects |
|
||||||
import io.realm.annotations.PrimaryKey |
import io.realm.annotations.PrimaryKey |
||||||
import io.realm.kotlin.where |
import net.pokeranalytics.android.model.ObjectSavable |
||||||
import net.pokeranalytics.android.R |
import net.pokeranalytics.android.ui.adapter.components.LiveDataDataSource |
||||||
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus |
import net.pokeranalytics.android.ui.adapter.components.RowRepresentableDataSource |
||||||
import net.pokeranalytics.android.model.interfaces.Identifiable |
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData |
||||||
import net.pokeranalytics.android.model.interfaces.NameManageable |
import net.pokeranalytics.android.ui.view.BankrollRow |
||||||
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus |
import net.pokeranalytics.android.ui.view.RowEditable |
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
import net.pokeranalytics.android.ui.view.RowUpdatable |
import net.pokeranalytics.android.ui.view.SimpleRow |
||||||
import net.pokeranalytics.android.ui.view.rows.BankrollPropertiesRow |
|
||||||
import net.pokeranalytics.android.ui.view.rows.SessionPropertiesRow |
|
||||||
import net.pokeranalytics.android.ui.view.rows.SimpleRow |
|
||||||
import net.pokeranalytics.android.util.Preferences |
|
||||||
import net.pokeranalytics.android.util.UserDefaults |
|
||||||
import java.util.* |
import java.util.* |
||||||
|
import kotlin.collections.ArrayList |
||||||
|
|
||||||
enum class ResultCaptureType { |
open class Bankroll(name: String = "") : RealmObject(), RowRepresentableDataSource, |
||||||
|
RowEditable, ObjectSavable { |
||||||
BUYIN_CASHED_OUT, |
|
||||||
NET_RESULT; |
|
||||||
|
|
||||||
companion object { |
companion object { |
||||||
val buyinCashedOutFields = listOf( |
fun newInstance() : Bankroll { |
||||||
SessionPropertiesRow.CASHED_OUT, |
var bankroll: Bankroll = Bankroll() |
||||||
SessionPropertiesRow.BUY_IN, |
return bankroll |
||||||
SessionPropertiesRow.TIPS) |
|
||||||
val netResultFields = listOf(SessionPropertiesRow.NET_RESULT) |
|
||||||
} |
|
||||||
|
|
||||||
val rowRepresentables: List<RowRepresentable> |
|
||||||
get() { |
|
||||||
return when (this) { |
|
||||||
BUYIN_CASHED_OUT -> buyinCashedOutFields |
|
||||||
NET_RESULT -> netResultFields |
|
||||||
} |
} |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
open class Bankroll : RealmObject(), NameManageable, RowUpdatable, RowRepresentable { |
|
||||||
|
|
||||||
@PrimaryKey |
@PrimaryKey |
||||||
override var id = UUID.randomUUID().toString() |
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
override var name: String = "" |
// the name of the bankroll |
||||||
|
var name: String = name |
||||||
|
|
||||||
// Indicates whether the bankroll is live or online |
// Indicates whether the bankroll is live or online |
||||||
var live: Boolean = true |
var live: Boolean = true |
||||||
|
|
||||||
/** |
// The list of transactions of the bankroll |
||||||
* The list of transactions of the bankroll |
var transactions: RealmList<Transaction> = RealmList() |
||||||
*/ |
|
||||||
@LinkingObjects("bankroll") |
|
||||||
val transactions: RealmResults<Transaction>? = null |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of transactions where the bankroll is the destination |
|
||||||
*/ |
|
||||||
@LinkingObjects("destination") |
|
||||||
val destinationTransactions: RealmResults<Transaction>? = null |
|
||||||
|
|
||||||
// The currency of the bankroll |
// The currency of the bankroll |
||||||
var currency: Currency? = null |
var currency: Currency? = null |
||||||
|
|
||||||
// The initial value of the bankroll |
// @todo rate management |
||||||
var initialValue: Double = 0.0 |
|
||||||
|
|
||||||
val rate: Double |
|
||||||
get() { |
|
||||||
return this.currency?.rate ?: Currency.DEFAULT_RATE |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return this.name |
|
||||||
} |
|
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
override fun uniqueIdentifier(): String { |
||||||
return realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty() |
return this.id |
||||||
&& realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty() |
|
||||||
} |
} |
||||||
|
|
||||||
override fun getDeleteStatus(context: Context, realm: Realm): DeleteValidityStatus { |
override val adapterRows: ArrayList<RowRepresentable> |
||||||
return if (!realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty()) { |
get() { |
||||||
DeleteValidityStatus.SESSIONS_LINKED |
val rows = ArrayList<RowRepresentable>() |
||||||
} else if (!realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty()) { |
rows.add(SimpleRow.NAME) |
||||||
DeleteValidityStatus.TRANSACTIONS_LINKED |
rows.addAll(BankrollRow.values()) |
||||||
} else { |
return rows |
||||||
DeleteValidityStatus.VALID |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
|
||||||
return when (status) { |
|
||||||
DeleteValidityStatus.SESSIONS_LINKED -> R.string.bankroll_relationship_error |
|
||||||
DeleteValidityStatus.TRANSACTIONS_LINKED -> R.string.bankroll_relationship_error_transactions |
|
||||||
else -> super.getFailedDeleteMessage(status) |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
override fun stringForRow(row: RowRepresentable): String { |
||||||
return when (status) { |
return when (row) { |
||||||
SaveValidityStatus.DATA_INVALID -> R.string.empty_name_for_br_error |
SimpleRow.NAME -> this.name |
||||||
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_bankroll_name_error |
BankrollRow.LIVE -> if (this.live) "live" else "online" |
||||||
else -> super.getFailedSaveMessage(status) |
BankrollRow.CURRENCY -> this.currency?.code?: "" |
||||||
|
else -> return super.stringForRow(row) |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
fun resultCaptureType(context: Context): ResultCaptureType { |
override fun boolForRow(row: RowRepresentable): Boolean { |
||||||
return Preferences.getResultCaptureType(this, context) |
when (row) { |
||||||
?: run { |
BankrollRow.LIVE -> return true |
||||||
when (this.live) { |
else -> return super.boolForRow(row) |
||||||
true -> ResultCaptureType.BUYIN_CASHED_OUT |
|
||||||
else -> ResultCaptureType.NET_RESULT |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
companion object { |
override fun getBottomSheetData(row: RowRepresentable): ArrayList<BottomSheetData> { |
||||||
|
val data = java.util.ArrayList<BottomSheetData>() |
||||||
fun getOrCreate(realm: Realm, name: String, live: Boolean = true, currencyCode: String? = null, currencyRate: Double? = null) : Bankroll { |
when (row) { |
||||||
|
SimpleRow.NAME -> data.add(BottomSheetData(this.name, SimpleRow.NAME.resId, InputType.TYPE_CLASS_TEXT)) |
||||||
val br = realm.where<Bankroll>().equalTo("name", name).findFirst() |
|
||||||
return if (br != null) { |
|
||||||
br |
|
||||||
} else { |
|
||||||
val bankroll = Bankroll() |
|
||||||
bankroll.name = name |
|
||||||
bankroll.live = live |
|
||||||
val currency = Currency() |
|
||||||
currency.code = currencyCode |
|
||||||
currency.rate = currencyRate |
|
||||||
bankroll.currency = currency |
|
||||||
realm.copyToRealm(bankroll) |
|
||||||
} |
|
||||||
} |
} |
||||||
|
return data |
||||||
} |
} |
||||||
|
|
||||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||||
when (row) { |
when (row) { |
||||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||||
BankrollPropertiesRow.ONLINE -> { |
|
||||||
this.live = if (value is Boolean) !value else false |
|
||||||
} |
|
||||||
BankrollPropertiesRow.INITIAL_VALUE -> { |
|
||||||
this.initialValue = value as Double? ?: 0.0 |
|
||||||
} |
|
||||||
BankrollPropertiesRow.CURRENCY -> { |
|
||||||
//TODO handle a use default currency option |
|
||||||
this.currency?.code = value as String? |
|
||||||
} |
|
||||||
BankrollPropertiesRow.RATE -> { |
|
||||||
this.currency?.rate = value as Double? |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
val utilCurrency: java.util.Currency |
override fun isValidForSave(): Boolean { |
||||||
get() { |
val realm = Realm.getDefaultInstance() |
||||||
this.currency?.code?.let { |
return (realm.where(Bankroll::class.java) |
||||||
return java.util.Currency.getInstance(it) |
.notEqualTo("id", this.id) |
||||||
} |
.equalTo("name", this.name) |
||||||
return UserDefaults.currency |
.findAll().size == 0) |
||||||
|
return this.name.isNotEmpty() |
||||||
} |
} |
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = Bankroll::class.java |
|
||||||
|
|
||||||
} |
} |
||||||
@ -1,79 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmObject |
|
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.PrimaryKey |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.exceptions.ModelException |
|
||||||
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus |
|
||||||
import net.pokeranalytics.android.model.interfaces.Identifiable |
|
||||||
import net.pokeranalytics.android.model.interfaces.Manageable |
|
||||||
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus |
|
||||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowUpdatable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
open class Comment : RealmObject(), Manageable, RowRepresentable, RowUpdatable { |
|
||||||
|
|
||||||
@PrimaryKey |
|
||||||
override var id = UUID.randomUUID().toString() |
|
||||||
var content: String = "" |
|
||||||
var date: Date = Date() |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = Comment::class.java |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val viewType: Int = RowViewType.CONTENT.ordinal |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val bottomSheetType: BottomSheetType = BottomSheetType.EDIT_TEXT_MULTI_LINES |
|
||||||
|
|
||||||
// @Ignore |
|
||||||
// override val valueCanBeClearedWhenEditing: Boolean = false |
|
||||||
|
|
||||||
override fun localizedTitle(context: Context): String { |
|
||||||
return context.getString(R.string.comment) |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return if (content.isNotEmpty()) content else NULL_TEXT |
|
||||||
} |
|
||||||
|
|
||||||
// override fun startEditing(dataSource: Any?, parent: Fragment?) { |
|
||||||
// if (parent == null) return |
|
||||||
// if (parent !is RowRepresentableDelegate) return |
|
||||||
// val data = RowEditableDataSource() |
|
||||||
// data.append(this.content, R.string.value) |
|
||||||
// InputFragment.buildAndShow(this, parent, data, isDeletable = true) |
|
||||||
// } |
|
||||||
|
|
||||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
|
||||||
this.content = value as String? ?: "" |
|
||||||
} |
|
||||||
|
|
||||||
override fun isValidForSave(): Boolean { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
override fun alreadyExists(realm: Realm): Boolean { |
|
||||||
return realm.where(this::class.java).notEqualTo("id", this.id).findAll().isNotEmpty() |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
|
||||||
throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled") |
|
||||||
} |
|
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
|
||||||
return R.string.cf_entry_delete_popup_message |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,68 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import io.realm.RealmObject |
|
||||||
import net.pokeranalytics.android.model.filter.Filterable |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
|
|
||||||
open class ComputableResult : RealmObject(), Filterable { |
|
||||||
|
|
||||||
var ratedNet: Double = 0.0 |
|
||||||
|
|
||||||
var bbNet: BB = 0.0 |
|
||||||
|
|
||||||
var hasBigBlind: Int = 0 |
|
||||||
|
|
||||||
var isPositive: Int = 0 |
|
||||||
|
|
||||||
var ratedBuyin: Double = 0.0 |
|
||||||
|
|
||||||
var estimatedHands: Double = 0.0 |
|
||||||
|
|
||||||
var bbPer100Hands: BB = 0.0 |
|
||||||
|
|
||||||
var session: Session? = null |
|
||||||
|
|
||||||
var ratedTips: Double = 0.0 |
|
||||||
|
|
||||||
fun updateWith(session: Session) { |
|
||||||
|
|
||||||
val rate = session.bankroll?.currency?.rate ?: 1.0 |
|
||||||
|
|
||||||
session.result?.let { result -> |
|
||||||
this.ratedNet = result.net * rate |
|
||||||
this.isPositive = result.isPositive |
|
||||||
this.ratedBuyin = (result.buyin ?: 0.0) * rate |
|
||||||
this.ratedTips = (result.tips ?: 0.0) * rate |
|
||||||
} |
|
||||||
|
|
||||||
this.bbNet = session.bbNet |
|
||||||
this.hasBigBlind = if (session.cgBiggestBet != null) 1 else 0 |
|
||||||
this.estimatedHands = session.estimatedHands |
|
||||||
this.bbPer100Hands = |
|
||||||
session.bbNet / (session.numberOfHandsPerHour * session.hourlyDuration) * 100 |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
enum class Field(val identifier: String) { |
|
||||||
RATED_NET("ratedNet"), |
|
||||||
BB_NET("bbNet"), |
|
||||||
HAS_BIG_BLIND("hasBigBlind"), |
|
||||||
IS_POSITIVE("isPositive"), |
|
||||||
RATED_BUYIN("ratedBuyin"), |
|
||||||
ESTIMATED_HANDS("estimatedHands"), |
|
||||||
RATED_TIPS("ratedTips"), |
|
||||||
// BB_PER100HANDS("bbPer100Hands") |
|
||||||
} |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
fun fieldNameForQueryType(queryCondition: Class<out QueryCondition>): String? { |
|
||||||
Session.fieldNameForQueryType(queryCondition)?.let { |
|
||||||
return "session.$it" |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,68 +1,19 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
import io.realm.RealmObject |
import io.realm.RealmObject |
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.PrimaryKey |
import io.realm.annotations.PrimaryKey |
||||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
|
||||||
import net.pokeranalytics.android.util.UserDefaults |
|
||||||
import java.util.* |
import java.util.* |
||||||
|
|
||||||
open class Currency : RealmObject() { |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
@Ignore |
|
||||||
val DEFAULT_RATE: Double = 1.0 |
|
||||||
|
|
||||||
} |
open class Currency : RealmObject() { |
||||||
|
|
||||||
@PrimaryKey |
@PrimaryKey |
||||||
var id = UUID.randomUUID().toString() |
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
/** |
// The currency code of the currency, i.e. USD, EUR... |
||||||
* The currency code of the currency, i.e. USD, EUR... |
|
||||||
*/ |
|
||||||
var code: String? = null |
var code: String? = null |
||||||
set(value) { |
|
||||||
try { |
|
||||||
if (value != null) { |
|
||||||
java.util.Currency.getInstance(value) // test validity of code |
|
||||||
} |
|
||||||
field = value |
|
||||||
} catch (e: Exception) { |
|
||||||
// make app crash earlier than later, possibly to show an error message to the user in the future |
|
||||||
throw PAIllegalStateException(e.localizedMessage ?: e.toString()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The rate of the currency with the main currency |
|
||||||
*/ |
|
||||||
var rate: Double? = DEFAULT_RATE |
|
||||||
|
|
||||||
fun refreshRelatedRatedValues() { |
|
||||||
|
|
||||||
val rate = this.rate ?: DEFAULT_RATE |
|
||||||
val query = this.realm.where(ComputableResult::class.java) |
|
||||||
query.`in`("session.bankroll.currency.id", arrayOf(this.id)) |
|
||||||
val cResults = query.findAll() |
|
||||||
cResults.forEach { computable -> |
|
||||||
computable.session?.result?.net?.let { |
|
||||||
computable.ratedNet = it * rate |
|
||||||
} |
|
||||||
computable.session?.result?.buyin?.let { |
|
||||||
computable.ratedBuyin = it * rate |
|
||||||
} |
|
||||||
|
|
||||||
computable.session?.bankrollHasBeenUpdated() |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
fun hasMainCurrencyCode() : Boolean { |
// The rate of the currency with the main currency |
||||||
this.code?.let { return it == UserDefaults.currency.currencyCode } |
var rate: Double? = null |
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
} |
} |
||||||
@ -1,347 +1,19 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
import android.content.Context |
|
||||||
import android.text.InputType |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmList |
|
||||||
import io.realm.RealmObject |
import io.realm.RealmObject |
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.PrimaryKey |
import io.realm.annotations.PrimaryKey |
||||||
import io.realm.kotlin.where |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.model.Criteria |
|
||||||
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus |
|
||||||
import net.pokeranalytics.android.model.interfaces.Identifiable |
|
||||||
import net.pokeranalytics.android.model.interfaces.NameManageable |
|
||||||
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus |
|
||||||
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource |
|
||||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor |
|
||||||
import net.pokeranalytics.android.ui.view.RowUpdatable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.ui.view.rows.CustomFieldPropertiesRow |
|
||||||
import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.rows.SimpleRow |
|
||||||
import net.pokeranalytics.android.util.enumerations.IntIdentifiable |
|
||||||
import timber.log.Timber |
|
||||||
import java.util.* |
import java.util.* |
||||||
import kotlin.collections.ArrayList |
|
||||||
|
|
||||||
|
|
||||||
open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameManageable, StaticRowRepresentableDataSource { |
open class CustomField : RealmObject() { |
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
fun getOrCreate(realm: Realm, name: String, type: Int): CustomField { |
|
||||||
val cf = realm.where(CustomField::class.java).equalTo("name", name).findFirst() |
|
||||||
return if (cf != null) { |
|
||||||
cf |
|
||||||
} else { |
|
||||||
val customField = CustomField() |
|
||||||
customField.name = name |
|
||||||
customField.type = type |
|
||||||
realm.copyToRealm(customField) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = CustomField::class.java |
|
||||||
|
|
||||||
/** |
|
||||||
* The custom field type: a list of items, a number or an amont |
|
||||||
*/ |
|
||||||
enum class Type(override var uniqueIdentifier: Int, var resId: Int, var isEnabled: Boolean = true) : |
|
||||||
IntIdentifiable { |
|
||||||
LIST(0, R.string.enum_custom_field_type), |
|
||||||
NUMBER(1, R.string.number), |
|
||||||
AMOUNT(2, R.string.amount) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The sorting used for the list, either custom, or alphabetically asc/desc |
|
||||||
*/ |
|
||||||
enum class Sort(override var uniqueIdentifier: Int) : IntIdentifiable { |
|
||||||
DEFAULT(0), |
|
||||||
ASCENDING(1), |
|
||||||
DESCENDING(2) |
|
||||||
} |
|
||||||
|
|
||||||
@PrimaryKey |
@PrimaryKey |
||||||
override var id = UUID.randomUUID().toString() |
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
/** |
|
||||||
* The name of the custom field |
|
||||||
*/ |
|
||||||
override var name: String = "" |
|
||||||
|
|
||||||
// The type of the custom fields, mapped with the CustomField.Type enum |
|
||||||
var type: Int = Type.LIST.uniqueIdentifier |
|
||||||
set(value) { |
|
||||||
if (field == Type.LIST.uniqueIdentifier && value != Type.LIST.uniqueIdentifier) { |
|
||||||
this.entriesToDelete.addAll(this.entries) |
|
||||||
this.entries.clear() |
|
||||||
} |
|
||||||
field = value |
|
||||||
|
|
||||||
this.updateRowRepresentation() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Indicates whether the custom field value should be copied when a session is duplicated |
|
||||||
*/ |
|
||||||
var duplicateValue: Boolean = false |
|
||||||
|
|
||||||
/** |
|
||||||
* The list of entries for the LIST type |
|
||||||
*/ |
|
||||||
var entries: RealmList<CustomFieldEntry> = RealmList() |
|
||||||
|
|
||||||
/** |
|
||||||
* The sorting of the entries, mapped with the CustomField.Sort enum |
|
||||||
*/ |
|
||||||
var sortCondition: Int = Sort.DEFAULT.uniqueIdentifier |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
sortEntries() |
|
||||||
updateRowRepresentation() |
|
||||||
} |
|
||||||
|
|
||||||
@Ignore |
|
||||||
private var entriesToDelete: ArrayList<CustomFieldEntry> = ArrayList() |
|
||||||
|
|
||||||
@Ignore |
|
||||||
private var rowRepresentation: List<RowRepresentable> = mutableListOf() |
|
||||||
|
|
||||||
//helper |
|
||||||
|
|
||||||
val isListType: Boolean |
|
||||||
get() { |
|
||||||
return this.type == Type.LIST.uniqueIdentifier |
|
||||||
} |
|
||||||
|
|
||||||
val isAmountType: Boolean |
|
||||||
get() { |
|
||||||
return this.type == Type.AMOUNT.uniqueIdentifier |
|
||||||
} |
|
||||||
|
|
||||||
override fun adapterRows(): List<RowRepresentable>? { |
|
||||||
return rowRepresentation |
|
||||||
} |
|
||||||
|
|
||||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
|
||||||
when (row) { |
|
||||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
|
||||||
CustomFieldPropertiesRow.TYPE -> this.type = (value as Type?)?.uniqueIdentifier ?: Type.LIST.uniqueIdentifier |
|
||||||
CustomFieldPropertiesRow.COPY_ON_DUPLICATE -> this.duplicateValue = value as Boolean? ?: false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun isValidForSave(): Boolean { |
|
||||||
return super.isValidForSave() |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
|
||||||
return when (status) { |
|
||||||
SaveValidityStatus.DATA_INVALID -> R.string.cf_empty_field_error |
|
||||||
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_cf_error |
|
||||||
else -> super.getFailedSaveMessage(status) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun alreadyExists(realm: Realm): Boolean { |
|
||||||
return realm.where(this::class.java).equalTo("name", this.name).and().notEqualTo("id", this.id).findAll() |
|
||||||
.isNotEmpty() |
|
||||||
} |
|
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
|
||||||
return true |
|
||||||
// val sessions = realm.where<Session>().contains("customFieldEntries.customFields.id", id).findAll() |
|
||||||
// return sessions.isEmpty() |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
|
||||||
//TODO: |
|
||||||
return R.string.cf_entry_delete_popup_message |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
override fun deleteDependencies(realm: Realm) { |
|
||||||
if (isValid) { |
|
||||||
val entries = realm.where<CustomFieldEntry>().equalTo("customFields.id", id).findAll() |
|
||||||
entries.deleteAllFromRealm() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? { |
|
||||||
return when (row) { |
|
||||||
is CustomFieldEntry -> row.editingDescriptors( |
|
||||||
mapOf( |
|
||||||
"defaultValue" to row.value |
|
||||||
) |
|
||||||
) |
|
||||||
else -> null |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Update the row representation |
|
||||||
*/ |
|
||||||
private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> { |
|
||||||
val rows = ArrayList<RowRepresentable>() |
|
||||||
rows.add(SimpleRow.NAME) |
|
||||||
rows.add(CustomFieldPropertiesRow.TYPE) |
|
||||||
|
|
||||||
if (type == Type.LIST.uniqueIdentifier && entries.size >= 0) { |
|
||||||
if (entries.isNotEmpty()) { |
|
||||||
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, R.string.items_list)) |
|
||||||
sortEntries() |
|
||||||
entries.forEach { customFieldEntry -> |
|
||||||
customFieldEntry.isMovable = sortCondition == Sort.DEFAULT.uniqueIdentifier |
|
||||||
} |
|
||||||
rows.addAll(entries) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return rows |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sort the entries element |
|
||||||
*/ |
|
||||||
private fun sortEntries() { |
|
||||||
when (sortCondition) { |
|
||||||
Sort.ASCENDING.uniqueIdentifier -> entries.sortBy { it.value } |
|
||||||
Sort.DESCENDING.uniqueIdentifier -> entries.sortByDescending { it.value } |
|
||||||
} |
|
||||||
entries.forEachIndexed { index, customFieldEntry -> |
|
||||||
customFieldEntry.order = index |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun updateRowRepresentation() { |
|
||||||
this.rowRepresentation = this.updatedRowRepresentationForCurrentState() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Add an entry |
|
||||||
*/ |
|
||||||
fun addEntry(): CustomFieldEntry { |
|
||||||
val entry = CustomFieldEntry() |
|
||||||
this.entries.add(entry) |
|
||||||
sortEntries() |
|
||||||
updateRowRepresentation() |
|
||||||
return entry |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Delete an entry |
|
||||||
*/ |
|
||||||
fun deleteEntry(entry: CustomFieldEntry) { |
|
||||||
entriesToDelete.add(entry) |
|
||||||
entries.remove(entry) |
|
||||||
|
|
||||||
sortEntries() |
|
||||||
updateRowRepresentation() |
|
||||||
} |
|
||||||
|
|
||||||
fun cleanupEntries() { // called when saving the custom field |
|
||||||
|
|
||||||
val realm = Realm.getDefaultInstance() |
|
||||||
realm.executeTransaction { |
|
||||||
this.entriesToDelete.forEach { // entries are out of realm |
|
||||||
realm.where<CustomFieldEntry>().equalTo("id", it.id).findFirst()?.deleteFromRealm() |
|
||||||
} |
|
||||||
} |
|
||||||
realm.close() |
|
||||||
this.entriesToDelete.clear() |
|
||||||
} |
|
||||||
|
|
||||||
fun getOrCreateEntry(realm: Realm, value: String): CustomFieldEntry { |
|
||||||
this.entries.find { it.value == value }?.let { |
|
||||||
Timber.d("L>> get") |
|
||||||
return it |
|
||||||
} ?: run { |
|
||||||
Timber.d("L>> create") |
|
||||||
val entry = realm.copyToRealm(CustomFieldEntry()) |
|
||||||
entry.value = value |
|
||||||
this.entries.add(entry) |
|
||||||
return entry |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Clean the entries if the type is not a list & remove the deleted entries from realm |
|
||||||
*/ |
|
||||||
// fun cleanEntries(realm: Realm) { |
|
||||||
// realm.executeTransaction { |
|
||||||
// |
|
||||||
// if (!isListType) { |
|
||||||
// entriesToDelete.addAll(entries) |
|
||||||
// entries.clear() |
|
||||||
// } |
|
||||||
// |
|
||||||
// // @TODO |
|
||||||
// entriesToDelete.forEach { |
|
||||||
// Timber.d("Delete entry: V=${it.value} N=${it.numericValue} / ID=${it.id}") |
|
||||||
// realm.where<CustomFieldEntry>().equalTo("id", it.id).findFirst()?.deleteFromRealm() |
|
||||||
// } |
|
||||||
// entriesToDelete.clear() |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns a comparison criteria based on this custom field |
|
||||||
*/ |
|
||||||
val criteria: Criteria |
|
||||||
get() { |
|
||||||
return when (this.type) { |
|
||||||
Type.LIST.uniqueIdentifier -> Criteria.ListCustomFields(this.id) |
|
||||||
else -> Criteria.ValueCustomFields(this.id) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override var viewType: Int = RowViewType.TITLE_VALUE_ARROW.ordinal |
|
||||||
|
|
||||||
override fun localizedTitle(context: Context): String { |
|
||||||
return this.name |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
// The name of the currency field |
||||||
return this.name |
var name: String = "" |
||||||
} |
|
||||||
|
|
||||||
override val bottomSheetType: BottomSheetType |
// @todo |
||||||
get() { |
|
||||||
return when (this.type) { |
|
||||||
Type.LIST.uniqueIdentifier -> BottomSheetType.LIST_STATIC |
|
||||||
else -> BottomSheetType.NUMERIC_TEXT |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor> { |
|
||||||
return when (this.type) { |
|
||||||
Type.LIST.uniqueIdentifier -> { |
|
||||||
val defaultValue: Any? by map |
|
||||||
val data: RealmList<CustomFieldEntry>? by map |
|
||||||
arrayListOf( |
|
||||||
RowRepresentableEditDescriptor(defaultValue, staticData = data) |
|
||||||
) |
|
||||||
} |
|
||||||
else -> { |
|
||||||
val defaultValue: Double? by map |
|
||||||
arrayListOf( |
|
||||||
RowRepresentableEditDescriptor( |
|
||||||
defaultValue, inputType = InputType.TYPE_CLASS_NUMBER |
|
||||||
or InputType.TYPE_NUMBER_FLAG_DECIMAL |
|
||||||
or InputType.TYPE_NUMBER_FLAG_SIGNED |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
} |
||||||
@ -1,153 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import android.text.InputType |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmObject |
|
||||||
import io.realm.RealmResults |
|
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.LinkingObjects |
|
||||||
import io.realm.annotations.PrimaryKey |
|
||||||
import io.realm.kotlin.where |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.exceptions.ModelException |
|
||||||
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus |
|
||||||
import net.pokeranalytics.android.model.interfaces.Identifiable |
|
||||||
import net.pokeranalytics.android.model.interfaces.NameManageable |
|
||||||
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus |
|
||||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor |
|
||||||
import net.pokeranalytics.android.ui.view.RowUpdatable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import net.pokeranalytics.android.util.extensions.toCurrency |
|
||||||
import java.text.NumberFormat |
|
||||||
import java.util.* |
|
||||||
import java.util.Currency |
|
||||||
|
|
||||||
|
|
||||||
open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable, RowUpdatable { |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = CustomFieldEntry::class.java |
|
||||||
|
|
||||||
@PrimaryKey |
|
||||||
override var id = UUID.randomUUID().toString() |
|
||||||
|
|
||||||
/** |
|
||||||
* The order in the list |
|
||||||
*/ |
|
||||||
var order: Int = 0 |
|
||||||
|
|
||||||
/** |
|
||||||
* The inverse relationship with CustomField |
|
||||||
*/ |
|
||||||
@LinkingObjects("entries") |
|
||||||
val customFields: RealmResults<CustomField>? = null |
|
||||||
|
|
||||||
val customField: CustomField? |
|
||||||
get() { |
|
||||||
return this.customFields?.first() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The string value of the entry |
|
||||||
*/ |
|
||||||
var value: String = "" |
|
||||||
|
|
||||||
/** |
|
||||||
* The numeric value of the entry |
|
||||||
*/ |
|
||||||
var numericValue: Double? = null |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override var name: String = value |
|
||||||
get() { return value } |
|
||||||
|
|
||||||
@Ignore |
|
||||||
var isMovable: Boolean = false |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val viewType: Int = RowViewType.TITLE_VALUE_ACTION.ordinal |
|
||||||
|
|
||||||
override val imageRes: Int? |
|
||||||
get() { |
|
||||||
return if (isMovable) R.drawable.ic_reorder else null |
|
||||||
} |
|
||||||
|
|
||||||
override val imageTint: Int? |
|
||||||
get() { |
|
||||||
return R.color.kaki |
|
||||||
} |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val bottomSheetType: BottomSheetType = BottomSheetType.EDIT_TEXT |
|
||||||
|
|
||||||
override fun localizedTitle(context: Context): String { |
|
||||||
return context.getString(R.string.value) |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return if (value.isNotEmpty()) value else NULL_TEXT |
|
||||||
} |
|
||||||
|
|
||||||
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? { |
|
||||||
val defaultValue: Any? by map |
|
||||||
return arrayListOf( |
|
||||||
RowRepresentableEditDescriptor(defaultValue, R.string.value, InputType.TYPE_CLASS_TEXT) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
override fun isValidForSave(): Boolean { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
override fun alreadyExists(realm: Realm): Boolean { |
|
||||||
return realm.where(this::class.java).notEqualTo("id", this.id).findAll().isNotEmpty() |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
|
||||||
throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled") |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
|
||||||
return R.string.cf_entry_delete_popup_message |
|
||||||
} |
|
||||||
|
|
||||||
override fun deleteDependencies(realm: Realm) { |
|
||||||
if (isValid) { |
|
||||||
val entries = realm.where<Session>().contains("customFieldEntries.id", id).findAll() |
|
||||||
entries.deleteAllFromRealm() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
|
||||||
this.value = value as String? ?: "" |
|
||||||
} |
|
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
|
||||||
if (realm.where<Session>().contains("customFieldEntries.id", id).findAll().isNotEmpty()) { |
|
||||||
return false |
|
||||||
} |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Return the amount |
|
||||||
*/ |
|
||||||
fun getFormattedValue(currency: Currency? = null): String { |
|
||||||
return when (customField?.type) { |
|
||||||
CustomField.Type.AMOUNT.uniqueIdentifier -> { |
|
||||||
numericValue?.toCurrency(currency) ?: run { NULL_TEXT } |
|
||||||
} |
|
||||||
CustomField.Type.NUMBER.uniqueIdentifier -> { |
|
||||||
NumberFormat.getInstance().format(this.numericValue) |
|
||||||
} |
|
||||||
else -> { |
|
||||||
value |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,202 +1,25 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
import android.content.Context |
import io.realm.MutableRealmInteger |
||||||
import io.realm.* |
import io.realm.RealmObject |
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.PrimaryKey |
import io.realm.annotations.PrimaryKey |
||||||
import io.realm.kotlin.where |
|
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.model.filter.Filterable |
|
||||||
import net.pokeranalytics.android.model.filter.Query |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.interfaces.* |
|
||||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType |
|
||||||
import net.pokeranalytics.android.ui.modules.filter.FilterableType |
|
||||||
import net.pokeranalytics.android.ui.view.* |
|
||||||
import net.pokeranalytics.android.ui.view.rows.FilterCategoryRow |
|
||||||
import net.pokeranalytics.android.ui.view.rows.FilterItemRow |
|
||||||
import timber.log.Timber |
|
||||||
import java.util.* |
import java.util.* |
||||||
|
|
||||||
/** |
//import net.pokeranalytics.android.FilterComponent |
||||||
* A [Filter] is the top level representation of the filtering system |
|
||||||
* 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(), RowRepresentable, RowUpdatable, Deletable, UsageCountable, ImageDecorator { |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = Filter::class.java |
|
||||||
|
|
||||||
companion object { |
|
||||||
|
|
||||||
// Create a new instance |
|
||||||
fun newInstance(filterableTypeUniqueIdentifier: Int): Filter { |
|
||||||
val filter = Filter() |
|
||||||
filter.filterableTypeUniqueIdentifier = filterableTypeUniqueIdentifier |
|
||||||
return filter |
|
||||||
} |
|
||||||
|
|
||||||
inline fun <reified T : Filterable> queryOn(realm: Realm, query: Query, sortField: String? = null): RealmResults<T> { |
|
||||||
val rootQuery = realm.where<T>() |
|
||||||
var realmQuery = query.queryWith(rootQuery) |
|
||||||
sortField?.let { |
|
||||||
realmQuery = realmQuery.sort(it) |
|
||||||
} |
|
||||||
return realmQuery.findAll() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override val viewType: Int |
|
||||||
get() = RowViewType.TITLE_VALUE_ACTION.ordinal |
|
||||||
override val imageRes: Int? |
|
||||||
get() = R.drawable.ic_outline_settings |
|
||||||
override val imageTint: Int? |
|
||||||
get() = R.color.green |
|
||||||
override val imageClickable: Boolean? |
|
||||||
get() = true |
|
||||||
|
|
||||||
|
open class Filter : RealmObject() { |
||||||
|
|
||||||
@PrimaryKey |
@PrimaryKey |
||||||
override var id = UUID.randomUUID().toString() |
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
// the queryWith name |
// the filter name |
||||||
var name: String = "" |
var name: String = "" |
||||||
get() { |
|
||||||
if (field.isEmpty()) { |
|
||||||
return this.query.defaultName |
|
||||||
} |
|
||||||
return field |
|
||||||
} |
|
||||||
|
|
||||||
override var useCount: Int = 0 |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val ownerClass: Class<out RealmModel> = Session::class.java |
|
||||||
|
|
||||||
var filterConditions: RealmList<FilterCondition> = RealmList() |
|
||||||
private set |
|
||||||
|
|
||||||
private var filterableTypeUniqueIdentifier: Int? = null |
|
||||||
|
|
||||||
val filterableType: FilterableType |
|
||||||
get() { |
|
||||||
this.filterableTypeUniqueIdentifier?.let { |
|
||||||
return FilterableType.valueByIdentifier(it) |
|
||||||
} |
|
||||||
return FilterableType.ALL |
|
||||||
} |
|
||||||
|
|
||||||
fun createOrUpdateFilterConditions(filterConditionRows: List<FilterItemRow>) { |
|
||||||
|
|
||||||
Timber.d("list of querys saving: ${filterConditionRows.map { it.queryCondition?.id }}") |
|
||||||
Timber.d("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}") |
|
||||||
|
|
||||||
filterConditionRows |
|
||||||
.mapNotNull { |
|
||||||
it.queryCondition?.groupId |
|
||||||
} |
|
||||||
.distinct() |
|
||||||
.forEach { groupId -> |
|
||||||
filterConditionRows |
|
||||||
.filter { |
|
||||||
it.queryCondition?.groupId == groupId |
|
||||||
} |
|
||||||
.apply { |
|
||||||
val conditions = this.mapNotNull { it.queryCondition } |
|
||||||
Timber.d("list of querys: ${conditions.map { it.id }}") |
|
||||||
val newFilterCondition = FilterCondition(conditions, this.first().filterSectionRow) |
|
||||||
val previousCondition = filterConditions.filter { |
|
||||||
it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator |
|
||||||
} |
|
||||||
filterConditions.removeAll(previousCondition) |
|
||||||
filterConditions.add(newFilterCondition) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun remove(filterCategoryRow: FilterCategoryRow) { |
|
||||||
val sections = filterCategoryRow.filterSectionRows.map { it.name } |
|
||||||
val savedSections = filterConditions.filter { sections.contains(it.sectionName) } |
|
||||||
this.filterConditions.removeAll(savedSections) |
|
||||||
} |
|
||||||
|
|
||||||
fun countBy(filterCategoryRow: FilterCategoryRow): Int { |
|
||||||
val sections = filterCategoryRow.filterSectionRows.map { it.name } |
|
||||||
Timber.d("list of sections $sections") |
|
||||||
val savedSections = filterConditions.filter { sections.contains(it.sectionName) }.flatMap { it.queryCondition.id } |
|
||||||
Timber.d("list of savedSections $savedSections") |
|
||||||
return savedSections.size |
|
||||||
} |
|
||||||
|
|
||||||
fun contains(filterElementRow: QueryCondition): Boolean { |
|
||||||
Timber.d("list of saved queries ${filterConditions.map { it.queryCondition.id }}") |
|
||||||
Timber.d("list of contains ${filterElementRow.id}") |
|
||||||
val contained = filterConditions.flatMap { it.queryCondition.id }.contains(filterElementRow.id.first()) |
|
||||||
Timber.d("is contained: $contained") |
|
||||||
return contained |
|
||||||
} |
|
||||||
|
|
||||||
fun filterCondition(filterElementRow: QueryCondition): FilterCondition? { |
|
||||||
return filterConditions.firstOrNull { |
|
||||||
it.queryCondition.id.contains(filterElementRow.id.first()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
inline fun <reified T : Filterable> query(firstField: String? = null, vararg remainingFields: String): RealmQuery<T> { |
|
||||||
val realmQuery = realm.where<T>() |
|
||||||
|
|
||||||
if (firstField != null) { |
|
||||||
return this.query.queryWith(realmQuery).distinct(firstField, *remainingFields) |
|
||||||
} |
|
||||||
|
|
||||||
return this.query.queryWith(realmQuery) |
|
||||||
} |
|
||||||
|
|
||||||
inline fun <reified T : Filterable> results(firstField: String? = null, vararg remainingFields: String): RealmResults<T> { |
|
||||||
return this.query<T>(firstField, *remainingFields).findAll() |
|
||||||
} |
|
||||||
|
|
||||||
val query: Query |
|
||||||
get() { |
|
||||||
val query = Query() |
|
||||||
this.filterConditions.forEach { |
|
||||||
query.add(it.queryCondition) |
|
||||||
} |
|
||||||
return query |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
if (name.isNotEmpty()) return name |
|
||||||
return this.query.getName(context) |
|
||||||
} |
|
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
|
||||||
return R.string.relationship_error |
|
||||||
} |
|
||||||
|
|
||||||
override val bottomSheetType: BottomSheetType |
|
||||||
get() { |
|
||||||
return BottomSheetType.EDIT_TEXT |
|
||||||
} |
|
||||||
|
|
||||||
override fun localizedTitle(context: Context): String { |
// the number of use of the filter, |
||||||
return context.getString(R.string.name) |
// for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters |
||||||
} |
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0) |
||||||
|
|
||||||
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? { |
// var components: List<FilterComponent> = listOf() |
||||||
val defaultValue: String? by map |
|
||||||
return arrayListOf(RowRepresentableEditDescriptor(defaultValue, R.string.name)) |
|
||||||
} |
|
||||||
|
|
||||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
|
||||||
val newName = value as String? ?: "" |
|
||||||
if (newName.isNotEmpty()) { |
|
||||||
name = newName |
|
||||||
} |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|||||||
@ -1,111 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import io.realm.RealmList |
|
||||||
import io.realm.RealmObject |
|
||||||
import io.realm.RealmResults |
|
||||||
import io.realm.annotations.LinkingObjects |
|
||||||
import net.pokeranalytics.android.exceptions.PokerAnalyticsException |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.ui.view.rows.FilterSectionRow |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
open class FilterCondition() : RealmObject() { |
|
||||||
|
|
||||||
private constructor(filterName: String, sectionName: String) : this() { |
|
||||||
this.filterName = filterName |
|
||||||
this.sectionName = sectionName |
|
||||||
} |
|
||||||
|
|
||||||
constructor(filterElementRows: List<QueryCondition>, section: FilterSectionRow) : this(filterElementRows.first().baseId, section.name) { |
|
||||||
val row = filterElementRows.first() |
|
||||||
this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName |
|
||||||
this.operator = row.operator.ordinal |
|
||||||
if (row is QueryCondition.CustomFieldRelated) { |
|
||||||
this.stringValue = row.customFieldId |
|
||||||
} |
|
||||||
when (row) { |
|
||||||
is QueryCondition.SingleInt -> this.setValue(row.singleValue) |
|
||||||
is QueryCondition.SingleDate -> this.setValue(row.singleValue) |
|
||||||
is QueryCondition.ListOfDouble -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfDouble).listOfValues }) |
|
||||||
is QueryCondition.ListOfInt -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfInt).listOfValues }) |
|
||||||
is QueryCondition.ListOfString -> this.setValues(filterElementRows.flatMap { (it as QueryCondition.ListOfString).listOfValues }) |
|
||||||
else -> {} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var filterName: String? = null |
|
||||||
var sectionName: String? = null |
|
||||||
|
|
||||||
val queryCondition : QueryCondition |
|
||||||
get() = QueryCondition.valueOf<QueryCondition>(this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName) |
|
||||||
.apply { |
|
||||||
this.updateValueBy(this@FilterCondition) |
|
||||||
} |
|
||||||
|
|
||||||
var doubleValues: RealmList<Double>? = null |
|
||||||
var intValues: RealmList<Int>? = null |
|
||||||
var stringValues: RealmList<String>? = null |
|
||||||
var dateValue: Date? = null |
|
||||||
var doubleValue: Double? = null |
|
||||||
var intValue: Int? = null |
|
||||||
var stringValue: String? = null |
|
||||||
var operator: Int? = null |
|
||||||
|
|
||||||
@LinkingObjects("filterConditions") |
|
||||||
val filters: RealmResults<Filter>? = null |
|
||||||
|
|
||||||
inline fun <reified T> getValues(): ArrayList <T> { |
|
||||||
return when (T::class) { |
|
||||||
Int::class -> ArrayList<T>().apply { intValues?.map { add(it as T) } } |
|
||||||
Double::class -> ArrayList<T>().apply { doubleValues?.map { add(it as T) } } |
|
||||||
String::class -> ArrayList<T>().apply { stringValues?.map { add(it as T) } } |
|
||||||
else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun <T> getv(clazz: Class<T>) : T { |
|
||||||
return when (clazz) { |
|
||||||
Int::class -> intValue ?: 0 |
|
||||||
Double::class -> doubleValue?: 0.0 |
|
||||||
Date::class -> dateValue ?: Date() |
|
||||||
String::class -> stringValue ?: "" |
|
||||||
else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
} as T |
|
||||||
} |
|
||||||
|
|
||||||
inline fun <reified T> getValue(): T { |
|
||||||
return when (T::class) { |
|
||||||
Int::class -> intValue ?: 0 |
|
||||||
Double::class -> doubleValue?: 0.0 |
|
||||||
Date::class -> dateValue ?: Date() |
|
||||||
String::class -> stringValue ?: "" |
|
||||||
else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
} as T |
|
||||||
} |
|
||||||
|
|
||||||
private inline fun <reified T> setValues(values:List<T>) { |
|
||||||
when (T::class) { |
|
||||||
Int::class -> intValues = RealmList<Int>().apply { values.map { it as Int }.forEach { add(it) } } |
|
||||||
Double::class -> doubleValues = RealmList<Double>().apply { values.map { it as Double }.forEach { add(it) } } |
|
||||||
String::class -> stringValues = RealmList<String>().apply { values.map { it as String }.forEach { add(it) } } |
|
||||||
else -> throw PokerAnalyticsException.QueryValueMapUnexpectedValue |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun setValue(value:Double) { |
|
||||||
doubleValue = value |
|
||||||
} |
|
||||||
|
|
||||||
fun setValue(value:Date) { |
|
||||||
dateValue = value |
|
||||||
} |
|
||||||
|
|
||||||
fun setValue(value:Int) { |
|
||||||
intValue= value |
|
||||||
} |
|
||||||
|
|
||||||
fun setValue(value:String) { |
|
||||||
stringValue = value |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,136 +1,66 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
import android.content.Context |
import android.text.InputType |
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmModel |
|
||||||
import io.realm.RealmObject |
import io.realm.RealmObject |
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.PrimaryKey |
import io.realm.annotations.PrimaryKey |
||||||
import io.realm.kotlin.where |
import net.pokeranalytics.android.model.ObjectSavable |
||||||
import net.pokeranalytics.android.R |
import net.pokeranalytics.android.ui.adapter.components.LiveDataDataSource |
||||||
import net.pokeranalytics.android.model.interfaces.UsageCountable |
import net.pokeranalytics.android.ui.adapter.components.RowRepresentableDataSource |
||||||
import net.pokeranalytics.android.model.interfaces.Identifiable |
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData |
||||||
import net.pokeranalytics.android.model.interfaces.NameManageable |
import net.pokeranalytics.android.ui.view.GameRow |
||||||
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus |
import net.pokeranalytics.android.ui.view.RowEditable |
||||||
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||||
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor |
import net.pokeranalytics.android.ui.view.SimpleRow |
||||||
import net.pokeranalytics.android.ui.view.RowUpdatable |
|
||||||
import net.pokeranalytics.android.ui.view.rows.GamePropertiesRow |
|
||||||
import net.pokeranalytics.android.ui.view.rows.SimpleRow |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import java.util.* |
import java.util.* |
||||||
import kotlin.collections.ArrayList |
|
||||||
|
|
||||||
open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable, RowUpdatable, UsageCountable { |
open class Game : RealmObject(), RowRepresentableDataSource, RowEditable, ObjectSavable { |
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = Game::class.java |
|
||||||
|
|
||||||
companion object { |
|
||||||
val rowRepresentation : List<RowRepresentable> by lazy { |
|
||||||
val rows = ArrayList<RowRepresentable>() |
|
||||||
rows.add(SimpleRow.NAME) |
|
||||||
rows |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@PrimaryKey |
@PrimaryKey |
||||||
override var id = UUID.randomUUID().toString() |
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
// The name of the game |
// The name of the game |
||||||
override var name: String = "" |
var name: String = "" |
||||||
|
|
||||||
// A shorter name for the game |
// A shorter name for the game |
||||||
var shortName: String? = null |
var shortName: String? = null |
||||||
|
|
||||||
// CountableUsage |
override fun uniqueIdentifier(): String { |
||||||
override var useCount: Int = 0 |
return this.id |
||||||
|
|
||||||
@Ignore |
|
||||||
override val ownerClass: Class<out RealmModel> = Session::class.java |
|
||||||
|
|
||||||
fun getNotNullShortName() : String { |
|
||||||
this.shortName?.let { |
|
||||||
return it |
|
||||||
} |
|
||||||
return this.name |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return this.name |
|
||||||
} |
} |
||||||
|
|
||||||
override fun adapterRows(): List<RowRepresentable>? { |
override val adapterRows: ArrayList<RowRepresentable> |
||||||
return rowRepresentation |
get() { |
||||||
|
val rows = ArrayList<RowRepresentable>() |
||||||
|
rows.add(SimpleRow.NAME) |
||||||
|
rows.addAll(GameRow.values()) |
||||||
|
return rows |
||||||
} |
} |
||||||
|
|
||||||
override fun charSequenceForRow( |
override fun stringForRow(row: RowRepresentable): String { |
||||||
row: RowRepresentable, |
|
||||||
context: Context, |
|
||||||
tag: Int |
|
||||||
): CharSequence { |
|
||||||
return when (row) { |
return when (row) { |
||||||
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT |
SimpleRow.NAME -> this.name |
||||||
GamePropertiesRow.SHORT_NAME -> this.shortName ?: NULL_TEXT |
GameRow.SHORT_NAME -> this.shortName?:"" |
||||||
else -> return super.charSequenceForRow(row, context, 0) |
else -> return super.stringForRow(row) |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? { |
override fun getBottomSheetData(row: RowRepresentable): ArrayList<BottomSheetData> { |
||||||
return when (row) { |
val data = java.util.ArrayList<BottomSheetData>() |
||||||
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name)) |
when (row) { |
||||||
GamePropertiesRow.SHORT_NAME -> row.editingDescriptors(mapOf("defaultValue" to this.shortName)) |
SimpleRow.NAME -> data.add(BottomSheetData(this.name, SimpleRow.NAME.resId, InputType.TYPE_CLASS_TEXT)) |
||||||
else -> null |
GameRow.SHORT_NAME -> data.add(BottomSheetData(this.shortName, GameRow.SHORT_NAME.resId, InputType.TYPE_CLASS_TEXT)) |
||||||
} |
} |
||||||
|
return data |
||||||
} |
} |
||||||
|
|
||||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||||
when (row) { |
when (row) { |
||||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||||
GamePropertiesRow.SHORT_NAME -> this.shortName = value as String? ?: "" |
GameRow.SHORT_NAME -> this.shortName = value as String |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
override fun isValidForSave(): Boolean { |
override fun isValidForSave(): Boolean { |
||||||
return name.isNotEmpty() |
return this.name.isNotEmpty() |
||||||
} |
|
||||||
|
|
||||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
|
||||||
return when (status) { |
|
||||||
SaveValidityStatus.DATA_INVALID -> R.string.variant_empty_name_error |
|
||||||
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_variant_error |
|
||||||
else -> super.getFailedSaveMessage(status) |
|
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
|
||||||
return realm.where<Session>().equalTo("game.id", id).findAll().isEmpty() |
|
||||||
} |
|
||||||
|
|
||||||
val playerHandMaxCards: Int? |
|
||||||
get() { |
|
||||||
return when { |
|
||||||
isHoldem -> 2 |
|
||||||
isOmaha5 -> 5 |
|
||||||
isOmaha4 -> 4 |
|
||||||
else -> null |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private val isHoldem: Boolean |
|
||||||
get() { |
|
||||||
return name.contains("texas", ignoreCase = true) || name.contains("holdem", ignoreCase = true) || name.contains("hold'em", ignoreCase = true) || name.contains("HE") |
|
||||||
} |
|
||||||
|
|
||||||
private val isOmaha4: Boolean |
|
||||||
get() { |
|
||||||
return name.contains("omaha", ignoreCase = true) || name.contains("PLO", ignoreCase = true) |
|
||||||
} |
|
||||||
|
|
||||||
private val isOmaha5: Boolean |
|
||||||
get() { |
|
||||||
return name.contains("5") |
|
||||||
} |
|
||||||
|
|
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,16 @@ |
|||||||
|
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
|
import io.realm.RealmObject |
||||||
|
import io.realm.annotations.PrimaryKey |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
|
||||||
|
open class HandHistory : RealmObject() { |
||||||
|
|
||||||
|
@PrimaryKey |
||||||
|
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
|
// the date of the hand history |
||||||
|
var date: Date = Date() |
||||||
|
|
||||||
|
} |
||||||
@ -1,72 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmObject |
|
||||||
import io.realm.annotations.PrimaryKey |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.model.LiveOnline |
|
||||||
import net.pokeranalytics.android.ui.view.rows.StaticReport |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import net.pokeranalytics.android.util.extensions.lookupForNameInAllTablesById |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
|
|
||||||
interface PerformanceKey { |
|
||||||
val resId: Int? |
|
||||||
val value: Int |
|
||||||
} |
|
||||||
|
|
||||||
open class Performance() : RealmObject() { |
|
||||||
|
|
||||||
@PrimaryKey |
|
||||||
var id: String = UUID.randomUUID().toString() |
|
||||||
|
|
||||||
constructor( |
|
||||||
report: StaticReport, |
|
||||||
key: PerformanceKey, |
|
||||||
name: String? = null, |
|
||||||
objectId: String? = null, |
|
||||||
customFieldId: String? = null, |
|
||||||
value: Double? = null |
|
||||||
) : this() { |
|
||||||
|
|
||||||
this.reportId = report.uniqueIdentifier |
|
||||||
this.key = key.value |
|
||||||
this.name = name |
|
||||||
this.objectId = objectId |
|
||||||
this.customFieldId = customFieldId |
|
||||||
this.value = value |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
var reportId: Int = 0 |
|
||||||
var key: Int = 0 |
|
||||||
var name: String? = null |
|
||||||
var objectId: String? = null |
|
||||||
var customFieldId: String? = null |
|
||||||
var value: Double? = null |
|
||||||
|
|
||||||
fun toStaticReport(realm: Realm): StaticReport { |
|
||||||
return StaticReport.newInstance(realm, this.reportId, this.customFieldId) |
|
||||||
} |
|
||||||
|
|
||||||
fun displayValue(realm: Realm): CharSequence { |
|
||||||
this.name?.let { return it } |
|
||||||
this.objectId?.let { realm.lookupForNameInAllTablesById(it) } |
|
||||||
return NULL_TEXT |
|
||||||
} |
|
||||||
|
|
||||||
val stat: Stat |
|
||||||
get() { |
|
||||||
return Stat.valueByIdentifier(this.key.toInt()) |
|
||||||
} |
|
||||||
|
|
||||||
val resId: Int? |
|
||||||
get() { |
|
||||||
return when (this.reportId) { |
|
||||||
StaticReport.OptimalDuration.uniqueIdentifier -> LiveOnline.valueByIdentifier(this.key).resId |
|
||||||
else -> stat.resId |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,101 +1,15 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
import android.content.Context |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmList |
|
||||||
import io.realm.RealmObject |
import io.realm.RealmObject |
||||||
import io.realm.RealmResults |
|
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.PrimaryKey |
import io.realm.annotations.PrimaryKey |
||||||
import net.pokeranalytics.android.R |
|
||||||
import net.pokeranalytics.android.model.interfaces.* |
|
||||||
import net.pokeranalytics.android.model.realm.handhistory.HandHistory |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowUpdatable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.ui.view.rows.PlayerPropertiesRow |
|
||||||
import net.pokeranalytics.android.util.RANDOM_PLAYER |
|
||||||
import java.util.* |
import java.util.* |
||||||
|
|
||||||
open class Player : RealmObject(), NameManageable, Savable, Deletable, RowRepresentable, RowUpdatable { |
open class Player : RealmObject() { |
||||||
|
|
||||||
@PrimaryKey |
@PrimaryKey |
||||||
override var id = UUID.randomUUID().toString() |
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
// The name of the player |
// The name of the player |
||||||
override var name: String = "" |
var name: String = "" |
||||||
|
|
||||||
// New fields |
|
||||||
var summary: String = "" |
|
||||||
var color: Int? = null |
|
||||||
var picture: String? = null |
|
||||||
var comments: RealmList<Comment> = RealmList() |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = Player::class.java |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val viewType: Int = RowViewType.ROW_PLAYER.ordinal |
|
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
|
||||||
//TODO |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
|
||||||
return when(status) { |
|
||||||
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_user_error |
|
||||||
SaveValidityStatus.DATA_INVALID -> R.string.user_empty_field_error |
|
||||||
else -> super.getFailedSaveMessage(status) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
|
||||||
//TODO |
|
||||||
return R.string.relationship_error |
|
||||||
} |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return this.name |
|
||||||
} |
|
||||||
|
|
||||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
|
||||||
when (row) { |
|
||||||
PlayerPropertiesRow.NAME -> this.name = value as String? ?: "" |
|
||||||
PlayerPropertiesRow.SUMMARY -> this.summary = value as String? ?: "" |
|
||||||
PlayerPropertiesRow.IMAGE -> this.picture = value as? String |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
/** |
|
||||||
* Return if the player has a picture |
|
||||||
*/ |
|
||||||
fun hasPicture(): Boolean { |
|
||||||
return picture != null && picture?.isNotEmpty() == true |
|
||||||
} |
|
||||||
|
|
||||||
val initials: String |
|
||||||
get() { |
|
||||||
return if (this.name.isNotEmpty()) { |
|
||||||
val playerData = this.name.split(" ") |
|
||||||
when { |
|
||||||
playerData.size > 1 -> { |
|
||||||
playerData[0].first().toString() + playerData[1].first().toString() |
|
||||||
} |
|
||||||
this.name.length > 1 -> { |
|
||||||
this.name.substring(0, 2) |
|
||||||
} |
|
||||||
else -> { |
|
||||||
this.name.substring(0, this.name.length) |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
RANDOM_PLAYER |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fun hands(realm: Realm): RealmResults<HandHistory> { |
|
||||||
return realm.where(HandHistory::class.java).equalTo("playerSetups.player.id", this.id).findAll() |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,33 @@ |
|||||||
|
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
|
import io.realm.RealmList |
||||||
|
import io.realm.RealmObject |
||||||
|
import io.realm.annotations.PrimaryKey |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
enum class ReportDisplay { |
||||||
|
TABLE, |
||||||
|
GRAPH, |
||||||
|
MAP |
||||||
|
} |
||||||
|
|
||||||
|
open class Report : RealmObject() { |
||||||
|
|
||||||
|
@PrimaryKey |
||||||
|
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
|
// The name of the report |
||||||
|
var name: String = "" |
||||||
|
|
||||||
|
// The type of display of the report |
||||||
|
var display: Int = ReportDisplay.TABLE.ordinal |
||||||
|
|
||||||
|
// @todo define the configuration options |
||||||
|
|
||||||
|
// var comparators: List<Int> = listOf() |
||||||
|
// var stats: List<Int> = listOf() |
||||||
|
|
||||||
|
// The filters associated with the report |
||||||
|
var filters: RealmList<Filter> = RealmList() |
||||||
|
|
||||||
|
} |
||||||
@ -1,103 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import android.content.Context |
|
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmList |
|
||||||
import io.realm.RealmObject |
|
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.PrimaryKey |
|
||||||
import net.pokeranalytics.android.calculus.calcul.ReportDisplay |
|
||||||
import net.pokeranalytics.android.calculus.Calculator |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.model.Criteria |
|
||||||
import net.pokeranalytics.android.model.interfaces.Deletable |
|
||||||
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus |
|
||||||
import net.pokeranalytics.android.model.interfaces.Identifiable |
|
||||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
||||||
import net.pokeranalytics.android.ui.view.RowViewType |
|
||||||
import net.pokeranalytics.android.util.extensions.findById |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
|
|
||||||
open class ReportSetup : RealmObject(), RowRepresentable, Deletable { |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = ReportSetup::class.java |
|
||||||
|
|
||||||
@PrimaryKey |
|
||||||
override var id = UUID.randomUUID().toString() |
|
||||||
|
|
||||||
// The name of the report |
|
||||||
var name: String = "" |
|
||||||
|
|
||||||
// The type of display of the report |
|
||||||
var display: Int = ReportDisplay.TABLE.ordinal |
|
||||||
|
|
||||||
/** |
|
||||||
* A list of statIds to compute |
|
||||||
* Must contain at least 1 |
|
||||||
*/ |
|
||||||
var statIds: RealmList<Int> = RealmList() |
|
||||||
|
|
||||||
/** |
|
||||||
* An optional list of criteriaIds to compare statIds |
|
||||||
*/ |
|
||||||
var criteriaIds: RealmList<Int> = RealmList() |
|
||||||
|
|
||||||
/** |
|
||||||
* An optional list of custom fields ids to be compared |
|
||||||
*/ |
|
||||||
var criteriaCustomFieldIds: RealmList<String> = RealmList() |
|
||||||
|
|
||||||
/** |
|
||||||
* An optional filter to narrow the results |
|
||||||
*/ |
|
||||||
var filter: Filter? = null |
|
||||||
|
|
||||||
// RowRepresentable |
|
||||||
override fun getDisplayName(context: Context): String { |
|
||||||
return this.name |
|
||||||
} |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val viewType: Int = RowViewType.TITLE_ARROW.ordinal |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the Options based on the ReportSetup parameters |
|
||||||
*/ |
|
||||||
fun options(realm: Realm, reportDisplay: ReportDisplay): Calculator.Options { |
|
||||||
|
|
||||||
val stats = this.statIds.map { Stat.valueByIdentifier(it) } |
|
||||||
|
|
||||||
// Comparison criteria |
|
||||||
val criteria = this.criteriaIds.map { Criteria.valueByIdentifier(it) } |
|
||||||
|
|
||||||
val customFields = this.criteriaCustomFieldIds.mapNotNull { realm.findById<CustomField>(it) } |
|
||||||
|
|
||||||
val cfCriteria = customFields.map { it.criteria } |
|
||||||
|
|
||||||
val allCriteria = mutableListOf<Criteria>() |
|
||||||
allCriteria.addAll(criteria) |
|
||||||
allCriteria.addAll(cfCriteria) |
|
||||||
|
|
||||||
return Calculator.Options( |
|
||||||
stats = stats, |
|
||||||
progressValues = reportDisplay.progressValues, |
|
||||||
criterias = allCriteria, |
|
||||||
filter = this.filter, |
|
||||||
userGenerated = true, |
|
||||||
reportSetupId = this.id |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
// Deletable |
|
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
|
||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,146 +1,63 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
import android.content.Context |
|
||||||
import io.realm.Realm |
import io.realm.Realm |
||||||
import io.realm.RealmObject |
import io.realm.RealmObject |
||||||
import io.realm.RealmResults |
import io.realm.RealmResults |
||||||
import io.realm.annotations.Ignore |
import io.realm.annotations.Ignore |
||||||
import io.realm.annotations.LinkingObjects |
import io.realm.annotations.LinkingObjects |
||||||
import io.realm.annotations.PrimaryKey |
|
||||||
import net.pokeranalytics.android.calculus.Stat |
|
||||||
import net.pokeranalytics.android.calculus.StatFormattingException |
|
||||||
import net.pokeranalytics.android.model.filter.Filterable |
|
||||||
import net.pokeranalytics.android.model.filter.QueryCondition |
|
||||||
import net.pokeranalytics.android.model.interfaces.Identifiable |
|
||||||
import net.pokeranalytics.android.model.interfaces.Timed |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import net.pokeranalytics.android.util.TextFormat |
|
||||||
import java.text.DateFormat |
|
||||||
import java.util.* |
|
||||||
|
|
||||||
|
|
||||||
open class SessionSet() : RealmObject(), Timed, Filterable { |
|
||||||
|
|
||||||
@PrimaryKey |
|
||||||
override var id = UUID.randomUUID().toString() |
|
||||||
|
|
||||||
var startDate: Date = Date() |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.computeNetDuration() |
|
||||||
} |
|
||||||
|
|
||||||
var endDate: Date = Date() |
|
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.computeNetDuration() |
|
||||||
} |
|
||||||
|
|
||||||
override fun startDate(): Date? { |
|
||||||
return this.startDate |
|
||||||
} |
|
||||||
|
|
||||||
override fun endDate(): Date { |
|
||||||
return this.endDate |
|
||||||
} |
|
||||||
|
|
||||||
override var breakDuration: Long = 0L |
open class SessionSet() : RealmObject() { |
||||||
set(value) { |
|
||||||
field = value |
|
||||||
this.computeNetDuration() |
|
||||||
} |
|
||||||
|
|
||||||
/** |
/** |
||||||
* The start date of the break |
* The timeframe of the set, i.e. its start & end date |
||||||
*/ |
*/ |
||||||
override var pauseDate: Date? = null |
|
||||||
|
|
||||||
/** |
var timeFrame: TimeFrame? = null |
||||||
* the net duration of the set (READONLY) |
|
||||||
*/ |
|
||||||
override var netDuration: Long = 0L |
|
||||||
|
|
||||||
fun computeStats() { |
|
||||||
this.ratedNet = this.sessions?.sumOf { it.computableResult?.ratedNet ?: 0.0 } ?: 0.0 |
|
||||||
this.estimatedHands = this.sessions?.sumOf { it.estimatedHands } ?: 0.0 |
|
||||||
this.bbNet = this.sessions?.sumOf { it.bbNet } ?: 0.0 |
|
||||||
this.breakDuration = this.sessions?.max("breakDuration")?.toLong() ?: 0L |
|
||||||
} |
|
||||||
|
|
||||||
/** |
/** |
||||||
* The list of endedSessions associated with this set |
* The list of sessions associated with this set |
||||||
*/ |
*/ |
||||||
@LinkingObjects("sessionSet") |
@LinkingObjects("sessionSet") |
||||||
val sessions: RealmResults<Session>? = null |
val sessions: RealmResults<Session>? = null |
||||||
|
|
||||||
var ratedNet: Double = 0.0 |
@Ignore // a duration shortcut |
||||||
|
var duration: Long = 0L |
||||||
val hourlyRate: Double |
|
||||||
get() { |
get() { |
||||||
return this.ratedNet / this.hourlyDuration |
return this.timeFrame?.duration ?: 0L |
||||||
} |
} |
||||||
|
|
||||||
var estimatedHands: Double = 0.0 |
@Ignore // a duration in hour |
||||||
|
var hourlyDuration: Double = 0.0 |
||||||
var bbNet: BB = 0.0 |
|
||||||
|
|
||||||
val bbHourlyRate: BB |
|
||||||
get() { |
get() { |
||||||
return this.bbNet / this.hourlyDuration |
return this.timeFrame?.hourlyDuration ?: 0.0 |
||||||
} |
} |
||||||
|
|
||||||
enum class Field(val identifier: String) { |
@Ignore // a netResult shortcut |
||||||
START_DATE("startDate"), |
var netResult: Double = 0.0 |
||||||
RATED_NET("ratedNet"), |
get () { |
||||||
HOURLY_RATE("hourlyRate"), |
return this.sessions?.sumByDouble { it.value } ?: 0.0 |
||||||
BB_NET("bbNet"), |
|
||||||
ESTIMATED_HANDS("estimatedHands"), |
|
||||||
NET_DURATION("netDuration") |
|
||||||
} |
} |
||||||
|
|
||||||
companion object { |
@Ignore // a duration shortcut |
||||||
|
var hourlyRate: Double = 0.0 |
||||||
|
|
||||||
fun newInstance(realm: Realm) : SessionSet { |
@Ignore |
||||||
val sessionSet = SessionSet() |
var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0) |
||||||
return realm.copyToRealm(sessionSet) |
|
||||||
} |
|
||||||
|
|
||||||
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { |
|
||||||
Session.fieldNameForQueryType(queryCondition)?.let { |
|
||||||
return "sessions.$it" |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
} |
@Ignore |
||||||
|
var bbNetResult: Double = 0.0 |
||||||
|
|
||||||
// Stat Base |
companion object { |
||||||
|
|
||||||
override fun entryTitle(context: Context): String { |
fun newInstance(realm: Realm) : SessionSet { |
||||||
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate) |
val sessionSet: SessionSet = realm.createObject(SessionSet::class.java) |
||||||
|
sessionSet.timeFrame = realm.createObject(TimeFrame::class.java) |
||||||
|
return sessionSet |
||||||
} |
} |
||||||
|
|
||||||
override fun formattedValue(stat: Stat) : TextFormat { |
|
||||||
return when (stat) { |
|
||||||
Stat.NET_RESULT, Stat.AVERAGE -> stat.textFormat(this.ratedNet, currency = null) |
|
||||||
Stat.HOURLY_DURATION, Stat.AVERAGE_HOURLY_DURATION -> stat.textFormat(this.hourlyDuration, currency = null) |
|
||||||
Stat.HOURLY_RATE -> stat.textFormat(this.hourlyRate, currency = null) |
|
||||||
Stat.HANDS_PLAYED -> stat.textFormat(this.estimatedHands, currency = null) |
|
||||||
Stat.HOURLY_RATE_BB -> stat.textFormat(this.bbHourlyRate, currency = null) |
|
||||||
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { |
|
||||||
val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands) |
|
||||||
if (netBBPer100Hands != null) { |
|
||||||
return stat.textFormat(this.estimatedHands, currency = null) |
|
||||||
} else { |
|
||||||
return TextFormat(NULL_TEXT) |
|
||||||
} |
|
||||||
} |
|
||||||
else -> throw StatFormattingException("format undefined for stat ${stat.name}") |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = SessionSet::class.java |
|
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
|
|||||||
@ -1,288 +1,230 @@ |
|||||||
//package net.pokeranalytics.android.model.realm |
package net.pokeranalytics.android.model.realm |
||||||
// |
|
||||||
//import io.realm.RealmObject |
import io.realm.Realm |
||||||
//import io.realm.RealmQuery |
import io.realm.RealmObject |
||||||
//import io.realm.RealmResults |
import io.realm.RealmQuery |
||||||
//import io.realm.annotations.Ignore |
import io.realm.RealmResults |
||||||
//import io.realm.annotations.LinkingObjects |
import io.realm.annotations.Ignore |
||||||
//import net.pokeranalytics.android.exceptions.ModelException |
import io.realm.annotations.LinkingObjects |
||||||
//import timber.log.Timber |
import net.pokeranalytics.android.exceptions.ModelException |
||||||
//import java.util.* |
import timber.log.Timber |
||||||
// |
import java.util.* |
||||||
//open class TimeFrame : RealmObject() { |
|
||||||
// |
open class TimeFrame : RealmObject() { |
||||||
// // A start date |
|
||||||
// var startDate: Date = Date() |
// A start date |
||||||
// private set(value) { |
var startDate: Date = Date() |
||||||
// field = value |
private set(value) { |
||||||
// this.computeNetDuration() |
field = value |
||||||
// } |
this.computeDuration() |
||||||
// |
} |
||||||
// // An end date |
|
||||||
// var endDate: Date? = null |
// An end date |
||||||
// private set(value) { |
var endDate: Date? = null |
||||||
// field = value |
private set(value) { |
||||||
// this.computeNetDuration() |
field = value |
||||||
// } |
this.computeDuration() |
||||||
// |
} |
||||||
// // The latest pause date |
|
||||||
// var pauseDate: Date? = null |
// The break duration |
||||||
// set(value) { |
var breakDuration: Long = 0L |
||||||
// field?.let { |
set(value) { |
||||||
// if (value == null && field != null) { |
field = value |
||||||
// breakDuration += Date().time - it.time |
this.computeDuration() |
||||||
// } |
} |
||||||
// } |
|
||||||
// field = value |
// the total duration |
||||||
// this.computeNetDuration() |
var duration: Long = 0L |
||||||
// } |
private set |
||||||
// |
|
||||||
// // The break netDuration |
var hourlyDuration: Double = 0.0 |
||||||
// var breakDuration: Long = 0L |
get() { |
||||||
// set(value) { |
return this.duration / 3600000.0 // 3.6 millions of milliseconds |
||||||
// field = value |
} |
||||||
// this.computeNetDuration() |
|
||||||
// } |
// indicates a state of pause |
||||||
// |
var paused: Boolean = false |
||||||
// // the total netDuration |
|
||||||
// var netDuration: Long = 0L |
// Session |
||||||
// private set |
@LinkingObjects("timeFrame") |
||||||
// |
private val sessions: RealmResults<Session>? = null // we should have only one session |
||||||
// var hourlyDuration: Double = 0.0 |
|
||||||
// get() { |
@Ignore |
||||||
// return this.netDuration / 3600000.0 // 3.6 millions of milliseconds |
var session: Session? = null |
||||||
// } |
get() = if (this.sessions != null && this.sessions.isEmpty()) null else this.sessions?.first() |
||||||
// |
|
||||||
// // Session |
// Group |
||||||
// @LinkingObjects("timeFrame") |
@LinkingObjects("timeFrame") |
||||||
// private val endedSessions: RealmResults<Session>? = null // we should have only one session |
private val sets: RealmResults<SessionSet>? = null // we should have only one sessionGroup |
||||||
// |
|
||||||
// @Ignore |
@Ignore |
||||||
// var session: Session? = null |
var set: SessionSet? = null |
||||||
// get() = if (this.endedSessions != null && this.endedSessions.isEmpty()) null else this.endedSessions?.first() |
get() = this.sets?.first() |
||||||
// |
|
||||||
// // Group |
fun setDate(startDate: Date?, endDate: Date?) { |
||||||
// @LinkingObjects("timeFrame") |
|
||||||
// private val sets: RealmResults<SessionSet>? = null // we should have only one sessionGroup |
startDate?.let { |
||||||
// |
this.startDate = startDate |
||||||
// @Ignore |
} |
||||||
// var set: SessionSet? = null |
|
||||||
// get() = this.sets?.first() |
this.endDate = endDate |
||||||
// |
|
||||||
// fun setStart(startDate: Date) { |
this.computeDuration() |
||||||
// this.startDate = startDate |
|
||||||
// this.session?.let { |
if (this.session != null) { |
||||||
// this.notifySessionDateChange(it) |
this.notifySessionDateChange() |
||||||
// } |
} |
||||||
// } |
} |
||||||
// |
|
||||||
// fun setEnd(endDate: Date?) { |
private fun computeDuration() { |
||||||
// this.endDate = endDate |
var endDate: Date = this.endDate ?: Date() |
||||||
// this.session?.let { |
val netDuration = endDate.time - this.startDate.time - this.breakDuration |
||||||
// this.notifySessionDateChange(it) |
this.duration = netDuration |
||||||
// } |
} |
||||||
// } |
|
||||||
// |
fun notifySessionDateChange() { |
||||||
// fun setDate(startDate: Date, endDate: Date?) { |
val realm = Realm.getDefaultInstance() |
||||||
// this.startDate = startDate |
var query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java) |
||||||
// this.endDate = endDate |
query.isNotNull("timeFrame") |
||||||
// |
|
||||||
// this.session?.let { |
// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}") |
||||||
// this.notifySessionDateChange(it) |
|
||||||
// } |
if (this.endDate == null) { |
||||||
// } |
query.greaterThan("timeFrame.startDate", this.startDate.time).or().greaterThan("timeFrame.endDate", this.startDate.time) |
||||||
// |
} else { |
||||||
// /** |
val endDate = this.endDate!! |
||||||
// * Computes the net netDuration of the session |
query |
||||||
// */ |
.lessThan("timeFrame.startDate", this.startDate) |
||||||
// private fun computeNetDuration() { |
.greaterThan("timeFrame.endDate", this.startDate) |
||||||
// var endDate: Date = this.endDate ?: Date() |
.or() |
||||||
// this.netDuration = endDate.time - this.startDate.time - this.breakDuration |
.lessThan("timeFrame.startDate", endDate) |
||||||
// } |
.greaterThan("timeFrame.endDate", endDate) |
||||||
// |
.or() |
||||||
// /** |
.greaterThan("timeFrame.startDate", this.startDate) |
||||||
// * Queries all time frames that might be impacted by the date change |
.lessThan("timeFrame.endDate", endDate) |
||||||
// * Makes all necessary changes to keep sequential time frames |
} |
||||||
// */ |
|
||||||
// fun notifySessionDateChange(owner: Session) { |
val sessionGroups = query.findAll() |
||||||
// |
|
||||||
// var query: RealmQuery<SessionSet> = this.realm.where(SessionSet::class.java) |
this.updateTimeFrames(sessionGroups) |
||||||
// query.isNotNull("timeFrame") |
|
||||||
// |
// realm.close() |
||||||
//// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}") |
} |
||||||
// |
|
||||||
// val sets = realm.where(SessionSet::class.java).findAll() |
/** |
||||||
//// Timber.d("set count = ${sets.size}") |
* Update Time frames from sets |
||||||
// |
*/ |
||||||
// if (this.endDate == null) { |
private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>) { |
||||||
// query.greaterThanOrEqualTo("timeFrame.startDate", this.startDate) |
|
||||||
// .or() |
when (sessionSets.size) { |
||||||
// .greaterThanOrEqualTo("timeFrame.endDate", this.startDate) |
0 -> this.createSessionGroup() |
||||||
// .or() |
1 -> this.updateSingleSessionGroup(sessionSets.first()!!) |
||||||
// .isNull("timeFrame.endDate") |
else -> this.mergeSessionGroups(sessionSets) |
||||||
// } else { |
} |
||||||
// val endDate = this.endDate!! |
|
||||||
// query |
} |
||||||
// .lessThanOrEqualTo("timeFrame.startDate", this.startDate) |
|
||||||
// .greaterThanOrEqualTo("timeFrame.endDate", this.startDate) |
/** |
||||||
// .or() |
* Creates the session sessionGroup when the session has none |
||||||
// .lessThanOrEqualTo("timeFrame.startDate", endDate) |
*/ |
||||||
// .greaterThanOrEqualTo("timeFrame.endDate", endDate) |
private fun createSessionGroup() { |
||||||
// .or() |
|
||||||
// .greaterThanOrEqualTo("timeFrame.startDate", this.startDate) |
val realm = Realm.getDefaultInstance() |
||||||
// .lessThanOrEqualTo("timeFrame.endDate", endDate) |
|
||||||
// .or() |
val set: SessionSet = SessionSet.newInstance(realm) |
||||||
// .isNull("timeFrame.endDate") |
set.timeFrame?.let { |
||||||
// .lessThanOrEqualTo("timeFrame.startDate", endDate) |
it.startDate = this.startDate |
||||||
// } |
it.endDate = this.endDate |
||||||
// |
} ?: run { |
||||||
// val sessionGroups = query.findAll() |
throw ModelException("TimeFrame should never be null here") |
||||||
// |
} |
||||||
// this.updateTimeFrames(sessionGroups, owner) |
|
||||||
// |
this.session?.let { |
||||||
// } |
it.sessionSet = set |
||||||
// |
} ?: run { |
||||||
// /** |
throw ModelException("Session should never be null here") |
||||||
// * Update Time frames from sets |
} |
||||||
// */ |
|
||||||
// private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, owner: Session) { |
Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}") |
||||||
// |
|
||||||
// when (sessionSets.size) { |
} |
||||||
// 0 -> this.createOrUpdateSessionSet(owner) |
|
||||||
// 1 -> this.updateSessionGroup(owner, sessionSets.first()!!) |
/** |
||||||
// else -> this.mergeSessionGroups(owner, sessionSets) |
* Single session sessionGroup update |
||||||
// } |
* Changes the sessionGroup timeframe using the current timeframe dates |
||||||
// |
*/ |
||||||
// } |
private fun updateSingleSessionGroup(sessionSet: SessionSet) { |
||||||
// |
|
||||||
// /** |
var groupTimeFrame: TimeFrame = sessionSet.timeFrame!! // tested in the query |
||||||
// * Creates the session sessionGroup when the session has none |
|
||||||
// */ |
if (this.startDate.before(groupTimeFrame.startDate)) { |
||||||
// private fun createOrUpdateSessionSet(owner: Session) { |
groupTimeFrame.startDate = this.startDate |
||||||
// |
} |
||||||
// val set = owner.sessionSet |
val endDate = this.endDate |
||||||
// if (set != null) { |
if (endDate != null && groupTimeFrame.endDate != null && endDate.after(groupTimeFrame.endDate)) { |
||||||
// set.timeFrame?.startDate = this.startDate |
groupTimeFrame.endDate = endDate |
||||||
// set.timeFrame?.endDate = this.endDate |
} else if (endDate == null) { |
||||||
// } else { |
groupTimeFrame.endDate = null |
||||||
// this.createSessionSet(owner) |
} |
||||||
// } |
|
||||||
// |
this.session?.sessionSet = sessionSet |
||||||
//// Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}") |
|
||||||
// Timber.d("netDuration 1 = : ${set?.timeFrame?.netDuration}") |
} |
||||||
// |
|
||||||
// } |
/** |
||||||
// |
* Multiple session sets update: |
||||||
// fun createSessionSet(owner: Session) { |
* Merges all sets into one (delete all then create a new one) |
||||||
// val set: SessionSet = SessionSet.newInstanceForResult(this.realm) |
*/ |
||||||
// set.timeFrame?.let { |
private fun mergeSessionGroups(sessionSets: RealmResults<SessionSet>) { |
||||||
// it.startDate = this.startDate |
|
||||||
// it.endDate = this.endDate |
var startDate: Date = this.startDate |
||||||
// } ?: run { |
var endDate: Date? = this.endDate |
||||||
// throw ModelException("TimeFrame should never be null here") |
|
||||||
// } |
// find earlier and later dates from all sets |
||||||
// |
val timeFrames = sessionSets.mapNotNull { it.timeFrame } |
||||||
// owner.sessionSet = set |
timeFrames.forEach { tf -> |
||||||
// } |
if (tf.startDate.before(startDate)) { |
||||||
// |
startDate = tf.startDate |
||||||
// |
} |
||||||
// /** |
|
||||||
// * Single SessionSet update, the session might be the owner |
endDate?.let { ed -> |
||||||
// * Changes the sessionGroup timeframe using the current timeframe dates |
tf.endDate?.let { tfed -> |
||||||
// */ |
if (tfed.after(ed)) { |
||||||
// private fun updateSessionGroup(owner: Session, sessionSet: SessionSet) { |
endDate = tfed |
||||||
// |
} |
||||||
// var timeFrame: TimeFrame = sessionSet.timeFrame!! // tested in the query |
} |
||||||
//// timeFrame.setDate(this.startDate, this.endDate) |
} ?: run { |
||||||
// |
endDate = tf.endDate |
||||||
// val sisterSessions = sessionSet.endedSessions!! // shouldn't crash ever |
} |
||||||
// |
|
||||||
// // if we have only one session in the set and that it corresponds to the set |
} |
||||||
// if (sessionSet.endedSessions?.size == 1 && sessionSet.endedSessions?.first() == owner) { |
|
||||||
// timeFrame.setDate(this.startDate, this.endDate) |
// get all sessions from sets |
||||||
// } else { // there are 2+ endedSessions to manage and possible splits |
var sessions = mutableSetOf<Session>() |
||||||
// |
sessionSets.forEach { it.sessions?.asIterable()?.let { it1 -> sessions.addAll(it1) } } |
||||||
// val endDate = this.endDate |
|
||||||
// |
// delete all sets |
||||||
// // case where all endedSessions are over but the set is not, we might have a split, so we delete the set and save everything again |
sessionSets.deleteAllFromRealm() |
||||||
// if (endDate != null && sisterSessions.all { it.timeFrame?.endDate != null } && timeFrame.endDate == null) { |
|
||||||
// var endedSessions = mutableListOf<Session>(owner) |
// Create a new sets |
||||||
// sessionSet.endedSessions?.forEach { endedSessions.add(it) } |
val set: SessionSet = SessionSet.newInstance(realm) |
||||||
// sessionSet.deleteFromRealm() |
set.timeFrame?.let { |
||||||
// endedSessions.forEach { it.timeFrame?.notifySessionDateChange(it) } |
it.startDate = startDate |
||||||
// } else { |
it.endDate = endDate |
||||||
// |
} ?: run { |
||||||
// if (this.startDate.before(timeFrame.startDate)) { |
throw ModelException("TimeFrame should never be null here") |
||||||
// timeFrame.startDate = this.startDate |
} |
||||||
// } |
|
||||||
// if (endDate != null && timeFrame.endDate != null && endDate.after(timeFrame.endDate)) { |
// Add the session linked to this timeframe to the new sessionGroup |
||||||
// timeFrame.endDate = endDate |
this.sessions?.first()?.let { |
||||||
// } else if (endDate == null) { |
it.sessionSet = set |
||||||
// timeFrame.endDate = null |
} ?: run { |
||||||
// } |
throw ModelException("TimeFrame should never be null here") |
||||||
// |
} |
||||||
// owner.sessionSet = sessionSet |
|
||||||
// |
// Add all orphan sessions |
||||||
//// Timber.d("sd = : ${sessionSet.timeFrame?.startDate}, ed = ${sessionSet.timeFrame?.endDate}") |
sessions.forEach { it.sessionSet = set } |
||||||
// Timber.d("netDuration 2 = : ${sessionSet.timeFrame?.netDuration}") |
|
||||||
// } |
} |
||||||
// |
|
||||||
// } |
} |
||||||
// |
|
||||||
// } |
|
||||||
// |
|
||||||
// /** |
|
||||||
// * Multiple session sets update: |
|
||||||
// * Merges all sets into one (delete all then create a new one) |
|
||||||
// */ |
|
||||||
// private fun mergeSessionGroups(owner: Session, sessionSets: RealmResults<SessionSet>) { |
|
||||||
// |
|
||||||
// var startDate: Date = this.startDate |
|
||||||
// var endDate: Date? = this.endDate |
|
||||||
// |
|
||||||
// // find earlier and later dates from all sets |
|
||||||
// val timeFrames = sessionSets.mapNotNull { it.timeFrame } |
|
||||||
// timeFrames.forEach { tf -> |
|
||||||
// if (tf.startDate.before(startDate)) { |
|
||||||
// startDate = tf.startDate |
|
||||||
// } |
|
||||||
// |
|
||||||
// endDate?.let { ed -> |
|
||||||
// tf.endDate?.let { tfed -> |
|
||||||
// if (tfed.after(ed)) { |
|
||||||
// endDate = tfed |
|
||||||
// } |
|
||||||
// } |
|
||||||
// } ?: run { |
|
||||||
// endDate = tf.endDate |
|
||||||
// } |
|
||||||
// |
|
||||||
// } |
|
||||||
// |
|
||||||
// // get all endedSessions from sets |
|
||||||
// var endedSessions = mutableSetOf<Session>() |
|
||||||
// sessionSets.forEach { set -> |
|
||||||
// set.endedSessions?.asIterable()?.let { endedSessions.addAll(it) } |
|
||||||
// } |
|
||||||
// |
|
||||||
// // delete all sets |
|
||||||
// sessionSets.deleteAllFromRealm() |
|
||||||
// |
|
||||||
// // Create a new sets |
|
||||||
// val set: SessionSet = SessionSet.newInstanceForResult(this.realm) |
|
||||||
// set.timeFrame?.let { |
|
||||||
// it.setDate(startDate, endDate) |
|
||||||
// } ?: run { |
|
||||||
// throw ModelException("TimeFrame should never be null here") |
|
||||||
// } |
|
||||||
// |
|
||||||
// // Add the session linked to this timeframe to the new sessionGroup |
|
||||||
// owner.sessionSet = set |
|
||||||
// |
|
||||||
// // Add all orphan endedSessions |
|
||||||
// endedSessions.forEach { it.sessionSet = set } |
|
||||||
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}") |
|
||||||
// |
|
||||||
// } |
|
||||||
// |
|
||||||
//} |
|
||||||
|
|||||||
@ -1,92 +1,60 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
import android.content.Context |
import android.text.InputType |
||||||
import io.realm.Realm |
|
||||||
import io.realm.RealmModel |
|
||||||
import io.realm.RealmObject |
import io.realm.RealmObject |
||||||
import io.realm.annotations.Ignore |
|
||||||
import io.realm.annotations.PrimaryKey |
import io.realm.annotations.PrimaryKey |
||||||
import io.realm.kotlin.where |
import net.pokeranalytics.android.model.ObjectSavable |
||||||
import net.pokeranalytics.android.R |
import net.pokeranalytics.android.ui.adapter.components.* |
||||||
import net.pokeranalytics.android.model.interfaces.UsageCountable |
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData |
||||||
import net.pokeranalytics.android.model.interfaces.Identifiable |
import net.pokeranalytics.android.ui.view.RowEditable |
||||||
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.RowRepresentable |
||||||
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor |
import net.pokeranalytics.android.ui.view.SimpleRow |
||||||
import net.pokeranalytics.android.ui.view.RowUpdatable |
import net.pokeranalytics.android.ui.view.TournamentFeatureRow |
||||||
import net.pokeranalytics.android.ui.view.rows.SimpleRow |
|
||||||
import net.pokeranalytics.android.ui.view.rows.TournamentFeatureRow |
|
||||||
import net.pokeranalytics.android.util.NULL_TEXT |
|
||||||
import java.util.* |
import java.util.* |
||||||
import kotlin.collections.ArrayList |
|
||||||
|
|
||||||
open class TournamentFeature : RealmObject(), RowRepresentable, RowUpdatable, NameManageable, StaticRowRepresentableDataSource, UsageCountable { |
open class TournamentFeature : RealmObject(), RowRepresentableDataSource, RowEditable, ObjectSavable { |
||||||
|
|
||||||
companion object { |
|
||||||
val rowRepresentation : List<RowRepresentable> by lazy { |
|
||||||
val rows = ArrayList<RowRepresentable>() |
|
||||||
rows.add(SimpleRow.NAME) |
|
||||||
rows.addAll(TournamentFeatureRow.values()) |
|
||||||
rows |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val realmObjectClass: Class<out Identifiable> = TournamentFeature::class.java |
|
||||||
|
|
||||||
@PrimaryKey |
@PrimaryKey |
||||||
override var id = UUID.randomUUID().toString() |
var id = UUID.randomUUID().toString() |
||||||
|
|
||||||
// The name of the feature |
// The name of the feature |
||||||
override var name: String = "" |
var name: String = "" |
||||||
|
|
||||||
// CountableUsage |
|
||||||
override var useCount: Int = 0 |
|
||||||
|
|
||||||
@Ignore |
|
||||||
override val ownerClass: Class<out RealmModel> = Session::class.java |
|
||||||
|
|
||||||
override fun getDisplayName(context: Context): String { |
override fun uniqueIdentifier(): String { |
||||||
return this.name |
return this.id |
||||||
} |
} |
||||||
|
|
||||||
override fun adapterRows(): List<RowRepresentable>? { |
override val adapterRows: ArrayList<RowRepresentable> |
||||||
return rowRepresentation |
get() { |
||||||
|
val rows = ArrayList<RowRepresentable>() |
||||||
|
rows.add(SimpleRow.NAME) |
||||||
|
rows.addAll(TournamentFeatureRow.values()) |
||||||
|
return rows |
||||||
} |
} |
||||||
|
|
||||||
override fun charSequenceForRow( |
override fun stringForRow(row: RowRepresentable): String { |
||||||
row: RowRepresentable, |
|
||||||
context: Context, |
|
||||||
tag: Int |
|
||||||
): CharSequence { |
|
||||||
return when (row) { |
return when (row) { |
||||||
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT |
SimpleRow.NAME -> this.name |
||||||
else -> return super.charSequenceForRow(row, context, 0) |
else -> return super.stringForRow(row) |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? { |
|
||||||
return row.editingDescriptors(mapOf( |
|
||||||
"defaultValue" to this.name)) |
|
||||||
} |
|
||||||
|
|
||||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
override fun getBottomSheetData(row: RowRepresentable): ArrayList<BottomSheetData> { |
||||||
|
val data = java.util.ArrayList<BottomSheetData>() |
||||||
when (row) { |
when (row) { |
||||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
SimpleRow.NAME -> data.add(BottomSheetData(this.name, SimpleRow.NAME.resId, InputType.TYPE_CLASS_TEXT)) |
||||||
} |
} |
||||||
|
return data |
||||||
} |
} |
||||||
|
|
||||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||||
return when (status) { |
when (row) { |
||||||
SaveValidityStatus.DATA_INVALID -> R.string.tournament_feature_empty_field_error |
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||||
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_tournament_feature_error |
|
||||||
else -> super.getFailedSaveMessage(status) |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
override fun isValidForDelete(realm: Realm): Boolean { |
override fun isValidForSave(): Boolean { |
||||||
return realm.where<Session>().equalTo("tournamentFeatures.id", id).findAll().isEmpty() |
return this.name.isNotEmpty() |
||||||
} |
} |
||||||
} |
} |
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue