Merge hand history branch

bs
Laurent 5 years ago
commit 1ce9ee3ee3
  1. 32
      app/build.gradle
  2. 32
      app/src/main/AndroidManifest.xml
  3. 11
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  4. 80
      app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt
  5. 47
      app/src/main/java/net/pokeranalytics/android/api/FreeConverterApi.kt
  6. 25
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  7. 7
      app/src/main/java/net/pokeranalytics/android/calculus/ComputedStat.kt
  8. 10
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  9. 26
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  10. 170
      app/src/main/java/net/pokeranalytics/android/calculus/optimalduration/CashGameOptimalDurationCalculator.kt
  11. 49
      app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
  12. 24
      app/src/main/java/net/pokeranalytics/android/model/TableSize.kt
  13. 2
      app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt
  14. 47
      app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt
  15. 61
      app/src/main/java/net/pokeranalytics/android/model/filter/Query.kt
  16. 14
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  17. 80
      app/src/main/java/net/pokeranalytics/android/model/handhistory/BoardManager.kt
  18. 104
      app/src/main/java/net/pokeranalytics/android/model/handhistory/HandSetup.kt
  19. 53
      app/src/main/java/net/pokeranalytics/android/model/handhistory/Position.kt
  20. 50
      app/src/main/java/net/pokeranalytics/android/model/handhistory/Street.kt
  21. 8
      app/src/main/java/net/pokeranalytics/android/model/interfaces/TimeFilterable.kt
  22. 56
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  23. 27
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  24. 3
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt
  25. 21
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  26. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/FilterCondition.kt
  27. 39
      app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt
  28. 16
      app/src/main/java/net/pokeranalytics/android/model/realm/HandHistory.kt
  29. 33
      app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt
  30. 6
      app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt
  31. 63
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  32. 12
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  33. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt
  34. 12
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentName.kt
  35. 4
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  36. 11
      app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt
  37. 252
      app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/Action.kt
  38. 248
      app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/Card.kt
  39. 662
      app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/HandHistory.kt
  40. 31
      app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/PlayerSetup.kt
  41. 17
      app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/WonPot.kt
  42. 2
      app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt
  43. 58
      app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt
  44. 71
      app/src/main/java/net/pokeranalytics/android/ui/activity/FilterDetailsActivity.kt
  45. 74
      app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt
  46. 58
      app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersListActivity.kt
  47. 1
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  48. 7
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/BaseActivity.kt
  49. 9
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt
  50. 77
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt
  51. 20
      app/src/main/java/net/pokeranalytics/android/ui/adapter/ComparisonChartPagerAdapter.kt
  52. 240
      app/src/main/java/net/pokeranalytics/android/ui/adapter/FilterSectionAdapter.kt
  53. 36
      app/src/main/java/net/pokeranalytics/android/ui/adapter/HomePagerAdapter.kt
  54. 26
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RecyclerAdapter.kt
  55. 12
      app/src/main/java/net/pokeranalytics/android/ui/adapter/ReportPagerAdapter.kt
  56. 17
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt
  57. 63
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt
  58. 53
      app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
  59. 16
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ComparisonChartFragment.kt
  60. 8
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CurrenciesFragment.kt
  61. 201
      app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
  62. 122
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersListFragment.kt
  63. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/MoreFragment.kt
  64. 26
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportCreationFragment.kt
  65. 8
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ReportsFragment.kt
  66. 35
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  67. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt
  68. 17
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt
  69. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BaseFragment.kt
  70. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt
  71. 15
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/FilterableFragment.kt
  72. 3
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/LoaderDialogFragment.kt
  73. 7
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetDoubleEditTextFragment.kt
  74. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextFragment.kt
  75. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetEditTextMultiLinesFragment.kt
  76. 82
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetFragment.kt
  77. 12
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListFragment.kt
  78. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetListGameFragment.kt
  79. 14
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetMultiSelectionFragment.kt
  80. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetNumericTextFragment.kt
  81. 12
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetStaticListFragment.kt
  82. 10
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetSumFragment.kt
  83. 34
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetTableSizeGridFragment.kt
  84. 18
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/AbstractReportFragment.kt
  85. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt
  86. 10
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt
  87. 2
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt
  88. 33
      app/src/main/java/net/pokeranalytics/android/ui/helpers/SwipeToDeleteCallback.kt
  89. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollActivity.kt
  90. 5
      app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollDetailsActivity.kt
  91. 29
      app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollDetailsFragment.kt
  92. 12
      app/src/main/java/net/pokeranalytics/android/ui/modules/bankroll/BankrollFragment.kt
  93. 18
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsActivity.kt
  94. 15
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt
  95. 2
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsViewModel.kt
  96. 21
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt
  97. 80
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/BankrollDataFragment.kt
  98. 21
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/CustomFieldDataFragment.kt
  99. 38
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/DataManagerFragment.kt
  100. 7
      app/src/main/java/net/pokeranalytics/android/ui/modules/data/EditableDataActivity.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -9,6 +9,7 @@ apply plugin: 'com.google.gms.google-services' // Crashlytics
