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: 'kotlin-android' |
||||
//apply plugin: 'kotlin-android-extensions' |
||||
apply plugin: 'kotlin-android-extensions' |
||||
apply plugin: 'kotlin-kapt' |
||||
apply plugin: 'realm-android' |
||||
// Crashlytics |
||||
apply plugin: 'com.google.gms.google-services' |
||||
apply plugin: 'com.google.firebase.crashlytics' |
||||
// Serialization |
||||
apply plugin: "kotlinx-serialization" |
||||
|
||||
//apply plugin: 'io.fabric' |
||||
|
||||
repositories { |
||||
maven { url 'https://jitpack.io' } // required for MPAndroidChart |
||||
jcenter() // for kotlin serialization |
||||
maven { url 'https://maven.fabric.io/public' } |
||||
} |
||||
|
||||
android { |
||||
|
||||
compileSdkVersion 35 |
||||
buildToolsVersion "30.0.3" |
||||
compileSdkVersion 28 |
||||
|
||||
compileOptions { |
||||
sourceCompatibility JavaVersion.VERSION_1_8 |
||||
targetCompatibility JavaVersion.VERSION_1_8 |
||||
} |
||||
|
||||
kotlinOptions { |
||||
jvmTarget = JavaVersion.VERSION_1_8 |
||||
} |
||||
|
||||
|
||||
defaultConfig { |
||||
applicationId "net.pokeranalytics.android" |
||||
minSdkVersion 23 |
||||
targetSdkVersion 35 |
||||
versionCode 180 |
||||
versionName "6.0.38" |
||||
targetSdkVersion 28 |
||||
versionCode 1 |
||||
versionName "1.0" |
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" |
||||
} |
||||
|
||||
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 { |
||||
minifyEnabled true |
||||
|
||||
minifyEnabled false |
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
||||
applicationVariants.all { variant -> |
||||
variant.outputs.all { output -> |
||||
def date = new Date() |
||||
def formattedDate = date.format('yyMMdd_HHmm') |
||||
def appName = "PokerAnalytics" |
||||
def buildType = variant.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 { |
||||
implementation fileTree(dir: 'libs', include: ['*.jar']) |
||||
|
||||
// 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" |
||||
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" |
||||
|
||||
// Android |
||||
implementation 'androidx.appcompat:appcompat:1.1.0' |
||||
implementation 'androidx.core:core-ktx:1.3.1' |
||||
implementation 'com.google.android.material:material:1.3.0' |
||||
implementation 'androidx.appcompat:appcompat:1.0.2' |
||||
implementation 'androidx.core:core-ktx:1.1.0-alpha04' |
||||
implementation 'com.google.android.material:material:1.0.0' |
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' |
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.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' |
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' |
||||
|
||||
// Places |
||||
implementation 'com.google.android.libraries.places:places:2.3.0' |
||||
// Firebase |
||||
implementation 'com.google.firebase:firebase-core:16.0.7' |
||||
|
||||
// Billing / Subscriptions |
||||
implementation 'com.android.billingclient:billing:7.0.0' |
||||
// Crashlytics |
||||
//implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9' |
||||
|
||||
// Import the BoM for the Firebase platform |
||||
implementation platform('com.google.firebase:firebase-bom:26.1.0') |
||||
// Declare the dependencies for the Crashlytics and Analytics libraries |
||||
// 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' |
||||
// Kotlin |
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1' |
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1" |
||||
|
||||
// Logs |
||||
implementation 'com.jakewharton.timber:timber:4.7.1' |
||||
|
||||
// MPAndroidChart |
||||
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' |
||||
|
||||
// CSV Parser: https://mvnrepository.com/artifact/org.apache.commons/commons-csv |
||||
implementation 'org.apache.commons:commons-csv:1.7' |
||||
|
||||
// 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" |
||||
// Test |
||||
androidTestImplementation 'androidx.test:core:1.0.0' |
||||
androidTestImplementation 'androidx.test:runner:1.1.0' |
||||
androidTestImplementation 'androidx.test:rules:1.1.0' |
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0' |
||||
|
||||
// Retrofit |
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0' |
||||
|
||||
// Volley |
||||
implementation 'com.android.volley:volley:1.2.1' |
||||
|
||||
// Instrumented Tests |
||||
androidTestImplementation 'androidx.test:core:1.6.1' |
||||
androidTestImplementation 'androidx.test:runner:1.6.2' |
||||
androidTestImplementation 'androidx.test:rules:1.6.1' |
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1' |
||||
// Required -- JUnit 4 framework |
||||
testImplementation 'junit:junit:4.12' |
||||
// Optional -- Robolectric environment |
||||
// testImplementation 'androidx.test:core:1.1.0' |
||||
// Optional -- Mockito framework |
||||
testImplementation 'com.android.support.test:runner:1.0.1' |
||||
testImplementation 'com.android.support.test:rules:1.0.1' |
||||
|
||||
// Test |
||||
testImplementation 'junit:junit:4.13.2' |
||||
testImplementation 'com.android.support.test:runner:1.0.2' |
||||
testImplementation 'com.android.support.test:rules:1.0.2' |
||||
// testImplementation 'androidx.test.espresso:espresso-core:3.1.0' |
||||
|
||||
// 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"?> |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:tools="http://schemas.android.com/tools"> |
||||
|
||||
<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 |
||||
android:name=".PokerAnalyticsApplication" |
||||
android:allowBackup="true" |
||||
android:icon="@mipmap/ic_launcher" |
||||
android:label="@string/app_name" |
||||
android:roundIcon="@mipmap/ic_launcher_round" |
||||
android:supportsRtl="true" |
||||
android:largeHeap="true" |
||||
android:theme="@style/PokerAnalyticsTheme"> |
||||
|
||||
<meta-data |
||||
android:name="firebase_crashlytics_collection_enabled" |
||||
android:value="true" /> |
||||
|
||||
<activity |
||||
android:name="net.pokeranalytics.android.ui.activity.HomeActivity" |
||||
android:label="@string/app_name" |
||||
android:launchMode="singleTop" |
||||
android:screenOrientation="portrait" |
||||
android:exported="true"> |
||||
<intent-filter> |
||||
<action android:name="android.intent.action.MAIN" /> |
||||
<action android:name="android.intent.action.VIEW" /> |
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" /> |
||||
</intent-filter> |
||||
|
||||
</activity> |
||||
|
||||
<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 |
||||
android:name="net.pokeranalytics.android.ui.activity.GraphActivity" |
||||
android:launchMode="singleTop" |
||||
android:screenOrientation="portrait" |
||||
android:exported="true" /> |
||||
|
||||
<activity |
||||
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 |
||||
android:name="preloaded_fonts" |
||||
android:resource="@array/preloaded_fonts" /> |
||||
|
||||
<provider |
||||
android:name="androidx.core.content.FileProvider" |
||||
android:authorities="${applicationId}.fileprovider" |
||||
android:exported="false" |
||||
android:grantUriPermissions="true"> |
||||
<meta-data |
||||
android:name="android.support.FILE_PROVIDER_PATHS" |
||||
android:resource="@xml/provider_paths" /> |
||||
</provider> |
||||
|
||||
</application> |
||||
package="net.pokeranalytics.android"> |
||||
|
||||
<application |
||||
android:allowBackup="true" |
||||
android:icon="@mipmap/ic_launcher" |
||||
android:label="@string/app_name" |
||||
android:roundIcon="@mipmap/ic_launcher_round" |
||||
android:supportsRtl="true" |
||||
android:name=".PokerAnalyticsApplication" |
||||
android:theme="@style/PokerAnalyticsTheme"> |
||||
|
||||
<activity |
||||
android:name=".ui.activity.HomeActivity" |
||||
android:label="@string/app_name"> |
||||
<intent-filter> |
||||
<action android:name="android.intent.action.MAIN"/> |
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/> |
||||
</intent-filter> |
||||
</activity> |
||||
<activity android:name=".ui.activity.DataListActivity" /> |
||||
<activity |
||||
android:name=".ui.activity.SessionActivity" |
||||
android:launchMode="singleTop"/> |
||||
|
||||
<activity android:name=".ui.activity.EditableDataActivity"/> |
||||
|
||||
<meta-data |
||||
android:name="preloaded_fonts" |
||||
android:resource="@array/preloaded_fonts" /> |
||||
|
||||
</application> |
||||
|
||||
</manifest> |
||||
|
Before Width: | Height: | Size: 161 KiB |
@ -1,116 +1,60 @@ |
||||
package net.pokeranalytics.android |
||||
|
||||
import android.app.Application |
||||
import android.content.Context |
||||
import android.os.Build |
||||
import com.google.firebase.FirebaseApp |
||||
import io.realm.Realm |
||||
import io.realm.RealmConfiguration |
||||
import io.realm.kotlin.where |
||||
import kotlinx.coroutines.CoroutineScope |
||||
import kotlinx.coroutines.Dispatchers |
||||
import kotlinx.coroutines.launch |
||||
import net.pokeranalytics.android.calculus.ReportWhistleBlower |
||||
import net.pokeranalytics.android.model.migrations.Patcher |
||||
import net.pokeranalytics.android.model.migrations.PokerAnalyticsMigration |
||||
import io.realm.RealmResults |
||||
import net.pokeranalytics.android.model.realm.Session |
||||
import net.pokeranalytics.android.model.utils.Seed |
||||
import net.pokeranalytics.android.util.* |
||||
import net.pokeranalytics.android.util.billing.AppGuard |
||||
import net.pokeranalytics.android.util.PokerAnalyticsLogs |
||||
import timber.log.Timber |
||||
import java.util.* |
||||
|
||||
|
||||
class PokerAnalyticsApplication : Application() { |
||||
|
||||
var reportWhistleBlower: ReportWhistleBlower? = null |
||||
var backupOperator: BackupOperator? = null |
||||
|
||||
companion object { |
||||
|
||||
fun timeSinceInstall(context: Context): Long { |
||||
val installTime: Long = context.packageManager.getPackageInfo(context.packageName, 0).firstInstallTime |
||||
return System.currentTimeMillis() - installTime |
||||
} |
||||
|
||||
} |
||||
|
||||
override fun onCreate() { |
||||
super.onCreate() |
||||
|
||||
if (!BuildConfig.DEBUG) { |
||||
FirebaseApp.initializeApp(this) |
||||
} |
||||
|
||||
UserDefaults.init(this) |
||||
|
||||
// AppGuard / Billing services |
||||
AppGuard.load(this.applicationContext) |
||||
|
||||
// Realm |
||||
Realm.init(this) |
||||
val realmConfiguration = RealmConfiguration.Builder() |
||||
.name(Realm.DEFAULT_REALM_NAME) |
||||
.schemaVersion(14) |
||||
.allowWritesOnUiThread(true) |
||||
.migration(PokerAnalyticsMigration()) |
||||
.initialData(Seed(this)) |
||||
.build() |
||||
Realm.setDefaultConfiguration(realmConfiguration) |
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
||||
val locales = resources.configuration.locales |
||||
CrashLogging.log("App onCreate. Locales = $locales") |
||||
} |
||||
|
||||
if (BuildConfig.DEBUG) { |
||||
// Logs |
||||
Timber.plant(PokerAnalyticsLogs()) |
||||
} |
||||
Timber.d("SDK version = ${Build.VERSION.SDK_INT}") |
||||
|
||||
if (BuildConfig.DEBUG) { |
||||
Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}") |
||||
Timber.d("Realm path = ${Realm.getDefaultInstance().path}") |
||||
|
||||
// this.createFakeSessions() |
||||
} |
||||
|
||||
// Patch |
||||
Patcher.patchAll(this) |
||||
|
||||
// Report |
||||
this.reportWhistleBlower = ReportWhistleBlower(this.applicationContext) |
||||
|
||||
// Backups |
||||
this.backupOperator = BackupOperator(this.applicationContext) |
||||
|
||||
// Infos |
||||
val locale = Locale.getDefault() |
||||
CrashLogging.log("Country: ${locale.country}, language: ${locale.language}") |
||||
|
||||
|
||||
// Realm.getDefaultInstance().executeTransaction { |
||||
// it.delete(Performance::class.java) |
||||
// } |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Create fake sessions if we have less than 10 sessions |
||||
*/ |
||||
private fun createFakeSessions() { |
||||
|
||||
val realm = Realm.getDefaultInstance() |
||||
val sessionsCount = realm.where<Session>().count() |
||||
realm.close() |
||||
|
||||
if (sessionsCount < 10) { |
||||
CoroutineScope(context = Dispatchers.IO).launch { |
||||
FakeDataManager.createFakeSessions(500) |
||||
} |
||||
} |
||||
|
||||
} |
||||
class PokerAnalyticsApplication: Application() { |
||||
|
||||
var sessions: RealmResults<Session>? = null |
||||
|
||||
// private val listener: OrderedRealmCollectionChangeListener<RealmResults<Session>> = |
||||
// OrderedRealmCollectionChangeListener() { realmResults: RealmResults<Session>, changeSet: OrderedCollectionChangeSet -> |
||||
// |
||||
// if (changeSet == null) { |
||||
// return@OrderedRealmCollectionChangeListener |
||||
// } |
||||
// |
||||
// val realm: Realm = Realm.getDefaultInstance() |
||||
// |
||||
// val deletedSessions = realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll() |
||||
// deletedSessions.forEach { it.cleanup() } |
||||
// |
||||
// } |
||||
|
||||
override fun onCreate() { |
||||
super.onCreate() |
||||
|
||||
// Realm |
||||
Realm.init(this) |
||||
val realmConfiguration = RealmConfiguration.Builder() |
||||
.name(Realm.DEFAULT_REALM_NAME) |
||||
.deleteRealmIfMigrationNeeded() |
||||
.build() |
||||
Realm.setDefaultConfiguration(realmConfiguration) |
||||
|
||||
val realm: Realm = Realm.getDefaultInstance() |
||||
// Add observer on session time frame changes |
||||
this.sessions = realm.where(Session::class.java).findAll() // monitor session deletions |
||||
this.sessions?.addChangeListener { t, changeSet -> |
||||
|
||||
val deletedSessions = realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll() |
||||
deletedSessions.forEach { it.cleanup() } |
||||
|
||||
} |
||||
|
||||
if (BuildConfig.DEBUG) { |
||||
// Logs |
||||
Timber.plant(PokerAnalyticsLogs()) |
||||
} else { |
||||
//Fabric.with(this, Crashlytics()) |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -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 |
||||
|
||||
import android.content.Context |
||||
import io.realm.Realm |
||||
import net.pokeranalytics.android.calculus.Stat.* |
||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
||||
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 |
||||
import net.pokeranalytics.android.model.realm.SessionSet |
||||
|
||||
/** |
||||
* The class performing statIds computation |
||||
* The class performing stats computation |
||||
*/ |
||||
class Calculator { |
||||
|
||||
/** |
||||
* The options used for calculations and display |
||||
*/ |
||||
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 |
||||
*/ |
||||
// var progressValues: ProgressValues = progressValues |
||||
// get() { |
||||
// if (field == ProgressValues.NONE && this.display.requireProgressValues) { |
||||
// return ProgressValues.STANDARD |
||||
// } |
||||
// return field |
||||
// } |
||||
|
||||
init { |
||||
this.aggregationType?.let { |
||||
this.criterias = it.criterias |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* The type of evolution numericValues |
||||
*/ |
||||
enum class ProgressValues { |
||||
NONE, |
||||
STANDARD, |
||||
TIMED |
||||
} |
||||
|
||||
/** |
||||
* This function determines whether the standard deviation should be computed |
||||
*/ |
||||
val computeStandardDeviation: Boolean |
||||
get() { |
||||
this.stats.forEach { |
||||
if (it.isStandardDeviation) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
/** |
||||
* 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 { |
||||
|
||||
fun computeStatsWithEvolutionByAggregationType( |
||||
realm: Realm, |
||||
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 |
||||
*/ |
||||
fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options = Options()): Report { |
||||
|
||||
val report = Report(options) |
||||
groups.forEach { group -> |
||||
|
||||
// Clean existing computables / sessionSets if group is reused |
||||
group.cleanup() |
||||
|
||||
// Computes actual sessionGroup statIds |
||||
val results: ComputedResults = this.compute(realm, group, options) |
||||
|
||||
// Computes the compared sessionGroup if existing |
||||
val comparedGroup = group.comparedGroup |
||||
if (comparedGroup != null) { |
||||
val comparedResults = this.compute(realm, comparedGroup, options) |
||||
group.comparedComputedResults = comparedResults |
||||
results.computeStatVariations(comparedResults) |
||||
} |
||||
|
||||
if (options.shouldManageMultiGroupProgressValues) { |
||||
group.comparedComputedResults = report.results.lastOrNull() |
||||
} |
||||
|
||||
results.finalize() // later treatment, such as evolution numericValues sorting |
||||
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 |
||||
*/ |
||||
fun compute(realm: Realm, computableGroup: ComputableGroup, options: 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() |
||||
results.addStat(TOTAL_TIPS, totalTips) |
||||
|
||||
// Timber.d("########## totalBuyin = ${totalBuyin} ### sum = ${sum}") |
||||
|
||||
val maxNetResult = computables.max(ComputableResult.Field.RATED_NET.identifier)?.toDouble() |
||||
maxNetResult?.let { |
||||
results.addStat(MAXIMUM_NET_RESULT, it) |
||||
} |
||||
|
||||
val minNetResult = computables.min(ComputableResult.Field.RATED_NET.identifier)?.toDouble() |
||||
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 = |
||||
computable.session ?: throw PAIllegalStateException("Computing lone ComputableResult") |
||||
results.addEvolutionValue(tSum, stat = NET_RESULT, data = session) |
||||
results.addEvolutionValue(tSum / index, stat = AVERAGE, data = session) |
||||
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 -> |
||||
results.addEvolutionValue(roi, stat = ROI, data = session) |
||||
} |
||||
|
||||
} |
||||
|
||||
if (currentStreak >= 0) { |
||||
longestWinStreak = max(longestWinStreak, currentStreak) |
||||
} else { |
||||
longestLoseStreak = min(longestLoseStreak, currentStreak) |
||||
} |
||||
|
||||
// loseStreak is negative and we want it positive |
||||
results.addStat(LONGEST_STREAKS, longestWinStreak.toDouble(), -longestLoseStreak.toDouble()) |
||||
|
||||
} |
||||
|
||||
val sessionSets = computableGroup.sessionSets(realm, options.shouldSortValues) |
||||
results.addStat(NUMBER_OF_SETS, sessionSets.size.toDouble()) |
||||
|
||||
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 |
||||
|
||||
sessionSets.forEach { sessionSet -> |
||||
tIndex++ |
||||
|
||||
val setStats = SSStats(sessionSet, computableGroup.query) |
||||
|
||||
tRatedNetSum += setStats.ratedNet |
||||
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) |
||||
|
||||
} |
||||
Options.ProgressValues.TIMED -> { |
||||
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()) |
||||
|
||||
} |
||||
|
||||
gHourlyDuration = tHourlyDuration |
||||
gBBSum = tBBSum |
||||
maxDuration = tMaxDuration |
||||
|
||||
} |
||||
|
||||
var hourlyRate = 0.0 |
||||
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 |
||||
|
||||
// Standard Deviation |
||||
if (options.computeStandardDeviation) { |
||||
|
||||
// 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) |
||||
} |
||||
|
||||
} |
||||
|
||||
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 |
||||
} |
||||
/** |
||||
* The options used for calculations or display |
||||
*/ |
||||
class Options { |
||||
|
||||
/** |
||||
* The way the stats are going to be displayed |
||||
*/ |
||||
enum class Display { |
||||
TABLE, |
||||
EVOLUTION, |
||||
COMPARISON, |
||||
MAP, |
||||
POLYNOMIAL |
||||
} |
||||
|
||||
/** |
||||
* The type of evolution values |
||||
*/ |
||||
enum class EvolutionValues { |
||||
NONE, |
||||
STANDARD, |
||||
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 |
||||
*/ |
||||
fun shouldComputeStandardDeviation() : Boolean { |
||||
this.displayedStats.forEach { stat -> |
||||
return when (stat) { |
||||
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true |
||||
else -> false |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// var aggregation: Aggregation? = null |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
fun computePreAggregation(sets: List<SessionSet>, options: Options): List<ComputedResults> { |
||||
return listOf() |
||||
} |
||||
|
||||
/** |
||||
* Computes all stats for list of Session sessionGroup |
||||
*/ |
||||
fun computeGroups(groups: List<SessionGroup>, options: Options): List<ComputedResults> { |
||||
|
||||
var computedResults: MutableList<ComputedResults> = mutableListOf() |
||||
groups.forEach { group -> |
||||
// Computes actual sessionGroup stats |
||||
val results: ComputedResults = Calculator.compute(group, options = options) |
||||
|
||||
// Computes the compared sessionGroup if existing |
||||
val comparedGroup = group.comparedSessions |
||||
if (comparedGroup != null) { |
||||
val comparedResults = Calculator.compute(comparedGroup, options = options) |
||||
group.comparedComputedResults = comparedResults |
||||
results.computeStatVariations(comparedResults) |
||||
} |
||||
|
||||
results.finalize(options) // later treatment, such as evolution values sorting |
||||
computedResults.add(results) |
||||
} |
||||
|
||||
return computedResults |
||||
} |
||||
|
||||
/** |
||||
* Computes stats for a SessionSet |
||||
*/ |
||||
fun compute(sessionGroup: SessionGroup, options: Options) : ComputedResults { |
||||
|
||||
val sessions: List<SessionInterface> = sessionGroup.sessions |
||||
var sessionSets = sessionGroup.sessions.mapNotNull { it.sessionSet }.toHashSet() |
||||
|
||||
var results: ComputedResults = ComputedResults() |
||||
|
||||
var sum: Double = 0.0 |
||||
var totalHands: Double = 0.0 |
||||
var bbSum: Double = 0.0 |
||||
var bbSessionCount: Int = 0 |
||||
var winningSessionCount: Int = 0 |
||||
var totalBuyin: Double = 0.0 |
||||
|
||||
// Compute for each session |
||||
var index: Int = 0 |
||||
sessions.forEach { s -> |
||||
index++; |
||||
|
||||
sum += s.value |
||||
bbSum += s.bbNetResult |
||||
bbSessionCount += s.bigBlindSessionCount |
||||
if (s.value >= 0) { |
||||
winningSessionCount++ |
||||
} |
||||
totalBuyin += s.buyin |
||||
totalHands += s.estimatedHands |
||||
|
||||
if (options.evolutionValues == Options.EvolutionValues.STANDARD) { |
||||
|
||||
results.addEvolutionValue(sum, NETRESULT) |
||||
results.addEvolutionValue(sum / index, AVERAGE) |
||||
results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES) |
||||
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) |
||||
} |
||||
|
||||
} |
||||
|
||||
// Compute for each serie |
||||
var duration: Double = 0.0 |
||||
var hourlyRate: Double = 0.0; var hourlyRateBB: Double = 0.0 |
||||
|
||||
var gIndex = 0; var gSum = 0.0; var gTotalHands = 0.0; var gBBSum = 0.0; |
||||
sessionSets.forEach { sessionSet -> |
||||
gIndex++ |
||||
duration += sessionSet.hourlyDuration |
||||
gSum += sessionSet.netResult |
||||
gTotalHands += sessionSet.estimatedHands |
||||
gBBSum += sessionSet.bbNetResult |
||||
|
||||
hourlyRate = gSum / duration |
||||
hourlyRateBB = gBBSum / duration |
||||
|
||||
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) |
||||
} |
||||
} |
||||
|
||||
val average: Double = sum / sessions.size.toDouble() |
||||
|
||||
// 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 |
||||
if (options.shouldComputeStandardDeviation()) { |
||||
|
||||
var stdSum: Double = 0.0 |
||||
sessions.forEach { s -> |
||||
stdSum += Math.pow(s.value - average, 2.0) |
||||
} |
||||
val standardDeviation: Double = Math.sqrt(stdSum / sessions.size) |
||||
|
||||
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 |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
} |
||||
@ -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 |
||||
|
||||
import android.content.Context |
||||
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.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 |
||||
*/ |
||||
enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable, PerformanceKey { |
||||
|
||||
NET_RESULT(1), |
||||
BB_NET_RESULT(2), |
||||
HOURLY_RATE(3), |
||||
AVERAGE(4), |
||||
NUMBER_OF_SETS(5), |
||||
NUMBER_OF_GAMES(6), |
||||
HOURLY_DURATION(7), |
||||
AVERAGE_HOURLY_DURATION(8), |
||||
NET_BB_PER_100_HANDS(9), |
||||
HOURLY_RATE_BB(10), |
||||
AVERAGE_NET_BB(11), |
||||
WIN_RATIO(12), |
||||
AVERAGE_BUYIN(13), |
||||
ROI(14), |
||||
STANDARD_DEVIATION(15), |
||||
STANDARD_DEVIATION_HOURLY(16), |
||||
STANDARD_DEVIATION_BB_PER_100_HANDS(17), |
||||
HANDS_PLAYED(18), |
||||
LOCATIONS_PLAYED(19), |
||||
LONGEST_STREAKS(20), |
||||
MAXIMUM_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() |
||||
} |
||||
|
||||
val userSelectableList: List<Stat> |
||||
get() { |
||||
return values().filter { it.canBeUserSelected } |
||||
} |
||||
|
||||
val evolutionValuesList: List<Stat> |
||||
get() { |
||||
return values().filter { it.hasProgressValues } |
||||
} |
||||
|
||||
fun returnOnInvestment(netResult: Double, buyin: Double): Double? { |
||||
if (buyin == 0.0) { |
||||
return null |
||||
} |
||||
return netResult / buyin |
||||
} |
||||
|
||||
fun netBBPer100Hands(netBB: Double, numberOfHands: Double): Double? { |
||||
if (numberOfHands == 0.0) { |
||||
return null |
||||
} |
||||
return netBB / numberOfHands * 100 |
||||
} |
||||
|
||||
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? |
||||
get() { |
||||
return when (this) { |
||||
NET_RESULT -> R.string.net_result |
||||
BB_NET_RESULT -> R.string.total_net_result_bb_ |
||||
HOURLY_RATE -> R.string.average_hour_rate |
||||
AVERAGE -> R.string.average |
||||
NUMBER_OF_SETS -> R.string.number_of_sessions |
||||
NUMBER_OF_GAMES -> R.string.number_of_records |
||||
HOURLY_DURATION -> R.string.duration |
||||
AVERAGE_HOURLY_DURATION -> R.string.average_hours_played |
||||
NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands |
||||
HOURLY_RATE_BB -> R.string.average_hour_rate_bb_ |
||||
AVERAGE_NET_BB -> R.string.average_net_result_bb_ |
||||
WIN_RATIO -> R.string.win_ratio |
||||
AVERAGE_BUYIN -> R.string.average_buyin |
||||
ROI -> R.string.tournament_roi |
||||
STANDARD_DEVIATION -> R.string.standard_deviation |
||||
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour |
||||
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands |
||||
STANDARD_DEVIATION_BB -> R.string.bb_standard_deviation |
||||
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") |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Formats the value of the stat to be suitable for display |
||||
*/ |
||||
fun textFormat(value: Double, secondValue: Double? = null, currency: Currency? = null): TextFormat { |
||||
|
||||
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 |
||||
get() { |
||||
return when (this) { |
||||
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 |
||||
*/ |
||||
fun cumulativeLabelResId(context: Context): String { |
||||
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 |
||||
*/ |
||||
val aggregationTypes: List<AggregationType> |
||||
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 |
||||
*/ |
||||
val hasProgressGraph: Boolean |
||||
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 |
||||
*/ |
||||
val graphSignificantIndividualValue: Boolean |
||||
get() { |
||||
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 |
||||
*/ |
||||
val graphShouldShowNumberOfSessions: Boolean |
||||
get() { |
||||
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 |
||||
} |
||||
} |
||||
enum class Stat : RowRepresentable { |
||||
|
||||
NETRESULT, |
||||
HOURLY_RATE, |
||||
AVERAGE, |
||||
NUMBER_OF_SETS, |
||||
NUMBER_OF_GAMES, |
||||
DURATION, |
||||
AVERAGE_DURATION, |
||||
NET_BB_PER_100_HANDS, |
||||
HOURLY_RATE_BB, |
||||
AVERAGE_NET_BB, |
||||
WIN_RATIO, |
||||
AVERAGE_BUYIN, |
||||
ROI, |
||||
STANDARD_DEVIATION, |
||||
STANDARD_DEVIATION_HOURLY, |
||||
STANDARD_DEVIATION_BB_PER_100_HANDS, |
||||
HANDS_PLAYED; |
||||
|
||||
/** |
||||
* Returns whether the stat evolution 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 |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
fun returnOnInvestment(netResult: Double, buyin: Double) : Double { |
||||
return netResult / buyin |
||||
} |
||||
|
||||
fun netBBPer100Hands(netBB: Double, numberOfHands: Double) : Double { |
||||
return netBB / numberOfHands * 100 |
||||
} |
||||
|
||||
} |
||||
|
||||
override val resId: Int? |
||||
get() { |
||||
return when (this) { |
||||
NETRESULT -> R.string.net_result |
||||
HOURLY_RATE -> R.string.average_hour_rate |
||||
AVERAGE -> R.string.average |
||||
NUMBER_OF_SETS -> R.string.number_of_groups |
||||
NUMBER_OF_GAMES -> R.string.number_of_games |
||||
DURATION -> R.string.duration |
||||
AVERAGE_DURATION -> R.string.average_duration |
||||
NET_BB_PER_100_HANDS -> R.string.net_bb_per_100_hands |
||||
HOURLY_RATE_BB -> R.string.hourly_rate_bb |
||||
AVERAGE_NET_BB -> R.string.average_net_bb |
||||
WIN_RATIO -> R.string.win_ratio |
||||
AVERAGE_BUYIN -> R.string.average_buyin |
||||
ROI -> R.string.roi |
||||
STANDARD_DEVIATION -> R.string.standard_deviation |
||||
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_hourly |
||||
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands |
||||
HANDS_PLAYED -> R.string.hands_played |
||||
} |
||||
} |
||||
|
||||
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal |
||||
} |
||||
|
||||
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 |
||||
} |
||||
} |
||||
/** |
||||
* ComputedStat contains a [stat] and their associated [value] |
||||
*/ |
||||
class ComputedStat(stat: Stat, value: Double) { |
||||
|
||||
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { |
||||
if (previousValue != null) { |
||||
this.variation = (value - previousValue) / previousValue |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* The statistic type |
||||
*/ |
||||
var stat: Stat = stat |
||||
|
||||
/** |
||||
* The stat value |
||||
*/ |
||||
var value: Double = value |
||||
|
||||
/** |
||||
* The variation of the stat |
||||
*/ |
||||
var variation: Double? = null |
||||
|
||||
/** |
||||
* Formats the value of the stat to be suitable for display |
||||
*/ |
||||
fun format() : StatFormat { |
||||
return StatFormat() |
||||
} |
||||
|
||||
/** |
||||
* Returns a StatFormat instance for an evolution value located at the specified [index] |
||||
*/ |
||||
fun evolutionValueFormat(index: Int) : StatFormat { |
||||
return StatFormat() |
||||
} |
||||
|
||||
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 |
||||
|
||||
import android.content.Context |
||||
import android.text.InputType |
||||
import io.realm.Realm |
||||
import io.realm.RealmList |
||||
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.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.model.ObjectSavable |
||||
import net.pokeranalytics.android.ui.adapter.components.LiveDataDataSource |
||||
import net.pokeranalytics.android.ui.adapter.components.RowRepresentableDataSource |
||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData |
||||
import net.pokeranalytics.android.ui.view.BankrollRow |
||||
import net.pokeranalytics.android.ui.view.RowEditable |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.RowUpdatable |
||||
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 net.pokeranalytics.android.ui.view.SimpleRow |
||||
import java.util.* |
||||
|
||||
enum class ResultCaptureType { |
||||
|
||||
BUYIN_CASHED_OUT, |
||||
NET_RESULT; |
||||
|
||||
companion object { |
||||
val buyinCashedOutFields = listOf( |
||||
SessionPropertiesRow.CASHED_OUT, |
||||
SessionPropertiesRow.BUY_IN, |
||||
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 |
||||
override var id = UUID.randomUUID().toString() |
||||
|
||||
override var name: String = "" |
||||
|
||||
// Indicates whether the bankroll is live or online |
||||
var live: Boolean = true |
||||
|
||||
/** |
||||
* The list of transactions of the bankroll |
||||
*/ |
||||
@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 |
||||
var currency: Currency? = null |
||||
|
||||
// The initial value of the bankroll |
||||
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 { |
||||
return realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty() |
||||
&& realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty() |
||||
} |
||||
|
||||
override fun getDeleteStatus(context: Context, realm: Realm): DeleteValidityStatus { |
||||
return if (!realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty()) { |
||||
DeleteValidityStatus.SESSIONS_LINKED |
||||
} else if (!realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty()) { |
||||
DeleteValidityStatus.TRANSACTIONS_LINKED |
||||
} else { |
||||
DeleteValidityStatus.VALID |
||||
} |
||||
} |
||||
|
||||
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int { |
||||
return when (status) { |
||||
DeleteValidityStatus.SESSIONS_LINKED -> R.string.bankroll_relationship_error |
||||
DeleteValidityStatus.TRANSACTIONS_LINKED -> R.string.bankroll_relationship_error_transactions |
||||
else -> super.getFailedDeleteMessage(status) |
||||
} |
||||
} |
||||
|
||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
||||
return when (status) { |
||||
SaveValidityStatus.DATA_INVALID -> R.string.empty_name_for_br_error |
||||
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_bankroll_name_error |
||||
else -> super.getFailedSaveMessage(status) |
||||
} |
||||
} |
||||
|
||||
fun resultCaptureType(context: Context): ResultCaptureType { |
||||
return Preferences.getResultCaptureType(this, context) |
||||
?: run { |
||||
when (this.live) { |
||||
true -> ResultCaptureType.BUYIN_CASHED_OUT |
||||
else -> ResultCaptureType.NET_RESULT |
||||
} |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
fun getOrCreate(realm: Realm, name: String, live: Boolean = true, currencyCode: String? = null, currencyRate: Double? = null) : Bankroll { |
||||
|
||||
val br = realm.where<Bankroll>().equalTo("name", name).findFirst() |
||||
return if (br != null) { |
||||
br |
||||
} else { |
||||
val bankroll = Bankroll() |
||||
bankroll.name = name |
||||
bankroll.live = live |
||||
val currency = Currency() |
||||
currency.code = currencyCode |
||||
currency.rate = currencyRate |
||||
bankroll.currency = currency |
||||
realm.copyToRealm(bankroll) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||
when (row) { |
||||
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 |
||||
get() { |
||||
this.currency?.code?.let { |
||||
return java.util.Currency.getInstance(it) |
||||
} |
||||
return UserDefaults.currency |
||||
} |
||||
|
||||
@Ignore |
||||
override val realmObjectClass: Class<out Identifiable> = Bankroll::class.java |
||||
|
||||
import kotlin.collections.ArrayList |
||||
|
||||
open class Bankroll(name: String = "") : RealmObject(), RowRepresentableDataSource, |
||||
RowEditable, ObjectSavable { |
||||
|
||||
companion object { |
||||
fun newInstance() : Bankroll { |
||||
var bankroll: Bankroll = Bankroll() |
||||
return bankroll |
||||
} |
||||
} |
||||
|
||||
@PrimaryKey |
||||
var id = UUID.randomUUID().toString() |
||||
|
||||
// the name of the bankroll |
||||
var name: String = name |
||||
|
||||
// Indicates whether the bankroll is live or online |
||||
var live: Boolean = true |
||||
|
||||
// The list of transactions of the bankroll |
||||
var transactions: RealmList<Transaction> = RealmList() |
||||
|
||||
// The currency of the bankroll |
||||
var currency: Currency? = null |
||||
|
||||
// @todo rate management |
||||
|
||||
override fun uniqueIdentifier(): String { |
||||
return this.id |
||||
} |
||||
|
||||
override val adapterRows: ArrayList<RowRepresentable> |
||||
get() { |
||||
val rows = ArrayList<RowRepresentable>() |
||||
rows.add(SimpleRow.NAME) |
||||
rows.addAll(BankrollRow.values()) |
||||
return rows |
||||
} |
||||
|
||||
override fun stringForRow(row: RowRepresentable): String { |
||||
return when (row) { |
||||
SimpleRow.NAME -> this.name |
||||
BankrollRow.LIVE -> if (this.live) "live" else "online" |
||||
BankrollRow.CURRENCY -> this.currency?.code?: "" |
||||
else -> return super.stringForRow(row) |
||||
} |
||||
} |
||||
|
||||
override fun boolForRow(row: RowRepresentable): Boolean { |
||||
when (row) { |
||||
BankrollRow.LIVE -> return true |
||||
else -> return super.boolForRow(row) |
||||
} |
||||
} |
||||
|
||||
override fun getBottomSheetData(row: RowRepresentable): ArrayList<BottomSheetData> { |
||||
val data = java.util.ArrayList<BottomSheetData>() |
||||
when (row) { |
||||
SimpleRow.NAME -> data.add(BottomSheetData(this.name, SimpleRow.NAME.resId, InputType.TYPE_CLASS_TEXT)) |
||||
} |
||||
return data |
||||
} |
||||
|
||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||
when (row) { |
||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||
} |
||||
} |
||||
|
||||
override fun isValidForSave(): Boolean { |
||||
val realm = Realm.getDefaultInstance() |
||||
return (realm.where(Bankroll::class.java) |
||||
.notEqualTo("id", this.id) |
||||
.equalTo("name", this.name) |
||||
.findAll().size == 0) |
||||
return this.name.isNotEmpty() |
||||
} |
||||
} |
||||
@ -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 |
||||
|
||||
import io.realm.RealmObject |
||||
import io.realm.annotations.Ignore |
||||
import io.realm.annotations.PrimaryKey |
||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
||||
import net.pokeranalytics.android.util.UserDefaults |
||||
import java.util.* |
||||
|
||||
open class Currency : RealmObject() { |
||||
|
||||
companion object { |
||||
|
||||
@Ignore |
||||
val DEFAULT_RATE: Double = 1.0 |
||||
|
||||
} |
||||
open class Currency : RealmObject() { |
||||
|
||||
@PrimaryKey |
||||
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 |
||||
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 { |
||||
this.code?.let { return it == UserDefaults.currency.currencyCode } |
||||
return false |
||||
} |
||||
// The rate of the currency with the main currency |
||||
var rate: Double? = null |
||||
|
||||
} |
||||
@ -1,347 +1,19 @@ |
||||
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.annotations.Ignore |
||||
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 kotlin.collections.ArrayList |
||||
|
||||
|
||||
open class CustomField : RealmObject(), RowRepresentable, RowUpdatable, NameManageable, StaticRowRepresentableDataSource { |
||||
open class CustomField : RealmObject() { |
||||
|
||||
companion object { |
||||
@PrimaryKey |
||||
var id = UUID.randomUUID().toString() |
||||
|
||||
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) |
||||
} |
||||
} |
||||
// The name of the currency field |
||||
var name: String = "" |
||||
|
||||
} |
||||
// @todo |
||||
|
||||
@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 |
||||
override var id = UUID.randomUUID().toString() |
||||
|
||||
/** |
||||
* The name of the custom field |
||||
*/ |
||||
override var name: String = "" |
||||
|
||||
// The type of the custom fields, mapped with the CustomField.Type enum |
||||
var type: Int = Type.LIST.uniqueIdentifier |
||||
set(value) { |
||||
if (field == Type.LIST.uniqueIdentifier && value != Type.LIST.uniqueIdentifier) { |
||||
this.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 { |
||||
return this.name |
||||
} |
||||
|
||||
override val bottomSheetType: BottomSheetType |
||||
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 |
||||
|
||||
import android.content.Context |
||||
import io.realm.* |
||||
import io.realm.annotations.Ignore |
||||
import io.realm.MutableRealmInteger |
||||
import io.realm.RealmObject |
||||
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.* |
||||
|
||||
/** |
||||
* 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 { |
||||
//import net.pokeranalytics.android.FilterComponent |
||||
|
||||
@Ignore |
||||
override val realmObjectClass: Class<out Identifiable> = Filter::class.java |
||||
|
||||
companion object { |
||||
open class Filter : RealmObject() { |
||||
|
||||
// Create a new instance |
||||
fun newInstance(filterableTypeUniqueIdentifier: Int): Filter { |
||||
val filter = Filter() |
||||
filter.filterableTypeUniqueIdentifier = filterableTypeUniqueIdentifier |
||||
return filter |
||||
} |
||||
@PrimaryKey |
||||
var id = UUID.randomUUID().toString() |
||||
|
||||
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() |
||||
} |
||||
} |
||||
// the filter name |
||||
var name: String = "" |
||||
|
||||
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 |
||||
// the number of use of the filter, |
||||
// for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters |
||||
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0) |
||||
|
||||
// var components: List<FilterComponent> = listOf() |
||||
|
||||
@PrimaryKey |
||||
override var id = UUID.randomUUID().toString() |
||||
|
||||
// the queryWith name |
||||
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 { |
||||
return context.getString(R.string.name) |
||||
} |
||||
|
||||
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? { |
||||
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 |
||||
|
||||
import android.content.Context |
||||
import io.realm.Realm |
||||
import io.realm.RealmModel |
||||
import android.text.InputType |
||||
import io.realm.RealmObject |
||||
import io.realm.annotations.Ignore |
||||
import io.realm.annotations.PrimaryKey |
||||
import io.realm.kotlin.where |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.model.interfaces.UsageCountable |
||||
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.model.ObjectSavable |
||||
import net.pokeranalytics.android.ui.adapter.components.LiveDataDataSource |
||||
import net.pokeranalytics.android.ui.adapter.components.RowRepresentableDataSource |
||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData |
||||
import net.pokeranalytics.android.ui.view.GameRow |
||||
import net.pokeranalytics.android.ui.view.RowEditable |
||||
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.rows.GamePropertiesRow |
||||
import net.pokeranalytics.android.ui.view.rows.SimpleRow |
||||
import net.pokeranalytics.android.util.NULL_TEXT |
||||
import net.pokeranalytics.android.ui.view.SimpleRow |
||||
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 |
||||
@PrimaryKey |
||||
var id = UUID.randomUUID().toString() |
||||
|
||||
companion object { |
||||
val rowRepresentation : List<RowRepresentable> by lazy { |
||||
val rows = ArrayList<RowRepresentable>() |
||||
rows.add(SimpleRow.NAME) |
||||
rows |
||||
} |
||||
} |
||||
// The name of the game |
||||
var name: String = "" |
||||
|
||||
@PrimaryKey |
||||
override var id = UUID.randomUUID().toString() |
||||
// A shorter name for the game |
||||
var shortName: String? = null |
||||
|
||||
// The name of the game |
||||
override var name: String = "" |
||||
|
||||
// A shorter name for the game |
||||
var shortName: String? = null |
||||
|
||||
// CountableUsage |
||||
override var useCount: Int = 0 |
||||
|
||||
@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>? { |
||||
return rowRepresentation |
||||
} |
||||
|
||||
override fun charSequenceForRow( |
||||
row: RowRepresentable, |
||||
context: Context, |
||||
tag: Int |
||||
): CharSequence { |
||||
return when (row) { |
||||
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT |
||||
GamePropertiesRow.SHORT_NAME -> this.shortName ?: NULL_TEXT |
||||
else -> return super.charSequenceForRow(row, context, 0) |
||||
} |
||||
} |
||||
|
||||
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? { |
||||
return when (row) { |
||||
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name)) |
||||
GamePropertiesRow.SHORT_NAME -> row.editingDescriptors(mapOf("defaultValue" to this.shortName)) |
||||
else -> null |
||||
} |
||||
} |
||||
|
||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||
when (row) { |
||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||
GamePropertiesRow.SHORT_NAME -> this.shortName = value as String? ?: "" |
||||
} |
||||
} |
||||
override fun uniqueIdentifier(): String { |
||||
return this.id |
||||
} |
||||
|
||||
override fun isValidForSave(): Boolean { |
||||
return name.isNotEmpty() |
||||
} |
||||
override val adapterRows: ArrayList<RowRepresentable> |
||||
get() { |
||||
val rows = ArrayList<RowRepresentable>() |
||||
rows.add(SimpleRow.NAME) |
||||
rows.addAll(GameRow.values()) |
||||
return rows |
||||
} |
||||
|
||||
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 stringForRow(row: RowRepresentable): String { |
||||
return when (row) { |
||||
SimpleRow.NAME -> this.name |
||||
GameRow.SHORT_NAME -> this.shortName?:"" |
||||
else -> return super.stringForRow(row) |
||||
} |
||||
} |
||||
|
||||
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) |
||||
} |
||||
override fun getBottomSheetData(row: RowRepresentable): ArrayList<BottomSheetData> { |
||||
val data = java.util.ArrayList<BottomSheetData>() |
||||
when (row) { |
||||
SimpleRow.NAME -> data.add(BottomSheetData(this.name, SimpleRow.NAME.resId, InputType.TYPE_CLASS_TEXT)) |
||||
GameRow.SHORT_NAME -> data.add(BottomSheetData(this.shortName, GameRow.SHORT_NAME.resId, InputType.TYPE_CLASS_TEXT)) |
||||
} |
||||
return data |
||||
} |
||||
|
||||
private val isOmaha5: Boolean |
||||
get() { |
||||
return name.contains("5") |
||||
} |
||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||
when (row) { |
||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||
GameRow.SHORT_NAME -> this.shortName = value as String |
||||
} |
||||
} |
||||
|
||||
override fun isValidForSave(): Boolean { |
||||
return this.name.isNotEmpty() |
||||
} |
||||
} |
||||
|
||||
@ -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,71 +1,66 @@ |
||||
package net.pokeranalytics.android.model.realm |
||||
|
||||
import android.content.Context |
||||
import com.google.android.libraries.places.api.model.Place |
||||
import io.realm.Realm |
||||
import android.text.InputType |
||||
import io.realm.RealmObject |
||||
import io.realm.annotations.Ignore |
||||
import io.realm.annotations.PrimaryKey |
||||
import io.realm.kotlin.where |
||||
import net.pokeranalytics.android.R |
||||
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.model.ObjectSavable |
||||
import net.pokeranalytics.android.ui.adapter.components.* |
||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData |
||||
import net.pokeranalytics.android.ui.view.RowEditable |
||||
import net.pokeranalytics.android.ui.view.LocationRow |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.RowUpdatable |
||||
import net.pokeranalytics.android.ui.view.rows.SimpleRow |
||||
import net.pokeranalytics.android.ui.view.SimpleRow |
||||
import java.util.* |
||||
|
||||
|
||||
open class Location : RealmObject(), NameManageable, RowRepresentable, RowUpdatable { |
||||
open class Location : RealmObject(), RowRepresentableDataSource, RowEditable, ObjectSavable { |
||||
|
||||
@Ignore |
||||
override val realmObjectClass: Class<out Identifiable> = Location::class.java |
||||
@PrimaryKey |
||||
var id = UUID.randomUUID().toString() |
||||
|
||||
@PrimaryKey |
||||
override var id = UUID.randomUUID().toString() |
||||
// The name of the location |
||||
var name: String = "" |
||||
|
||||
// The name of the location |
||||
override var name: String = "" |
||||
// the longitude of the location |
||||
var longitude: Double? = null |
||||
|
||||
// The readable address of the location |
||||
private var address: String? = null |
||||
// the latitude of the location |
||||
var latitude: Double? = null |
||||
|
||||
// the longitude of the location |
||||
var longitude: Double? = null |
||||
override fun uniqueIdentifier(): String { |
||||
return this.id |
||||
} |
||||
|
||||
// the latitude of the location |
||||
var latitude: Double? = null |
||||
|
||||
override fun getDisplayName(context: Context): String { |
||||
return this.name |
||||
} |
||||
override val adapterRows: ArrayList<RowRepresentable> |
||||
get() { |
||||
val rows = ArrayList<RowRepresentable>() |
||||
rows.add(SimpleRow.NAME) |
||||
rows.addAll(LocationRow.values()) |
||||
return rows |
||||
} |
||||
|
||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||
when (row) { |
||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||
} |
||||
} |
||||
override fun stringForRow(row: RowRepresentable): String { |
||||
return when (row) { |
||||
SimpleRow.NAME -> this.name |
||||
else -> return super.stringForRow(row) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Fill the location attributes with a place object |
||||
*/ |
||||
fun setPlace(place: Place) { |
||||
this.name = place.name ?: "" |
||||
this.address = place.address ?: "" |
||||
this.latitude = place.latLng?.latitude |
||||
this.longitude = place.latLng?.longitude |
||||
} |
||||
override fun getBottomSheetData(row: RowRepresentable): ArrayList<BottomSheetData> { |
||||
val data = java.util.ArrayList<BottomSheetData>() |
||||
when (row) { |
||||
SimpleRow.NAME -> data.add(BottomSheetData(this.name, SimpleRow.NAME.resId, InputType.TYPE_CLASS_TEXT)) |
||||
} |
||||
return data |
||||
} |
||||
|
||||
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { |
||||
return when (status) { |
||||
SaveValidityStatus.DATA_INVALID -> R.string.location_empty_field_error |
||||
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_location_error |
||||
else -> super.getFailedSaveMessage(status) |
||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||
when (row) { |
||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun isValidForDelete(realm: Realm): Boolean { |
||||
return realm.where<Session>().equalTo("location.id", id).findAll().isEmpty() |
||||
} |
||||
override fun isValidForSave(): Boolean { |
||||
return this.name.isNotEmpty() |
||||
} |
||||
} |
||||
|
||||
@ -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 |
||||
|
||||
import android.content.Context |
||||
import io.realm.Realm |
||||
import io.realm.RealmList |
||||
import io.realm.RealmObject |
||||
import io.realm.RealmResults |
||||
import io.realm.annotations.Ignore |
||||
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.* |
||||
|
||||
open class Player : RealmObject(), NameManageable, Savable, Deletable, RowRepresentable, RowUpdatable { |
||||
open class Player : RealmObject() { |
||||
|
||||
@PrimaryKey |
||||
override var id = UUID.randomUUID().toString() |
||||
var id = UUID.randomUUID().toString() |
||||
|
||||
// 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 |
||||
|
||||
import android.content.Context |
||||
import io.realm.Realm |
||||
import io.realm.RealmObject |
||||
import io.realm.RealmResults |
||||
import io.realm.annotations.Ignore |
||||
import io.realm.annotations.LinkingObjects |
||||
import io.realm.annotations.PrimaryKey |
||||
import net.pokeranalytics.android.calculus.Stat |
||||
import net.pokeranalytics.android.calculus.StatFormattingException |
||||
import net.pokeranalytics.android.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() |
||||
|
||||
|
||||
open class SessionSet() : RealmObject() { |
||||
|
||||
/** |
||||
* The timeframe of the set, i.e. its start & end date |
||||
*/ |
||||
|
||||
var timeFrame: TimeFrame? = null |
||||
|
||||
/** |
||||
* The list of sessions associated with this set |
||||
*/ |
||||
@LinkingObjects("sessionSet") |
||||
val sessions: RealmResults<Session>? = null |
||||
|
||||
@Ignore // a duration shortcut |
||||
var duration: Long = 0L |
||||
get() { |
||||
return this.timeFrame?.duration ?: 0L |
||||
} |
||||
|
||||
var endDate: Date = Date() |
||||
set(value) { |
||||
field = value |
||||
this.computeNetDuration() |
||||
@Ignore // a duration in hour |
||||
var hourlyDuration: Double = 0.0 |
||||
get() { |
||||
return this.timeFrame?.hourlyDuration ?: 0.0 |
||||
} |
||||
|
||||
override fun startDate(): Date? { |
||||
return this.startDate |
||||
} |
||||
@Ignore // a netResult shortcut |
||||
var netResult: Double = 0.0 |
||||
get () { |
||||
return this.sessions?.sumByDouble { it.value } ?: 0.0 |
||||
} |
||||
|
||||
override fun endDate(): Date { |
||||
return this.endDate |
||||
} |
||||
@Ignore // a duration shortcut |
||||
var hourlyRate: Double = 0.0 |
||||
|
||||
override var breakDuration: Long = 0L |
||||
set(value) { |
||||
field = value |
||||
this.computeNetDuration() |
||||
} |
||||
@Ignore |
||||
var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0) |
||||
|
||||
/** |
||||
* The start date of the break |
||||
*/ |
||||
override var pauseDate: Date? = null |
||||
@Ignore |
||||
var bbNetResult: Double = 0.0 |
||||
|
||||
/** |
||||
* the net duration of the set (READONLY) |
||||
*/ |
||||
override var netDuration: Long = 0L |
||||
companion object { |
||||
|
||||
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 |
||||
} |
||||
fun newInstance(realm: Realm) : SessionSet { |
||||
val sessionSet: SessionSet = realm.createObject(SessionSet::class.java) |
||||
sessionSet.timeFrame = realm.createObject(TimeFrame::class.java) |
||||
return sessionSet |
||||
} |
||||
|
||||
/** |
||||
* The list of endedSessions associated with this set |
||||
*/ |
||||
@LinkingObjects("sessionSet") |
||||
val sessions: RealmResults<Session>? = null |
||||
} |
||||
|
||||
var ratedNet: Double = 0.0 |
||||
|
||||
val hourlyRate: Double |
||||
get() { |
||||
return this.ratedNet / this.hourlyDuration |
||||
} |
||||
|
||||
var estimatedHands: Double = 0.0 |
||||
|
||||
var bbNet: BB = 0.0 |
||||
|
||||
val bbHourlyRate: BB |
||||
get() { |
||||
return this.bbNet / this.hourlyDuration |
||||
} |
||||
|
||||
enum class Field(val identifier: String) { |
||||
START_DATE("startDate"), |
||||
RATED_NET("ratedNet"), |
||||
HOURLY_RATE("hourlyRate"), |
||||
BB_NET("bbNet"), |
||||
ESTIMATED_HANDS("estimatedHands"), |
||||
NET_DURATION("netDuration") |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
fun newInstance(realm: Realm) : SessionSet { |
||||
val sessionSet = SessionSet() |
||||
return realm.copyToRealm(sessionSet) |
||||
} |
||||
|
||||
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { |
||||
Session.fieldNameForQueryType(queryCondition)?.let { |
||||
return "sessions.$it" |
||||
} |
||||
return null |
||||
} |
||||
|
||||
} |
||||
|
||||
// Stat Base |
||||
|
||||
override fun entryTitle(context: Context): String { |
||||
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate) |
||||
} |
||||
|
||||
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 |
||||
// |
||||
//import io.realm.RealmObject |
||||
//import io.realm.RealmQuery |
||||
//import io.realm.RealmResults |
||||
//import io.realm.annotations.Ignore |
||||
//import io.realm.annotations.LinkingObjects |
||||
//import net.pokeranalytics.android.exceptions.ModelException |
||||
//import timber.log.Timber |
||||
//import java.util.* |
||||
// |
||||
//open class TimeFrame : RealmObject() { |
||||
// |
||||
// // A start date |
||||
// var startDate: Date = Date() |
||||
// private set(value) { |
||||
// field = value |
||||
// this.computeNetDuration() |
||||
// } |
||||
// |
||||
// // An end date |
||||
// var endDate: Date? = null |
||||
// private set(value) { |
||||
// field = value |
||||
// this.computeNetDuration() |
||||
// } |
||||
// |
||||
// // The latest pause date |
||||
// var pauseDate: Date? = null |
||||
// set(value) { |
||||
// field?.let { |
||||
// if (value == null && field != null) { |
||||
// breakDuration += Date().time - it.time |
||||
// } |
||||
// } |
||||
// field = value |
||||
// this.computeNetDuration() |
||||
// } |
||||
// |
||||
// // The break netDuration |
||||
// var breakDuration: Long = 0L |
||||
// set(value) { |
||||
// field = value |
||||
// this.computeNetDuration() |
||||
// } |
||||
// |
||||
// // the total netDuration |
||||
// var netDuration: Long = 0L |
||||
// private set |
||||
// |
||||
// var hourlyDuration: Double = 0.0 |
||||
// get() { |
||||
// return this.netDuration / 3600000.0 // 3.6 millions of milliseconds |
||||
// } |
||||
// |
||||
// // Session |
||||
// @LinkingObjects("timeFrame") |
||||
// private val endedSessions: RealmResults<Session>? = null // we should have only one session |
||||
// |
||||
// @Ignore |
||||
// var session: Session? = null |
||||
// get() = if (this.endedSessions != null && this.endedSessions.isEmpty()) null else this.endedSessions?.first() |
||||
// |
||||
// // Group |
||||
// @LinkingObjects("timeFrame") |
||||
// private val sets: RealmResults<SessionSet>? = null // we should have only one sessionGroup |
||||
// |
||||
// @Ignore |
||||
// var set: SessionSet? = null |
||||
// get() = this.sets?.first() |
||||
// |
||||
// fun setStart(startDate: Date) { |
||||
// this.startDate = startDate |
||||
// this.session?.let { |
||||
// this.notifySessionDateChange(it) |
||||
// } |
||||
// } |
||||
// |
||||
// fun setEnd(endDate: Date?) { |
||||
// this.endDate = endDate |
||||
// this.session?.let { |
||||
// this.notifySessionDateChange(it) |
||||
// } |
||||
// } |
||||
// |
||||
// fun setDate(startDate: Date, endDate: Date?) { |
||||
// this.startDate = startDate |
||||
// this.endDate = endDate |
||||
// |
||||
// this.session?.let { |
||||
// this.notifySessionDateChange(it) |
||||
// } |
||||
// } |
||||
// |
||||
// /** |
||||
// * Computes the net netDuration of the session |
||||
// */ |
||||
// private fun computeNetDuration() { |
||||
// var endDate: Date = this.endDate ?: Date() |
||||
// this.netDuration = endDate.time - this.startDate.time - this.breakDuration |
||||
// } |
||||
// |
||||
// /** |
||||
// * Queries all time frames that might be impacted by the date change |
||||
// * Makes all necessary changes to keep sequential time frames |
||||
// */ |
||||
// fun notifySessionDateChange(owner: Session) { |
||||
// |
||||
// var query: RealmQuery<SessionSet> = this.realm.where(SessionSet::class.java) |
||||
// query.isNotNull("timeFrame") |
||||
// |
||||
//// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}") |
||||
// |
||||
// val sets = realm.where(SessionSet::class.java).findAll() |
||||
//// Timber.d("set count = ${sets.size}") |
||||
// |
||||
// if (this.endDate == null) { |
||||
// query.greaterThanOrEqualTo("timeFrame.startDate", this.startDate) |
||||
// .or() |
||||
// .greaterThanOrEqualTo("timeFrame.endDate", this.startDate) |
||||
// .or() |
||||
// .isNull("timeFrame.endDate") |
||||
// } else { |
||||
// val endDate = this.endDate!! |
||||
// query |
||||
// .lessThanOrEqualTo("timeFrame.startDate", this.startDate) |
||||
// .greaterThanOrEqualTo("timeFrame.endDate", this.startDate) |
||||
// .or() |
||||
// .lessThanOrEqualTo("timeFrame.startDate", endDate) |
||||
// .greaterThanOrEqualTo("timeFrame.endDate", endDate) |
||||
// .or() |
||||
// .greaterThanOrEqualTo("timeFrame.startDate", this.startDate) |
||||
// .lessThanOrEqualTo("timeFrame.endDate", endDate) |
||||
// .or() |
||||
// .isNull("timeFrame.endDate") |
||||
// .lessThanOrEqualTo("timeFrame.startDate", endDate) |
||||
// } |
||||
// |
||||
// val sessionGroups = query.findAll() |
||||
// |
||||
// this.updateTimeFrames(sessionGroups, owner) |
||||
// |
||||
// } |
||||
// |
||||
// /** |
||||
// * Update Time frames from sets |
||||
// */ |
||||
// private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, owner: Session) { |
||||
// |
||||
// when (sessionSets.size) { |
||||
// 0 -> this.createOrUpdateSessionSet(owner) |
||||
// 1 -> this.updateSessionGroup(owner, sessionSets.first()!!) |
||||
// else -> this.mergeSessionGroups(owner, sessionSets) |
||||
// } |
||||
// |
||||
// } |
||||
// |
||||
// /** |
||||
// * Creates the session sessionGroup when the session has none |
||||
// */ |
||||
// private fun createOrUpdateSessionSet(owner: Session) { |
||||
// |
||||
// val set = owner.sessionSet |
||||
// if (set != null) { |
||||
// set.timeFrame?.startDate = this.startDate |
||||
// set.timeFrame?.endDate = this.endDate |
||||
// } else { |
||||
// this.createSessionSet(owner) |
||||
// } |
||||
// |
||||
//// Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}") |
||||
// Timber.d("netDuration 1 = : ${set?.timeFrame?.netDuration}") |
||||
// |
||||
// } |
||||
// |
||||
// fun createSessionSet(owner: Session) { |
||||
// val set: SessionSet = SessionSet.newInstanceForResult(this.realm) |
||||
// set.timeFrame?.let { |
||||
// it.startDate = this.startDate |
||||
// it.endDate = this.endDate |
||||
// } ?: run { |
||||
// throw ModelException("TimeFrame should never be null here") |
||||
// } |
||||
// |
||||
// owner.sessionSet = set |
||||
// } |
||||
// |
||||
// |
||||
// /** |
||||
// * Single SessionSet update, the session might be the owner |
||||
// * Changes the sessionGroup timeframe using the current timeframe dates |
||||
// */ |
||||
// private fun updateSessionGroup(owner: Session, sessionSet: SessionSet) { |
||||
// |
||||
// var timeFrame: TimeFrame = sessionSet.timeFrame!! // tested in the query |
||||
//// timeFrame.setDate(this.startDate, this.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) |
||||
// } else { // there are 2+ endedSessions to manage and possible splits |
||||
// |
||||
// val endDate = this.endDate |
||||
// |
||||
// // 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 |
||||
// if (endDate != null && sisterSessions.all { it.timeFrame?.endDate != null } && timeFrame.endDate == null) { |
||||
// var endedSessions = mutableListOf<Session>(owner) |
||||
// sessionSet.endedSessions?.forEach { endedSessions.add(it) } |
||||
// sessionSet.deleteFromRealm() |
||||
// endedSessions.forEach { it.timeFrame?.notifySessionDateChange(it) } |
||||
// } else { |
||||
// |
||||
// if (this.startDate.before(timeFrame.startDate)) { |
||||
// timeFrame.startDate = this.startDate |
||||
// } |
||||
// if (endDate != null && timeFrame.endDate != null && endDate.after(timeFrame.endDate)) { |
||||
// timeFrame.endDate = endDate |
||||
// } else if (endDate == null) { |
||||
// timeFrame.endDate = null |
||||
// } |
||||
// |
||||
// owner.sessionSet = sessionSet |
||||
// |
||||
//// Timber.d("sd = : ${sessionSet.timeFrame?.startDate}, ed = ${sessionSet.timeFrame?.endDate}") |
||||
// 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}") |
||||
// |
||||
// } |
||||
// |
||||
//} |
||||
package net.pokeranalytics.android.model.realm |
||||
|
||||
import io.realm.Realm |
||||
import io.realm.RealmObject |
||||
import io.realm.RealmQuery |
||||
import io.realm.RealmResults |
||||
import io.realm.annotations.Ignore |
||||
import io.realm.annotations.LinkingObjects |
||||
import net.pokeranalytics.android.exceptions.ModelException |
||||
import timber.log.Timber |
||||
import java.util.* |
||||
|
||||
open class TimeFrame : RealmObject() { |
||||
|
||||
// A start date |
||||
var startDate: Date = Date() |
||||
private set(value) { |
||||
field = value |
||||
this.computeDuration() |
||||
} |
||||
|
||||
// An end date |
||||
var endDate: Date? = null |
||||
private set(value) { |
||||
field = value |
||||
this.computeDuration() |
||||
} |
||||
|
||||
// The break duration |
||||
var breakDuration: Long = 0L |
||||
set(value) { |
||||
field = value |
||||
this.computeDuration() |
||||
} |
||||
|
||||
// the total duration |
||||
var duration: Long = 0L |
||||
private set |
||||
|
||||
var hourlyDuration: Double = 0.0 |
||||
get() { |
||||
return this.duration / 3600000.0 // 3.6 millions of milliseconds |
||||
} |
||||
|
||||
// indicates a state of pause |
||||
var paused: Boolean = false |
||||
|
||||
// Session |
||||
@LinkingObjects("timeFrame") |
||||
private val sessions: RealmResults<Session>? = null // we should have only one session |
||||
|
||||
@Ignore |
||||
var session: Session? = null |
||||
get() = if (this.sessions != null && this.sessions.isEmpty()) null else this.sessions?.first() |
||||
|
||||
// Group |
||||
@LinkingObjects("timeFrame") |
||||
private val sets: RealmResults<SessionSet>? = null // we should have only one sessionGroup |
||||
|
||||
@Ignore |
||||
var set: SessionSet? = null |
||||
get() = this.sets?.first() |
||||
|
||||
fun setDate(startDate: Date?, endDate: Date?) { |
||||
|
||||
startDate?.let { |
||||
this.startDate = startDate |
||||
} |
||||
|
||||
this.endDate = endDate |
||||
|
||||
this.computeDuration() |
||||
|
||||
if (this.session != null) { |
||||
this.notifySessionDateChange() |
||||
} |
||||
} |
||||
|
||||
private fun computeDuration() { |
||||
var endDate: Date = this.endDate ?: Date() |
||||
val netDuration = endDate.time - this.startDate.time - this.breakDuration |
||||
this.duration = netDuration |
||||
} |
||||
|
||||
fun notifySessionDateChange() { |
||||
val realm = Realm.getDefaultInstance() |
||||
var query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java) |
||||
query.isNotNull("timeFrame") |
||||
|
||||
// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}") |
||||
|
||||
if (this.endDate == null) { |
||||
query.greaterThan("timeFrame.startDate", this.startDate.time).or().greaterThan("timeFrame.endDate", this.startDate.time) |
||||
} else { |
||||
val endDate = this.endDate!! |
||||
query |
||||
.lessThan("timeFrame.startDate", this.startDate) |
||||
.greaterThan("timeFrame.endDate", this.startDate) |
||||
.or() |
||||
.lessThan("timeFrame.startDate", endDate) |
||||
.greaterThan("timeFrame.endDate", endDate) |
||||
.or() |
||||
.greaterThan("timeFrame.startDate", this.startDate) |
||||
.lessThan("timeFrame.endDate", endDate) |
||||
} |
||||
|
||||
val sessionGroups = query.findAll() |
||||
|
||||
this.updateTimeFrames(sessionGroups) |
||||
|
||||
// realm.close() |
||||
} |
||||
|
||||
/** |
||||
* Update Time frames from sets |
||||
*/ |
||||
private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>) { |
||||
|
||||
when (sessionSets.size) { |
||||
0 -> this.createSessionGroup() |
||||
1 -> this.updateSingleSessionGroup(sessionSets.first()!!) |
||||
else -> this.mergeSessionGroups(sessionSets) |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Creates the session sessionGroup when the session has none |
||||
*/ |
||||
private fun createSessionGroup() { |
||||
|
||||
val realm = Realm.getDefaultInstance() |
||||
|
||||
val set: SessionSet = SessionSet.newInstance(realm) |
||||
set.timeFrame?.let { |
||||
it.startDate = this.startDate |
||||
it.endDate = this.endDate |
||||
} ?: run { |
||||
throw ModelException("TimeFrame should never be null here") |
||||
} |
||||
|
||||
this.session?.let { |
||||
it.sessionSet = set |
||||
} ?: run { |
||||
throw ModelException("Session should never be null here") |
||||
} |
||||
|
||||
Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}") |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 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 |
||||
|
||||
if (this.startDate.before(groupTimeFrame.startDate)) { |
||||
groupTimeFrame.startDate = this.startDate |
||||
} |
||||
val endDate = this.endDate |
||||
if (endDate != null && groupTimeFrame.endDate != null && endDate.after(groupTimeFrame.endDate)) { |
||||
groupTimeFrame.endDate = endDate |
||||
} else if (endDate == null) { |
||||
groupTimeFrame.endDate = null |
||||
} |
||||
|
||||
this.session?.sessionSet = sessionSet |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Multiple session sets update: |
||||
* Merges all sets into one (delete all then create a new one) |
||||
*/ |
||||
private fun mergeSessionGroups(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 sessions from sets |
||||
var sessions = mutableSetOf<Session>() |
||||
sessionSets.forEach { it.sessions?.asIterable()?.let { it1 -> sessions.addAll(it1) } } |
||||
|
||||
// delete all sets |
||||
sessionSets.deleteAllFromRealm() |
||||
|
||||
// Create a new sets |
||||
val set: SessionSet = SessionSet.newInstance(realm) |
||||
set.timeFrame?.let { |
||||
it.startDate = startDate |
||||
it.endDate = endDate |
||||
} ?: run { |
||||
throw ModelException("TimeFrame should never be null here") |
||||
} |
||||
|
||||
// Add the session linked to this timeframe to the new sessionGroup |
||||
this.sessions?.first()?.let { |
||||
it.sessionSet = set |
||||
} ?: run { |
||||
throw ModelException("TimeFrame should never be null here") |
||||
} |
||||
|
||||
// Add all orphan sessions |
||||
sessions.forEach { it.sessionSet = set } |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
@ -1,92 +1,60 @@ |
||||
package net.pokeranalytics.android.model.realm |
||||
|
||||
import android.content.Context |
||||
import io.realm.Realm |
||||
import io.realm.RealmModel |
||||
import android.text.InputType |
||||
import io.realm.RealmObject |
||||
import io.realm.annotations.Ignore |
||||
import io.realm.annotations.PrimaryKey |
||||
import io.realm.kotlin.where |
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.model.interfaces.UsageCountable |
||||
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.model.ObjectSavable |
||||
import net.pokeranalytics.android.ui.adapter.components.* |
||||
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData |
||||
import net.pokeranalytics.android.ui.view.RowEditable |
||||
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.rows.SimpleRow |
||||
import net.pokeranalytics.android.ui.view.rows.TournamentFeatureRow |
||||
import net.pokeranalytics.android.util.NULL_TEXT |
||||
import net.pokeranalytics.android.ui.view.SimpleRow |
||||
import net.pokeranalytics.android.ui.view.TournamentFeatureRow |
||||
import java.util.* |
||||
import kotlin.collections.ArrayList |
||||
|
||||
open class TournamentFeature : RealmObject(), RowRepresentable, RowUpdatable, NameManageable, StaticRowRepresentableDataSource, UsageCountable { |
||||
|
||||
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 |
||||
open class TournamentFeature : RealmObject(), RowRepresentableDataSource, RowEditable, ObjectSavable { |
||||
|
||||
@PrimaryKey |
||||
override var id = UUID.randomUUID().toString() |
||||
var id = UUID.randomUUID().toString() |
||||
|
||||
// The name of the feature |
||||
override var name: String = "" |
||||
|
||||
// CountableUsage |
||||
override var useCount: Int = 0 |
||||
var name: String = "" |
||||
|
||||
@Ignore |
||||
override val ownerClass: Class<out RealmModel> = Session::class.java |
||||
|
||||
override fun getDisplayName(context: Context): String { |
||||
return this.name |
||||
override fun uniqueIdentifier(): String { |
||||
return this.id |
||||
} |
||||
|
||||
override fun adapterRows(): List<RowRepresentable>? { |
||||
return rowRepresentation |
||||
} |
||||
override val adapterRows: ArrayList<RowRepresentable> |
||||
get() { |
||||
val rows = ArrayList<RowRepresentable>() |
||||
rows.add(SimpleRow.NAME) |
||||
rows.addAll(TournamentFeatureRow.values()) |
||||
return rows |
||||
} |
||||
|
||||
override fun charSequenceForRow( |
||||
row: RowRepresentable, |
||||
context: Context, |
||||
tag: Int |
||||
): CharSequence { |
||||
override fun stringForRow(row: RowRepresentable): String { |
||||
return when (row) { |
||||
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT |
||||
else -> return super.charSequenceForRow(row, context, 0) |
||||
SimpleRow.NAME -> this.name |
||||
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) { |
||||
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 { |
||||
return when (status) { |
||||
SaveValidityStatus.DATA_INVALID -> R.string.tournament_feature_empty_field_error |
||||
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_tournament_feature_error |
||||
else -> super.getFailedSaveMessage(status) |
||||
override fun updateValue(value: Any?, row: RowRepresentable) { |
||||
when (row) { |
||||
SimpleRow.NAME -> this.name = value as String? ?: "" |
||||
} |
||||
} |
||||
|
||||
override fun isValidForDelete(realm: Realm): Boolean { |
||||
return realm.where<Session>().equalTo("tournamentFeatures.id", id).findAll().isEmpty() |
||||
override fun isValidForSave(): Boolean { |
||||
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