repositories {
maven { url 'https://maven.fabric.io/public' }
maven { url 'https://jitpack.io' } // required for MPAndroidChart
jcenter() // for kotlin serialization
}
android {
@ -33,8 +34,8 @@ android {
applicationId "net.pokeranalytics.android"
minSdkVersion 23
targetSdkVersion 28
versionCode 84
versionName "2.4.3"
versionCode 94
versionName "5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -97,23 +98,18 @@ dependencies {
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0") // JVM dependency
// Android
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.2.0-alpha03'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.browser:browser:1.0.0'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'androidx.work:work-runtime-ktx:2.3.4'
// Places
implementation 'com.google.android.libraries.places:places:1.1.0'
@ -124,7 +120,7 @@ dependencies {
implementation 'com.android.billingclient:billing:1.2.2'
// Firebase
implementation 'com.google.firebase:firebase-core:17.0.0'
implementation 'com.google.firebase:firebase-core:17.4.2'
// Crashlytics
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
@ -138,6 +134,9 @@ dependencies {
// 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'
// Instrumented Tests
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
@ -149,5 +148,8 @@ dependencies {
testImplementation 'com.android.support.test:runner:1.0.2'
testImplementation 'com.android.support.test:rules:1.0.2'
// gross, somehow needed to make the stop notif work
implementation 'com.google.guava:guava:27.0.1-android'
}

@ -16,6 +16,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:largeHeap="true"
android:theme="@style/PokerAnalyticsTheme">
<meta-data
@ -55,24 +56,30 @@
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.SessionActivity"
android:name="net.pokeranalytics.android.ui.modules.session.SessionActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing" />
<!-- No screenOrientation="portrait" to fix Oreo crash -->
<activity
android:name="net.pokeranalytics.android.ui.activity.NewDataMenuActivity"
android:name="net.pokeranalytics.android.ui.modules.feed.NewDataMenuActivity"
android:launchMode="singleTop"
android:theme="@style/PokerAnalyticsTheme.MenuDialog" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BankrollActivity"
android:name="net.pokeranalytics.android.ui.modules.bankroll.BankrollActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BankrollDetailsActivity"
android:name="net.pokeranalytics.android.ui.modules.handhistory.HandHistoryActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateAlwaysHidden"/>
<activity
android:name="net.pokeranalytics.android.ui.modules.bankroll.BankrollDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
@ -102,7 +109,7 @@
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.CalendarDetailsActivity"
android:name="net.pokeranalytics.android.ui.modules.calendar.CalendarDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
@ -112,17 +119,17 @@
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.DataListActivity"
android:name="net.pokeranalytics.android.ui.modules.datalist.DataListActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FiltersListActivity"
android:name="net.pokeranalytics.android.ui.modules.filter.FiltersListActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.EditableDataActivity"
android:name="net.pokeranalytics.android.ui.modules.data.EditableDataActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
@ -132,12 +139,7 @@
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FiltersActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FilterDetailsActivity"
android:name="net.pokeranalytics.android.ui.modules.filter.FiltersActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
@ -167,6 +169,8 @@
android:theme="@style/PokerAnalyticsTheme.AlertDialog"
android:launchMode="singleTop"/>
<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" />

@ -20,6 +20,7 @@ import net.pokeranalytics.android.util.PokerAnalyticsLogs
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.billing.AppGuard
import timber.log.Timber
import java.util.*
class PokerAnalyticsApplication : Application() {
@ -44,12 +45,13 @@ class PokerAnalyticsApplication : Application() {
Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(8)
.schemaVersion(9)
.migration(PokerAnalyticsMigration())
.initialData(Seed(this))
.build()
Realm.setDefaultConfiguration(realmConfiguration)
// val realm = Realm.getDefaultInstance()
// realm.executeTransaction {
// realm.where(Session::class.java).findAll().deleteAllFromRealm()
@ -75,11 +77,16 @@ class PokerAnalyticsApplication : Application() {
if (BuildConfig.DEBUG) {
Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}")
Timber.d("Realm path = ${Realm.getDefaultInstance().path}")
// this.createFakeSessions()
}
Patcher.patchAll(this.applicationContext)
val locale = Locale.getDefault()
Crashlytics.log("Country: ${locale.country}, language: ${locale.language}")
}
/**
@ -93,7 +100,7 @@ class PokerAnalyticsApplication : Application() {
if (sessionsCount < 10) {
GlobalScope.launch {
FakeDataManager.createFakeSessions(200)
FakeDataManager.createFakeSessions(500)
}
}

@ -1,80 +0,0 @@
package net.pokeranalytics.android.api
import android.content.Context
import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.retrofit.CurrencyConverterValue
import net.pokeranalytics.android.util.URL
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query
import java.util.concurrent.TimeUnit
/**
* CurrencyCode Converter API
*/
interface CurrencyConverterApi {
companion object {
private var currencyConverterApi: CurrencyConverterApi? = null
fun getApi(context: Context): CurrencyConverterApi? {
if (currencyConverterApi == null) {
var serviceEndpoint = URL.API_CURRENCY_CONVERTER
val httpClient = OkHttpClient.Builder()
// Logging interceptor
if (BuildConfig.DEBUG) {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BASIC
httpClient.addInterceptor(interceptor)
}
// Add headers
val interceptor = Interceptor { chain ->
val original = chain.request()
val originalHttpUrl = original.url()
val url = originalHttpUrl.newBuilder()
.addQueryParameter("apiKey", context.getString(R.string.currency_converter_api))
.build()
val requestBuilder = original.newBuilder()
.url(url)
chain.proceed(requestBuilder.build())
}
httpClient.addInterceptor(interceptor)
val client = httpClient
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(serviceEndpoint.value)
.client(client)
.build()
currencyConverterApi = retrofit.create(CurrencyConverterApi::class.java)
}
return currencyConverterApi
}
}
@GET("convert")
fun convert(@Query("q") currencies: String, @Query("compact") compact: String = "y"): Call<Map<String, CurrencyConverterValue>>
}

@ -0,0 +1,47 @@
package net.pokeranalytics.android.api
import android.content.Context
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import timber.log.Timber
class FreeConverterApi {
companion object {
fun currencyRate(pair: String, context: Context, callback: (Double) -> (Unit)) {
val queue = Volley.newRequestQueue(context)
val url = "https://free.currconv.com/api/v7/convert?q=${pair}&compact=ultra&apiKey=5ba8d38995282fe8b1c8"
// https://free.currconv.com/api/v7/convert?q=GBP_USD&compact=ultra&apiKey=5ba8d38995282fe8b1c8
// { "USD_PHP": 44.1105, "PHP_USD": 0.0227 }
val stringRequest = StringRequest(
Request.Method.GET, url,
Response.Listener { response ->
val json = Json(JsonConfiguration.Stable)
val f = json.parseJson(response)
f.jsonObject[pair]?.primitive?.double?.let { rate ->
callback(rate)
} ?: run {
Timber.d("no rate: $response")
}
},
Response.ErrorListener {
Timber.d("Api call failed: ${it.message}")
})
queue.add(stringRequest)
}
}
}

@ -21,6 +21,8 @@ import net.pokeranalytics.android.util.extensions.startOfDay
import java.util.*
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
/**
* The class performing statIds computation
@ -119,7 +121,7 @@ class Calculator {
val computeStandardDeviation: Boolean
get() {
this.stats.forEach {
if (it == STANDARD_DEVIATION_BB_PER_100_HANDS || it == STANDARD_DEVIATION || it == STANDARD_DEVIATION_HOURLY) {
if (it.isStandardDeviation) {
return true
}
}
@ -335,6 +337,11 @@ class Calculator {
)
)
}
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)
@ -561,15 +568,19 @@ class Calculator {
// Session
var stdSum = 0.0
var stdBBSum = 0.0
var stdBBper100HandsSum = 0.0
computables.forEach { session ->
stdSum += Math.pow(session.ratedNet - average, 2.0)
stdBBper100HandsSum += Math.pow(session.bbPer100Hands - bbPer100Hands, 2.0)
stdSum += (session.ratedNet - average).pow(2.0)
stdBBSum += (session.bbNet - averageBB).pow(2.0)
stdBBper100HandsSum += (session.bbPer100Hands - bbPer100Hands).pow(2.0)
}
val standardDeviation = Math.sqrt(stdSum / computables.size)
val standardDeviationBBper100Hands = Math.sqrt(stdBBper100HandsSum / computables.size)
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
@ -578,9 +589,9 @@ class Calculator {
sessionSets.forEach { set ->
val ssStats = SSStats(set, computableGroup.query)
val sHourlyRate = ssStats.hourlyRate
hourlyStdSum += Math.pow(sHourlyRate - hourlyRate, 2.0)
hourlyStdSum += (sHourlyRate - hourlyRate).pow(2.0)
}
val hourlyStandardDeviation = Math.sqrt(hourlyStdSum / sessionSets.size)
val hourlyStandardDeviation = sqrt(hourlyStdSum / sessionSets.size)
results.addStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation)
}

@ -27,8 +27,9 @@ class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? =
/**
* Formats the value of the stat to be suitable for display
*/
fun format(): TextFormat {
return this.stat.format(this.value, this.secondValue, this.currency)
}
val textFormat: TextFormat
get() {
return this.stat.textFormat(this.value, this.secondValue, this.currency)
}
}

@ -441,7 +441,7 @@ class ComputedResults(group: ComputableGroup,
override fun formattedValue(stat: Stat): TextFormat {
this.computedStat(stat)?.let {
return it.format()
return it.textFormat
} ?: run {
throw PAIllegalStateException("Missing stat in results")
}
@ -460,12 +460,12 @@ class ComputedResults(group: ComputableGroup,
GraphFragment.Style.BAR -> {
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
val totalStatValue = stat.textFormat(entry.y.toDouble(), currency = null)
DefaultLegendValues(this.entryTitle(context), totalStatValue)
}
else -> {
val entryValue = this.formattedValue(stat)
val countValue = this.computedStat(Stat.NUMBER_OF_GAMES)?.format()
val countValue = this.computedStat(Stat.NUMBER_OF_GAMES)?.textFormat
DefaultLegendValues(this.entryTitle(context), entryValue, countValue)
}
}
@ -474,12 +474,12 @@ class ComputedResults(group: ComputableGroup,
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
val totalStatValue = stat.textFormat(entry.y.toDouble(), currency = null)
DefaultLegendValues(this.entryTitle(context), totalStatValue)
}
else -> {
val entryValue = this.formattedValue(stat)
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
val totalStatValue = stat.textFormat(entry.y.toDouble(), currency = null)
DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue)
}
}

@ -52,6 +52,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
BB_SESSION_COUNT(26),
TOTAL_BUYIN(27),
RISK_OF_RUIN(28),
STANDARD_DEVIATION_BB(29),
;
companion object : IntSearchable<Stat> {
@ -119,6 +120,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
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
@ -135,7 +137,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
/**
* Formats the value of the stat to be suitable for display
*/
fun format(value: Double, secondValue: Double? = null, currency: Currency? = null): TextFormat {
fun textFormat(value: Double, secondValue: Double? = null, currency: Currency? = null): TextFormat {
if (value.isNaN()) {
return TextFormat(NULL_TEXT, R.color.white)
@ -150,7 +152,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
// 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)
return TextFormat(value.formatted, color)
}
// white integers
NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED -> {
@ -161,15 +163,15 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
} // red/green percentages
WIN_RATIO, ROI -> {
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color)
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)
return TextFormat("${(value * 100).formatted}%", color)
}
// white amountsr
AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS, TOTAL_BUYIN -> {
STANDARD_DEVIATION_BB_PER_100_HANDS, STANDARD_DEVIATION_BB, TOTAL_BUYIN -> {
return TextFormat(value.toCurrency(currency))
}
LONGEST_STREAKS -> {
@ -200,6 +202,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
NUMBER_OF_GAMES -> R.string.number_of_records
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 -> return this.localizedTitle(context)
@ -235,12 +238,21 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
val hasProgressGraph: Boolean
get() {
return when (this) {
HOURLY_DURATION, AVERAGE_HOURLY_DURATION,
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> false
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, STANDARD_DEVIATION_BB,
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS -> 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() {

@ -0,0 +1,170 @@
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.BigBlindNotNull) // 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,10 +1,15 @@
package net.pokeranalytics.android.model
import android.content.Context
import androidx.fragment.app.Fragment
import io.realm.Realm
import io.realm.Sort
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.modules.handhistory.HandHistoryActivity
import net.pokeranalytics.android.ui.view.Localizable
import net.pokeranalytics.android.util.extensions.findById
@ -22,7 +27,8 @@ enum class LiveData : Localizable {
FILTER,
CUSTOM_FIELD,
REPORT_SETUP,
PLAYER;
PLAYER,
HAND_HISTORY;
var subType:Int? = null
@ -40,6 +46,7 @@ enum class LiveData : Localizable {
CUSTOM_FIELD -> CustomField::class.java
REPORT_SETUP -> ReportSetup::class.java
PLAYER -> Player::class.java
HAND_HISTORY -> HandHistory::class.java
}
}
@ -81,6 +88,7 @@ enum class LiveData : Localizable {
CUSTOM_FIELD -> R.string.custom_field
REPORT_SETUP -> R.string.custom
PLAYER -> R.string.player
HAND_HISTORY -> R.string.hand_history
}
}
@ -98,6 +106,7 @@ enum class LiveData : Localizable {
CUSTOM_FIELD -> R.string.custom_fields
REPORT_SETUP -> R.string.custom
PLAYER -> R.string.players
HAND_HISTORY -> R.string.hands_history
}
}
@ -115,10 +124,17 @@ enum class LiveData : Localizable {
CUSTOM_FIELD -> R.string.new_custom_field
REPORT_SETUP -> R.string.new_report
PLAYER -> R.string.new_friend
HAND_HISTORY -> R.string.new_hand
}
}
val isSearchable: Boolean
get() {
return when (this) {
PLAYER, LOCATION -> true
else -> false
}
}
/**
* Return the new entity titleResId
@ -141,4 +157,33 @@ enum class LiveData : Localizable {
return context.getString(this.pluralResId, context)
}
fun openEditActivity(fragment: Fragment, primaryKey: String? = null, requestCode: Int) {
when (this) {
HAND_HISTORY -> {
HandHistoryActivity.newInstance(fragment, primaryKey)
}
else -> {
EditableDataActivity.newInstanceForResult(fragment, this, primaryKey, requestCode)
}
}
}
val sortFields: Array<String>
get() {
return when (this) {
TRANSACTION, HAND_HISTORY -> arrayOf("date")
FILTER -> arrayOf("useCount", "name")
else -> arrayOf("name")
}
}
val sortOrders: Array<Sort>
get() {
return when (this) {
TRANSACTION, HAND_HISTORY -> arrayOf(Sort.DESCENDING)
FILTER -> arrayOf(Sort.DESCENDING, Sort.ASCENDING)
else -> arrayOf(Sort.ASCENDING)
}
}
}

@ -6,15 +6,19 @@ 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) : RowRepresentable {
class TableSize(
var numberOfPlayer: Int,
var rowViewType: Int = RowViewType.TITLE_GRID.ordinal,
var alternativeLabels: Boolean = false
) : RowRepresentable {
companion object {
val all: List<TableSize>
get() {
return Array(9, init =
{ index -> TableSize(index + 2) }).toList()
}
fun all(alternativeLabels: Boolean): List<TableSize> {
return Array(9, init = { index ->
TableSize(index + 2, alternativeLabels = alternativeLabels)
}).toList()
}
fun valueForLabel(label: String) : Int? {
@ -32,6 +36,10 @@ class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITL
}
override fun getDisplayName(context: Context): String {
if (this.alternativeLabels) {
return this.numberOfPlayer.toString()
}
return if (this.numberOfPlayer == 2) {
return "HU"
} else {
@ -49,6 +57,10 @@ class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITL
}
override fun localizedTitle(context: Context): String {
if (this.alternativeLabels) {
return this.numberOfPlayer.toString()
}
this.resId?.let {
return if (this.numberOfPlayer == 2) {
context.getString(it)

@ -12,7 +12,7 @@ enum class TournamentType(val label: String) : RowRepresentable {
companion object {
val all : List<TournamentType>
get() {
return values() as List<TournamentType>
return values().toList()
}
fun getValueForLabel(label: String) : TournamentType? {

@ -1,11 +1,17 @@
package net.pokeranalytics.android.model.extensions
import android.content.Context
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.NotificationSchedule
import net.pokeranalytics.android.util.extensions.toCurrency
import java.util.*
import java.util.concurrent.TimeUnit
enum class SessionState {
PENDING,
@ -14,7 +20,7 @@ enum class SessionState {
PAUSED,
FINISHED;
var hasStarted: Boolean = false
val hasStarted: Boolean
get() {
return when (this) {
STARTED, PAUSED, FINISHED -> true
@ -46,11 +52,11 @@ fun Session.getState(): SessionState {
}
/**
* Formate the session game type
* Format the session game type
*/
fun Session.getFormattedGameType(context: Context): String {
var parameters = mutableListOf<String>()
val parameters = mutableListOf<String>()
if (isTournament()) {
tournamentEntryFee?.let {
@ -84,6 +90,39 @@ fun Session.getFormattedGameType(context: Context): String {
return parameters.joinToString(separator = " ")
}
fun Session.currentDuration(): Long {
return this.startDate?.let {
this.endDate().time - it.time
} ?: 0L
}
fun Session.cancelStopNotification(context: Context) {
WorkManager.getInstance(context).cancelAllWorkByTag(this.id)
}
fun Session.scheduleStopNotification(context: Context, optimalDuration: Long) {
val timeDelay = optimalDuration - this.currentDuration()
if (timeDelay <= 0) throw PAIllegalStateException("A stop notif has been setup for the past: start=${this.startDate}, end=${this.endDate}, optimalDuration=$optimalDuration")
val title = context.getString(R.string.stop_notification_title)
val body = context.getString(R.string.stop_notification_body)
val data = Data.Builder()
.putString(NotificationSchedule.ParamKeys.TITLE.value, title)
.putString(NotificationSchedule.ParamKeys.BODY.value, body)
val work = OneTimeWorkRequestBuilder<NotificationSchedule>()
.setInitialDelay(timeDelay, TimeUnit.MILLISECONDS)
.setInputData(data.build())
.addTag(this.id)
.build()
WorkManager.getInstance(context).enqueue(work)
}
val AbstractList<Session>.hourlyDuration: Double
get() {
val intervals = mutableListOf<TimeInterval>()
@ -113,7 +152,7 @@ fun MutableList<TimeInterval>.update(timeInterval: TimeInterval): MutableList<Ti
}
if (overlapped.size == 0) { // add
if (overlapped.isEmpty()) { // add
this.add(timeInterval)
} else { // update

@ -12,7 +12,7 @@ fun List<Query>.mapFirstCondition() : List<QueryCondition> {
class Query {
constructor(vararg elements: QueryCondition) {
if (elements.size > 0) {
if (elements.isNotEmpty()) {
this.add(elements.asList())
}
}
@ -23,22 +23,25 @@ class Query {
return this._conditions
}
fun add(vararg elements: QueryCondition) {
if (elements.size > 0) {
fun add(vararg elements: QueryCondition): Query {
if (elements.isNotEmpty()) {
this.add(elements.asList())
}
return this
}
fun add(queryCondition: QueryCondition) {
fun add(queryCondition: QueryCondition): Query {
this._conditions.add(queryCondition)
return this
}
fun remove(queryCondition: QueryCondition) {
this._conditions.remove(queryCondition)
fun add(queryConditions: List<QueryCondition>): Query{
this._conditions.addAll(queryConditions)
return this
}
fun add(queryConditions: List<QueryCondition>) {
this._conditions.addAll(queryConditions)
fun remove(queryCondition: QueryCondition) {
this._conditions.remove(queryCondition)
}
val defaultName: String
@ -59,28 +62,32 @@ class Query {
inline fun <reified T : Filterable> queryWith(query: RealmQuery<T>): RealmQuery<T> {
var realmQuery = query
val queryFromTime = this.conditions.filter {
it is QueryCondition.StartedFromTime
}.firstOrNull()
val queryToTime = this.conditions.filter {
it is QueryCondition.EndedToTime
}.firstOrNull()
this.conditions.forEach {
if (it is QueryCondition.StartedFromTime) {
realmQuery = it.queryWith(realmQuery, queryToTime)
} else if (it is QueryCondition.EndedToTime) {
realmQuery = it.queryWith(realmQuery, queryFromTime)
} else {
realmQuery = it.queryWith(realmQuery)
}
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.filter {
it is QueryCondition.Last
}.firstOrNull()
queryLast?.let {qc ->
val queryLast = this.conditions.firstOrNull {
it is QueryCondition.Last
}
queryLast?.let {qc ->
(qc as QueryCondition.Last).singleValue?.let {
return realmQuery.limit(it.toLong())
}

@ -96,16 +96,17 @@ sealed class QueryCondition : FilterElementRow {
val groupId: String
get() {
when (this.operator) {
return when (this.operator) {
Operator.MORE, Operator.LESS -> return "${this.operator.name.toLowerCase().capitalize()}$baseId"
else -> this.baseId
}
return baseId
}
val id: List<String>
get() {
when (this.operator) {
Operator.MORE, Operator.LESS -> return listOf("$baseId+${this.operator.name}")
else -> {}
}
return when (this) {
@ -306,6 +307,9 @@ sealed class QueryCondition : FilterElementRow {
abstract class TrueQueryCondition : QueryCondition() {
override var operator: Operator = Operator.TRUE
}
abstract class NotNullQueryCondition : QueryCondition() {
override var operator: Operator = Operator.NOTNULL
}
object IsLive : TrueQueryCondition()
@ -601,9 +605,9 @@ sealed class QueryCondition : FilterElementRow {
}
}
object DateNotNull : QueryCondition() {
override var operator = Operator.NOTNULL
}
object DateNotNull : NotNullQueryCondition()
object EndDateNotNull : NotNullQueryCondition()
object BigBlindNotNull : NotNullQueryCondition()
class StartedFromTime() : TimeQuery() {
override var operator = Operator.MORE

@ -0,0 +1,80 @@
package net.pokeranalytics.android.model.handhistory
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.handhistory.Card
/***
* An interface used for board changes notifications
*/
interface BoardChangedListener {
fun boardChanged()
}
/***
* The BoardManager purpose is to manage the cards from a hand history board
* and notify its listener when a change occurs
*/
class BoardManager(cards: List<Card>, var listener: BoardChangedListener) {
/***
* The sorted list of cards
*/
private var sortedBoardCards: MutableList<Card> = mutableListOf()
/***
* All cards
*/
val allCards: List<Card>
get() {
return this.sortedBoardCards
}
init {
this.sortedBoardCards = cards.sortedBy { it.index }.toMutableList()
}
/***
* Adds a card to the board, notifies the listener
*/
fun add(card: Card) {
this.sortedBoardCards.lastOrNull()?.let {
if (it.suit == null) {
it.suit = Card.Suit.UNDEFINED
}
}
if (this.sortedBoardCards.size == 5) {
throw PAIllegalStateException("Can't add anymore cards")
}
card.index = this.sortedBoardCards.size
this.sortedBoardCards.add(card)
this.listener.boardChanged()
}
/***
* Clears the street's cards, notifies the listener
*/
fun clearStreet(street: Street) {
this.sortedBoardCards.removeAll { it.street == street }
this.listener.boardChanged()
}
/***
* Returns the last card of a given [street]
*/
fun lastCard(street: Street) : Card? {
return this.sortedBoardCards.lastOrNull { it.street == street }
}
/***
* Remove the given [card], notifies the listener
*/
fun remove(card: Card) {
this.sortedBoardCards.remove(card)
this.listener.boardChanged()
}
}

@ -0,0 +1,104 @@
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
import java.util.*
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()
}
}
}
private fun configure(handHistory: HandHistory) {
this.smallBlind = handHistory.smallBlind
this.bigBlind = handHistory.bigBlind
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.smallBlind = session.cgSmallBlind
this.bigBlind = session.cgBigBlind
this.tableSize = session.tableSize
}
var type: Session.Type? = null
var smallBlind: Double? = 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()
}
/***
* 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}")
}
}

@ -0,0 +1,53 @@
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
}
}

@ -0,0 +1,50 @@
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]
}
}
}

@ -19,10 +19,10 @@ interface TimeFilterable {
startDate?.let {
val cal = Calendar.getInstance()
cal.time = it
dayOfWeek = cal.get(Calendar.DAY_OF_WEEK)
dayOfMonth = cal.get(Calendar.DAY_OF_MONTH)
month = cal.get(Calendar.MONTH)
year = cal.get(Calendar.YEAR)
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)
}
}
}

@ -2,6 +2,7 @@ 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.*
@ -171,6 +172,61 @@ class PokerAnalyticsMigration : RealmMigration {
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++
}
}
override fun equals(other: Any?): Boolean {

@ -6,19 +6,19 @@ import net.pokeranalytics.android.model.filter.QueryCondition
open class ComputableResult() : RealmObject(), Filterable {
var ratedNet: Double = 0.0
var ratedNet: Double = 0.0
var bbNet: BB = 0.0
var bbNet: BB = 0.0
var hasBigBlind: Int = 0
var hasBigBlind: Int = 0
var isPositive: Int = 0
var isPositive: Int = 0
var ratedBuyin: Double = 0.0
var ratedBuyin: Double = 0.0
var estimatedHands: Double = 0.0
var estimatedHands: Double = 0.0
var bbPer100Hands: BB = 0.0
var bbPer100Hands: BB = 0.0
var session: Session? = null
@ -35,7 +35,8 @@ open class ComputableResult() : RealmObject(), Filterable {
this.bbNet = session.bbNet
this.hasBigBlind = if (session.cgBigBlind != null) 1 else 0
this.estimatedHands = session.estimatedHands
this.bbPer100Hands = session.bbNet / (session.numberOfHandsPerHour * session.hourlyDuration) * 100
this.bbPer100Hands =
session.bbNet / (session.numberOfHandsPerHour * session.hourlyDuration) * 100
}
@ -51,11 +52,11 @@ open class ComputableResult() : RealmObject(), Filterable {
companion object {
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? {
Session.fieldNameForQueryType(queryCondition)?.let {
return "session.$it"
}
return null
fun fieldNameForQueryType(queryCondition: Class<out QueryCondition>): String? {
Session.fieldNameForQueryType(queryCondition)?.let {
return "session.$it"
}
return null
}
}

@ -193,7 +193,7 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
return when (row) {
is CustomFieldEntry -> row.editingDescriptors(
mapOf(
@ -290,7 +290,6 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
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()

@ -11,7 +11,7 @@ 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.interfaces.FilterableType
import net.pokeranalytics.android.ui.modules.filter.FilterableType
import net.pokeranalytics.android.ui.view.ImageDecorator
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
@ -105,11 +105,8 @@ open class Filter : RealmObject(), RowRepresentable, Editable, Deletable, Counta
it.groupId == groupId
}
.apply {
Timber.d("list of querys: ${this.map { it.id }}")
val casted = arrayListOf<QueryCondition>()
casted.addAll(this)
val newFilterCondition = FilterCondition(casted)
val newFilterCondition = FilterCondition(this)
val previousCondition = filterConditions.filter {
it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator
}
@ -153,22 +150,22 @@ open class Filter : RealmObject(), RowRepresentable, Editable, Deletable, Counta
}
}
inline fun <reified T : Filterable> query(firstField: String? = null, secondField: String? = null): RealmQuery<T> {
inline fun <reified T : Filterable> query(firstField: String? = null, vararg remainingFields: String): RealmQuery<T> {
val realmQuery = realm.where<T>()
if (firstField != null && secondField != null) {
return this.query.queryWith(realmQuery).distinct(firstField, secondField)
}
// if (firstField != null && secondField != null) {
// return this.query.queryWith(realmQuery).distinct(firstField, secondField)
// }
if (firstField != null) {
return this.query.queryWith(realmQuery).distinct(firstField)
return this.query.queryWith(realmQuery).distinct(firstField, *remainingFields)
}
return this.query.queryWith(realmQuery)
}
inline fun <reified T : Filterable> results(firstField: String? = null, secondField: String? = null): RealmResults<T> {
return this.query<T>(firstField, secondField).findAll()
inline fun <reified T : Filterable> results(firstField: String? = null, vararg remainingFields: String): RealmResults<T> {
return this.query<T>(firstField, *remainingFields).findAll()
}
val query: Query

@ -14,7 +14,7 @@ open class FilterCondition() : RealmObject() {
this.sectionName = sectionName
}
constructor(filterElementRows: ArrayList<QueryCondition>) : this(filterElementRows.first().baseId, filterElementRows.first().filterSectionRow.name) {
constructor(filterElementRows: List<QueryCondition>) : this(filterElementRows.first().baseId, filterElementRows.first().filterSectionRow.name) {
val row = filterElementRows.first()
this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName
this.operator = row.operator.ordinal

@ -30,7 +30,6 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
val rowRepresentation : List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.add(SimpleRow.NAME)
// rows.addAll(GameRow.values())
rows
}
}
@ -62,18 +61,22 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
}
override fun adapterRows(): List<RowRepresentable>? {
return Game.rowRepresentation
return rowRepresentation
}
override fun stringForRow(row: RowRepresentable): String {
override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
GameRow.SHORT_NAME -> this.shortName ?: NULL_TEXT
else -> return super.stringForRow(row)
else -> return super.charSequenceForRow(row, context, 0)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name))
GameRow.SHORT_NAME -> row.editingDescriptors(mapOf("defaultValue" to this.shortName))
@ -103,4 +106,30 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
override fun isValidForDelete(realm: Realm): Boolean {
return realm.where<Session>().equalTo("game.id", id).findAll().isEmpty()
}
val playerHandMaxCards: Int?
get() {
return when {
isHoldem -> 2
isOmaha5 -> 5
isOmaha4 -> 4
else -> null
}
}
private val isHoldem: Boolean
get() {
return name.contains("texas", ignoreCase = true) || name.contains("holdem", ignoreCase = true) || name.contains("hold'em", ignoreCase = true) || name.contains("HE")
}
private val isOmaha4: Boolean
get() {
return name.contains("omaha", ignoreCase = true) || name.contains("PLO", ignoreCase = true)
}
private val isOmaha5: Boolean
get() {
return name.contains("5")
}
}

@ -1,16 +0,0 @@
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()
}

@ -17,12 +17,13 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepres
import net.pokeranalytics.android.ui.view.rowrepresentable.PlayerRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.RANDOM_PLAYER
import net.pokeranalytics.android.util.extensions.isSameDay
import net.pokeranalytics.android.util.extensions.mediumDate
import java.util.*
import kotlin.collections.ArrayList
open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresentableDataSource, RowRepresentable {
open class Player : RealmObject(), NameManageable, Savable, Deletable, StaticRowRepresentableDataSource, RowRepresentable {
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -75,10 +76,14 @@ open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresent
return this.name
}
override fun stringForRow(row: RowRepresentable): String {
override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) {
PlayerRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
else -> return super.stringForRow(row)
else -> return super.charSequenceForRow(row, context, 0)
}
}
@ -90,7 +95,6 @@ open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresent
}
}
/**
* Update the row representation
*/
@ -178,7 +182,7 @@ open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresent
this.commentsToDelete.clear()
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
when (row) {
PlayerRow.NAME -> return row.editingDescriptors(mapOf("defaultValue" to this.name))
@ -188,5 +192,24 @@ open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresent
return null
}
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
}
}
}

@ -64,15 +64,15 @@ open class ReportSetup : RealmObject(), RowRepresentable, Deletable {
/**
* Returns the Options based on the ReportSetup parameters
*/
val options: Calculator.Options
get() {
fun options(realm: Realm): Calculator.Options {
val realm = Realm.getDefaultInstance()
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>()

@ -1,6 +1,7 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import com.crashlytics.android.Crashlytics
import com.github.mikephil.charting.data.Entry
import io.realm.Realm
import io.realm.RealmList
@ -21,11 +22,13 @@ import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.extensions.SessionState
import net.pokeranalytics.android.model.extensions.cancelStopNotification
import net.pokeranalytics.android.model.extensions.getState
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.filter.QueryCondition.*
import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
@ -116,11 +119,22 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomFieldAmountQuery::class.java, CustomFieldNumberQuery::class.java -> "customFieldEntries.numericValue"
CustomFieldQuery::class.java -> "customFieldEntries.customFields.id"
DateNotNull::class.java -> "startDate"
EndDateNotNull::class.java -> "endDate"
BigBlindNotNull::class.java -> "cgBigBlind"
else -> null
}
}
}
/***
* Returns an automatic date for a new hand history
*/
val handHistoryAutomaticDate: Date
get() {
val hhCount = this.handHistories?.size ?: 0
return Date(this.date.time + hhCount * 1000 * 60) // one HH per minute
}
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -130,6 +144,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
@Index
var type: Int = Type.CASH_GAME.ordinal
val sessionType: Type
get() { return Type.values()[this.type] }
// The result of the main user
var result: Result? = null
@ -264,8 +281,11 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The number of tables played at the same time
var numberOfTables: Int = 1
// The hands list associated with the Session
var hands: RealmList<HandHistory> = RealmList()
// var hands: RealmList<HandHistory> = RealmList()
// The hand histories of the session
@LinkingObjects("session")
val handHistories: RealmResults<HandHistory>? = null
// The list of opponents who participated to the session
var opponents: RealmList<Player> = RealmList()
@ -496,7 +516,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/
fun startOrContinue() {
realm.executeTransaction {
when (getState()) {
when (val state = getState()) {
SessionState.PENDING, SessionState.PLANNED -> {
this.startDate = Date()
this.defineDefaultTournamentBuyinIfNecessary()
@ -511,7 +531,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
this.pauseDate = null
}
else -> {
throw PAIllegalStateException("unmanaged session state")
throw PAIllegalStateException("unmanaged session state: $state")
}
}
}
@ -528,11 +548,11 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/
fun pause() {
realm.executeTransaction {
when (getState()) {
when (val state = getState()) {
SessionState.STARTED -> {
this.pauseDate = Date()
}
else -> throw PAIllegalStateException("Pausing a session in an unmanaged state")
else -> throw PAIllegalStateException("Pausing a session in an unmanaged state: $state")
}
}
}
@ -540,15 +560,16 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
/**
* Stop a session
*/
fun stop() {
fun stop(context: Context) {
realm.executeTransaction {
when (getState()) {
when (val state = getState()) {
SessionState.STARTED, SessionState.PAUSED -> {
this.end()
}
else -> throw Exception("Stopping session in unmanaged state")
else -> throw Exception("Stopping session in unmanaged state: $state")
}
}
cancelStopNotification(context)
}
/**
@ -621,7 +642,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
if (cgBigBlind == null) return
cgBigBlind?.let { bb ->
val sb = cgSmallBlind ?: bb / 2.0
val preFormattedBlinds = "${sb.formatted()}/${bb.round()}"
val preFormattedBlinds = "${sb.formatted}/${bb.round()}"
// println("<<<<<< bb.toCurrency(currency) : ${bb.toCurrency(currency)}")
// println("<<<<<< preFormattedBlinds : $preFormattedBlinds")
val regex = Regex("-?\\d+(\\.\\d+)?")
@ -636,11 +657,15 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
* Delete the object from realm
*/
fun delete() {
Crashlytics.log("Deletes session. Id = ${this.id}")
if (isValid) {
realm.executeTransaction {
cleanup()
deleteFromRealm()
}
} else {
Crashlytics.log("Attempt to delete an invalid session")
}
}
@ -707,7 +732,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getFormattedDuration(),
computedStat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency)
valueTextFormat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency).textFormat
)
)
rows.add(SeparatorRow())
@ -717,7 +742,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG,
resId = R.string.pause,
computedStat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency)
valueTextFormat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency).textFormat
)
)
rows.add(SeparatorRow())
@ -727,14 +752,14 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getFormattedDuration(),
computedStat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency)
valueTextFormat = ComputedStat(Stat.NET_RESULT, result?.net ?: 0.0, currency = currency).textFormat
)
)
rows.add(
CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT,
resId = R.string.hour_rate_without_pauses,
computedStat = ComputedStat(Stat.HOURLY_RATE, this.hourlyRate, currency = currency)
valueTextFormat = ComputedStat(Stat.HOURLY_RATE, this.hourlyRate, currency = currency).textFormat
)
)
@ -777,7 +802,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return false
}
override fun stringForRow(row: RowRepresentable, context: Context): String {
override fun charSequenceForRow(row: RowRepresentable, context: Context): String {
return when (row) {
SessionRow.BANKROLL -> bankroll?.name ?: NULL_TEXT
SessionRow.BLINDS -> getFormattedBlinds()
@ -816,6 +841,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
}
SessionRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT
SessionRow.HANDS -> this.handHistories?.size.toString()
is CustomField -> {
customFieldEntries.find { it.customField?.id == row.id }?.let { customFieldEntry ->
return customFieldEntry.getFormattedValue(currency)
@ -835,7 +861,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
return when (row) {
SessionRow.BANKROLL -> row.editingDescriptors(
mapOf(
@ -1123,7 +1149,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
value?.let {
return stat.format(it, currency = currency)
return stat.textFormat(it, currency = currency)
} ?: run {
return TextFormat(NULL_TEXT)
}
@ -1162,7 +1188,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
if (!hasMainCurrencyCode) {
this.computableResult?.ratedNet?.let { ratedNet ->
right = Stat.NET_RESULT.format(ratedNet)
right = Stat.NET_RESULT.textFormat(ratedNet)
}
}
@ -1178,6 +1204,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
@Ignore
override val realmObjectClass: Class<out Identifiable> = Session::class.java

@ -122,15 +122,15 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
override fun formattedValue(stat: Stat) : TextFormat {
return when (stat) {
Stat.NET_RESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null)
Stat.HOURLY_DURATION, Stat.AVERAGE_HOURLY_DURATION -> stat.format(this.hourlyDuration, currency = null)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null)
Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, currency = null)
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.format(this.estimatedHands, currency = null)
return stat.textFormat(this.estimatedHands, currency = null)
} else {
return TextFormat(NULL_TEXT)
}

@ -56,14 +56,18 @@ open class TournamentFeature : RealmObject(), NameManageable, StaticRowRepresent
return rowRepresentation
}
override fun stringForRow(row: RowRepresentable): String {
override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
else -> return super.stringForRow(row)
else -> return super.charSequenceForRow(row, context, 0)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf(
"defaultValue" to this.name))
}

@ -50,17 +50,21 @@ open class TournamentName : RealmObject(), NameManageable, StaticRowRepresentabl
}
override fun adapterRows(): List<RowRepresentable>? {
return TournamentName.rowRepresentation
return rowRepresentation
}
override fun stringForRow(row: RowRepresentable): String {
override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
else -> return super.stringForRow(row)
else -> return super.charSequenceForRow(row, context,0)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf("defaultValue" to this.name))
}

@ -158,7 +158,7 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
}
override fun formattedValue(stat: Stat): TextFormat {
return stat.format(this.amount, currency = this.bankroll?.utilCurrency)
return stat.textFormat(this.amount, currency = this.bankroll?.utilCurrency)
}
override fun legendValues(
@ -170,7 +170,7 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
): LegendContent {
val entryValue = this.formattedValue(stat)
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
val totalStatValue = stat.textFormat(entry.y.toDouble(), currency = null)
val leftName = context.getString(R.string.amount)
return DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName)
}

@ -1,7 +1,6 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import com.crashlytics.android.Crashlytics
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmObject
@ -119,10 +118,14 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
return rowRepresentation
}
override fun stringForRow(row: RowRepresentable): String {
override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> this.name
else -> return super.stringForRow(row)
else -> return super.charSequenceForRow(row, context, 0)
}
}
@ -133,7 +136,7 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf("defaultValue" to this.name))
}

@ -0,0 +1,252 @@
package net.pokeranalytics.android.model.realm.handhistory
import io.realm.RealmObject
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.handhistory.Position
import net.pokeranalytics.android.model.handhistory.Street
import net.pokeranalytics.android.ui.modules.handhistory.model.ActionReadRow
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.extensions.formatted
/***
* An extension to transform a list of ComputedAction into
* a more compact and read-friendly list of ActionReadRow
*/
fun List<Action>.compact(positions: LinkedHashSet<Position>, heroIndex: Int?): List<ActionReadRow> {
val rows = mutableListOf<ActionReadRow>()
this.forEach {
if (it.type == Action.Type.FOLD && rows.lastOrNull()?.action == Action.Type.FOLD) {
rows.lastOrNull()?.positions?.add(positions.elementAt(it.position))
} else {
rows.add(it.toReadRow(positions, heroIndex, null)) // TODO stack. The method is used for text export only atm
}
}
return rows
}
fun Action.toReadRow(positions: LinkedHashSet<Position>, heroIndex: Int?, stack: Double?): ActionReadRow {
val pos = positions.elementAt(this.position)
val isHero = (heroIndex == this.position)
var amount = this.amount
if (this.type?.isCall == true) {
amount = this.effectiveAmount
}
return ActionReadRow(mutableListOf(pos), this.position, this.type, amount, stack, isHero)
}
open class Action : RealmObject() {
enum class Type(override var resId: Int) : RowRepresentable {
POST_SB(R.string.posts_sb),
POST_BB(R.string.post_bb),
STRADDLE(R.string.straddle),
FOLD(R.string.fold),
CHECK(R.string.check),
CALL(R.string.call),
BET(R.string.bet),
POT(R.string.pot),
RAISE(R.string.raise),
UNDEFINED_ALLIN(R.string.allin),
CALL_ALLIN(R.string.callin),
BET_ALLIN(R.string.ballin),
RAISE_ALLIN(R.string.rallin);
val isBlind: Boolean
get() {
return when (this) {
POST_SB, POST_BB -> true
else -> false
}
}
val isSignificant: Boolean
get() {
return when (this) {
POST_SB, POST_BB, STRADDLE, BET, POT, RAISE, BET_ALLIN, RAISE_ALLIN -> true
UNDEFINED_ALLIN -> throw PAIllegalStateException("Can't ask for UNDEFINED_ALLIN")
else -> false
}
}
/***
* Tells if the action pulls the player out from any new decision
*/
val isPullOut: Boolean
get() {
return when (this) {
FOLD, BET_ALLIN, RAISE_ALLIN, CALL_ALLIN, UNDEFINED_ALLIN -> true
else -> false
}
}
/***
* Returns if the action is passive
*/
val isPassive: Boolean
get() {
return when (this) {
FOLD, CHECK -> true
else -> false
}
}
val isCall: Boolean
get() {
return when (this) {
CALL, CALL_ALLIN -> true
else -> false
}
}
val isAllin: Boolean
get() {
return when (this) {
UNDEFINED_ALLIN, BET_ALLIN, RAISE_ALLIN, CALL_ALLIN -> true
else -> false
}
}
val color: Int
get() {
return when (this) {
POST_SB, POST_BB, CALL, CHECK, CALL_ALLIN -> R.color.kaki_lighter
FOLD -> R.color.red
else -> R.color.green
}
}
val background: Int
get() {
return when (this) {
POST_SB, POST_BB, CALL, CHECK, CALL_ALLIN -> R.drawable.rounded_kaki_medium_rect
FOLD -> R.drawable.rounded_red_rect
else -> R.drawable.rounded_green_rect
}
}
override val viewType: Int = RowViewType.TITLE_GRID.ordinal
companion object {
val defaultTypes: List<Type> by lazy {
listOf(FOLD, CHECK, BET, POT, CALL, RAISE, UNDEFINED_ALLIN)
}
}
}
/***
* The street of the action
*/
private var streetIdentifier: Int = 0
var street: Street
set(value) {
this.streetIdentifier = value.ordinal
}
get() {
return streetIdentifier.let { Street.values()[it] }
}
/***
* The index of the action
*/
var index: Int = 0
/***
* The position of the user making the action
*/
var position: Int = 0
/***
* The type of action: check, fold, raise...
*/
private var typeIdentifier: Int? = null
var type: Type?
set(value) {
this.typeIdentifier = value?.ordinal
}
get() {
return typeIdentifier?.let { Type.values()[it] }
}
/***
* The amount linked for a bet, raise...
*/
var amount: Double? = null
set(value) {
field = value
// Timber.d("/// set value = $value")
}
var effectiveAmount: Double = 0.0
var positionRemainingStack: Double? = null
val isActionSignificant: Boolean
get() {
return this.type?.isSignificant ?: false
}
val formattedAmount: String?
get() {
val amount = when (this.type) {
Type.CALL, Type.CALL_ALLIN -> this.effectiveAmount
else -> this.amount
}
return amount?.formatted
}
fun toggleType(remainingStack: Double) {
when (this.type) {
Type.BET -> {
if (remainingStack == 0.0) {
this.type = Type.BET_ALLIN
}
}
Type.BET_ALLIN -> {
if (remainingStack > 0.0) {
this.type = Type.BET
}
}
Type.RAISE -> {
if (remainingStack == 0.0) {
this.type = Type.RAISE_ALLIN
}
}
Type.RAISE_ALLIN -> {
if (remainingStack > 0.0) {
this.type = Type.RAISE
}
}
Type.CALL -> {
if (remainingStack == 0.0) {
this.type = Type.CALL_ALLIN
}
}
Type.CALL_ALLIN -> {
if (remainingStack > 0.0) {
this.type = Type.CALL
}
}
else -> {}
}
}
val displayedAmount: Double?
get() {
if (this.type?.isCall == true) {
return this.effectiveAmount
}
return this.amount
}
}

@ -0,0 +1,248 @@
package net.pokeranalytics.android.model.realm.handhistory
import android.content.Context
import android.text.SpannableString
import android.text.TextUtils
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
import io.realm.RealmObject
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.handhistory.Street
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
interface CardProperty
/***
* Returns a CharSequence containing each card representation
*/
fun List<Card>.formatted(context: Context) : CharSequence? {
var span: CharSequence? = null
this.forEach {
val formatted = it.formatted(context)
if (span == null) {
span = formatted
}
else {
span = TextUtils.concat(span, formatted)
}
}
return span
}
open class Card : RealmObject() {
companion object {
fun newInstance(value: Int? = null, suit: Suit? = null, index: Int = 0) : Card {
val card = Card()
value?.let { card.value = it }
suit?.let { card.suit = it}
card.index = index
return card
}
}
/***
* The Value of a Card
* Can represent all values from Deuce to Ace, or a blank
*/
class Value(var value: Int?) : CardProperty, RowRepresentable {
companion object {
val undefined: Value = Value(null)
val values: List<Value> by lazy {
val v = mutableListOf(Value(null)) // null for x
(2..14).forEach { v.add(Value(it)) }
v
}
fun format(value: Int?) : String {
return when(value) {
in 2..9 -> "$value"
10 -> "T"
11 -> "J"
12 -> "Q"
13 -> "K"
14 -> "A"
else -> "X"
}
}
}
override fun getDisplayName(context: Context): String {
return this.formatted
}
val formatted: String
get() { return format(this.value) }
override val viewType: Int = RowViewType.TITLE_GRID.ordinal
}
enum class Suit(val value: String) : CardProperty, RowRepresentable {
UNDEFINED("x"),
SPADES(""),
HEART(""),
DIAMOND(""),
CLOVER("");
companion object {
val displaySuits: List<Suit> by lazy {
listOf(SPADES, HEART, DIAMOND, CLOVER, UNDEFINED)
}
fun format(suit: Suit?) : String {
return suit?.value ?: UNDEFINED.value
}
fun color(suit: Suit?) : Int {
return suit?.color ?: R.color.white
}
fun valueOf(suit: Int): Suit {
return when (suit) {
0 -> SPADES
1 -> DIAMOND
2 -> CLOVER
3 -> HEART
else -> throw PAIllegalStateException("unknow value: $suit")
}
}
}
val color: Int
get() {
return when (this) {
UNDEFINED -> R.color.grey
SPADES -> R.color.black
HEART -> R.color.red
DIAMOND -> R.color.blue
CLOVER -> R.color.clover
}
}
override fun getDisplayName(context: Context): String {
return this.value
}
override val viewType: Int = RowViewType.TITLE_GRID.ordinal
val evaluatorSuit: Int?
get() {
return when (this) {
SPADES -> net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card.SPADES
CLOVER -> net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card.CLUBS
DIAMOND -> net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card.DIAMONDS
HEART -> net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card.HEARTS
else -> null
}
}
}
/***
* The card value: 2..A
* can be undefined
*/
var value: Int? = null
/***
* Returns the formatted value of the card
*/
val formattedValue: String
get() { return Value.format(this.value) }
/***
* The card suit identifier: heart, spades...
*/
var suitIdentifier: Int? = null
/***
* Returns the suit of the card
*/
var suit: Suit?
get() {
return this.suitIdentifier?.let { Suit.values()[it] }
}
set(value) {
this.suitIdentifier = value?.ordinal
}
/***
* The card index in a list
*/
var index: Int = 0
/***
* Returns the street where the card belongs based on its [index]
*/
val street: Street
get() {
return when (this.index) {
in 0..2 -> Street.FLOP
3 -> Street.TURN
4 -> Street.RIVER
else -> throw PAIllegalStateException("Card doesn't belong to any street")
}
}
/***
* Formats the Card into a Spannable String with the appropriate color for the card suit
* [lineBreak] adds "\n" between the value and the suit
*/
fun formatted(context: Context, lineBreak: Boolean = false) : SpannableString {
val suit = Suit.format(this.suit)
val spannable = if (lineBreak) {
SpannableString(this.formattedValue + "\n" + suit)
} else {
SpannableString(this.formattedValue + suit)
}
val suitStart = spannable.indexOf(suit)
spannable.setSpan(
ForegroundColorSpan(context.getColor(Suit.color(this.suit))),
suitStart,
spannable.length,
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE
)
// Timber.d("card format = $spannable")
return spannable
}
/***
* Returns a view showing the card [value] and [suit]
*/
fun view(context: Context, layoutInflater: LayoutInflater, root: ViewGroup, blank: Boolean = false): View {
val textView = layoutInflater.inflate(R.layout.view_card, root, false) as AppCompatTextView
if (blank) {
textView.text = "\n"
} else {
textView.text = this.formatted(context, true)
}
return textView
}
fun toEvaluatorCard():net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card? {
val rank = this.value
val suit = this.suit?.evaluatorSuit
return if (rank != null && suit != null) {
net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card(rank - 2, suit)
} else {
null
}
}
val isWildCard: Boolean
get() {
return this.value == null || this.suit == null || this.suit == Suit.UNDEFINED
}
}

@ -0,0 +1,662 @@
package net.pokeranalytics.android.model.realm.handhistory
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
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.R
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.handhistory.HandSetup
import net.pokeranalytics.android.model.handhistory.Position
import net.pokeranalytics.android.model.handhistory.Street
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.model.interfaces.TimeFilterable
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.modules.handhistory.evaluator.EvaluatorBridge
import net.pokeranalytics.android.ui.modules.handhistory.model.ActionReadRow
import net.pokeranalytics.android.ui.modules.handhistory.model.CardHolder
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.extensions.addLineReturn
import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.fullDate
import java.util.*
import kotlin.math.max
data class PositionAmount(var position: Int, var amount: Double, var isAllin: Boolean)
open class HandHistory : RealmObject(), Deletable, RowRepresentable, Filterable, TimeFilterable,
CardHolder, Comparator<PositionAmount> {
@PrimaryKey
override var id = UUID.randomUUID().toString()
@Ignore
override val realmObjectClass: Class<out Identifiable> = HandHistory::class.java
/***
* The date of the hand history
*/
var date: Date = Date()
set(value) {
field = value
this.updateTimeParameter(value)
}
init {
this.date = Date() // force the custom setter call
}
/***
* The session whose hand was played
*/
var session: Session? = null
/***
* The small blind
*/
var smallBlind: Double? = null
set(value) {
field = value
if (this.bigBlind == null && value != null) {
this.bigBlind = value * 2
}
}
/***
* The big blind
*/
var bigBlind: Double? = null
set(value) {
field = value
if (this.smallBlind == null && value != null) {
this.smallBlind = value / 2
}
}
/***
* The ante
*/
var ante: Double = 0.0
/***
* Big blind ante
*/
var bigBlindAnte: Boolean = false
/***
* Number of players in the hand
*/
var numberOfPlayers: Int = 9
/***
* Number of players in the hand
*/
var comment: String? = null
/***
* The position index of the hero
*/
var heroIndex: Int? = null
/***
* Indicates if the hero wins the hand
*/
var winnerPots: RealmList<WonPot> = RealmList()
/***
* The board
*/
var board: RealmList<Card> = RealmList()
/***
* The players actions
*/
var actions: RealmList<Action> = RealmList()
/***
* A list of players initial data: user, position, hand, stack
*/
var playerSetups: RealmList<PlayerSetup> = RealmList()
// Timed interface
override var dayOfWeek: Int? = null
override var month: Int? = null
override var year: Int? = null
override var dayOfMonth: Int? = null
/***
* Returns the indexes of all players
*/
val positionIndexes: IntRange
get() { return (0 until this.numberOfPlayers) }
// 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.
}
override fun deleteDependencies(realm: Realm) {
this.board.deleteAllFromRealm()
this.playerSetups.deleteAllFromRealm()
this.actions.deleteAllFromRealm()
}
/***
* Configures a hand history with a [handSetup]
*/
fun configure(handSetup: HandSetup) {
this.playerSetups.removeAll(this.playerSetups)
handSetup.tableSize?.let { this.numberOfPlayers = it }
handSetup.smallBlind?.let { this.smallBlind = it }
handSetup.bigBlind?.let { this.bigBlind = it }
this.session = handSetup.session
this.date = this.session?.handHistoryAutomaticDate ?: Date()
this.createActions(handSetup)
}
/***
* Creates the initial actions of the hand history
*/
private fun createActions(handSetup: HandSetup) {
this.actions.clear()
this.addAction(0, Action.Type.POST_SB, this.smallBlind)
this.addAction(1, Action.Type.POST_BB, this.bigBlind)
// var lastStraddler: Int? = null
val positions = Position.positionsPerPlayers(this.numberOfPlayers)
handSetup.straddlePositions.forEach { position -> // position are sorted here
val positionIndex = positions.indexOf(position)
this.addAction(positionIndex, Action.Type.STRADDLE)
// lastStraddler = positionIndex
}
// val totalActions = this.actions.size
// val startingPosition = lastStraddler?.let { it + 1 } ?: totalActions
// for (i in this.positionIndexes - 1) { // we don't add the BB / straddler by default in case of a walk
// this.addAction((startingPosition + i) % this.numberOfPlayers)
// }
}
/***
* Adds an action with the given [position], [type] and [amount] to the actions list
*/
private fun addAction(position: Int, type: Action.Type? = null, amount: Double? = null) {
val action = Action()
action.index = this.actions.size
action.position = position
action.type = type
action.amount = amount
action.effectiveAmount = amount ?: 0.0
this.actions.add(action)
}
/***
* Returns the board cards for a given [street]
*/
fun cardsForStreet(street: Street): MutableList<Card> {
return this.board.sortedBy { it.index }.take(street.totalBoardCards).toMutableList()
}
/***
* Returns the optional PlayerSetup object at the [position]
*/
fun playerSetupForPosition(position: Int): PlayerSetup? {
return this.playerSetups.firstOrNull { it.position == position }
}
override val cards: RealmList<Card>
get() { return this.board }
/***
* Returns the ante sum
*/
val anteSum: Double
get() {
return if (bigBlindAnte) {
this.bigBlind ?: 0.0
} else {
this.ante * this.numberOfPlayers
}
}
/***
* Returns the sorted list of actions by index
*/
private val sortedActions: List<Action>
get() {
return this.actions.sortedBy { it.index }
}
/***
* Returns the list of undefined positions,
* meaning the positions where no PlayerSetup has been created
*/
fun undefinedPositions(): List<Position> {
val positions = Position.positionsPerPlayers(this.numberOfPlayers)
val copy = positions.clone() as LinkedHashSet<Position>
this.playerSetups.forEach {
copy.remove(positions.elementAt(it.position))
}
return copy.toList()
}
/***
* Creates and affect a PlayerSetup at the given [positionIndex]
*/
fun createPlayerSetup(positionIndex: Int): PlayerSetup {
val playerSetup = if (this.realm != null) {
this.realm.createObject(PlayerSetup::class.java) }
else {
PlayerSetup()
}
playerSetup.position = positionIndex
this.playerSetups.add(playerSetup)
return playerSetup
}
/***
* Returns the pot size at the start of the given [street]
*/
fun potSizeForStreet(street: Street): Double {
val sortedActions = this.sortedActions
val firstIndexOfStreet = sortedActions.firstOrNull { it.street == street }?.index
?: sortedActions.size
return this.anteSum + sortedActions.take(firstIndexOfStreet).sumByDouble { it.effectiveAmount }
}
@Ignore
override val viewType: Int = RowViewType.HAND_HISTORY.ordinal
override fun localizedString(context: Context): CharSequence {
val positions = Position.positionsPerPlayers(this.numberOfPlayers)
var string = ""
// Settings
val players = "${this.numberOfPlayers} ${context.getString(R.string.players)}"
val firstLineComponents = mutableListOf(this.date.fullDate(), players)
this.smallBlind?.let { sb ->
this.bigBlind?.let { bb ->
firstLineComponents.add("${sb.formatted}/${bb.formatted}")
}
}
if (this.ante > 0.0) {
firstLineComponents.add("ante ${this.ante}")
}
string = string.plus(firstLineComponents.joinToString(" - "))
string = string.addLineReturn(2)
// Comment
this.comment?.let { comment ->
string = string.plus(comment)
string = string.addLineReturn(2)
}
// Players
this.playerSetups.sortedBy { it.position }.forEach {
string = string.plus(localizedPlayerSetup(it, positions, context))
string = string.addLineReturn()
}
// Actions per street
val sortedActions = this.actions.sortedBy { it.index }
// val actionList = ActionList()
// actionList.load(this)
Street.values().forEach { street ->
string = string.addLineReturn(2)
val streetActions = sortedActions.filter { it.street == street }.compact(positions, this.heroIndex)
if (streetActions.isNotEmpty()) {
val streetItems = mutableListOf<CharSequence>(context.getString(street.resId))
val potSize = this.potSizeForStreet(street)
if (potSize > 0) {
// streetItems.add(context.getString(R.string.pot_size))
streetItems.add("(" + potSize.formatted + ")")
}
string = string.plus(streetItems.joinToString(" "))
val streetCards = this.cardsForStreet(street)
if (streetCards.isNotEmpty()) {
string = string.addLineReturn()
string = string.plus(streetCards.formatted(context) ?: "")
}
string = string.addLineReturn()
string = string.plus("-----------")
string = string.addLineReturn()
streetActions.forEach { action ->
string = string.plus(localizedAction(action, context))
string = string.addLineReturn()
}
}
}
return string
}
fun anteForPosition(position: Position): Double {
return if (this.bigBlindAnte) {
if (position == Position.BB) {
this.bigBlind ?: 0.0
} else {
0.0
}
} else {
this.ante
}
}
/***
* Returns a string representation of the [playerSetup]
*/
private fun localizedPlayerSetup(playerSetup: PlayerSetup, positions: LinkedHashSet<Position>, context: Context): String {
val playerItems = mutableListOf(positions.elementAt(playerSetup.position).value)
val isHero = (playerSetup.position == this.heroIndex)
if (isHero) {
val heroString = context.getString(R.string.hero)
playerItems.add("- $heroString")
}
playerItems.add("[${playerSetup.cards.formatted(context)}]")
playerSetup.stack?.let { stack ->
playerItems.add("- $stack")
}
return playerItems.joinToString(" ")
}
/***
* Returns a string representation of the [actionReadRow]
*/
private fun localizedAction(actionReadRow: ActionReadRow, context: Context): String {
val formattedPositions = actionReadRow.positions.joinToString(", ") { it.value }
val actionItems = mutableListOf(formattedPositions)
actionReadRow.action?.let { type ->
actionItems.add(context.getString(type.resId))
}
actionReadRow.amount?.let { amount ->
actionItems.add(amount.formatted)
}
return actionItems.joinToString(" ")
}
/***
* Returns if the hero has won the hand, or part of the pot
*/
val heroWins: Boolean?
get() {
return this.heroIndex?.let { heroIndex ->
this.winnerPots.any { it.position == heroIndex }
} ?: run {
null
}
}
/***
* Creates a list of cards for the hand history to give a representation of the hand
* We will try to add a minimum of 5 cards using by priority:
* - the hero hand
* - the opponents hands
* - the board
*/
fun cardViews(context: Context, viewGroup: ViewGroup): List<View> {
val views = mutableListOf<View>()
val layoutInflater = LayoutInflater.from(context)
// Create all the possible cards list: hero, opponents, board
val cardSets = mutableListOf<List<Card>>()
// Hero
this.heroIndex?.let { hIndex ->
this.playerSetupForPosition(hIndex)?.cards?.let {
cardSets.add(it)
}
}
// Opponents
this.playerSetups.filter { it.cards.isNotEmpty() && it.position != this.heroIndex }.forEach {
cardSets.add(it.cards)
}
// Board
cardSets.add(this.board)
// Try to add cards but not too much
while (views.size < 5 && cardSets.isNotEmpty()) {
val cardSet = cardSets.removeAt(0)
if (views.isNotEmpty() && cardSet.isNotEmpty()) { // add separator with previous set of cards
val view = layoutInflater.inflate(R.layout.view_card_separator, viewGroup, false) as AppCompatTextView
views.add(view)
}
cardSet.forEach { views.add(it.view(context, layoutInflater, viewGroup)) }
}
// Add 5 blank cards if no card has been added
if (views.isEmpty()) {
val blankCard = Card()
(1..5).forEach { _ ->
val view = blankCard.view(context, layoutInflater, viewGroup, true)
view.setBackgroundResource(R.drawable.rounded_kaki_medium_rect)
views.add(view)
}
}
return views
}
data class Pot(var amount: Double, var level: Double, var positions: MutableSet<Int> = mutableSetOf())
/***
* Defines which positions win the hand
*/
fun defineWinnerPositions() {
val folds = this.sortedActions.filter { it.type == Action.Type.FOLD }.map { it.position }
val activePositions = this.positionIndexes.toMutableList()
activePositions.removeAll(folds)
val wonPots = when (activePositions.size) {
0 -> listOf() // no winner, everyone has fold. Should not happen
1 -> { // One player has not fold, typically BET / FOLD
val pot = WonPot()
pot.position = activePositions.first()
pot.amount = potSizeForStreet(Street.SUMMARY)
listOf(pot)
}
else -> { // Several players remains, typically BET/FOLD or CHECKS
this.wonPots(getPots(activePositions))
}
}
this.winnerPots.clear()
this.winnerPots.addAll(wonPots)
}
/***
* Returns a list with all the different pots with the appropriate eligible players
*/
fun getPots(eligiblePositions: List<Int>): List<Pot> {
var runningPotAmount = 0.0
val pots = mutableListOf<Pot>()
Street.values().forEach { street ->
val streetActions = this.actions.filter { it.street == street }
val allinPositions = streetActions.filter { it.type?.isAllin == true }.map { it.position }
if (allinPositions.isEmpty()) {
runningPotAmount += streetActions.sumByDouble { it.effectiveAmount }
} else {
val amountsPerPosition = mutableListOf<PositionAmount>()
// get all committed amounts for the street by player, by allin
this.positionIndexes.map { position ->
val playerActions = streetActions.filter { it.position == position }
val sum = playerActions.sumByDouble { it.effectiveAmount }
amountsPerPosition.add(PositionAmount(position, sum, allinPositions.contains(position)))
}
amountsPerPosition.sortWith(this) // sort by value, then allin. Allin must be first of equal values sequence
// for each player
val streetPots = mutableListOf<Pot>()
amountsPerPosition.forEach { positionAmount ->
val amount = positionAmount.amount
val position = positionAmount.position
var rest = amount
var lastPotLevel = 0.0
// put invested amount in smaller pots
streetPots.forEach { pot ->
val added = pot.level - lastPotLevel
pot.amount += added
if (eligiblePositions.contains(position)) {
pot.positions.add(position)
}
rest -= added
lastPotLevel = pot.level
}
// Adds remaining chips to the running Pot
runningPotAmount += rest
// If the player is allin, create a new pot for the relevant amount
val isAllin = allinPositions.contains(position)
if (isAllin) {
streetPots.add(Pot(runningPotAmount, amount, mutableSetOf(position)))
runningPotAmount = 0.0
}
}
pots.addAll(streetPots)
}
}
// Create a pot with the remaining chips
if (runningPotAmount > 0.0) {
pots.add(Pot(runningPotAmount, 0.0, eligiblePositions.toMutableSet()))
}
return pots
}
private fun wonPots(pots: List<Pot>): Collection<WonPot> {
val wonPots = hashMapOf<Int, WonPot>()
pots.forEach { pot ->
val winningPositions = compareHands(pot.positions.toList())
// Distributes the pot for each winners
val share = pot.amount / winningPositions.size
winningPositions.forEach { p ->
val wp = wonPots[p]
if (wp == null) {
val wonPot = WonPot()
wonPot.position = p
wonPot.amount = share
wonPots[p] = wonPot
} else {
wp.amount += share
}
}
}
return wonPots.values
}
/***
* Compares the hands of the players at the given [activePositions]
* Returns the list of winning hands by position
*/
private fun compareHands(activePositions: List<Int>): List<Int> {
// Evaluate all hands
val results = activePositions.map {
this.playerSetupForPosition(it)?.cards?.let { hand ->
EvaluatorBridge.playerHand(hand, this.board)
} ?: run {
Int.MAX_VALUE
}
}
// Check who has best score (EvaluatorBridge gives a lowest score for a better hand)
return results.min()?.let { best ->
val winners = mutableListOf<Int>()
results.forEachIndexed { index, i ->
if (i == best && i != Int.MAX_VALUE) {
winners.add(activePositions[index])
}
}
winners
} ?: run {
listOf<Int>() // type needed for build
}
}
/***
* return a negative integer, zero, or a positive integer as the
* first argument is less than, equal to, or greater than the
* second.
*/
override fun compare(o1: PositionAmount, o2: PositionAmount): Int {
return if (o1.amount == o2.amount) {
if (o1.isAllin) -1 else 1
} else {
(o1.amount - o2.amount).toInt()
}
}
val maxPlayerCards: Int
get() {
var max = 0
this.playerSetups.forEach {
max = max(it.cards.size, max)
}
return max
}
val usesWildcards: Boolean
get() {
val boardHasWildCard = this.cards.any { it.isWildCard }
val playerCardHasWildCard = this.playerSetups.any { it.cards.any { it.isWildCard } }
return boardHasWildCard || playerCardHasWildCard
}
}

@ -0,0 +1,31 @@
package net.pokeranalytics.android.model.realm.handhistory
import io.realm.RealmList
import io.realm.RealmObject
import net.pokeranalytics.android.model.realm.Player
import net.pokeranalytics.android.ui.modules.handhistory.model.CardHolder
import net.pokeranalytics.android.ui.view.Localizable
open class PlayerSetup : RealmObject(), CardHolder, Localizable {
/***
* The player
*/
var player: Player? = null
/***
* The position at the table
*/
var position: Int = 0
/***
* The initial stack of the player
*/
var stack: Double? = null
/***
* The cards of the player
*/
override var cards: RealmList<Card> = RealmList()
}

@ -0,0 +1,17 @@
package net.pokeranalytics.android.model.realm.handhistory
import io.realm.RealmObject
open class WonPot: RealmObject() {
/***
* The position of the player
*/
var position: Int = 0
/***
* The amount won
*/
var amount: Double = 0.0
}

@ -15,7 +15,7 @@ private fun Session.parameterRepresentation(context: Context): String {
var representation = ""
this.significantFields().forEach {
representation += this.stringForRow(it, context)
representation += this.charSequenceForRow(it, context)
}
return representation

@ -1,58 +0,0 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_data_list.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.fragment.DataListFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
class DataListActivity : BaseActivity() {
enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"),
LIVE_DATA_TYPE("LIVE_DATA_TYPE"),
ITEM_DELETED("ITEM_DELETED"),
SHOW_ADD_BUTTON("SHOW_ADD_BUTTON"),
}
companion object {
fun newInstance(context: Context, dataType: Int) {
context.startActivity(getIntent(context, dataType))
}
fun newSelectInstance(fragment: Fragment, dataType: Int, showAddButton: Boolean = true) {
val context = fragment.requireContext()
fragment.startActivityForResult(getIntent(context, dataType, showAddButton), FilterActivityRequestCode.SELECT_FILTER.ordinal)
}
private fun getIntent(context: Context, dataType: Int, showAddButton: Boolean = true): Intent {
val intent = Intent(context, DataListActivity::class.java)
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType)
intent.putExtra(IntentKey.SHOW_ADD_BUTTON.keyName, showAddButton)
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_data_list)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0)
val showAddButton = intent.getBooleanExtra(IntentKey.SHOW_ADD_BUTTON.keyName, true)
val fragment = dataListFragment as DataListFragment
fragment.setData(dataType)
fragment.updateUI(showAddButton)
}
}

@ -1,71 +0,0 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.fragment.FilterDetailsFragment
class FilterDetailsActivity : BaseActivity() {
enum class IntentKey(val keyName: String) {
FILTER_ID("FILTER_ID"),
FILTER_CATEGORY_ORDINAL("FILTER_CATEGORY_ORDINAL")
}
companion object {
/**
* Default constructor
*/
fun newInstance(context: Context, filterId: String, filterCategoryOrdinal: Int) {
val intent = Intent(context, FilterDetailsActivity::class.java)
intent.putExtra(IntentKey.FILTER_ID.keyName, filterId)
intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal)
context.startActivity(intent)
}
/**
* Create a new instance for result
*/
fun newInstanceForResult(fragment: Fragment, filterId: String, filterCategoryOrdinal: Int, requestCode: Int, filter: Filter? = null) {
val intent = Intent(fragment.requireContext(), FilterDetailsActivity::class.java)
intent.putExtra(IntentKey.FILTER_ID.keyName, filterId)
intent.putExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, filterCategoryOrdinal)
fragment.startActivityForResult(intent, requestCode)
}
}
private lateinit var fragment: FilterDetailsFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_filter_details)
initUI()
}
override fun onBackPressed() {
fragment.onBackPressed()
}
/**
* Init UI
*/
private fun initUI() {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val filterId = intent.getStringExtra(IntentKey.FILTER_ID.keyName)
val filterCategoryOrdinal = intent.getIntExtra(IntentKey.FILTER_CATEGORY_ORDINAL.keyName, 0)
fragment = FilterDetailsFragment()
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
fragment.setData(filterId, filterCategoryOrdinal)
}
}

@ -1,74 +0,0 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.fragment.FiltersFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType
class FiltersActivity : BaseActivity() {
enum class IntentKey(val keyName: String) {
FILTER_ID("FILTER_ID"),
FILTERABLE_TYPE("FILTERABLE_TYPE"),
HIDE_MOST_USED_FILTERS("HIDE_MOST_USED_FILTERS"),
;
}
private lateinit var fragment: FiltersFragment
companion object {
/**
* Create a new instance for result
*/
fun newInstanceForResult(fragment: Fragment, filterId: String? = null, currentFilterable: FilterableType, hideMostUsedFilters: Boolean = false) {
val intent = getIntent(fragment.requireContext(), filterId, currentFilterable, hideMostUsedFilters)
fragment.startActivityForResult(intent, FilterActivityRequestCode.CREATE_FILTER.ordinal)
}
private fun getIntent(context: Context, filterId: String?, currentFilterable: FilterableType, hideMostUsedFilters: Boolean = false): Intent {
val intent = Intent(context, FiltersActivity::class.java)
intent.putExtra(IntentKey.FILTERABLE_TYPE.keyName, currentFilterable.uniqueIdentifier)
intent.putExtra(IntentKey.HIDE_MOST_USED_FILTERS.keyName, hideMostUsedFilters)
filterId?.let {
intent.putExtra(IntentKey.FILTER_ID.keyName, it)
}
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_filters)
initUI()
}
override fun onBackPressed() {
fragment.onBackPressed()
}
/**
* Init UI
*/
private fun initUI() {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val filterId = intent.getStringExtra(IntentKey.FILTER_ID.keyName)
val uniqueIdentifier= intent.getIntExtra(IntentKey.FILTERABLE_TYPE.keyName, 0)
val hideMostUsedFilters = intent.getBooleanExtra(IntentKey.HIDE_MOST_USED_FILTERS.keyName, false)
val filterableType = FilterableType.valueByIdentifier(uniqueIdentifier)
fragment = FiltersFragment()
fragment.setData(filterId, filterableType)
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
fragment.updateMostUsedFiltersVisibility(!hideMostUsedFilters)
}
}

@ -1,58 +0,0 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_filters_list.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.fragment.FiltersListFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
class FiltersListActivity : BaseActivity() {
enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"),
LIVE_DATA_TYPE("LIVE_DATA_TYPE"),
ITEM_DELETED("ITEM_DELETED"),
SHOW_ADD_BUTTON("SHOW_ADD_BUTTON"),
}
companion object {
fun newInstance(context: Context, dataType: Int) {
context.startActivity(getIntent(context, dataType))
}
fun newSelectInstance(fragment: Fragment, dataType: Int, showAddButton: Boolean = true) {
val context = fragment.requireContext()
fragment.startActivityForResult(getIntent(context, dataType, showAddButton), FilterActivityRequestCode.SELECT_FILTER.ordinal)
}
private fun getIntent(context: Context, dataType: Int, showAddButton: Boolean = true): Intent {
val intent = Intent(context, FiltersListActivity::class.java)
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType)
intent.putExtra(IntentKey.SHOW_ADD_BUTTON.keyName, showAddButton)
return intent
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_filters_list)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0)
val showAddButton = intent.getBooleanExtra(IntentKey.SHOW_ADD_BUTTON.keyName, true)
val fragment = filtersListFragment as FiltersListFragment
fragment.setData(dataType)
fragment.updateUI(showAddButton)
}
}

@ -53,6 +53,7 @@ class HomeActivity : BaseActivity() {
override fun onResume() {
super.onResume()
AppGuard.requestPurchasesUpdate()
this.homePagerAdapter?.activityResumed()
}
override fun onCreate(savedInstanceState: Bundle?) {

@ -9,6 +9,7 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.crashlytics.android.Crashlytics
import com.google.android.libraries.places.api.model.PlaceLikelihood
import io.realm.Realm
@ -101,6 +102,12 @@ abstract class BaseActivity : AppCompatActivity() {
return super.onOptionsItemSelected(item)
}
fun showFragment(fragment: Fragment, containerId: Int) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(containerId, fragment)
fragmentTransaction.commit()
}
/**
* Return the realm instance
*/

@ -4,12 +4,15 @@ enum class RequestCode(var value: Int) {
DEFAULT(1),
FEED_MENU(100),
FEED_TRANSACTION_DETAILS(101),
PLAYER_SELECTION(200),
BANKROLL_DETAILS(700),
BANKROLL_CREATE(701),
BANKROLL_EDIT(702),
NEW_SESSION(800),
NEW_TRANSACTION(801),
NEW_REPORT(802),
NEW_DATA(800),
NEW_SESSION(801),
NEW_TRANSACTION(802),
NEW_REPORT(803),
NEW_HAND_HISTORY(804),
IMPORT(900),
SUBSCRIPTION(901),
CURRENCY(902),

@ -4,7 +4,6 @@ import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.provider.MediaStore
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
@ -62,7 +61,7 @@ open class MediaActivity : BaseActivity() {
}
}
} else if (data?.clipData != null) {
data?.clipData?.let { clipData ->
data.clipData?.let { clipData ->
try {
GlobalScope.launch(Dispatchers.Main) {
@ -87,7 +86,7 @@ open class MediaActivity : BaseActivity() {
}
}
} else if (data?.data != null) {
data?.data?.let { uri ->
data.data?.let { uri ->
try {
GlobalScope.launch(Dispatchers.Main) {
@ -216,42 +215,42 @@ open class MediaActivity : BaseActivity() {
}
}
/**
* Ask for the acmera permission
*/
private fun askForCameraPermission() {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA),
PERMISSION_REQUEST_CAMERA)
}
}
/**
* Ask for camera and storage permission
*/
private fun askForCameraAndStoragePermissions() {
val permissions = ArrayList<String>()
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.CAMERA)
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (permissions.size > 0) {
ActivityCompat.requestPermissions(this, permissions.toArray(arrayOfNulls<String>(permissions.size)), PERMISSION_REQUEST_CAMERA)
}
}
/**
* Called when a bitmap is return
*
* @param bitmap the bitmap returned
*/
open fun getBitmapImage(file: File?, bitmap: Bitmap?) {}
// /**
// * Ask for the acmera permission
// */
// private fun askForCameraPermission() {
// // Here, thisActivity is the current activity
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA),
// PERMISSION_REQUEST_CAMERA)
// }
// }
// /**
// * Ask for camera and storage permission
// */
// private fun askForCameraAndStoragePermissions() {
//
// val permissions = ArrayList<String>()
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// permissions.add(Manifest.permission.CAMERA)
// }
//
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
// }
//
// if (permissions.size > 0) {
// ActivityCompat.requestPermissions(this, permissions.toArray(arrayOfNulls<String>(permissions.size)), PERMISSION_REQUEST_CAMERA)
// }
// }
//
// /**
// * Called when a bitmap is return
// *
// * @param bitmap the bitmap returned
// */
// open fun getBitmapImage(file: File?, bitmap: Bitmap?) {}
/**

@ -6,8 +6,8 @@ import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.fragment.CalendarFragment
import net.pokeranalytics.android.ui.fragment.FeedFragment
import net.pokeranalytics.android.ui.modules.calendar.CalendarFragment
import net.pokeranalytics.android.ui.modules.feed.FeedFragment
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.fragment.components.BaseFragment
import java.lang.ref.WeakReference
@ -15,9 +15,9 @@ import java.lang.ref.WeakReference
/**
* Comparison Chart Pager Adapter
*/
class ComparisonChartPagerAdapter(val context: Context, fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) {
class ComparisonChartPagerAdapter(val context: Context, fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
var weakReferences = SparseArray<WeakReference<BaseFragment>>()
private var weakReferences = SparseArray<WeakReference<BaseFragment>>()
override fun getItem(position: Int): BaseFragment {
return when (position) {
@ -59,11 +59,11 @@ class ComparisonChartPagerAdapter(val context: Context, fragmentManager: Fragmen
/**
* Return the fragment at the position key
*/
fun getFragment(key: Int): BaseFragment? {
if (weakReferences.get(key) != null) {
return weakReferences.get(key).get()
}
return null
}
// fun getFragment(key: Int): BaseFragment? {
// if (weakReferences.get(key) != null) {
// return weakReferences.get(key).get()
// }
// return null
// }
}

@ -0,0 +1,240 @@
package net.pokeranalytics.android.ui.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import io.realm.RealmModel
import io.realm.RealmQuery
import io.realm.RealmResults
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.modules.feed.FeedTransactionRowRepresentableAdapter
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.extensions.getMonthAndYear
import java.util.*
import kotlin.collections.HashMap
interface DateModel : RowRepresentable, RealmModel, Filterable {
var date: Date
}
class TransactionED : EntityDescriptor {
override fun bindableHolder(view: View): RecyclerView.ViewHolder {
TODO()
}
override val layout: Int = R.layout.row_transaction
override val viewType: Int = RowViewType.ROW_TRANSACTION.ordinal
override val sortFieldName: String = "date"
override fun distinctHeaders(realmQuery: RealmQuery<out DateModel>): RealmQuery<out DateModel> {
return realmQuery.distinct("year", "month")
}
}
interface EntityDescriptor {
fun bindableHolder(view: View): RecyclerView.ViewHolder
val layout: Int
val viewType: Int
val sortFieldName: String
fun distinctHeaders(realmQuery: RealmQuery<out DateModel>): RealmQuery<out DateModel>
// val clazz: Class<T>
}
/**
* An adapter capable of displaying a list of RowRepresentables
* @param dataSource the datasource providing rows
* @param delegate the delegate, notified of UI actions
*/
class FilterSectionAdapter(
override var delegate: RowRepresentableDelegate? = null,
override var dataSource: RowRepresentableDataSource,
var descriptor: EntityDescriptor,
var realmQuery: RealmQuery<out DateModel>
// var distinctTransactionsHeaders: RealmResults<Transaction>
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>(), RecyclerAdapter {
private var headersPositions = HashMap<Int, Date?>()
private lateinit var sortedHeaders: SortedMap<Int, Date?>
private var realmEntities: RealmResults<out DateModel>
private var realmHeaders: RealmResults<out DateModel>
var filter: Filter? = null
//
// companion object {
//
// inline fun <reified T : DateModel> build(delegate: RowRepresentableDelegate?, dataSource: RowRepresentableDataSource, descriptor: EntityDescriptor<T>, realmQuery: RealmQuery<T>) : FilterSectionAdapter<T> {
// val adapter = FilterSectionAdapter(delegate, dataSource, descriptor, realmQuery)
// adapter.load()
// return adapter
// }
//
// }
init {
// this.realmEntities = this.realmQuery.findAll().sort(this.descriptor.sortFieldName)
this.realmEntities = this.filter?.results() ?: this.realmQuery.findAll().sort(this.descriptor.sortFieldName)
this.realmHeaders = this.descriptor.distinctHeaders(this.realmQuery).findAll()
refreshData()
}
// fun load() {
//
// val f = this.filter?.results()?: this.realmQuery.findAll().sort(this.descriptor.sortFieldName)
// this.realmEntities = f
//
// }
// /**
// * Display a transaction view
// */
// inner class RowEntityViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
//
// fun bind(position: Int, row: DateModel?, adapter: FilterSectionAdapter) {
//
//// itemView.transactionRow.setData(row as Transaction)
//// val listener = View.OnClickListener {
//// adapter.delegate?.onRowSelected(position, row)
//// }
//// itemView.transactionRow.setOnClickListener(listener)
// }
// }
/**
* Display a header
*/
inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(title: String) {
// Title
itemView.findViewById<AppCompatTextView>(R.id.title)?.let {
it.text = title
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return if (viewType == this.descriptor.viewType) { // TODO layout
val layout = inflater.inflate(this.descriptor.layout, parent, false)
this.descriptor.bindableHolder(layout)
// RowEntityViewHolder(layout)
} else {
val layout = inflater.inflate(R.layout.row_header_title, parent, false)
HeaderTitleViewHolder(layout)
}
}
override fun getItemViewType(position: Int): Int {
return if (sortedHeaders.containsKey(position)) {
RowViewType.HEADER_TITLE.ordinal
} else {
this.descriptor.viewType // RowViewType.ROW_TRANSACTION.ordinal
}
}
override fun getItemCount(): Int {
return realmEntities.size + realmHeaders.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is BindableHolder) {
holder.onBind(position, getEntityForPosition(position), this)
} else if (holder is FeedTransactionRowRepresentableAdapter.HeaderTitleViewHolder) {
holder.bind(getHeaderForPosition(position))
}
}
/**
* Return the header
*/
private fun getHeaderForPosition(position: Int): String {
if (sortedHeaders.containsKey(position)) {
val realmHeaderPosition = sortedHeaders.keys.indexOf(position)
return realmHeaders[realmHeaderPosition]?.date?.getMonthAndYear() ?: ""
}
return NULL_TEXT
}
/**
* Get real index
*/
private fun getEntityForPosition(position: Int): DateModel {
// Row position
var headersBefore = 0
for (key in sortedHeaders.keys) {
if (position > key) {
headersBefore++
} else {
break
}
}
return realmEntities[position - headersBefore] ?: throw PAIllegalStateException("Nasty problem!")
}
/**
* Refresh headers positions
*/
fun refreshData() {
headersPositions.clear()
var previousYear = Int.MAX_VALUE
var previousMonth = Int.MAX_VALUE
val calendar = Calendar.getInstance()
// Add headers if the date doesn't exist yet
for ((index, transaction) in realmEntities.withIndex()) {
calendar.time = transaction.date
if (checkHeaderCondition(calendar, previousYear, previousMonth)) {
headersPositions[index + headersPositions.size] = transaction.date
previousYear = calendar.get(Calendar.YEAR)
previousMonth = calendar.get(Calendar.MONTH)
}
}
sortedHeaders = headersPositions.toSortedMap()
}
/**
* Check if we need to add a header
* Can be change to manage different condition
*/
private fun checkHeaderCondition(currentCalendar: Calendar, previousYear: Int, previousMonth: Int): Boolean {
return currentCalendar.get(Calendar.YEAR) == previousYear && currentCalendar.get(Calendar.MONTH) < previousMonth || (currentCalendar.get(Calendar.YEAR) < previousYear)
}
}

@ -4,16 +4,21 @@ import android.util.SparseArray
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import net.pokeranalytics.android.ui.fragment.*
import net.pokeranalytics.android.ui.modules.calendar.CalendarFragment
import net.pokeranalytics.android.ui.fragment.ReportsFragment
import net.pokeranalytics.android.ui.fragment.SettingsFragment
import net.pokeranalytics.android.ui.fragment.StatisticsFragment
import net.pokeranalytics.android.ui.fragment.components.BaseFragment
import net.pokeranalytics.android.ui.modules.feed.FeedFragment
import java.lang.ref.WeakReference
/**
* Home Adapter
*/
class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) {
class HomePagerAdapter(fragmentManager: FragmentManager) :
FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
var weakReferences = SparseArray<WeakReference<BaseFragment>>()
private var weakReferences = SparseArray<WeakReference<BaseFragment>>()
override fun getItem(position: Int): BaseFragment {
return when (position) {
@ -32,12 +37,12 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
weakReferences.remove(position)
this.weakReferences.remove(position)
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as BaseFragment
weakReferences.put(position, WeakReference(fragment))
this.weakReferences.put(position, WeakReference(fragment))
return fragment
}
@ -52,14 +57,21 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
}
}
/**
* Return the fragment at the position key
*/
fun getFragment(key: Int): BaseFragment? {
if (weakReferences.get(key) != null) {
return weakReferences.get(key).get()
fun activityResumed() {
val ref = this.weakReferences.get(0)
ref?.get()?.let {
(it as FeedFragment).activityResumed()
}
return null
}
// /**
// * Return the fragment at the position key
// */
// fun getFragment(key: Int): BaseFragment? {
// if (this.weakReferences.get(key) != null) {
// return this.weakReferences.get(key).get()
// }
// return null
// }
}

@ -0,0 +1,26 @@
package net.pokeranalytics.android.ui.adapter
import android.view.View
import net.pokeranalytics.android.ui.view.RowRepresentable
interface RecyclerAdapter {
var dataSource: RowRepresentableDataSource
var delegate: RowRepresentableDelegate?
}
interface RowRepresentableDelegate {
fun onRowSelected(position: Int, row: RowRepresentable, tag: Int = 0) {}
fun onRowDeselected(position: Int, row: RowRepresentable) {}
fun onRowValueChanged(value: Any?, row: RowRepresentable) {}
fun onRowDeleted(row: RowRepresentable) {}
fun onRowLongClick(itemView: View, row: RowRepresentable, position: Int) {}
fun onItemClick(position: Int, row: RowRepresentable, tag: Int = 0) {}
}
/**
* An interface used to factor the configuration of RecyclerView.ViewHolder
*/
interface BindableHolder {
fun onBind(position: Int, row: RowRepresentable, adapter: RecyclerAdapter) {
}
}

@ -14,17 +14,13 @@ import net.pokeranalytics.android.ui.viewmodel.ReportHolder
* Home Adapter
*/
class ReportPagerAdapter(private val context: Context,
val fragmentManager: FragmentManager,
private val reportHolder: ReportHolder) : FragmentPagerAdapter(fragmentManager) {
fragmentManager: FragmentManager,
private val reportHolder: ReportHolder) : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): BaseFragment {
return when (position) {
0 -> {
GraphFragment.newInstance(style = GraphFragment.Style.BAR)
}
1 -> {
GraphFragment.newInstance(style = GraphFragment.Style.MULTILINE)
}
0 -> GraphFragment.newInstance(style = GraphFragment.Style.BAR)
1 -> GraphFragment.newInstance(style = GraphFragment.Style.MULTILINE)
2 -> {
val report = this.reportHolder.report
ComposableTableReportFragment.newInstance(report)

@ -4,28 +4,19 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import net.pokeranalytics.android.ui.view.BindableHolder
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
interface RowRepresentableDelegate {
fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean = false) {}
fun onRowValueChanged(value: Any?, row: RowRepresentable) {}
fun onRowDeleted(row: RowRepresentable) {}
fun onRowLongClick(itemView: View, row: RowRepresentable, position: Int) {}
}
/**
* An adapter capable of displaying a list of RowRepresentables
* @param dataSource the datasource providing rows
* @param delegate the delegate, notified of UI actions
*/
class RowRepresentableAdapter(
var dataSource: RowRepresentableDataSource,
var delegate: RowRepresentableDelegate? = null
override var dataSource: RowRepresentableDataSource,
override var delegate: RowRepresentableDelegate? = null
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
RecyclerView.Adapter<RecyclerView.ViewHolder>(), RecyclerAdapter {
override fun getItemViewType(position: Int): Int {
return this.dataSource.viewTypeForPosition(position)
@ -42,7 +33,7 @@ class RowRepresentableAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
this.dataSource.rowRepresentableForPosition(position)?.let {
(holder as BindableHolder).bind(position, it, this)
(holder as BindableHolder).onBind(position, it, this)
}
}

@ -1,10 +1,12 @@
package net.pokeranalytics.android.ui.adapter
import android.content.Context
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.util.TextFormat
import kotlin.reflect.KClass
/**
* Base Interface to provide the RowRepresentable to the adapter
@ -92,11 +94,8 @@ class UnmanagedRowRepresentableException(message: String) : Exception(message) {
/**
* An interface used to provide RowRepresentableAdapter content and value in the form of rows
*
* Also returns the appropriate value for any RowRepresentable in the form of :
* - string
* - booleans
* - actionIcon
* to display the appropriate numericValues in graphical filterConditions, such as labels, textfields, switchs...
* Returns values for any RowRepresentable in various forms to display
* the appropriate numericValues in graphical filterConditions, such as labels, textfields, switchs...
*/
interface DisplayableDataSource {
@ -118,17 +117,17 @@ interface DisplayableDataSource {
return -1
}
/**
* Returns a localized string for a specific row
/***
* Returns a CharSequence representation of the [row]
*/
fun stringForRow(row: RowRepresentable, context: Context): String {
return stringForRow(row)
fun charSequenceForRow(row: RowRepresentable, context: Context): CharSequence {
return charSequenceForRow(row, context, 0)
}
/**
* Returns a string for a specific row
/***
* Returns a CharSequence representation of the [row] and [tag]
*/
fun stringForRow(row: RowRepresentable): String {
fun charSequenceForRow(row: RowRepresentable, context: Context, tag: Int = 0): CharSequence {
return ""
}
@ -146,10 +145,17 @@ interface DisplayableDataSource {
return 0
}
/***
* Returns an arrowIcon
*/
fun arrowIcon(position: Int, row: RowRepresentable): Int? {
return R.drawable.ic_arrow_right
}
/**
* Returns whether the row, or its component, shoudl be enabled
* Returns whether the row, or its component, should be enabled
*/
fun isEnabled(row: RowRepresentable): Boolean {
fun isEnabled(row: RowRepresentable, tag: Int): Boolean {
return true
}
@ -159,18 +165,43 @@ interface DisplayableDataSource {
fun isSelectable(row: RowRepresentable): Boolean {
return true
}
/***
* Returns a list of content for the [row], using a specific [clazz]
*/
fun <T : Any> contentForRow(row: RowRepresentable, context: Context, clazz: KClass<T>) : List<T> {
return listOf()
}
/***
* Returns if the selection of the [row] + [tag] is focusable
*/
fun isFocusable(position: Int, row: RowRepresentable, tag: Int): Boolean {
return true
}
fun backgroundColor(position: Int, row: RowRepresentable): Int? {
return null
}
fun textColor(position: Int, row: RowRepresentable): Int? {
return null
}
}
/**
* An interface providing a way to describe how the edition of a [RowRepresentable] will be displayed
*/
interface EditableDataSource {
/**
* A list of [RowRepresentableEditDescriptor] object specifying the way the edition will be handled
*/
fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
return null
}
}
@ -181,7 +212,7 @@ interface SelectableDataSource {
/**
* A boolean to indicate if the row is selected or not
*/
fun isSelected(row: RowRepresentable): Boolean {
fun isSelected(position: Int, row: RowRepresentable, tag: Int): Boolean {
return false
}
}

@ -5,17 +5,22 @@ import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.net.Uri
import android.util.TypedValue
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatTextView
import androidx.appcompat.widget.SearchView
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import net.pokeranalytics.android.BuildConfig
@ -25,6 +30,7 @@ import net.pokeranalytics.android.util.DeviceUtils
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.billing.AppGuard
import java.io.ByteArrayOutputStream
import java.io.File
@ -96,10 +102,8 @@ fun BaseActivity.openContactMail(subjectStringRes: Int, filePath: String? = null
// Open custom tab
fun Context.openUrl(url: String) {
val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
builder.setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary))
val customTabsIntent = builder.build()
customTabsIntent.launchUrl(this, Uri.parse(url))
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
ContextCompat.startActivity(this, browserIntent, null)
}
// Display Alert Dialog
@ -171,4 +175,41 @@ fun SearchView.removeMargins() {
layoutParams?.leftMargin = 0
layoutParams?.rightMargin = 0
searchEditFrame?.layoutParams = layoutParams
}
}
fun View.toByteArray() : ByteArray {
return this.convertToBitmap().toByteArray()
}
fun View.convertToBitmap(): Bitmap {
val b = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(b)
val background = this.background
this.background?.let {
background.draw(canvas)
} ?: run {
canvas.drawColor(Color.WHITE)
}
this.draw(canvas)
return b
}
fun ImageView.toByteArray() : ByteArray {
return this.drawable.toBitmap().toByteArray()
}
fun Bitmap.toByteArray() : ByteArray {
val baos = ByteArrayOutputStream()
this.compress(Bitmap.CompressFormat.PNG, 100, baos)
return baos.toByteArray()
}
fun Context.hideKeyboard(v: View) {
val imm = v.context.getSystemService(InputMethodManager::class.java)
imm?.hideSoftInputFromWindow(v.windowToken, 0)
}
//fun Context.showKeyboard(view: View) {
// val imm = getSystemService(InputMethodManager::class.java)
// imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
//}

@ -4,7 +4,7 @@ import android.os.Bundle
import android.view.*
import kotlinx.android.synthetic.main.fragment_comparison_chart.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.BankrollActivity
import net.pokeranalytics.android.ui.modules.bankroll.BankrollActivity
import net.pokeranalytics.android.ui.activity.SettingsActivity
import net.pokeranalytics.android.ui.adapter.ComparisonChartPagerAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
@ -52,15 +52,15 @@ class ComparisonChartFragment : BaseFragment(), StaticRowRepresentableDataSource
initUI()
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
menu?.clear()
inflater?.inflate(R.menu.toolbar_comparison_chart, menu)
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.clear()
inflater.inflate(R.menu.toolbar_comparison_chart, menu)
this.comparisonChartMenu = menu
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.settings -> openChangeStatistics()
}
return true
@ -71,8 +71,8 @@ class ComparisonChartFragment : BaseFragment(), StaticRowRepresentableDataSource
return rowRepresentation
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
super.onRowSelected(position, row, fromAction)
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
super.onRowSelected(position, row, tag)
when(row) {
MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext())
MoreTabRow.SETTINGS -> SettingsActivity.newInstance(requireContext())

@ -90,12 +90,16 @@ class CurrenciesFragment : BaseFragment(), StaticRowRepresentableDataSource, Row
}
override fun stringForRow(row: RowRepresentable): String {
override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return (row as CurrencyRow).currencyCodeAndSymbol
}
// RowRepresentableDelegate
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
val intent = Intent()
intent.putExtra(INTENT_CURRENCY_CODE, (row as CurrencyRow).currency.currencyCode)
this.activity?.setResult(Activity.RESULT_OK, intent)

@ -1,201 +0,0 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.*
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm
import io.realm.RealmResults
import kotlinx.android.synthetic.main.fragment_data_list.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.extensions.removeMargins
import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.extensions.find
import net.pokeranalytics.android.util.extensions.sorted
open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_DETAILS = 1000
}
private lateinit var identifiableClass: Class<out Deletable>
private lateinit var dataType: LiveData
private lateinit var items: RealmResults<out Deletable>
private var dataListMenu: Menu? = null
private var searchView: SearchView? = null
var isSearchable: Boolean = false
set(value) {
field = value
val searchMenuItem = dataListMenu?.findItem(R.id.action_search)
searchMenuItem?.isVisible = value
}
/**
* Set fragment data
*/
open fun setData(dataType: Int) {
this.dataType = LiveData.values()[dataType]
this.identifiableClass = this.dataType.relatedEntity
setToolbarTitle(this.dataType.pluralLocalizedTitle(requireContext()))
this.items = this.retrieveItems(getRealm())
this.isSearchable = when (this.dataType) {
LiveData.PLAYER, LiveData.LOCATION -> true
else -> false
}
}
open fun retrieveItems(realm: Realm): RealmResults<out Deletable> {
return realm.sorted(this.identifiableClass, editableOnly = true, filterableTypeUniqueIdentifier = dataType.subType)
}
override fun deletableItems() : List<Deletable> {
return this.items
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_data_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
setDisplayHomeAsUpEnabled(true)
val viewManager = LinearLayoutManager(requireContext())
dataListAdapter = RowRepresentableAdapter(this, this)
val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position ->
val item = this.items[position]
if (item != null) {
val itemId = item.id
deleteItem(dataListAdapter, items, itemId)
} else {
throw PAIllegalStateException("Item with position $position not found")
}
}
val itemTouchHelper = ItemTouchHelper(swipeToDelete)
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = dataListAdapter
itemTouchHelper.attachToRecyclerView(this)
}
this.addButton.setOnClickListener {
EditableDataActivity.newInstance(
requireContext(),
dataType = this.dataType.ordinal,
primaryKey = null
)
}
}
override fun onResume() {
super.onResume()
this.recyclerView?.adapter?.notifyDataSetChanged()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.clear()
inflater.inflate(R.menu.toolbar_data_list, menu)
this.dataListMenu = menu
val searchMenuItem = menu.findItem(R.id.action_search)
searchMenuItem.isVisible = isSearchable
searchView = searchMenuItem.actionView as SearchView?
searchView?.removeMargins()
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
filterItemsWithSearch(newText)
return false
}
})
super.onCreateOptionsMenu(menu, inflater)
}
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return this.items[position] as RowRepresentable
}
override fun numberOfRows(): Int {
return this.items.size
}
override fun viewTypeForPosition(position: Int): Int {
val viewType = (this.items[position] as RowRepresentable).viewType
return if (viewType != -1) viewType else RowViewType.DATA.ordinal
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (this.dataType) {
LiveData.FILTER -> {
val intent = Intent()
intent.putExtra(FiltersActivity.IntentKey.FILTER_ID.keyName, (row as Filter).id)
activity?.setResult(Activity.RESULT_OK, intent)
activity?.finish()
}
else -> {
val identifier = (row as Identifiable).id
EditableDataActivity.newInstanceForResult(this, this.dataType, identifier, REQUEST_CODE_DETAILS)
}
}
}
/**
* Update UI
*/
fun updateUI(showAddButton: Boolean) {
this.addButton.isVisible = showAddButton
}
/**
* Filter the items list with the given search content
*/
private fun filterItemsWithSearch(searchContent: String?) {
this.items = getRealm().find(this.identifiableClass, searchContent)
dataListAdapter.notifyDataSetChanged()
}
}

@ -1,122 +0,0 @@
package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.content.Context
import android.content.Intent
import io.realm.RealmResults
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.interfaces.FilterHandler.Companion.INTENT_FILTER_UPDATE_FILTER_UI
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.Preferences
import timber.log.Timber
open class FiltersListFragment : DataListFragment() {
private var identifiableClass: Class<out Deletable> = Filter::class.java
private var dataType: LiveData = LiveData.FILTER
private lateinit var items: RealmResults<Filter>
/**
* Set fragment data
*/
override fun setData(dataType: Int) {
super.setData(dataType)
this.dataType = LiveData.FILTER
this.identifiableClass = Filter::class.java
setToolbarTitle(this.dataType.pluralLocalizedTitle(requireContext()))
this.items = this.retrieveItems(getRealm()) as RealmResults<Filter>
}
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
Timber.d("rowRepresentableForPosition: ${this.items[position] as RowRepresentable}")
return this.items[position] as RowRepresentable
}
override fun numberOfRows(): Int {
return this.items.size
}
override fun adapterRows(): List<RowRepresentable>? {
return items
}
override fun viewTypeForPosition(position: Int): Int {
val viewType = (this.items[position] as RowRepresentable).viewType
return if (viewType != -1) viewType else RowViewType.DATA.ordinal
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) {
is Filter -> row.editingDescriptors(mapOf("defaultValue" to row.name))
else -> super.editDescriptors(row)
}
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
when (row) {
is Filter -> {
row.updateValue(value, row)
dataListAdapter.refreshRow(row)
updateFilterUIIfNecessary(requireContext(), row.id)
}
else -> super.onRowValueChanged(value, row)
}
}
override fun onRowDeleted(row: RowRepresentable) {
when (row) {
is Filter -> {
val filterId = row.id
deleteItem(dataListAdapter, items, filterId)
if (filterId == Preferences.getActiveFilterId(requireContext())) {
Preferences.setActiveFilterId("", requireContext())
updateFilterUIIfNecessary(requireContext(), "")
}
}
else -> super.onRowDeleted(row)
}
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
is Filter -> {
if (fromAction) {
val data = row.editingDescriptors(mapOf("defaultValue" to row.name))
BottomSheetFragment.create(fragmentManager, row, this, data, false, isDeletable = true, valueHasPlaceholder = false)
} else {
val intent = Intent()
intent.putExtra(FiltersActivity.IntentKey.FILTER_ID.keyName, row.id)
activity?.setResult(Activity.RESULT_OK, intent)
activity?.finish()
}
}
else -> {
val identifier = (row as Identifiable).id
EditableDataActivity.newInstanceForResult(this, this.dataType, identifier, REQUEST_CODE_DETAILS)
}
}
}
/**
* Update filter UI
*/
fun updateFilterUIIfNecessary(context: Context, filterId: String) {
if (filterId == Preferences.getActiveFilterId(context)) {
// Send broadcast
val intent = Intent()
intent.action = INTENT_FILTER_UPDATE_FILTER_UI
context.sendBroadcast(intent)
}
}
}

@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_more.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.BankrollActivity
import net.pokeranalytics.android.ui.modules.bankroll.BankrollActivity
import net.pokeranalytics.android.ui.activity.SettingsActivity
import net.pokeranalytics.android.ui.activity.Top10Activity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -59,8 +59,8 @@ class MoreFragment : BaseFragment(), StaticRowRepresentableDataSource, RowRepres
return rowRepresentation
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
super.onRowSelected(position, row, fromAction)
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
super.onRowSelected(position, row, tag)
when(row) {
MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext())
MoreTabRow.TOP_10 -> Top10Activity.newInstance(requireContext())

@ -14,14 +14,14 @@ import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.CustomFieldCriteria
import net.pokeranalytics.android.model.realm.CustomField
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.modules.filter.FiltersActivity
import net.pokeranalytics.android.ui.activity.ReportCreationActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.modules.filter.FilterActivityRequestCode
import net.pokeranalytics.android.ui.modules.filter.FilterableType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
@ -60,16 +60,16 @@ class ReportCreationFragment : RealmFragment(), RowRepresentableDataSource, RowR
}
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
menu?.clear()
inflater?.inflate(R.menu.toolbar_report_creation, menu)
menu?.findItem(R.id.add)?.isVisible = false
menu.clear()
inflater.inflate(R.menu.toolbar_report_creation, menu)
menu.findItem(R.id.add)?.isVisible = false
reportCreationMenu = menu
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.add -> {
if (this.assistant.step == Assistant.Step.FILTER) {
FiltersActivity.newInstanceForResult(
@ -182,13 +182,17 @@ class ReportCreationFragment : RealmFragment(), RowRepresentableDataSource, RowR
}
}
override fun isSelected(row: RowRepresentable): Boolean {
override fun isSelected(
position: Int,
row: RowRepresentable,
tag: Int
): Boolean {
return this.assistant.isSelected(row)
}
// RowRepresentableDelegate
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
val newStep = this.assistant.onRowSelected(position - 1)
if (newStep) {

@ -22,7 +22,7 @@ import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.ReportSetup
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.activity.ReportCreationActivity
import net.pokeranalytics.android.ui.activity.components.ReportActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
@ -143,8 +143,8 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
return this.adapterRows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
super.onRowSelected(position, row, fromAction)
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
super.onRowSelected(position, row, tag)
when (row) {
is ReportRow -> {
@ -152,7 +152,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
launchComputation(row.criteria, reportName)
}
is ReportSetup -> {
launchReportWithOptions(row.options, row.name)
launchReportWithOptions(row.options(getRealm()), row.name)
}
}
}

@ -2,6 +2,7 @@ package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@ -28,12 +29,11 @@ import net.pokeranalytics.android.ui.extensions.openContactMail
import net.pokeranalytics.android.ui.extensions.openPlayStorePage
import net.pokeranalytics.android.ui.extensions.openUrl
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.modules.bankroll.BankrollActivity
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow
import net.pokeranalytics.android.util.FileUtils
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.*
import net.pokeranalytics.android.util.billing.AppGuard
import net.pokeranalytics.android.util.billing.IAPProducts
import net.pokeranalytics.android.util.csv.ProductCSVDescriptors
@ -112,7 +112,11 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep
return rowRepresentation
}
override fun stringForRow(row: RowRepresentable): String {
override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) {
SettingRow.SUBSCRIPTION -> AppGuard.subscriptionStatus(requireContext())
SettingRow.VERSION -> BuildConfig.VERSION_NAME + if (BuildConfig.DEBUG) " (${BuildConfig.VERSION_CODE}) DEBUG" else ""
@ -121,7 +125,14 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep
}
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun boolForRow(row: RowRepresentable): Boolean {
return when (row) {
SettingRow.STOP_NOTIFICATION -> Preferences.showStopNotifications(requireContext())
else -> false
}
}
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
when (row) {
SettingRow.BANKROLL_REPORT -> BankrollActivity.newInstance(requireContext())
SettingRow.TOP_10 -> Top10Activity.newInstance(requireContext())
@ -159,6 +170,18 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep
}
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
when (row) {
SettingRow.STOP_NOTIFICATION -> {
val show = value as Boolean
if (!show) {
StopNotificationManager.stopAllStopNotifications(requireContext())
}
Preferences.setShowStopNotifications(show, requireContext())
}
else -> {}
}
}
/**
* Init UI

@ -22,11 +22,11 @@ import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.modules.filter.FiltersActivity
import net.pokeranalytics.android.ui.fragment.components.FilterableFragment
import net.pokeranalytics.android.ui.fragment.report.ComposableTableReportFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.modules.filter.FilterActivityRequestCode
import net.pokeranalytics.android.ui.modules.filter.FilterableType
import timber.log.Timber
import java.util.*
import kotlin.coroutines.CoroutineContext

@ -102,14 +102,17 @@ class SubscriptionFragment : BaseFragment(), SkuDetailsResponseListener, Purchas
val ssb = SpannableStringBuilder(upgradeString)
val indexOfLastSpace = upgradeString.lastIndexOf(" ")
val end = upgradeString.chars().count().toInt()
val lightTypeFace = ResourcesCompat.getFont(requireContext(), R.font.roboto_light)
val boldTypeFace = ResourcesCompat.getFont(requireContext(), R.font.roboto_bold)
if (indexOfLastSpace >= 0) {
if (lightTypeFace != null && boldTypeFace != null) {
ssb.setSpan(TypefaceSpan(lightTypeFace), 0, indexOfLastSpace, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
ssb.setSpan(TypefaceSpan(boldTypeFace), indexOfLastSpace, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
val end = upgradeString.chars().count().toInt()
val lightTypeFace = ResourcesCompat.getFont(requireContext(), R.font.roboto_light)
val boldTypeFace = ResourcesCompat.getFont(requireContext(), R.font.roboto_bold)
if (lightTypeFace != null && boldTypeFace != null) {
ssb.setSpan(TypefaceSpan(lightTypeFace), 0, indexOfLastSpace, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
ssb.setSpan(TypefaceSpan(boldTypeFace), indexOfLastSpace, end, Spanned.SPAN_EXCLUSIVE_INCLUSIVE)
}
}
this.title.text = ssb
@ -156,7 +159,7 @@ class SubscriptionFragment : BaseFragment(), SkuDetailsResponseListener, Purchas
* A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
* sequence.
*/
private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
private inner class ScreenSlidePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private var fragments: HashMap<Int, WeakReference<ScreenSlidePageFragment>> = HashMap()

@ -17,8 +17,10 @@ abstract class BaseFragment : Fragment() {
enum class BundleKey(val value: String) {
STYLE("style"),
PRIMARY_KEY("primary_key"),
SECONDARY_KEY("secondary_key"),
DATA_TYPE("data_type"),
SHOW_MESSAGE("show_message")
SHOW_MESSAGE("show_message"),
ATTACHED("attached")
}
override fun onCreate(savedInstanceState: Bundle?) {

@ -15,7 +15,7 @@ import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
/**

@ -14,9 +14,9 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.interfaces.FilterHandler
import net.pokeranalytics.android.ui.interfaces.FilterHandler.Companion.INTENT_FILTER_UPDATE_FILTER_UI
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.modules.filter.FilterHandler
import net.pokeranalytics.android.ui.modules.filter.FilterHandler.Companion.INTENT_FILTER_UPDATE_FILTER_UI
import net.pokeranalytics.android.ui.modules.filter.FilterableType
import net.pokeranalytics.android.util.Preferences
@ -26,7 +26,8 @@ import net.pokeranalytics.android.util.Preferences
* - Listen for INTENT_FILTER_UPDATE_FILTER_UI
* -
*/
open class FilterableFragment : RealmFragment(), FilterHandler {
open class FilterableFragment : RealmFragment(),
FilterHandler {
override var currentFilterable: FilterableType = FilterableType.ALL
set(value) {
@ -69,7 +70,7 @@ open class FilterableFragment : RealmFragment(), FilterHandler {
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
view?.findViewById<Toolbar>(R.id.toolbar)?.let { toolbar ->
toolbar.menu.removeItem(R.id.menu_item_filter)
@ -79,8 +80,8 @@ open class FilterableFragment : RealmFragment(), FilterHandler {
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_item_filter -> {
manageFilters(this)
}

@ -46,8 +46,7 @@ class LoaderDialogFragment: DialogFragment() {
override fun onStart() {
super.onStart()
val window = dialog.window
window?.setBackgroundDrawableResource(android.R.color.transparent)
dialog?.window?.setBackgroundDrawableResource(android.R.color.transparent)
}
}

@ -56,9 +56,9 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() {
// values[0] = (data[0].defaultValue ?: "").toString()
// values[1] = (data[1].defaultValue ?: "").toString()
data[0].hint?.let { editText.hint = getString(it) }
data[0].hintResId?.let { editText.hint = getString(it) }
editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
data[1].hint?.let { editText2.hint = getString(it) }
data[1].hintResId?.let { editText2.hint = getString(it) }
editText2.inputType = data[1].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
if (this.viewModel.valueAsPlaceholder) {
@ -96,10 +96,9 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() {
}
}
editText2.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// this.delegate.onRowValueChanged(values, row)
dismiss()
true

@ -36,7 +36,7 @@ class BottomSheetEditTextFragment : BottomSheetFragment() {
LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_edit_text, view?.bottomSheetContainer, true)
data[0].hint?.let { editText.hint = getString(it) }
data[0].hintResId?.let { editText.hint = getString(it) }
editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText.addTextChangedListener {
this.viewModel.stringValue = it?.toString()
@ -52,7 +52,7 @@ class BottomSheetEditTextFragment : BottomSheetFragment() {
editText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// delegate.onRowValueChanged(getValue(), row)
dismiss()
true

@ -26,15 +26,15 @@ class BottomSheetEditTextMultiLinesFragment : BottomSheetFragment() {
* Init UI
*/
private fun initUI() {
val data = getDescriptors()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val data = getDescriptors() ?: throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
if (data.size != 1) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
}
LayoutInflater.from(requireContext()).inflate(net.pokeranalytics.android.R.layout.bottom_sheet_edit_text_multi_lines, view?.bottomSheetContainer, true)
data[0].hint?.let { editText.hint = getString(it) }
editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
data[0].hintResId?.let { editText.hint = getString(it) }
editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText.addTextChangedListener { this.viewModel.stringValue = it?.toString() }
data[0].defaultValue?.let {
editText.setText(it.toString())

@ -17,8 +17,9 @@ import kotlinx.android.synthetic.main.fragment_bottom_sheet.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
@ -29,13 +30,13 @@ import java.util.*
class BottomSheetConfig(var row: RowRepresentable,
var delegate: RowRepresentableDelegate,
var rowRepresentableEditDescriptors: ArrayList<RowRepresentableEditDescriptor>?,
var rowRepresentableEditDescriptors: List<RowRepresentableEditDescriptor>?,
var isClearable: Boolean? = true,
var currentCurrency: Currency? = null,
var isDeletable: Boolean? = false,
var valueHasPlaceholder: Boolean? = null) {
}
var valueHasPlaceholder: Boolean? = null,
var alternativeLabels: Boolean = false
)
open class BottomSheetFragment : BottomSheetDialogFragment() {
@ -46,6 +47,10 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
ViewModelProviders.of(this).get(BottomSheetViewModel::class.java)
}
private var delegate: RowRepresentableDelegate? = null
protected lateinit var dataAdapter: RowRepresentableAdapter
companion object {
private var config: BottomSheetConfig? = null
@ -53,18 +58,19 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
const val REQUEST_CODE_ADD_NEW_OBJECT = 100
fun create(
fragmentManager: FragmentManager?,
fragmentManager: FragmentManager,
row: RowRepresentable,
delegate: RowRepresentableDelegate,
rowRepresentableEditDescriptors: ArrayList<RowRepresentableEditDescriptor>?,
rowRepresentableEditDescriptors: List<RowRepresentableEditDescriptor>?,
isClearable: Boolean? = true,
currentCurrency: Currency? = null,
isDeletable: Boolean? = false,
valueHasPlaceholder: Boolean? = null
valueHasPlaceholder: Boolean? = null,
alternativeLabels: Boolean = false
): BottomSheetFragment {
val bottomSheetFragment = newInstance(row.bottomSheetType)
bottomSheetFragment.show(fragmentManager, "bottomSheet")
this.config = BottomSheetConfig(row, delegate, rowRepresentableEditDescriptors, isClearable, currentCurrency, isDeletable, valueHasPlaceholder)
this.config = BottomSheetConfig(row, delegate, rowRepresentableEditDescriptors, isClearable, currentCurrency, isDeletable, valueHasPlaceholder, alternativeLabels)
return bottomSheetFragment
}
@ -108,12 +114,14 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
private fun configure(configuration: BottomSheetConfig) {
this.viewModel.row = configuration.row
this.viewModel.delegate = configuration.delegate
this.viewModel.rowRepresentableEditDescriptors = configuration.rowRepresentableEditDescriptors
this.viewModel.isClearable = configuration.isClearable ?: true
this.viewModel.currentCurrency = configuration.currentCurrency
this.viewModel.isDeletable = configuration.isDeletable ?: false
this.viewModel.valueAsPlaceholder = configuration.valueHasPlaceholder ?: false
this.viewModel.alternativeLabels = configuration.alternativeLabels
this.delegate = configuration.delegate
}
@ -128,16 +136,16 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
val pokerAnalyticsActivity = activity as BaseActivity
val liveDataType = LiveData.values()[dataType]
this.viewModel.addedData = liveDataType.getData(pokerAnalyticsActivity.getRealm(), primaryKey)
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// this.delegate.onRowValueChanged(proxyItem, this.row)
dismiss()
}
}
@SuppressLint("RestrictedApi")
override fun setupDialog(dialog: Dialog?, style: Int) {
override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style)
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
}
/**
@ -157,14 +165,14 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
// Menu
bottomSheetToolbar.menu.findItem(R.id.actionClear).setOnMenuItemClickListener {
this.viewModel.onClear()
this.onClear()
// delegate.onRowValueChanged(null, row)
dismiss()
true
}
bottomSheetToolbar.menu.findItem(R.id.actionDelete).setOnMenuItemClickListener {
this.viewModel.onRowDeleted()
this.onRowDeleted()
// delegate.onRowDeleted(row)
dismiss()
true
@ -190,7 +198,7 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
true
}
bottomSheetToolbar.menu.findItem(R.id.actionCheck).setOnMenuItemClickListener {
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// this.delegate.onRowValueChanged(getValue(), row)
dismiss()
true
@ -204,7 +212,7 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
/**
* Return the data list
*/
fun getDescriptors(): ArrayList<RowRepresentableEditDescriptor>? {
fun getDescriptors(): List<RowRepresentableEditDescriptor>? {
return this.viewModel.rowRepresentableEditDescriptors
}
@ -212,4 +220,44 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
return this.viewModel.getValue()
}
private fun onClear() {
this.delegate?.onRowValueChanged(null, this.viewModel.row)
}
fun onRowValueChanged() {
val row = this.viewModel.row
// if some data has been added
this.viewModel.addedData?.let {
this.delegate?.onRowValueChanged(it, row)
return
}
val value = this.viewModel.changedValue()
this.delegate?.onRowValueChanged(value, row)
}
private fun onRowDeleted() {
this.delegate?.onRowDeleted(this.viewModel.row)
}
fun onRowSelected(row: RowRepresentable) {
this.viewModel.rowSelected(row)
this.refreshRow(row)
}
fun onRowSelected(position: Int) {
val value = this.viewModel.rowSelected(position)
this.delegate?.onRowValueChanged(value, this.viewModel.row)
}
fun notifyDataSetChanged() {
this.dataAdapter.notifyDataSetChanged()
}
fun refreshRow(row: RowRepresentable) {
this.dataAdapter.refreshRow(row)
}
}

@ -28,7 +28,7 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable
override fun onResume() {
super.onResume()
this.viewModel.notifyDataSetChanged()
this.notifyDataSetChanged()
}
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
@ -49,9 +49,9 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable
return RowViewType.BOTTOM_SHEET_DATA.ordinal
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.viewModel.onRowSelected(position)
this.onRowSelected(position)
dismiss()
// this.viewModel.realmData?.let {
// val selectedData = it[position]
@ -61,7 +61,7 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable
// dismiss()
// }
// }
super.onRowSelected(position, row, fromAction)
// super.onRowSelected(position, row, tag)
}
/**
@ -86,9 +86,9 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable
val viewManager = LinearLayoutManager(requireContext())
val dataAdapter = RowRepresentableAdapter(this, this)
this.viewModel.dataAdapter = dataAdapter
this.dataAdapter = dataAdapter
reyclerView.apply {
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = dataAdapter

@ -25,12 +25,12 @@ class BottomSheetListGameFragment : BottomSheetListFragment() {
initUI()
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.viewModel.realmData?.let {
val selectedData = it[position]
selectedData?.let { data ->
this.viewModel.someValues[1] = data
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// this.delegate.onRowValueChanged(values, this.row)
dismiss()
}
@ -69,7 +69,7 @@ class BottomSheetListGameFragment : BottomSheetListFragment() {
val viewManager2 = LinearLayoutManager(requireContext())
val dataAdapter = RowRepresentableAdapter(this, this)
this.viewModel.dataAdapter = dataAdapter
this.dataAdapter = dataAdapter
recyclerView2.apply {
setHasFixedSize(true)

@ -4,7 +4,7 @@ import android.app.Activity
import android.content.Intent
import io.realm.RealmModel
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
@ -26,16 +26,20 @@ open class BottomSheetMultiSelectionFragment : BottomSheetListFragment() {
val liveDataType = LiveData.values()[dataType]
val proxyItem: RealmModel? = liveDataType.getData(pokerAnalyticsActivity.getRealm(), primaryKey)
this.viewModel.selectedRows.add(proxyItem as RowRepresentable)
this.viewModel.refreshRow(proxyItem as RowRepresentable)
this.refreshRow(proxyItem as RowRepresentable)
// dataAdapter.refreshRow(proxyItem as RowRepresentable)
}
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
this.viewModel.onRowSelected(row)
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.onRowSelected(row)
}
override fun isSelected(row: RowRepresentable): Boolean {
override fun isSelected(
position: Int,
row: RowRepresentable,
tag: Int
): Boolean {
return this.viewModel.isSelected(row)
// return selectedRows.contains(row)
}

@ -36,7 +36,7 @@ class BottomSheetNumericTextFragment : BottomSheetFragment() {
LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_edit_text, view?.bottomSheetContainer, true)
data[0].hint?.let { editText.hint = getString(it) }
data[0].hintResId?.let { editText.hint = getString(it) }
editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText.addTextChangedListener {
@ -68,7 +68,7 @@ class BottomSheetNumericTextFragment : BottomSheetFragment() {
editText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// delegate.onRowValueChanged(getValue(), row)
dismiss()
true

@ -25,7 +25,7 @@ class BottomSheetStaticListFragment : BottomSheetFragment(), StaticRowRepresenta
override fun onResume() {
super.onResume()
this.viewModel.notifyDataSetChanged()
this.notifyDataSetChanged()
// dataAdapter.notifyDataSetChanged()
}
@ -33,12 +33,12 @@ class BottomSheetStaticListFragment : BottomSheetFragment(), StaticRowRepresenta
return this.viewModel.staticRows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.viewModel.selectedRows.add(row)
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// this.delegate.onRowValueChanged(row, this.row)
dismiss()
super.onRowSelected(position, row, fromAction)
// super.onRowSelected(position, row, tag)
}
/**
@ -49,9 +49,9 @@ class BottomSheetStaticListFragment : BottomSheetFragment(), StaticRowRepresenta
val viewManager = LinearLayoutManager(requireContext())
val dataAdapter = RowRepresentableAdapter(this, this)
this.viewModel.dataAdapter = dataAdapter
this.dataAdapter = dataAdapter
reyclerView.apply {
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = dataAdapter

@ -65,7 +65,7 @@ class BottomSheetSumFragment : BottomSheetFragment() {
button1.setOnClickListener {
// val newValue = this.viewModel.currentDefaultValue + defaultValue1
this.viewModel.doubleValue = this.viewModel.currentDefaultValue + defaultValue1
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// this.delegate.onRowValueChanged(currentDefaultValue + defaultValue1, row)
dismiss()
}
@ -82,13 +82,13 @@ class BottomSheetSumFragment : BottomSheetFragment() {
button2.visibility = if (defaultValue2 > 0) View.VISIBLE else View.GONE
button2.setOnClickListener {
this.viewModel.doubleValue = this.viewModel.currentDefaultValue + defaultValue2
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// this.delegate.onRowValueChanged(currentDefaultValue + defaultValue2, row)
dismiss()
}
// First edit text
data[3].hint?.let { editText.hint = getString(it) }
data[3].hintResId?.let { editText.hint = getString(it) }
editText.inputType = data[3].inputType ?: InputType.TYPE_CLASS_TEXT
editText.addTextChangedListener {
val valueToAdd = try {
@ -101,7 +101,7 @@ class BottomSheetSumFragment : BottomSheetFragment() {
}
// Second edit text
data[4].hint?.let { editText2.hint = getString(it) }
data[4].hintResId?.let { editText2.hint = getString(it) }
editText2.inputType = data[4].inputType ?: InputType.TYPE_CLASS_TEXT
editText2.addTextChangedListener {
this.viewModel.doubleValue = try {
@ -113,7 +113,7 @@ class BottomSheetSumFragment : BottomSheetFragment() {
editText2.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
this.viewModel.onRowValueChanged()
this.onRowValueChanged()
// this.delegate.onRowValueChanged(value, row)
dismiss()
true

@ -6,6 +6,7 @@ import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import kotlinx.android.synthetic.main.bottom_sheet_grid.*
import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
@ -25,7 +26,7 @@ class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowReprese
override fun onResume() {
super.onResume()
this.viewModel.notifyDataSetChanged()
this.notifyDataSetChanged()
// dataAdapter.notifyDataSetChanged()
}
@ -34,17 +35,17 @@ class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowReprese
*/
private fun initUI() {
LayoutInflater.from(requireContext())
.inflate(net.pokeranalytics.android.R.layout.bottom_sheet_grid, view?.bottomSheetContainer, true)
.inflate(R.layout.bottom_sheet_grid, view?.bottomSheetContainer, true)
val viewManager = GridLayoutManager(requireContext(), 3)
val dataAdapter = RowRepresentableAdapter(this, this)
this.viewModel.dataAdapter = dataAdapter
this.dataAdapter = dataAdapter
val spanCount = 3
val spacing = 2.px
val includeEdge = false
reyclerView.apply {
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = dataAdapter
@ -53,20 +54,25 @@ class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowReprese
}
override fun adapterRows(): List<RowRepresentable>? {
return TableSize.all
return TableSize.all(this.viewModel.alternativeLabels)
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.viewModel.defaultSize = (row as TableSize).numberOfPlayer
this.viewModel.onRowValueChanged()
// this.delegate.onRowValueChanged((row as TableSize).numberOfPlayer, this.row)
this.onRowValueChanged()
dismiss()
}
override fun stringForRow(row: RowRepresentable): String {
this.context?.let {
return row.localizedTitle(it)
}
return "UNKNOWN CONTEXT FOR ROW $row"
}
// override fun charSequenceForRow(row: RowRepresentable, context: Context, tag: Int): String {
//
// if (this.viewModel.alternativeLabels) {
// return (row as TableSize).numberOfPlayer.toString()
// }
//
// this.context?.let {
// return row.localizedTitle(it)
// }
// return "UNKNOWN CONTEXT FOR ROW $row"
// }
}

@ -13,7 +13,7 @@ import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.CustomFieldCriteria
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.ReportSetup
import net.pokeranalytics.android.ui.fragment.data.DataManagerFragment
import net.pokeranalytics.android.ui.modules.data.DataManagerFragment
import net.pokeranalytics.android.ui.viewmodel.ReportViewModel
import net.pokeranalytics.android.ui.viewmodel.ViewModelHolder
import net.pokeranalytics.android.util.extensions.findById
@ -37,10 +37,10 @@ abstract class AbstractReportFragment : DataManagerFragment() {
private fun initData() {
this.viewModel.dataType = LiveData.REPORT_SETUP.ordinal
this.viewModel.primaryKey = this.selectedReport.options.reportSetupId
this.model.dataType = LiveData.REPORT_SETUP.ordinal
this.model.primaryKey = this.selectedReport.options.reportSetupId
this.viewModel.loadItemWithRealm(getRealm())
this.model.loadItemWithRealm(getRealm())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -48,7 +48,7 @@ abstract class AbstractReportFragment : DataManagerFragment() {
initData()
this.deleteButtonShouldAppear = (this.viewModel.primaryKey != null)
this.deleteButtonShouldAppear = (this.model.primaryKey != null)
this.saveButtonShouldAppear = this.selectedReport.options.userGenerated
setDisplayHomeAsUpEnabled(true)
@ -71,7 +71,7 @@ abstract class AbstractReportFragment : DataManagerFragment() {
view.findViewById<EditText>(net.pokeranalytics.android.R.id.reportName)
nameEditText.inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
this.viewModel.primaryKey?.let { id ->
this.model.primaryKey?.let { id ->
getRealm().findById<ReportSetup>(id)?.let { reportSetup ->
nameEditText.hint = reportSetup.name
}
@ -110,10 +110,10 @@ abstract class AbstractReportFragment : DataManagerFragment() {
private fun saveReport(name: String) {
this.reportViewModel.title = name
val rs = this.viewModel.item as ReportSetup
val rs = this.model.item as ReportSetup
getRealm().executeTransaction { realm ->
val firstSave = (this.viewModel.primaryKey == null)
val firstSave = (this.model.primaryKey == null)
if (firstSave) {
val options = this.selectedReport.options
rs.name = name
@ -139,7 +139,7 @@ abstract class AbstractReportFragment : DataManagerFragment() {
}
this.viewModel.primaryKey = rs.id
this.model.primaryKey = rs.id
this.deleteButtonShouldAppear = true
setToolbarTitle(this.reportViewModel.title)
}

@ -88,7 +88,7 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab
if (row is StatRow) {
context?.let { _ ->
row.computedStat?.let {
dc.textFormat = it.format()
dc.textFormat = it.textFormat
}
}
}
@ -98,7 +98,7 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab
override fun statFormatForRow(row: RowRepresentable): TextFormat {
if (row is StatRow) {
context?.let { _ ->
row.computedStat?.let { return it.format() }
row.computedStat?.let { return it.textFormat }
}
}
return TextFormat(NULL_TEXT)
@ -168,7 +168,7 @@ open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentab
// RowRepresentableDelegate
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
if (this.hasComputationInProgress) {
return

@ -70,21 +70,21 @@ class ProgressReportFragment : AbstractReportFragment() {
val fragmentManager = parentActivity?.supportFragmentManager
val fragmentTransaction = fragmentManager?.beginTransaction()
graphFragment = GraphFragment.newInstance(GraphFragment.Style.LINE)
fragmentTransaction?.add(R.id.graphContainer, graphFragment)
this.graphFragment = GraphFragment.newInstance(GraphFragment.Style.LINE)
fragmentTransaction?.add(R.id.graphContainer, this.graphFragment)
fragmentTransaction?.commit()
this.stat.aggregationTypes.firstOrNull()?.let {
reports[it] = this.selectedReport
this.reports[it] = this.selectedReport
}
this.stat.aggregationTypes.firstOrNull()?.let { aggregationType ->
reports[aggregationType]?.let { report ->
this.reports[aggregationType]?.let { report ->
setGraphData(report, aggregationType)
}
}
val aggregationTypes = stat.aggregationTypes
val aggregationTypes = this.stat.aggregationTypes
aggregationTypes.forEachIndexed { index, type ->
val chip = Chip(requireContext())

@ -22,7 +22,7 @@ interface GraphUnderlyingEntry {
): LegendContent {
val leftName = stat.localizedTitle(context)
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
val totalStatValue = stat.textFormat(entry.y.toDouble(), currency = null)
return if (stat.legendHideRightValue) {
DefaultLegendValues(this.entryTitle(context), totalStatValue, leftName = leftName)

@ -6,12 +6,12 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
/**
* Swipe to delete callback
*/
class SwipeToDeleteCallback(var adapter: RowRepresentableAdapter, var onDelete: ((position: Int) -> Unit)? = null) :
class SwipeToDeleteCallback(private var onDelete: ((position: Int) -> Unit)? = null) :
ItemTouchHelper.SimpleCallback(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.START or ItemTouchHelper.END) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
@ -27,8 +27,8 @@ class SwipeToDeleteCallback(var adapter: RowRepresentableAdapter, var onDelete:
c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean
) {
val foregroundView = viewHolder.itemView.findViewById<View?>(net.pokeranalytics.android.R.id.foreground)
val backgroundView = viewHolder.itemView.findViewById<View?>(net.pokeranalytics.android.R.id.background)
val foregroundView = viewHolder.itemView.findViewById<View?>(R.id.foreground)
val backgroundView = viewHolder.itemView.findViewById<View?>(R.id.background)
foregroundView?.let {
getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive)
backgroundView?.findViewById<View?>(R.id.leftIcon)?.isVisible = dX > 0
@ -36,9 +36,8 @@ class SwipeToDeleteCallback(var adapter: RowRepresentableAdapter, var onDelete:
}
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
val foregroundView = viewHolder.itemView.findViewById<View?>(net.pokeranalytics.android.R.id.foreground)
val foregroundView = viewHolder.itemView.findViewById<View?>(R.id.foreground)
foregroundView?.let {
getDefaultUIUtil().clearView(foregroundView)
}
@ -46,7 +45,7 @@ class SwipeToDeleteCallback(var adapter: RowRepresentableAdapter, var onDelete:
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
viewHolder?.let {
val foregroundView = viewHolder.itemView.findViewById<View?>(net.pokeranalytics.android.R.id.foreground)
val foregroundView = viewHolder.itemView.findViewById<View?>(R.id.foreground)
foregroundView?.let {
getDefaultUIUtil().onSelected(foregroundView)
}
@ -58,11 +57,29 @@ class SwipeToDeleteCallback(var adapter: RowRepresentableAdapter, var onDelete:
actionState: Int, isCurrentlyActive: Boolean
) {
viewHolder?.let {
val foregroundView = viewHolder.itemView.findViewById<View?>(net.pokeranalytics.android.R.id.foreground)
val foregroundView = viewHolder.itemView.findViewById<View?>(R.id.foreground)
foregroundView?.let {
getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive)
}
}
}
// override fun getMovementFlags(
// recyclerView: RecyclerView,
// viewHolder: RecyclerView.ViewHolder
// ): Int {
// // Set movement flags based on the layout manager
// // Set movement flags based on the layout manager
// return if (viewHolder is HandHistoryAdapter.RowPlayerSetupHolder) {
// val dragFlags =
// ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
// val swipeFlags = 0
// ItemTouchHelper.Callback.makeMovementFlags(dragFlags, swipeFlags)
// } else {
// val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
// val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
// ItemTouchHelper.Callback.makeMovementFlags(dragFlags, swipeFlags)
// }
// }
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.activity
package net.pokeranalytics.android.ui.modules.bankroll
import android.content.Context
import android.content.Intent

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.activity
package net.pokeranalytics.android.ui.modules.bankroll
import android.content.Intent
import android.os.Bundle
@ -6,7 +6,6 @@ import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.bankroll.BankrollReport
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.fragment.BankrollDetailsFragment
class BankrollDetailsActivity : BaseActivity() {
@ -18,7 +17,7 @@ class BankrollDetailsActivity : BaseActivity() {
* Default constructor
*/
fun newInstanceForResult(fragment: Fragment, bankrollReport: BankrollReport, requestCode: Int) {
this.bankrollReport = bankrollReport
Companion.bankrollReport = bankrollReport
val intent = Intent(fragment.requireContext(), BankrollDetailsActivity::class.java)
fragment.startActivityForResult(intent, requestCode)
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment
package net.pokeranalytics.android.ui.modules.bankroll
import android.app.Activity.RESULT_OK
import android.content.Intent
@ -14,8 +14,8 @@ import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
@ -32,7 +32,8 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
companion object {
fun newInstance(bankrollReport: BankrollReport): BankrollDetailsFragment {
val fragment = BankrollDetailsFragment()
val fragment =
BankrollDetailsFragment()
fragment.bankrollId = bankrollReport.setup.bankrollId
return fragment
}
@ -72,9 +73,9 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
}
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
menu?.clear()
inflater?.inflate(R.menu.toolbar_comparison_chart, menu) // TODO R.menu.toolbar_comparison_chart?
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.clear()
inflater.inflate(R.menu.toolbar_comparison_chart, menu) // TODO R.menu.toolbar_comparison_chart?
this.bankrollDetailsMenu = menu
updateMenuUI()
super.onCreateOptionsMenu(menu, inflater)
@ -134,21 +135,21 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
CustomizableRowRepresentable(
RowViewType.TITLE_VALUE,
resId = R.string.bankroll,
computedStat = totalComputedStat
valueTextFormat = totalComputedStat.textFormat
)
)
rows.add(
CustomizableRowRepresentable(
RowViewType.TITLE_VALUE,
resId = R.string.net_result,
computedStat = netComputedStat
valueTextFormat = netComputedStat.textFormat
)
)
rows.add(
CustomizableRowRepresentable(
RowViewType.TITLE_VALUE,
resId = R.string.net_banked,
computedStat = netBankedComputedStat
valueTextFormat = netBankedComputedStat.textFormat
)
)
@ -162,7 +163,7 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
CustomizableRowRepresentable(
RowViewType.TITLE_VALUE,
title = typeName,
computedStat = computedStat
valueTextFormat = computedStat.textFormat
)
)
}
@ -193,12 +194,12 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
// RowRepresentableDelegate
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.settings -> editBankroll()
}
return true

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment
package net.pokeranalytics.android.ui.modules.bankroll
import android.app.Activity
import android.content.Intent
@ -19,9 +19,8 @@ import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.activity.GraphActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -51,7 +50,8 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour
* Create new instance
*/
fun newInstance(): BankrollFragment {
val fragment = BankrollFragment()
val fragment =
BankrollFragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
@ -175,7 +175,7 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour
return rows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
when (row) {
is BankrollGraphRow -> {
val lineDataSet = row.dataSet as LineDataSet

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.activity
package net.pokeranalytics.android.ui.modules.calendar
import android.content.Context
import android.content.Intent
@ -8,7 +8,6 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.viewmodel.CalendarDetailsViewModel
class CalendarDetailsActivity : BaseActivity() {
@ -27,9 +26,9 @@ class CalendarDetailsActivity : BaseActivity() {
* Default constructor
*/
fun newInstance(context: Context, computedResults: ComputedResults, sessionTypeCondition: QueryCondition?, title: String?) {
this.computedResults = computedResults
this.sessionTypeCondition = sessionTypeCondition
this.detailsTitle = title
Companion.computedResults = computedResults
Companion.sessionTypeCondition = sessionTypeCondition
detailsTitle = title
val intent = Intent(context, CalendarDetailsActivity::class.java)
context.startActivity(intent)
}
@ -47,9 +46,12 @@ class CalendarDetailsActivity : BaseActivity() {
*/
private fun initData() {
this.viewModel.computedResults = computedResults
this.viewModel.sessionTypeCondition = sessionTypeCondition
this.viewModel.detailsTitle = detailsTitle
this.viewModel.computedResults =
computedResults
this.viewModel.sessionTypeCondition =
sessionTypeCondition
this.viewModel.detailsTitle =
detailsTitle
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment
package net.pokeranalytics.android.ui.modules.calendar
import android.content.Context
import android.content.Intent
@ -26,13 +26,13 @@ import net.pokeranalytics.android.ui.activity.GraphActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.fragment.components.BaseFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow
import net.pokeranalytics.android.ui.view.rowrepresentable.StatDoubleRow
import net.pokeranalytics.android.ui.viewmodel.CalendarDetailsViewModel
import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList
@ -134,7 +134,7 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
return rowRepresentables
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
when (row) {
is GraphRow -> {
val report = row.report
@ -146,10 +146,12 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
val dataSet = row.dataSet
when (dataSet) {
is LineDataSet-> {
GraphActivity.newInstance(requireContext(), listOf(dataSet), null, GraphFragment.Style.LINE, title)
GraphActivity.newInstance(requireContext(), listOf(dataSet), null,
GraphFragment.Style.LINE, title)
}
is BarDataSet -> {
GraphActivity.newInstance(requireContext(), null, listOf(dataSet), GraphFragment.Style.BAR, title)
GraphActivity.newInstance(requireContext(), null, listOf(dataSet),
GraphFragment.Style.BAR, title)
}
}
}
@ -172,7 +174,6 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
val startDate = Date()
val realm = Realm.getDefaultInstance()
val query = Query().merge(computedResults.group.query)
query.remove(QueryCondition.IsCash)
@ -188,7 +189,9 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
stats = requiredStats,
query = query
)
val realm = Realm.getDefaultInstance()
val report = Calculator.computeStats(realm, options)
realm.close()
Timber.d("Report take: ${System.currentTimeMillis() - startDate.time}ms")

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.viewmodel
package net.pokeranalytics.android.ui.modules.calendar
import androidx.lifecycle.ViewModel
import net.pokeranalytics.android.calculus.ComputedResults

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment
package net.pokeranalytics.android.ui.modules.calendar
import android.os.Bundle
import android.view.LayoutInflater
@ -22,7 +22,6 @@ import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.ui.activity.CalendarDetailsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -52,7 +51,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
* Create new instance
*/
fun newInstance(): CalendarFragment {
val fragment = CalendarFragment()
val fragment =
CalendarFragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
@ -71,7 +71,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
private var datesForRows: HashMap<CustomizableRowRepresentable, Date> = HashMap()
private var sessionTypeCondition: QueryCondition? = null
private var currentTimeFilter: TimeFilter = TimeFilter.MONTH
private var currentTimeFilter: TimeFilter =
TimeFilter.MONTH
private var currentStat = Stat.NET_RESULT
// Life Cycle
@ -96,7 +97,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
return rows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
when (currentTimeFilter) {
TimeFilter.MONTH -> {
@ -201,7 +202,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
// Manage time queryWith
filterTimeMonth.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
currentTimeFilter = TimeFilter.MONTH
currentTimeFilter =
TimeFilter.MONTH
filterTimeYear.isChecked = false
displayData()
} else if (currentTimeFilter == TimeFilter.MONTH) {
@ -211,7 +213,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
}
filterTimeYear.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
currentTimeFilter = TimeFilter.YEAR
currentTimeFilter =
TimeFilter.YEAR
filterTimeMonth.isChecked = false
displayData()
} else if (currentTimeFilter == TimeFilter.YEAR) {
@ -374,7 +377,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
val row = CustomizableRowRepresentable(
customViewType = RowViewType.TITLE_VALUE_ARROW,
title = date.getDateMonth(),
computedStat = computedStat,
valueTextFormat = computedStat.textFormat,
isSelectable = true
)
@ -391,7 +394,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
val row = CustomizableRowRepresentable(
customViewType = RowViewType.TITLE_VALUE_ARROW,
title = date.getDateYear(),
computedStat = computedStat,
valueTextFormat = computedStat.textFormat,
isSelectable = true
)

@ -1,13 +1,13 @@
package net.pokeranalytics.android.ui.fragment.data
package net.pokeranalytics.android.ui.modules.data
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import net.pokeranalytics.android.R
import net.pokeranalytics.android.api.CurrencyConverterApi
import net.pokeranalytics.android.api.FreeConverterApi
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.retrofit.CurrencyConverterValue
import net.pokeranalytics.android.ui.activity.CurrenciesActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
@ -23,12 +23,9 @@ import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.toCurrency
import net.pokeranalytics.android.util.extensions.toRate
import retrofit2.Call
import retrofit2.Response
import java.util.*
/**
* Custom EditableDataFragment to manage the Bankroll data
*/
@ -37,7 +34,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
// Return the item as a Bankroll object
private val bankroll: Bankroll
get() {
return this.viewModel.item as Bankroll
return this.model.item as Bankroll
}
private lateinit var defaultCurrency: Currency
@ -83,7 +80,11 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
return rows
}
override fun stringForRow(row: RowRepresentable): String {
override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> if (bankroll.name.isNotEmpty()) bankroll.name else NULL_TEXT
BankrollRow.CURRENCY -> {
@ -104,7 +105,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
val rate = this.bankroll.currency?.rate ?: 1.0
rate.toRate()
}
else -> super.stringForRow(row)
else -> super.charSequenceForRow(row, context, 0)
}
}
@ -116,7 +117,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.bankroll.name))
BankrollRow.INITIAL_VALUE -> {
@ -130,13 +131,13 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
}
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
when (row) {
BankrollRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@BankrollDataFragment,
RequestCode.CURRENCY.value
)
BankrollRow.REFRESH_RATE -> refreshRate()
else -> super.onRowSelected(position, row, fromAction)
else -> super.onRowSelected(position, row, tag)
}
}
@ -210,31 +211,44 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
if (System.currentTimeMillis() - lastRefreshRateCall < 10 * 1000 || isRefreshingRate) {
return
}
lastRefreshRateCall = System.currentTimeMillis()
this.lastRefreshRateCall = System.currentTimeMillis()
val currenciesConverterValue = "${bankroll.currency?.code}_${defaultCurrency.currencyCode}"
val call = CurrencyConverterApi.getApi(requireContext())?.convert(currenciesConverterValue)
call?.enqueue(object : retrofit2.Callback<Map<String, CurrencyConverterValue>> {
override fun onResponse(call: Call<Map<String, CurrencyConverterValue>>, response: Response<Map<String, CurrencyConverterValue>>) {
response.body()?.let {
it[currenciesConverterValue]?.value?.let { rate ->
onRowValueChanged(rate, BankrollRow.RATE)
}
}
isRefreshingRate = false
rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE)
}
override fun onFailure(call: Call<Map<String, CurrencyConverterValue>>, t: Throwable) {
isRefreshingRate = false
rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE)
}
})
FreeConverterApi.currencyRate(currenciesConverterValue, requireContext()) { rate ->
onRowValueChanged(rate, BankrollRow.RATE)
isRefreshingRate = false
rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE)
}
isRefreshingRate = true
rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE)
// val call = CurrencyConverterApi.getApi(requireContext())?.convert(currenciesConverterValue)
// call?.enqueue(object : retrofit2.Callback<Map<String, CurrencyConverterValue>> {
//
// override fun onResponse(call: Call<Map<String, CurrencyConverterValue>>, response: Response<Map<String, CurrencyConverterValue>>) {
// response.body()?.let {
// it[currenciesConverterValue]?.value?.let { rate ->
// Timber.d("rate found = $rate")
// onRowValueChanged(rate, BankrollRow.RATE)
// } ?: run {
// Timber.d("no rate for $currenciesConverterValue")
// }
// } ?: run {
// Timber.d("onResponse> no body in ${response}")
// }
//
// isRefreshingRate = false
// rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE)
// }
//
// override fun onFailure(call: Call<Map<String, CurrencyConverterValue>>, t: Throwable) {
// Timber.d("api call failed: ${t.message}")
// isRefreshingRate = false
// rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE)
// }
// })
this.isRefreshingRate = true
this.rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE)
}
}

@ -1,5 +1,6 @@
package net.pokeranalytics.android.ui.fragment.data
package net.pokeranalytics.android.ui.modules.data
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -33,7 +34,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
// Return the item as a Custom TypedCSVField object
private val customField: CustomField
get() {
return this.viewModel.item as CustomField
return this.model.item as CustomField
}
private val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
@ -120,10 +121,14 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
return customField.adapterRows()
}
override fun stringForRow(row: RowRepresentable): String {
override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) {
SimpleRow.NAME -> if (customField.name.isNotEmpty()) customField.name else NULL_TEXT
else -> super.stringForRow(row)
else -> super.charSequenceForRow(row, context, 0)
}
}
@ -142,7 +147,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.customField.name))
is CustomFieldEntry -> row.editingDescriptors(mapOf("defaultValue" to row.value))
@ -150,13 +155,13 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
}
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
when (row) {
is CustomFieldEntry -> {
val data = customField.editDescriptors(row)
BottomSheetFragment.create(fragmentManager, row, this, data, isClearable = false, isDeletable = true)
BottomSheetFragment.create(requireFragmentManager(), row, this, data, isClearable = false, isDeletable = true)
}
else -> super.onRowSelected(position, row, fromAction)
else -> super.onRowSelected(position, row, tag)
}
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.data
package net.pokeranalytics.android.ui.modules.data
import android.app.Activity
import android.content.Intent
@ -13,14 +13,13 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.ConfigurationException
import net.pokeranalytics.android.model.interfaces.Savable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel
open class DataManagerFragment : RealmFragment() {
protected val viewModel: DataManagerViewModel by lazy {
protected val model: DataManagerViewModel by lazy {
ViewModelProviders.of(this).get(DataManagerViewModel::class.java)
}
@ -41,7 +40,6 @@ open class DataManagerFragment : RealmFragment() {
this.updateMenuUI()
}
private var editableMenu: Menu? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -49,24 +47,24 @@ open class DataManagerFragment : RealmFragment() {
loadItem()
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
menu?.clear()
inflater?.inflate(R.menu.toolbar_editable_data, menu)
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.clear()
inflater.inflate(R.menu.toolbar_editable_data, menu)
this.editableMenu = menu
updateMenuUI()
super.onCreateOptionsMenu(menu, inflater)
}
/**
* Update menu UI
*/
* Update menu UI
*/
private fun updateMenuUI() {
editableMenu?.findItem(R.id.delete)?.isVisible = this.deleteButtonShouldAppear
editableMenu?.findItem(R.id.save)?.isVisible = this.saveButtonShouldAppear
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save -> saveData()
R.id.delete -> deleteData()
}
@ -79,7 +77,7 @@ open class DataManagerFragment : RealmFragment() {
private fun loadItem() {
// this.item = this.liveDataType.updateOrCreate(this.getRealm(), primaryKey)
this.deleteButtonShouldAppear = this.viewModel.primaryKey != null
this.deleteButtonShouldAppear = this.model.primaryKey != null
}
@ -88,7 +86,7 @@ open class DataManagerFragment : RealmFragment() {
*/
protected open fun saveData() {
val savable = this.viewModel.item
val savable = this.model.item
this.willSaveData()
when (savable) {
@ -97,7 +95,7 @@ open class DataManagerFragment : RealmFragment() {
when (status) {
SaveValidityStatus.VALID -> {
this.getRealm().executeTransaction {
val managedItem = it.copyToRealmOrUpdate(this.viewModel.item)
val managedItem = it.copyToRealmOrUpdate(this.model.item)
if (managedItem is Savable) {
val uniqueIdentifier = managedItem.id
finishActivityWithResult(uniqueIdentifier)
@ -135,14 +133,14 @@ open class DataManagerFragment : RealmFragment() {
val realm = this.getRealm()
if (this.viewModel.item.isValidForDelete(realm)) {
if (this.model.item.isValidForDelete(realm)) {
val intent = Intent()
intent.putExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, this.viewModel.item.id)
intent.putExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, this.model.item.id)
activity?.setResult(Activity.RESULT_OK, intent)
activity?.finish()
} else {
val status = this.viewModel.item.getDeleteStatus(requireContext(), realm)
val message = this.viewModel.item.getFailedDeleteMessage(status)
val status = this.model.item.getDeleteStatus(requireContext(), realm)
val message = this.model.item.getFailedDeleteMessage(status)
val builder = AlertDialog.Builder(requireContext())
.setMessage(message)
.setNegativeButton(R.string.ok, null)
@ -157,7 +155,7 @@ open class DataManagerFragment : RealmFragment() {
*/
private fun finishActivityWithResult(uniqueIdentifier: String) {
val intent = Intent()
intent.putExtra(EditableDataActivity.IntentKey.DATA_TYPE.keyName, viewModel.dataType)
intent.putExtra(EditableDataActivity.IntentKey.DATA_TYPE.keyName, model.dataType)
intent.putExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName, uniqueIdentifier)
activity?.setResult(Activity.RESULT_OK, intent)
activity?.finish()

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.activity
package net.pokeranalytics.android.ui.modules.data
import android.content.Context
import android.content.Intent
@ -7,7 +7,6 @@ import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.components.MediaActivity
import net.pokeranalytics.android.ui.fragment.data.*
import java.io.File
import java.util.*
@ -22,9 +21,9 @@ class EditableDataActivity : MediaActivity() {
/**
* Default constructor
*/
fun newInstance(context: Context, dataType: Int, primaryKey: String? = null) {
fun newInstance(context: Context, dataType: LiveData, primaryKey: String? = null) {
val intent = Intent(context, EditableDataActivity::class.java)
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType)
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType.ordinal)
primaryKey?.let {
intent.putExtra(IntentKey.PRIMARY_KEY.keyName, it)
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save