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. 51
      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 { repositories {
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
maven { url 'https://jitpack.io' } // required for MPAndroidChart maven { url 'https://jitpack.io' } // required for MPAndroidChart
jcenter() // for kotlin serialization
} }
android { android {
@ -33,8 +34,8 @@ android {
applicationId "net.pokeranalytics.android" applicationId "net.pokeranalytics.android"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 28 targetSdkVersion 28
versionCode 84 versionCode 94
versionName "2.4.3" versionName "5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -97,23 +98,18 @@ dependencies {
// Kotlin // Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 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-core:1.3.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0") // JVM dependency
// Android // Android
implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0-alpha03' implementation 'androidx.core:core-ktx:1.3.0'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.work:work-runtime-ktx:2.3.4'
// 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'
// Places // Places
implementation 'com.google.android.libraries.places:places:1.1.0' implementation 'com.google.android.libraries.places:places:1.1.0'
@ -124,7 +120,7 @@ dependencies {
implementation 'com.android.billingclient:billing:1.2.2' implementation 'com.android.billingclient:billing:1.2.2'
// Firebase // Firebase
implementation 'com.google.firebase:firebase-core:17.0.0' implementation 'com.google.firebase:firebase-core:17.4.2'
// Crashlytics // Crashlytics
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' 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 // CSV Parser: https://mvnrepository.com/artifact/org.apache.commons/commons-csv
implementation 'org.apache.commons:commons-csv:1.7' implementation 'org.apache.commons:commons-csv:1.7'
// Polynomial Regression
implementation 'org.apache.commons:commons-math3:3.6.1'
// Instrumented Tests // Instrumented Tests
androidTestImplementation 'androidx.test:core:1.2.0' androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test:runner: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:runner:1.0.2'
testImplementation 'com.android.support.test:rules: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:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:largeHeap="true"
android:theme="@style/PokerAnalyticsTheme"> android:theme="@style/PokerAnalyticsTheme">
<meta-data <meta-data
@ -55,24 +56,30 @@
</activity> </activity>
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.SessionActivity" android:name="net.pokeranalytics.android.ui.modules.session.SessionActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing" /> android:windowSoftInputMode="adjustNothing" />
<!-- No screenOrientation="portrait" to fix Oreo crash --> <!-- No screenOrientation="portrait" to fix Oreo crash -->
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.NewDataMenuActivity" android:name="net.pokeranalytics.android.ui.modules.feed.NewDataMenuActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/PokerAnalyticsTheme.MenuDialog" /> android:theme="@style/PokerAnalyticsTheme.MenuDialog" />
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.BankrollActivity" android:name="net.pokeranalytics.android.ui.modules.bankroll.BankrollActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity <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:launchMode="singleTop"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
@ -102,7 +109,7 @@
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.CalendarDetailsActivity" android:name="net.pokeranalytics.android.ui.modules.calendar.CalendarDetailsActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
@ -112,17 +119,17 @@
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.DataListActivity" android:name="net.pokeranalytics.android.ui.modules.datalist.DataListActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.FiltersListActivity" android:name="net.pokeranalytics.android.ui.modules.filter.FiltersListActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.EditableDataActivity" android:name="net.pokeranalytics.android.ui.modules.data.EditableDataActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
@ -132,12 +139,7 @@
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.FiltersActivity" android:name="net.pokeranalytics.android.ui.modules.filter.FiltersActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FilterDetailsActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
@ -167,6 +169,8 @@
android:theme="@style/PokerAnalyticsTheme.AlertDialog" android:theme="@style/PokerAnalyticsTheme.AlertDialog"
android:launchMode="singleTop"/> android:launchMode="singleTop"/>
<service android:name="net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayExportService" android:exported="false"/>
<meta-data <meta-data
android:name="preloaded_fonts" android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" /> android:resource="@array/preloaded_fonts" />

@ -20,6 +20,7 @@ import net.pokeranalytics.android.util.PokerAnalyticsLogs
import net.pokeranalytics.android.util.UserDefaults import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.billing.AppGuard import net.pokeranalytics.android.util.billing.AppGuard
import timber.log.Timber import timber.log.Timber
import java.util.*
class PokerAnalyticsApplication : Application() { class PokerAnalyticsApplication : Application() {
@ -44,12 +45,13 @@ class PokerAnalyticsApplication : Application() {
Realm.init(this) Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder() val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME) .name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(8) .schemaVersion(9)
.migration(PokerAnalyticsMigration()) .migration(PokerAnalyticsMigration())
.initialData(Seed(this)) .initialData(Seed(this))
.build() .build()
Realm.setDefaultConfiguration(realmConfiguration) Realm.setDefaultConfiguration(realmConfiguration)
// val realm = Realm.getDefaultInstance() // val realm = Realm.getDefaultInstance()
// realm.executeTransaction { // realm.executeTransaction {
// realm.where(Session::class.java).findAll().deleteAllFromRealm() // realm.where(Session::class.java).findAll().deleteAllFromRealm()
@ -75,11 +77,16 @@ class PokerAnalyticsApplication : Application() {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}") Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}")
Timber.d("Realm path = ${Realm.getDefaultInstance().path}")
// this.createFakeSessions() // this.createFakeSessions()
} }
Patcher.patchAll(this.applicationContext) 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) { if (sessionsCount < 10) {
GlobalScope.launch { 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 java.util.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
/** /**
* The class performing statIds computation * The class performing statIds computation
@ -119,7 +121,7 @@ class Calculator {
val computeStandardDeviation: Boolean val computeStandardDeviation: Boolean
get() { get() {
this.stats.forEach { this.stats.forEach {
if (it == STANDARD_DEVIATION_BB_PER_100_HANDS || it == STANDARD_DEVIATION || it == STANDARD_DEVIATION_HOURLY) { if (it.isStandardDeviation) {
return true 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 = val shouldIterateOverComputables =
(options.progressValues == Options.ProgressValues.STANDARD || options.computeLongestStreak) (options.progressValues == Options.ProgressValues.STANDARD || options.computeLongestStreak)
@ -561,15 +568,19 @@ class Calculator {
// Session // Session
var stdSum = 0.0 var stdSum = 0.0
var stdBBSum = 0.0
var stdBBper100HandsSum = 0.0 var stdBBper100HandsSum = 0.0
computables.forEach { session -> computables.forEach { session ->
stdSum += Math.pow(session.ratedNet - average, 2.0) stdSum += (session.ratedNet - average).pow(2.0)
stdBBper100HandsSum += Math.pow(session.bbPer100Hands - bbPer100Hands, 2.0) stdBBSum += (session.bbNet - averageBB).pow(2.0)
stdBBper100HandsSum += (session.bbPer100Hands - bbPer100Hands).pow(2.0)
} }
val standardDeviation = Math.sqrt(stdSum / computables.size) val standardDeviation = sqrt(stdSum / computables.size)
val standardDeviationBBper100Hands = Math.sqrt(stdBBper100HandsSum / computables.size) val standardDeviationBB = sqrt(stdBBSum / computables.size)
val standardDeviationBBper100Hands = sqrt(stdBBper100HandsSum / computables.size)
results.addStat(STANDARD_DEVIATION, standardDeviation) results.addStat(STANDARD_DEVIATION, standardDeviation)
results.addStat(STANDARD_DEVIATION_BB, standardDeviationBB)
results.addStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands) results.addStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands)
// Session Set // Session Set
@ -578,9 +589,9 @@ class Calculator {
sessionSets.forEach { set -> sessionSets.forEach { set ->
val ssStats = SSStats(set, computableGroup.query) val ssStats = SSStats(set, computableGroup.query)
val sHourlyRate = ssStats.hourlyRate 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) 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 * Formats the value of the stat to be suitable for display
*/ */
fun format(): TextFormat { val textFormat: TextFormat
return this.stat.format(this.value, this.secondValue, this.currency) 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 { override fun formattedValue(stat: Stat): TextFormat {
this.computedStat(stat)?.let { this.computedStat(stat)?.let {
return it.format() return it.textFormat
} ?: run { } ?: run {
throw PAIllegalStateException("Missing stat in results") throw PAIllegalStateException("Missing stat in results")
} }
@ -460,12 +460,12 @@ class ComputedResults(group: ComputableGroup,
GraphFragment.Style.BAR -> { GraphFragment.Style.BAR -> {
return when (stat) { return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { 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) DefaultLegendValues(this.entryTitle(context), totalStatValue)
} }
else -> { else -> {
val entryValue = this.formattedValue(stat) 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) DefaultLegendValues(this.entryTitle(context), entryValue, countValue)
} }
} }
@ -474,12 +474,12 @@ class ComputedResults(group: ComputableGroup,
return when (stat) { return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> { 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) DefaultLegendValues(this.entryTitle(context), totalStatValue)
} }
else -> { else -> {
val entryValue = this.formattedValue(stat) 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) DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue)
} }
} }

@ -52,6 +52,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
BB_SESSION_COUNT(26), BB_SESSION_COUNT(26),
TOTAL_BUYIN(27), TOTAL_BUYIN(27),
RISK_OF_RUIN(28), RISK_OF_RUIN(28),
STANDARD_DEVIATION_BB(29),
; ;
companion object : IntSearchable<Stat> { 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 -> R.string.standard_deviation
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour 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_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 HANDS_PLAYED -> R.string.number_of_hands
LOCATIONS_PLAYED -> R.string.locations_played LOCATIONS_PLAYED -> R.string.locations_played
LONGEST_STREAKS -> R.string.longest_streaks 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 * 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()) { if (value.isNaN()) {
return TextFormat(NULL_TEXT, R.color.white) return TextFormat(NULL_TEXT, R.color.white)
@ -150,7 +152,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
// Red/green numericValues // Red/green numericValues
HOURLY_RATE_BB, AVERAGE_NET_BB, NET_BB_PER_100_HANDS, BB_NET_RESULT -> { 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 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 // white integers
NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED -> { 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 } // red/green percentages
WIN_RATIO, ROI -> { WIN_RATIO, ROI -> {
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red 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 -> { RISK_OF_RUIN -> {
val color = if (value * 100 <= this.threshold) R.color.green else R.color.red 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 // white amountsr
AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, 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)) return TextFormat(value.toCurrency(currency))
} }
LONGEST_STREAKS -> { LONGEST_STREAKS -> {
@ -200,6 +202,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
NUMBER_OF_GAMES -> R.string.number_of_records NUMBER_OF_GAMES -> R.string.number_of_records
NET_RESULT -> R.string.total NET_RESULT -> R.string.total
STANDARD_DEVIATION -> R.string.net_result 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_HOURLY -> R.string.hour_rate_without_pauses
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
WIN_RATIO, HOURLY_DURATION -> return this.localizedTitle(context) WIN_RATIO, HOURLY_DURATION -> return this.localizedTitle(context)
@ -235,12 +238,21 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
val hasProgressGraph: Boolean val hasProgressGraph: Boolean
get() { get() {
return when (this) { return when (this) {
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, HOURLY_DURATION, AVERAGE_HOURLY_DURATION, STANDARD_DEVIATION_BB,
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> false STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS -> false
else -> true 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 val legendHideRightValue: Boolean
get() { 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 package net.pokeranalytics.android.model
import android.content.Context import android.content.Context
import androidx.fragment.app.Fragment
import io.realm.Realm import io.realm.Realm
import io.realm.Sort
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.* 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.ui.view.Localizable
import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.findById
@ -22,7 +27,8 @@ enum class LiveData : Localizable {
FILTER, FILTER,
CUSTOM_FIELD, CUSTOM_FIELD,
REPORT_SETUP, REPORT_SETUP,
PLAYER; PLAYER,
HAND_HISTORY;
var subType:Int? = null var subType:Int? = null
@ -40,6 +46,7 @@ enum class LiveData : Localizable {
CUSTOM_FIELD -> CustomField::class.java CUSTOM_FIELD -> CustomField::class.java
REPORT_SETUP -> ReportSetup::class.java REPORT_SETUP -> ReportSetup::class.java
PLAYER -> Player::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 CUSTOM_FIELD -> R.string.custom_field
REPORT_SETUP -> R.string.custom REPORT_SETUP -> R.string.custom
PLAYER -> R.string.player PLAYER -> R.string.player
HAND_HISTORY -> R.string.hand_history
} }
} }
@ -98,6 +106,7 @@ enum class LiveData : Localizable {
CUSTOM_FIELD -> R.string.custom_fields CUSTOM_FIELD -> R.string.custom_fields
REPORT_SETUP -> R.string.custom REPORT_SETUP -> R.string.custom
PLAYER -> R.string.players 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 CUSTOM_FIELD -> R.string.new_custom_field
REPORT_SETUP -> R.string.new_report REPORT_SETUP -> R.string.new_report
PLAYER -> R.string.new_friend 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 * Return the new entity titleResId
@ -141,4 +157,33 @@ enum class LiveData : Localizable {
return context.getString(this.pluralResId, context) 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.ui.view.RowViewType
import net.pokeranalytics.android.util.Parser 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 { companion object {
val all: List<TableSize> fun all(alternativeLabels: Boolean): List<TableSize> {
get() { return Array(9, init = { index ->
return Array(9, init = TableSize(index + 2, alternativeLabels = alternativeLabels)
{ index -> TableSize(index + 2) }).toList() }).toList()
} }
fun valueForLabel(label: String) : Int? { 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 { override fun getDisplayName(context: Context): String {
if (this.alternativeLabels) {
return this.numberOfPlayer.toString()
}
return if (this.numberOfPlayer == 2) { return if (this.numberOfPlayer == 2) {
return "HU" return "HU"
} else { } else {
@ -49,6 +57,10 @@ class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITL
} }
override fun localizedTitle(context: Context): String { override fun localizedTitle(context: Context): String {
if (this.alternativeLabels) {
return this.numberOfPlayer.toString()
}
this.resId?.let { this.resId?.let {
return if (this.numberOfPlayer == 2) { return if (this.numberOfPlayer == 2) {
context.getString(it) context.getString(it)

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

@ -1,11 +1,17 @@
package net.pokeranalytics.android.model.extensions package net.pokeranalytics.android.model.extensions
import android.content.Context 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.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.TournamentType import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.NotificationSchedule
import net.pokeranalytics.android.util.extensions.toCurrency import net.pokeranalytics.android.util.extensions.toCurrency
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
enum class SessionState { enum class SessionState {
PENDING, PENDING,
@ -14,7 +20,7 @@ enum class SessionState {
PAUSED, PAUSED,
FINISHED; FINISHED;
var hasStarted: Boolean = false val hasStarted: Boolean
get() { get() {
return when (this) { return when (this) {
STARTED, PAUSED, FINISHED -> true 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 { fun Session.getFormattedGameType(context: Context): String {
var parameters = mutableListOf<String>() val parameters = mutableListOf<String>()
if (isTournament()) { if (isTournament()) {
tournamentEntryFee?.let { tournamentEntryFee?.let {
@ -84,6 +90,39 @@ fun Session.getFormattedGameType(context: Context): String {
return parameters.joinToString(separator = " ") 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 val AbstractList<Session>.hourlyDuration: Double
get() { get() {
val intervals = mutableListOf<TimeInterval>() 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) this.add(timeInterval)
} else { // update } else { // update

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

@ -96,16 +96,17 @@ sealed class QueryCondition : FilterElementRow {
val groupId: String val groupId: String
get() { get() {
when (this.operator) { return when (this.operator) {
Operator.MORE, Operator.LESS -> return "${this.operator.name.toLowerCase().capitalize()}$baseId" Operator.MORE, Operator.LESS -> return "${this.operator.name.toLowerCase().capitalize()}$baseId"
else -> this.baseId
} }
return baseId
} }
val id: List<String> val id: List<String>
get() { get() {
when (this.operator) { when (this.operator) {
Operator.MORE, Operator.LESS -> return listOf("$baseId+${this.operator.name}") Operator.MORE, Operator.LESS -> return listOf("$baseId+${this.operator.name}")
else -> {}
} }
return when (this) { return when (this) {
@ -306,6 +307,9 @@ sealed class QueryCondition : FilterElementRow {
abstract class TrueQueryCondition : QueryCondition() { abstract class TrueQueryCondition : QueryCondition() {
override var operator: Operator = Operator.TRUE override var operator: Operator = Operator.TRUE
} }
abstract class NotNullQueryCondition : QueryCondition() {
override var operator: Operator = Operator.NOTNULL
}
object IsLive : TrueQueryCondition() object IsLive : TrueQueryCondition()
@ -601,9 +605,9 @@ sealed class QueryCondition : FilterElementRow {
} }
} }
object DateNotNull : QueryCondition() { object DateNotNull : NotNullQueryCondition()
override var operator = Operator.NOTNULL object EndDateNotNull : NotNullQueryCondition()
} object BigBlindNotNull : NotNullQueryCondition()
class StartedFromTime() : TimeQuery() { class StartedFromTime() : TimeQuery() {
override var operator = Operator.MORE 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 { startDate?.let {
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
cal.time = it cal.time = it
dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) this.dayOfWeek = cal.get(Calendar.DAY_OF_WEEK)
dayOfMonth = cal.get(Calendar.DAY_OF_MONTH) this.dayOfMonth = cal.get(Calendar.DAY_OF_MONTH)
month = cal.get(Calendar.MONTH) this.month = cal.get(Calendar.MONTH)
year = cal.get(Calendar.YEAR) this.year = cal.get(Calendar.YEAR)
} }
} }
} }

@ -2,6 +2,7 @@ package net.pokeranalytics.android.model.migrations
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -171,6 +172,61 @@ class PokerAnalyticsMigration : RealmMigration {
currentVersion++ 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 { override fun equals(other: Any?): Boolean {

@ -6,19 +6,19 @@ import net.pokeranalytics.android.model.filter.QueryCondition
open class ComputableResult() : RealmObject(), Filterable { 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 var session: Session? = null
@ -35,7 +35,8 @@ open class ComputableResult() : RealmObject(), Filterable {
this.bbNet = session.bbNet this.bbNet = session.bbNet
this.hasBigBlind = if (session.cgBigBlind != null) 1 else 0 this.hasBigBlind = if (session.cgBigBlind != null) 1 else 0
this.estimatedHands = session.estimatedHands 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 { companion object {
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? { fun fieldNameForQueryType(queryCondition: Class<out QueryCondition>): String? {
Session.fieldNameForQueryType(queryCondition)?.let { Session.fieldNameForQueryType(queryCondition)?.let {
return "session.$it" return "session.$it"
} }
return null 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) { return when (row) {
is CustomFieldEntry -> row.editingDescriptors( is CustomFieldEntry -> row.editingDescriptors(
mapOf( mapOf(
@ -290,7 +290,6 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
fun cleanupEntries() { // called when saving the custom field fun cleanupEntries() { // called when saving the custom field
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
realm.executeTransaction { realm.executeTransaction {
this.entriesToDelete.forEach { // entries are out of realm this.entriesToDelete.forEach { // entries are out of realm
realm.where<CustomFieldEntry>().equalTo("id", it.id).findFirst()?.deleteFromRealm() 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.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.* import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType 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.ImageDecorator
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
@ -105,11 +105,8 @@ open class Filter : RealmObject(), RowRepresentable, Editable, Deletable, Counta
it.groupId == groupId it.groupId == groupId
} }
.apply { .apply {
Timber.d("list of querys: ${this.map { it.id }}") Timber.d("list of querys: ${this.map { it.id }}")
val casted = arrayListOf<QueryCondition>() val newFilterCondition = FilterCondition(this)
casted.addAll(this)
val newFilterCondition = FilterCondition(casted)
val previousCondition = filterConditions.filter { val previousCondition = filterConditions.filter {
it.filterName == newFilterCondition.filterName && it.operator == newFilterCondition.operator 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>() val realmQuery = realm.where<T>()
if (firstField != null && secondField != null) { // if (firstField != null && secondField != null) {
return this.query.queryWith(realmQuery).distinct(firstField, secondField) // return this.query.queryWith(realmQuery).distinct(firstField, secondField)
} // }
if (firstField != null) { if (firstField != null) {
return this.query.queryWith(realmQuery).distinct(firstField) return this.query.queryWith(realmQuery).distinct(firstField, *remainingFields)
} }
return this.query.queryWith(realmQuery) return this.query.queryWith(realmQuery)
} }
inline fun <reified T : Filterable> results(firstField: String? = null, secondField: String? = null): RealmResults<T> { inline fun <reified T : Filterable> results(firstField: String? = null, vararg remainingFields: String): RealmResults<T> {
return this.query<T>(firstField, secondField).findAll() return this.query<T>(firstField, *remainingFields).findAll()
} }
val query: Query val query: Query

@ -14,7 +14,7 @@ open class FilterCondition() : RealmObject() {
this.sectionName = sectionName 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() val row = filterElementRows.first()
this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName
this.operator = row.operator.ordinal this.operator = row.operator.ordinal

@ -30,7 +30,6 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
val rowRepresentation : List<RowRepresentable> by lazy { val rowRepresentation : List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
rows.add(SimpleRow.NAME) rows.add(SimpleRow.NAME)
// rows.addAll(GameRow.values())
rows rows
} }
} }
@ -62,18 +61,22 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
} }
override fun adapterRows(): List<RowRepresentable>? { 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) { return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
GameRow.SHORT_NAME -> this.shortName ?: 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) { return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name)) SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name))
GameRow.SHORT_NAME -> row.editingDescriptors(mapOf("defaultValue" to this.shortName)) 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 { override fun isValidForDelete(realm: Realm): Boolean {
return realm.where<Session>().equalTo("game.id", id).findAll().isEmpty() 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.PlayerRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
import net.pokeranalytics.android.util.NULL_TEXT 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.isSameDay
import net.pokeranalytics.android.util.extensions.mediumDate import net.pokeranalytics.android.util.extensions.mediumDate
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresentableDataSource, RowRepresentable { open class Player : RealmObject(), NameManageable, Savable, Deletable, StaticRowRepresentableDataSource, RowRepresentable {
@PrimaryKey @PrimaryKey
override var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
@ -75,10 +76,14 @@ open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresent
return this.name return this.name
} }
override fun stringForRow(row: RowRepresentable): String { override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) { return when (row) {
PlayerRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT 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 * Update the row representation
*/ */
@ -178,7 +182,7 @@ open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresent
this.commentsToDelete.clear() this.commentsToDelete.clear()
} }
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? { override fun editDescriptors(row: RowRepresentable): List<RowRepresentableEditDescriptor>? {
when (row) { when (row) {
PlayerRow.NAME -> return row.editingDescriptors(mapOf("defaultValue" to this.name)) PlayerRow.NAME -> return row.editingDescriptors(mapOf("defaultValue" to this.name))
@ -188,5 +192,24 @@ open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresent
return null 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 * Returns the Options based on the ReportSetup parameters
*/ */
val options: Calculator.Options fun options(realm: Realm): Calculator.Options {
get() {
val realm = Realm.getDefaultInstance()
val stats = this.statIds.map { Stat.valueByIdentifier(it) } val stats = this.statIds.map { Stat.valueByIdentifier(it) }
// Comparison criteria // Comparison criteria
val criteria = this.criteriaIds.map { Criteria.valueByIdentifier(it) } val criteria = this.criteriaIds.map { Criteria.valueByIdentifier(it) }
val customFields = this.criteriaCustomFieldIds.mapNotNull { realm.findById<CustomField>(it) } val customFields = this.criteriaCustomFieldIds.mapNotNull { realm.findById<CustomField>(it) }
val cfCriteria = customFields.map { it.criteria } val cfCriteria = customFields.map { it.criteria }
val allCriteria = mutableListOf<Criteria>() val allCriteria = mutableListOf<Criteria>()

@ -1,6 +1,7 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import android.content.Context import android.content.Context
import com.crashlytics.android.Crashlytics
import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.Entry
import io.realm.Realm import io.realm.Realm
import io.realm.RealmList 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.TableSize
import net.pokeranalytics.android.model.TournamentType import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.extensions.SessionState 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.extensions.getState
import net.pokeranalytics.android.model.filter.Filterable 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.filter.QueryCondition.* import net.pokeranalytics.android.model.filter.QueryCondition.*
import net.pokeranalytics.android.model.interfaces.* 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.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException 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" CustomFieldAmountQuery::class.java, CustomFieldNumberQuery::class.java -> "customFieldEntries.numericValue"
CustomFieldQuery::class.java -> "customFieldEntries.customFields.id" CustomFieldQuery::class.java -> "customFieldEntries.customFields.id"
DateNotNull::class.java -> "startDate" DateNotNull::class.java -> "startDate"
EndDateNotNull::class.java -> "endDate"
BigBlindNotNull::class.java -> "cgBigBlind"
else -> null 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 @PrimaryKey
override var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
@ -130,6 +144,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
@Index @Index
var type: Int = Type.CASH_GAME.ordinal var type: Int = Type.CASH_GAME.ordinal
val sessionType: Type
get() { return Type.values()[this.type] }
// The result of the main user // The result of the main user
var result: Result? = null var result: Result? = null
@ -264,8 +281,11 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The number of tables played at the same time // The number of tables played at the same time
var numberOfTables: Int = 1 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 // The list of opponents who participated to the session
var opponents: RealmList<Player> = RealmList() var opponents: RealmList<Player> = RealmList()
@ -496,7 +516,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/ */
fun startOrContinue() { fun startOrContinue() {
realm.executeTransaction { realm.executeTransaction {
when (getState()) { when (val state = getState()) {
SessionState.PENDING, SessionState.PLANNED -> { SessionState.PENDING, SessionState.PLANNED -> {
this.startDate = Date() this.startDate = Date()
this.defineDefaultTournamentBuyinIfNecessary() this.defineDefaultTournamentBuyinIfNecessary()
@ -511,7 +531,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
this.pauseDate = null this.pauseDate = null
} }
else -> { 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() { fun pause() {
realm.executeTransaction { realm.executeTransaction {
when (getState()) { when (val state = getState()) {
SessionState.STARTED -> { SessionState.STARTED -> {
this.pauseDate = Date() 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 * Stop a session
*/ */
fun stop() { fun stop(context: Context) {
realm.executeTransaction { realm.executeTransaction {
when (getState()) { when (val state = getState()) {
SessionState.STARTED, SessionState.PAUSED -> { SessionState.STARTED, SessionState.PAUSED -> {
this.end() 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 if (cgBigBlind == null) return
cgBigBlind?.let { bb -> cgBigBlind?.let { bb ->
val sb = cgSmallBlind ?: bb / 2.0 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("<<<<<< bb.toCurrency(currency) : ${bb.toCurrency(currency)}")
// println("<<<<<< preFormattedBlinds : $preFormattedBlinds") // println("<<<<<< preFormattedBlinds : $preFormattedBlinds")
val regex = Regex("-?\\d+(\\.\\d+)?") val regex = Regex("-?\\d+(\\.\\d+)?")
@ -636,11 +657,15 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
* Delete the object from realm * Delete the object from realm
*/ */
fun delete() { fun delete() {
Crashlytics.log("Deletes session. Id = ${this.id}")
if (isValid) { if (isValid) {
realm.executeTransaction { realm.executeTransaction {
cleanup() cleanup()
deleteFromRealm() deleteFromRealm()
} }
} else {
Crashlytics.log("Attempt to delete an invalid session")
} }
} }
@ -707,7 +732,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getFormattedDuration(), 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()) rows.add(SeparatorRow())
@ -717,7 +742,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
resId = R.string.pause, 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()) rows.add(SeparatorRow())
@ -727,14 +752,14 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getFormattedDuration(), 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( rows.add(
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT, RowViewType.HEADER_TITLE_AMOUNT,
resId = R.string.hour_rate_without_pauses, 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 return false
} }
override fun stringForRow(row: RowRepresentable, context: Context): String { override fun charSequenceForRow(row: RowRepresentable, context: Context): String {
return when (row) { return when (row) {
SessionRow.BANKROLL -> bankroll?.name ?: NULL_TEXT SessionRow.BANKROLL -> bankroll?.name ?: NULL_TEXT
SessionRow.BLINDS -> getFormattedBlinds() SessionRow.BLINDS -> getFormattedBlinds()
@ -816,6 +841,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
} }
SessionRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT SessionRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT
SessionRow.HANDS -> this.handHistories?.size.toString()
is CustomField -> { is CustomField -> {
customFieldEntries.find { it.customField?.id == row.id }?.let { customFieldEntry -> customFieldEntries.find { it.customField?.id == row.id }?.let { customFieldEntry ->
return customFieldEntry.getFormattedValue(currency) 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) { return when (row) {
SessionRow.BANKROLL -> row.editingDescriptors( SessionRow.BANKROLL -> row.editingDescriptors(
mapOf( mapOf(
@ -1123,7 +1149,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
value?.let { value?.let {
return stat.format(it, currency = currency) return stat.textFormat(it, currency = currency)
} ?: run { } ?: run {
return TextFormat(NULL_TEXT) return TextFormat(NULL_TEXT)
} }
@ -1162,7 +1188,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
if (!hasMainCurrencyCode) { if (!hasMainCurrencyCode) {
this.computableResult?.ratedNet?.let { ratedNet -> 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 @Ignore
override val realmObjectClass: Class<out Identifiable> = Session::class.java 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 { override fun formattedValue(stat: Stat) : TextFormat {
return when (stat) { return when (stat) {
Stat.NET_RESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null) Stat.NET_RESULT, Stat.AVERAGE -> stat.textFormat(this.ratedNet, currency = null)
Stat.HOURLY_DURATION, Stat.AVERAGE_HOURLY_DURATION -> stat.format(this.hourlyDuration, currency = null) Stat.HOURLY_DURATION, Stat.AVERAGE_HOURLY_DURATION -> stat.textFormat(this.hourlyDuration, currency = null)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null) Stat.HOURLY_RATE -> stat.textFormat(this.hourlyRate, currency = null)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null) Stat.HANDS_PLAYED -> stat.textFormat(this.estimatedHands, currency = null)
Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, 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 -> { Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands) val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands)
if (netBBPer100Hands != null) { if (netBBPer100Hands != null) {
return stat.format(this.estimatedHands, currency = null) return stat.textFormat(this.estimatedHands, currency = null)
} else { } else {
return TextFormat(NULL_TEXT) return TextFormat(NULL_TEXT)
} }

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

@ -50,17 +50,21 @@ open class TournamentName : RealmObject(), NameManageable, StaticRowRepresentabl
} }
override fun adapterRows(): List<RowRepresentable>? { 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) { return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT 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)) return row.editingDescriptors(mapOf("defaultValue" to this.name))
} }

@ -158,7 +158,7 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
} }
override fun formattedValue(stat: Stat): TextFormat { 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( override fun legendValues(
@ -170,7 +170,7 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
): LegendContent { ): LegendContent {
val entryValue = this.formattedValue(stat) 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) val leftName = context.getString(R.string.amount)
return DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName) return DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName)
} }

@ -1,7 +1,6 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import android.content.Context import android.content.Context
import com.crashlytics.android.Crashlytics
import io.realm.Realm import io.realm.Realm
import io.realm.RealmModel import io.realm.RealmModel
import io.realm.RealmObject import io.realm.RealmObject
@ -119,10 +118,14 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
return rowRepresentation return rowRepresentation
} }
override fun stringForRow(row: RowRepresentable): String { override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) { return when (row) {
SimpleRow.NAME -> this.name 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)) 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 = "" var representation = ""
this.significantFields().forEach { this.significantFields().forEach {
representation += this.stringForRow(it, context) representation += this.charSequenceForRow(it, context)
} }
return representation 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() { override fun onResume() {
super.onResume() super.onResume()
AppGuard.requestPurchasesUpdate() AppGuard.requestPurchasesUpdate()
this.homePagerAdapter?.activityResumed()
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

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

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

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

@ -6,8 +6,8 @@ import android.view.ViewGroup
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter import androidx.fragment.app.FragmentStatePagerAdapter
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.fragment.CalendarFragment import net.pokeranalytics.android.ui.modules.calendar.CalendarFragment
import net.pokeranalytics.android.ui.fragment.FeedFragment import net.pokeranalytics.android.ui.modules.feed.FeedFragment
import net.pokeranalytics.android.ui.fragment.GraphFragment import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.fragment.components.BaseFragment import net.pokeranalytics.android.ui.fragment.components.BaseFragment
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -15,9 +15,9 @@ import java.lang.ref.WeakReference
/** /**
* Comparison Chart Pager Adapter * 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 { override fun getItem(position: Int): BaseFragment {
return when (position) { return when (position) {
@ -59,11 +59,11 @@ class ComparisonChartPagerAdapter(val context: Context, fragmentManager: Fragmen
/** /**
* Return the fragment at the position key * Return the fragment at the position key
*/ */
fun getFragment(key: Int): BaseFragment? { // fun getFragment(key: Int): BaseFragment? {
if (weakReferences.get(key) != null) { // if (weakReferences.get(key) != null) {
return weakReferences.get(key).get() // return weakReferences.get(key).get()
} // }
return null // 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 android.view.ViewGroup
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter 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.fragment.components.BaseFragment
import net.pokeranalytics.android.ui.modules.feed.FeedFragment
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
/** /**
* Home Adapter * 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 { override fun getItem(position: Int): BaseFragment {
return when (position) { return when (position) {
@ -32,12 +37,12 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`) super.destroyItem(container, position, `object`)
weakReferences.remove(position) this.weakReferences.remove(position)
} }
override fun instantiateItem(container: ViewGroup, position: Int): Any { override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as BaseFragment val fragment = super.instantiateItem(container, position) as BaseFragment
weakReferences.put(position, WeakReference(fragment)) this.weakReferences.put(position, WeakReference(fragment))
return fragment return fragment
} }
@ -52,14 +57,21 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
} }
} }
/** fun activityResumed() {
* Return the fragment at the position key val ref = this.weakReferences.get(0)
*/ ref?.get()?.let {
fun getFragment(key: Int): BaseFragment? { (it as FeedFragment).activityResumed()
if (weakReferences.get(key) != null) {
return weakReferences.get(key).get()
} }
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 * Home Adapter
*/ */
class ReportPagerAdapter(private val context: Context, class ReportPagerAdapter(private val context: Context,
val fragmentManager: FragmentManager, fragmentManager: FragmentManager,
private val reportHolder: ReportHolder) : FragmentPagerAdapter(fragmentManager) { private val reportHolder: ReportHolder) : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): BaseFragment { override fun getItem(position: Int): BaseFragment {
return when (position) { return when (position) {
0 -> { 0 -> GraphFragment.newInstance(style = GraphFragment.Style.BAR)
GraphFragment.newInstance(style = GraphFragment.Style.BAR) 1 -> GraphFragment.newInstance(style = GraphFragment.Style.MULTILINE)
}
1 -> {
GraphFragment.newInstance(style = GraphFragment.Style.MULTILINE)
}
2 -> { 2 -> {
val report = this.reportHolder.report val report = this.reportHolder.report
ComposableTableReportFragment.newInstance(report) ComposableTableReportFragment.newInstance(report)

@ -4,28 +4,19 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView 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.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType 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 * An adapter capable of displaying a list of RowRepresentables
* @param dataSource the datasource providing rows * @param dataSource the datasource providing rows
* @param delegate the delegate, notified of UI actions * @param delegate the delegate, notified of UI actions
*/ */
class RowRepresentableAdapter( class RowRepresentableAdapter(
var dataSource: RowRepresentableDataSource, override var dataSource: RowRepresentableDataSource,
var delegate: RowRepresentableDelegate? = null override var delegate: RowRepresentableDelegate? = null
) : ) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>(), RecyclerAdapter {
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return this.dataSource.viewTypeForPosition(position) return this.dataSource.viewTypeForPosition(position)
@ -42,7 +33,7 @@ class RowRepresentableAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
this.dataSource.rowRepresentableForPosition(position)?.let { 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 package net.pokeranalytics.android.ui.adapter
import android.content.Context import android.content.Context
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.util.TextFormat import net.pokeranalytics.android.util.TextFormat
import kotlin.reflect.KClass
/** /**
* Base Interface to provide the RowRepresentable to the adapter * 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 * 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 : * Returns values for any RowRepresentable in various forms to display
* - string * the appropriate numericValues in graphical filterConditions, such as labels, textfields, switchs...
* - booleans
* - actionIcon
* to display the appropriate numericValues in graphical filterConditions, such as labels, textfields, switchs...
*/ */
interface DisplayableDataSource { interface DisplayableDataSource {
@ -118,17 +117,17 @@ interface DisplayableDataSource {
return -1 return -1
} }
/** /***
* Returns a localized string for a specific row * Returns a CharSequence representation of the [row]
*/ */
fun stringForRow(row: RowRepresentable, context: Context): String { fun charSequenceForRow(row: RowRepresentable, context: Context): CharSequence {
return stringForRow(row) 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 "" return ""
} }
@ -146,10 +145,17 @@ interface DisplayableDataSource {
return 0 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 return true
} }
@ -159,18 +165,43 @@ interface DisplayableDataSource {
fun isSelectable(row: RowRepresentable): Boolean { fun isSelectable(row: RowRepresentable): Boolean {
return true 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 * An interface providing a way to describe how the edition of a [RowRepresentable] will be displayed
*/ */
interface EditableDataSource { interface EditableDataSource {
/** /**
* A list of [RowRepresentableEditDescriptor] object specifying the way the edition will be handled * 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 return null
} }
} }
@ -181,7 +212,7 @@ interface SelectableDataSource {
/** /**
* A boolean to indicate if the row is selected or not * 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 return false
} }
} }

@ -5,17 +5,22 @@ import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import net.pokeranalytics.android.BuildConfig 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.TextFormat
import net.pokeranalytics.android.util.URL import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.billing.AppGuard import net.pokeranalytics.android.util.billing.AppGuard
import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
@ -96,10 +102,8 @@ fun BaseActivity.openContactMail(subjectStringRes: Int, filePath: String? = null
// Open custom tab // Open custom tab
fun Context.openUrl(url: String) { fun Context.openUrl(url: String) {
val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder() val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
builder.setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary)) ContextCompat.startActivity(this, browserIntent, null)
val customTabsIntent = builder.build()
customTabsIntent.launchUrl(this, Uri.parse(url))
} }
// Display Alert Dialog // Display Alert Dialog
@ -172,3 +176,40 @@ fun SearchView.removeMargins() {
layoutParams?.rightMargin = 0 layoutParams?.rightMargin = 0
searchEditFrame?.layoutParams = layoutParams 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 android.view.*
import kotlinx.android.synthetic.main.fragment_comparison_chart.* import kotlinx.android.synthetic.main.fragment_comparison_chart.*
import net.pokeranalytics.android.R 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.SettingsActivity
import net.pokeranalytics.android.ui.adapter.ComparisonChartPagerAdapter import net.pokeranalytics.android.ui.adapter.ComparisonChartPagerAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
@ -52,15 +52,15 @@ class ComparisonChartFragment : BaseFragment(), StaticRowRepresentableDataSource
initUI() initUI()
} }
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu?.clear() menu.clear()
inflater?.inflate(R.menu.toolbar_comparison_chart, menu) inflater.inflate(R.menu.toolbar_comparison_chart, menu)
this.comparisonChartMenu = menu this.comparisonChartMenu = menu
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item!!.itemId) { when (item.itemId) {
R.id.settings -> openChangeStatistics() R.id.settings -> openChangeStatistics()
} }
return true return true
@ -71,8 +71,8 @@ class ComparisonChartFragment : BaseFragment(), StaticRowRepresentableDataSource
return rowRepresentation return rowRepresentation
} }
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
super.onRowSelected(position, row, fromAction) super.onRowSelected(position, row, tag)
when(row) { when(row) {
MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext()) MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext())
MoreTabRow.SETTINGS -> SettingsActivity.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 return (row as CurrencyRow).currencyCodeAndSymbol
} }
// RowRepresentableDelegate // RowRepresentableDelegate
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
val intent = Intent() val intent = Intent()
intent.putExtra(INTENT_CURRENCY_CODE, (row as CurrencyRow).currency.currencyCode) intent.putExtra(INTENT_CURRENCY_CODE, (row as CurrencyRow).currency.currencyCode)
this.activity?.setResult(Activity.RESULT_OK, intent) 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 androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_more.* import kotlinx.android.synthetic.main.fragment_more.*
import net.pokeranalytics.android.R 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.SettingsActivity
import net.pokeranalytics.android.ui.activity.Top10Activity import net.pokeranalytics.android.ui.activity.Top10Activity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -59,8 +59,8 @@ class MoreFragment : BaseFragment(), StaticRowRepresentableDataSource, RowRepres
return rowRepresentation return rowRepresentation
} }
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
super.onRowSelected(position, row, fromAction) super.onRowSelected(position, row, tag)
when(row) { when(row) {
MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext()) MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext())
MoreTabRow.TOP_10 -> Top10Activity.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.CustomFieldCriteria
import net.pokeranalytics.android.model.realm.CustomField import net.pokeranalytics.android.model.realm.CustomField
import net.pokeranalytics.android.model.realm.Filter 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.activity.ReportCreationActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode import net.pokeranalytics.android.ui.modules.filter.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType import net.pokeranalytics.android.ui.modules.filter.FilterableType
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable 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) super.onCreateOptionsMenu(menu, inflater)
menu?.clear() menu.clear()
inflater?.inflate(R.menu.toolbar_report_creation, menu) inflater.inflate(R.menu.toolbar_report_creation, menu)
menu?.findItem(R.id.add)?.isVisible = false menu.findItem(R.id.add)?.isVisible = false
reportCreationMenu = menu reportCreationMenu = menu
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item?.itemId) { when (item.itemId) {
R.id.add -> { R.id.add -> {
if (this.assistant.step == Assistant.Step.FILTER) { if (this.assistant.step == Assistant.Step.FILTER) {
FiltersActivity.newInstanceForResult( 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) return this.assistant.isSelected(row)
} }
// RowRepresentableDelegate // 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) val newStep = this.assistant.onRowSelected(position - 1)
if (newStep) { if (newStep) {

@ -22,7 +22,7 @@ import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.ReportSetup 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.ReportCreationActivity
import net.pokeranalytics.android.ui.activity.components.ReportActivity import net.pokeranalytics.android.ui.activity.components.ReportActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.activity.components.RequestCode
@ -143,8 +143,8 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
return this.adapterRows return this.adapterRows
} }
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
super.onRowSelected(position, row, fromAction) super.onRowSelected(position, row, tag)
when (row) { when (row) {
is ReportRow -> { is ReportRow -> {
@ -152,7 +152,7 @@ class ReportsFragment : DeletableItemFragment(), StaticRowRepresentableDataSourc
launchComputation(row.criteria, reportName) launchComputation(row.criteria, reportName)
} }
is ReportSetup -> { 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.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle 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.openPlayStorePage
import net.pokeranalytics.android.ui.extensions.openUrl import net.pokeranalytics.android.ui.extensions.openUrl
import net.pokeranalytics.android.ui.fragment.components.RealmFragment 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
import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow
import net.pokeranalytics.android.util.FileUtils import net.pokeranalytics.android.util.*
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.billing.AppGuard import net.pokeranalytics.android.util.billing.AppGuard
import net.pokeranalytics.android.util.billing.IAPProducts import net.pokeranalytics.android.util.billing.IAPProducts
import net.pokeranalytics.android.util.csv.ProductCSVDescriptors import net.pokeranalytics.android.util.csv.ProductCSVDescriptors
@ -112,7 +112,11 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep
return rowRepresentation return rowRepresentation
} }
override fun stringForRow(row: RowRepresentable): String { override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) { return when (row) {
SettingRow.SUBSCRIPTION -> AppGuard.subscriptionStatus(requireContext()) SettingRow.SUBSCRIPTION -> AppGuard.subscriptionStatus(requireContext())
SettingRow.VERSION -> BuildConfig.VERSION_NAME + if (BuildConfig.DEBUG) " (${BuildConfig.VERSION_CODE}) DEBUG" else "" 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) { when (row) {
SettingRow.BANKROLL_REPORT -> BankrollActivity.newInstance(requireContext()) SettingRow.BANKROLL_REPORT -> BankrollActivity.newInstance(requireContext())
SettingRow.TOP_10 -> Top10Activity.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 * 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.filter.QueryCondition
import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Filter 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.components.FilterableFragment
import net.pokeranalytics.android.ui.fragment.report.ComposableTableReportFragment import net.pokeranalytics.android.ui.fragment.report.ComposableTableReportFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode import net.pokeranalytics.android.ui.modules.filter.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType import net.pokeranalytics.android.ui.modules.filter.FilterableType
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext

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

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

@ -15,7 +15,7 @@ import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.interfaces.Deletable 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 import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
/** /**

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

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

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

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

@ -26,15 +26,15 @@ class BottomSheetEditTextMultiLinesFragment : BottomSheetFragment() {
* Init UI * Init UI
*/ */
private fun initUI() { private fun initUI() {
val data = getDescriptors()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found") val data = getDescriptors() ?: throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
if (data.size != 1) { if (data.size != 1) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency") throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
} }
LayoutInflater.from(requireContext()).inflate(net.pokeranalytics.android.R.layout.bottom_sheet_edit_text_multi_lines, view?.bottomSheetContainer, true) 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) } 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.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() } editText.addTextChangedListener { this.viewModel.stringValue = it?.toString() }
data[0].defaultValue?.let { data[0].defaultValue?.let {
editText.setText(it.toString()) 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.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData 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.activity.components.BaseActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
@ -29,13 +30,13 @@ import java.util.*
class BottomSheetConfig(var row: RowRepresentable, class BottomSheetConfig(var row: RowRepresentable,
var delegate: RowRepresentableDelegate, var delegate: RowRepresentableDelegate,
var rowRepresentableEditDescriptors: ArrayList<RowRepresentableEditDescriptor>?, var rowRepresentableEditDescriptors: List<RowRepresentableEditDescriptor>?,
var isClearable: Boolean? = true, var isClearable: Boolean? = true,
var currentCurrency: Currency? = null, var currentCurrency: Currency? = null,
var isDeletable: Boolean? = false, var isDeletable: Boolean? = false,
var valueHasPlaceholder: Boolean? = null) { var valueHasPlaceholder: Boolean? = null,
} var alternativeLabels: Boolean = false
)
open class BottomSheetFragment : BottomSheetDialogFragment() { open class BottomSheetFragment : BottomSheetDialogFragment() {
@ -46,6 +47,10 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
ViewModelProviders.of(this).get(BottomSheetViewModel::class.java) ViewModelProviders.of(this).get(BottomSheetViewModel::class.java)
} }
private var delegate: RowRepresentableDelegate? = null
protected lateinit var dataAdapter: RowRepresentableAdapter
companion object { companion object {
private var config: BottomSheetConfig? = null private var config: BottomSheetConfig? = null
@ -53,18 +58,19 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
const val REQUEST_CODE_ADD_NEW_OBJECT = 100 const val REQUEST_CODE_ADD_NEW_OBJECT = 100
fun create( fun create(
fragmentManager: FragmentManager?, fragmentManager: FragmentManager,
row: RowRepresentable, row: RowRepresentable,
delegate: RowRepresentableDelegate, delegate: RowRepresentableDelegate,
rowRepresentableEditDescriptors: ArrayList<RowRepresentableEditDescriptor>?, rowRepresentableEditDescriptors: List<RowRepresentableEditDescriptor>?,
isClearable: Boolean? = true, isClearable: Boolean? = true,
currentCurrency: Currency? = null, currentCurrency: Currency? = null,
isDeletable: Boolean? = false, isDeletable: Boolean? = false,
valueHasPlaceholder: Boolean? = null valueHasPlaceholder: Boolean? = null,
alternativeLabels: Boolean = false
): BottomSheetFragment { ): BottomSheetFragment {
val bottomSheetFragment = newInstance(row.bottomSheetType) val bottomSheetFragment = newInstance(row.bottomSheetType)
bottomSheetFragment.show(fragmentManager, "bottomSheet") 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 return bottomSheetFragment
} }
@ -108,12 +114,14 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
private fun configure(configuration: BottomSheetConfig) { private fun configure(configuration: BottomSheetConfig) {
this.viewModel.row = configuration.row this.viewModel.row = configuration.row
this.viewModel.delegate = configuration.delegate
this.viewModel.rowRepresentableEditDescriptors = configuration.rowRepresentableEditDescriptors this.viewModel.rowRepresentableEditDescriptors = configuration.rowRepresentableEditDescriptors
this.viewModel.isClearable = configuration.isClearable ?: true this.viewModel.isClearable = configuration.isClearable ?: true
this.viewModel.currentCurrency = configuration.currentCurrency this.viewModel.currentCurrency = configuration.currentCurrency
this.viewModel.isDeletable = configuration.isDeletable ?: false this.viewModel.isDeletable = configuration.isDeletable ?: false
this.viewModel.valueAsPlaceholder = configuration.valueHasPlaceholder ?: 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 pokerAnalyticsActivity = activity as BaseActivity
val liveDataType = LiveData.values()[dataType] val liveDataType = LiveData.values()[dataType]
this.viewModel.addedData = liveDataType.getData(pokerAnalyticsActivity.getRealm(), primaryKey) this.viewModel.addedData = liveDataType.getData(pokerAnalyticsActivity.getRealm(), primaryKey)
this.viewModel.onRowValueChanged() this.onRowValueChanged()
// this.delegate.onRowValueChanged(proxyItem, this.row) // this.delegate.onRowValueChanged(proxyItem, this.row)
dismiss() dismiss()
} }
} }
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
override fun setupDialog(dialog: Dialog?, style: Int) { override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style) 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 // Menu
bottomSheetToolbar.menu.findItem(R.id.actionClear).setOnMenuItemClickListener { bottomSheetToolbar.menu.findItem(R.id.actionClear).setOnMenuItemClickListener {
this.viewModel.onClear() this.onClear()
// delegate.onRowValueChanged(null, row) // delegate.onRowValueChanged(null, row)
dismiss() dismiss()
true true
} }
bottomSheetToolbar.menu.findItem(R.id.actionDelete).setOnMenuItemClickListener { bottomSheetToolbar.menu.findItem(R.id.actionDelete).setOnMenuItemClickListener {
this.viewModel.onRowDeleted() this.onRowDeleted()
// delegate.onRowDeleted(row) // delegate.onRowDeleted(row)
dismiss() dismiss()
true true
@ -190,7 +198,7 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
true true
} }
bottomSheetToolbar.menu.findItem(R.id.actionCheck).setOnMenuItemClickListener { bottomSheetToolbar.menu.findItem(R.id.actionCheck).setOnMenuItemClickListener {
this.viewModel.onRowValueChanged() this.onRowValueChanged()
// this.delegate.onRowValueChanged(getValue(), row) // this.delegate.onRowValueChanged(getValue(), row)
dismiss() dismiss()
true true
@ -204,7 +212,7 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
/** /**
* Return the data list * Return the data list
*/ */
fun getDescriptors(): ArrayList<RowRepresentableEditDescriptor>? { fun getDescriptors(): List<RowRepresentableEditDescriptor>? {
return this.viewModel.rowRepresentableEditDescriptors return this.viewModel.rowRepresentableEditDescriptors
} }
@ -212,4 +220,44 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
return this.viewModel.getValue() 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() { override fun onResume() {
super.onResume() super.onResume()
this.viewModel.notifyDataSetChanged() this.notifyDataSetChanged()
} }
override fun rowRepresentableForPosition(position: Int): RowRepresentable? { override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
@ -49,9 +49,9 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable
return RowViewType.BOTTOM_SHEET_DATA.ordinal 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() dismiss()
// this.viewModel.realmData?.let { // this.viewModel.realmData?.let {
// val selectedData = it[position] // val selectedData = it[position]
@ -61,7 +61,7 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable
// dismiss() // 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 viewManager = LinearLayoutManager(requireContext())
val dataAdapter = RowRepresentableAdapter(this, this) val dataAdapter = RowRepresentableAdapter(this, this)
this.viewModel.dataAdapter = dataAdapter this.dataAdapter = dataAdapter
reyclerView.apply { recyclerView.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = viewManager layoutManager = viewManager
adapter = dataAdapter adapter = dataAdapter

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

@ -4,7 +4,7 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import io.realm.RealmModel import io.realm.RealmModel
import net.pokeranalytics.android.model.LiveData 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.activity.components.BaseActivity
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
@ -26,16 +26,20 @@ open class BottomSheetMultiSelectionFragment : BottomSheetListFragment() {
val liveDataType = LiveData.values()[dataType] val liveDataType = LiveData.values()[dataType]
val proxyItem: RealmModel? = liveDataType.getData(pokerAnalyticsActivity.getRealm(), primaryKey) val proxyItem: RealmModel? = liveDataType.getData(pokerAnalyticsActivity.getRealm(), primaryKey)
this.viewModel.selectedRows.add(proxyItem as RowRepresentable) this.viewModel.selectedRows.add(proxyItem as RowRepresentable)
this.viewModel.refreshRow(proxyItem as RowRepresentable) this.refreshRow(proxyItem as RowRepresentable)
// dataAdapter.refreshRow(proxyItem as RowRepresentable) // dataAdapter.refreshRow(proxyItem as RowRepresentable)
} }
} }
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
this.viewModel.onRowSelected(row) 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 this.viewModel.isSelected(row)
// return selectedRows.contains(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) 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.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText.addTextChangedListener { editText.addTextChangedListener {
@ -68,7 +68,7 @@ class BottomSheetNumericTextFragment : BottomSheetFragment() {
editText.setOnEditorActionListener { _, actionId, _ -> editText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {
this.viewModel.onRowValueChanged() this.onRowValueChanged()
// delegate.onRowValueChanged(getValue(), row) // delegate.onRowValueChanged(getValue(), row)
dismiss() dismiss()
true true

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

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

@ -6,6 +6,7 @@ import android.view.View
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import kotlinx.android.synthetic.main.bottom_sheet_grid.* import kotlinx.android.synthetic.main.bottom_sheet_grid.*
import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.TableSize import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
@ -25,7 +26,7 @@ class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowReprese
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
this.viewModel.notifyDataSetChanged() this.notifyDataSetChanged()
// dataAdapter.notifyDataSetChanged() // dataAdapter.notifyDataSetChanged()
} }
@ -34,17 +35,17 @@ class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowReprese
*/ */
private fun initUI() { private fun initUI() {
LayoutInflater.from(requireContext()) 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 viewManager = GridLayoutManager(requireContext(), 3)
val dataAdapter = RowRepresentableAdapter(this, this) val dataAdapter = RowRepresentableAdapter(this, this)
this.viewModel.dataAdapter = dataAdapter this.dataAdapter = dataAdapter
val spanCount = 3 val spanCount = 3
val spacing = 2.px val spacing = 2.px
val includeEdge = false val includeEdge = false
reyclerView.apply { recyclerView.apply {
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = viewManager layoutManager = viewManager
adapter = dataAdapter adapter = dataAdapter
@ -53,20 +54,25 @@ class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowReprese
} }
override fun adapterRows(): List<RowRepresentable>? { 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.defaultSize = (row as TableSize).numberOfPlayer
this.viewModel.onRowValueChanged() this.onRowValueChanged()
// this.delegate.onRowValueChanged((row as TableSize).numberOfPlayer, this.row)
dismiss() dismiss()
} }
override fun stringForRow(row: RowRepresentable): String { // override fun charSequenceForRow(row: RowRepresentable, context: Context, tag: Int): String {
this.context?.let { //
return row.localizedTitle(it) // if (this.viewModel.alternativeLabels) {
} // return (row as TableSize).numberOfPlayer.toString()
return "UNKNOWN CONTEXT FOR ROW $row" // }
} //
// 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.CustomFieldCriteria
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.ReportSetup 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.ReportViewModel
import net.pokeranalytics.android.ui.viewmodel.ViewModelHolder import net.pokeranalytics.android.ui.viewmodel.ViewModelHolder
import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.findById
@ -37,10 +37,10 @@ abstract class AbstractReportFragment : DataManagerFragment() {
private fun initData() { private fun initData() {
this.viewModel.dataType = LiveData.REPORT_SETUP.ordinal this.model.dataType = LiveData.REPORT_SETUP.ordinal
this.viewModel.primaryKey = this.selectedReport.options.reportSetupId this.model.primaryKey = this.selectedReport.options.reportSetupId
this.viewModel.loadItemWithRealm(getRealm()) this.model.loadItemWithRealm(getRealm())
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -48,7 +48,7 @@ abstract class AbstractReportFragment : DataManagerFragment() {
initData() initData()
this.deleteButtonShouldAppear = (this.viewModel.primaryKey != null) this.deleteButtonShouldAppear = (this.model.primaryKey != null)
this.saveButtonShouldAppear = this.selectedReport.options.userGenerated this.saveButtonShouldAppear = this.selectedReport.options.userGenerated
setDisplayHomeAsUpEnabled(true) setDisplayHomeAsUpEnabled(true)
@ -71,7 +71,7 @@ abstract class AbstractReportFragment : DataManagerFragment() {
view.findViewById<EditText>(net.pokeranalytics.android.R.id.reportName) view.findViewById<EditText>(net.pokeranalytics.android.R.id.reportName)
nameEditText.inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES nameEditText.inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
this.viewModel.primaryKey?.let { id -> this.model.primaryKey?.let { id ->
getRealm().findById<ReportSetup>(id)?.let { reportSetup -> getRealm().findById<ReportSetup>(id)?.let { reportSetup ->
nameEditText.hint = reportSetup.name nameEditText.hint = reportSetup.name
} }
@ -110,10 +110,10 @@ abstract class AbstractReportFragment : DataManagerFragment() {
private fun saveReport(name: String) { private fun saveReport(name: String) {
this.reportViewModel.title = name this.reportViewModel.title = name
val rs = this.viewModel.item as ReportSetup val rs = this.model.item as ReportSetup
getRealm().executeTransaction { realm -> getRealm().executeTransaction { realm ->
val firstSave = (this.viewModel.primaryKey == null) val firstSave = (this.model.primaryKey == null)
if (firstSave) { if (firstSave) {
val options = this.selectedReport.options val options = this.selectedReport.options
rs.name = name rs.name = name
@ -139,7 +139,7 @@ abstract class AbstractReportFragment : DataManagerFragment() {
} }
this.viewModel.primaryKey = rs.id this.model.primaryKey = rs.id
this.deleteButtonShouldAppear = true this.deleteButtonShouldAppear = true
setToolbarTitle(this.reportViewModel.title) setToolbarTitle(this.reportViewModel.title)
} }

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

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

@ -22,7 +22,7 @@ interface GraphUnderlyingEntry {
): LegendContent { ): LegendContent {
val leftName = stat.localizedTitle(context) 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) { return if (stat.legendHideRightValue) {
DefaultLegendValues(this.entryTitle(context), totalStatValue, leftName = leftName) 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.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
/** /**
* Swipe to delete callback * 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) { ItemTouchHelper.SimpleCallback(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.START or ItemTouchHelper.END) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { 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, c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean
) { ) {
val foregroundView = viewHolder.itemView.findViewById<View?>(net.pokeranalytics.android.R.id.foreground) val foregroundView = viewHolder.itemView.findViewById<View?>(R.id.foreground)
val backgroundView = viewHolder.itemView.findViewById<View?>(net.pokeranalytics.android.R.id.background) val backgroundView = viewHolder.itemView.findViewById<View?>(R.id.background)
foregroundView?.let { foregroundView?.let {
getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive) getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive)
backgroundView?.findViewById<View?>(R.id.leftIcon)?.isVisible = dX > 0 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) { 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 { foregroundView?.let {
getDefaultUIUtil().clearView(foregroundView) getDefaultUIUtil().clearView(foregroundView)
} }
@ -46,7 +45,7 @@ class SwipeToDeleteCallback(var adapter: RowRepresentableAdapter, var onDelete:
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
viewHolder?.let { 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 { foregroundView?.let {
getDefaultUIUtil().onSelected(foregroundView) getDefaultUIUtil().onSelected(foregroundView)
} }
@ -58,11 +57,29 @@ class SwipeToDeleteCallback(var adapter: RowRepresentableAdapter, var onDelete:
actionState: Int, isCurrentlyActive: Boolean actionState: Int, isCurrentlyActive: Boolean
) { ) {
viewHolder?.let { 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 { foregroundView?.let {
getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive) 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.Context
import android.content.Intent 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.content.Intent
import android.os.Bundle import android.os.Bundle
@ -6,7 +6,6 @@ import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.bankroll.BankrollReport import net.pokeranalytics.android.calculus.bankroll.BankrollReport
import net.pokeranalytics.android.ui.activity.components.BaseActivity import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.fragment.BankrollDetailsFragment
class BankrollDetailsActivity : BaseActivity() { class BankrollDetailsActivity : BaseActivity() {
@ -18,7 +17,7 @@ class BankrollDetailsActivity : BaseActivity() {
* Default constructor * Default constructor
*/ */
fun newInstanceForResult(fragment: Fragment, bankrollReport: BankrollReport, requestCode: Int) { fun newInstanceForResult(fragment: Fragment, bankrollReport: BankrollReport, requestCode: Int) {
this.bankrollReport = bankrollReport Companion.bankrollReport = bankrollReport
val intent = Intent(fragment.requireContext(), BankrollDetailsActivity::class.java) val intent = Intent(fragment.requireContext(), BankrollDetailsActivity::class.java)
fragment.startActivityForResult(intent, requestCode) 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.app.Activity.RESULT_OK
import android.content.Intent 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.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
@ -32,7 +32,8 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
companion object { companion object {
fun newInstance(bankrollReport: BankrollReport): BankrollDetailsFragment { fun newInstance(bankrollReport: BankrollReport): BankrollDetailsFragment {
val fragment = BankrollDetailsFragment() val fragment =
BankrollDetailsFragment()
fragment.bankrollId = bankrollReport.setup.bankrollId fragment.bankrollId = bankrollReport.setup.bankrollId
return fragment return fragment
} }
@ -72,9 +73,9 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
} }
} }
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu?.clear() menu.clear()
inflater?.inflate(R.menu.toolbar_comparison_chart, menu) // TODO R.menu.toolbar_comparison_chart? inflater.inflate(R.menu.toolbar_comparison_chart, menu) // TODO R.menu.toolbar_comparison_chart?
this.bankrollDetailsMenu = menu this.bankrollDetailsMenu = menu
updateMenuUI() updateMenuUI()
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
@ -134,21 +135,21 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.TITLE_VALUE, RowViewType.TITLE_VALUE,
resId = R.string.bankroll, resId = R.string.bankroll,
computedStat = totalComputedStat valueTextFormat = totalComputedStat.textFormat
) )
) )
rows.add( rows.add(
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.TITLE_VALUE, RowViewType.TITLE_VALUE,
resId = R.string.net_result, resId = R.string.net_result,
computedStat = netComputedStat valueTextFormat = netComputedStat.textFormat
) )
) )
rows.add( rows.add(
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.TITLE_VALUE, RowViewType.TITLE_VALUE,
resId = R.string.net_banked, resId = R.string.net_banked,
computedStat = netBankedComputedStat valueTextFormat = netBankedComputedStat.textFormat
) )
) )
@ -162,7 +163,7 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.TITLE_VALUE, RowViewType.TITLE_VALUE,
title = typeName, title = typeName,
computedStat = computedStat valueTextFormat = computedStat.textFormat
) )
) )
} }
@ -193,12 +194,12 @@ class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSourc
// RowRepresentableDelegate // 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 { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item?.itemId) { when (item.itemId) {
R.id.settings -> editBankroll() R.id.settings -> editBankroll()
} }
return true 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.app.Activity
import android.content.Intent 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.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.modules.data.EditableDataActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.GraphActivity import net.pokeranalytics.android.ui.activity.GraphActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -51,7 +50,8 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour
* Create new instance * Create new instance
*/ */
fun newInstance(): BankrollFragment { fun newInstance(): BankrollFragment {
val fragment = BankrollFragment() val fragment =
BankrollFragment()
val bundle = Bundle() val bundle = Bundle()
fragment.arguments = bundle fragment.arguments = bundle
return fragment return fragment
@ -175,7 +175,7 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour
return rows return rows
} }
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
when (row) { when (row) {
is BankrollGraphRow -> { is BankrollGraphRow -> {
val lineDataSet = row.dataSet as LineDataSet 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.Context
import android.content.Intent import android.content.Intent
@ -8,7 +8,6 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedResults import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.ui.activity.components.BaseActivity import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.viewmodel.CalendarDetailsViewModel
class CalendarDetailsActivity : BaseActivity() { class CalendarDetailsActivity : BaseActivity() {
@ -27,9 +26,9 @@ class CalendarDetailsActivity : BaseActivity() {
* Default constructor * Default constructor
*/ */
fun newInstance(context: Context, computedResults: ComputedResults, sessionTypeCondition: QueryCondition?, title: String?) { fun newInstance(context: Context, computedResults: ComputedResults, sessionTypeCondition: QueryCondition?, title: String?) {
this.computedResults = computedResults Companion.computedResults = computedResults
this.sessionTypeCondition = sessionTypeCondition Companion.sessionTypeCondition = sessionTypeCondition
this.detailsTitle = title detailsTitle = title
val intent = Intent(context, CalendarDetailsActivity::class.java) val intent = Intent(context, CalendarDetailsActivity::class.java)
context.startActivity(intent) context.startActivity(intent)
} }
@ -47,9 +46,12 @@ class CalendarDetailsActivity : BaseActivity() {
*/ */
private fun initData() { private fun initData() {
this.viewModel.computedResults = computedResults this.viewModel.computedResults =
this.viewModel.sessionTypeCondition = sessionTypeCondition computedResults
this.viewModel.detailsTitle = detailsTitle 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.Context
import android.content.Intent 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.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource 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.fragment.components.BaseFragment
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow
import net.pokeranalytics.android.ui.view.rowrepresentable.StatDoubleRow import net.pokeranalytics.android.ui.view.rowrepresentable.StatDoubleRow
import net.pokeranalytics.android.ui.viewmodel.CalendarDetailsViewModel
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -134,7 +134,7 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
return rowRepresentables return rowRepresentables
} }
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
when (row) { when (row) {
is GraphRow -> { is GraphRow -> {
val report = row.report val report = row.report
@ -146,10 +146,12 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
val dataSet = row.dataSet val dataSet = row.dataSet
when (dataSet) { when (dataSet) {
is LineDataSet-> { is LineDataSet-> {
GraphActivity.newInstance(requireContext(), listOf(dataSet), null, GraphFragment.Style.LINE, title) GraphActivity.newInstance(requireContext(), listOf(dataSet), null,
GraphFragment.Style.LINE, title)
} }
is BarDataSet -> { 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 startDate = Date()
val realm = Realm.getDefaultInstance()
val query = Query().merge(computedResults.group.query) val query = Query().merge(computedResults.group.query)
query.remove(QueryCondition.IsCash) query.remove(QueryCondition.IsCash)
@ -188,7 +189,9 @@ class CalendarDetailsFragment : BaseFragment(), StaticRowRepresentableDataSource
stats = requiredStats, stats = requiredStats,
query = query query = query
) )
val realm = Realm.getDefaultInstance()
val report = Calculator.computeStats(realm, options) val report = Calculator.computeStats(realm, options)
realm.close()
Timber.d("Report take: ${System.currentTimeMillis() - startDate.time}ms") 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 androidx.lifecycle.ViewModel
import net.pokeranalytics.android.calculus.ComputedResults 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.os.Bundle
import android.view.LayoutInflater 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.combined
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.ComputableResult 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.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -52,7 +51,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
* Create new instance * Create new instance
*/ */
fun newInstance(): CalendarFragment { fun newInstance(): CalendarFragment {
val fragment = CalendarFragment() val fragment =
CalendarFragment()
val bundle = Bundle() val bundle = Bundle()
fragment.arguments = bundle fragment.arguments = bundle
return fragment return fragment
@ -71,7 +71,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
private var datesForRows: HashMap<CustomizableRowRepresentable, Date> = HashMap() private var datesForRows: HashMap<CustomizableRowRepresentable, Date> = HashMap()
private var sessionTypeCondition: QueryCondition? = null private var sessionTypeCondition: QueryCondition? = null
private var currentTimeFilter: TimeFilter = TimeFilter.MONTH private var currentTimeFilter: TimeFilter =
TimeFilter.MONTH
private var currentStat = Stat.NET_RESULT private var currentStat = Stat.NET_RESULT
// Life Cycle // Life Cycle
@ -96,7 +97,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
return rows return rows
} }
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, tag: Int) {
when (currentTimeFilter) { when (currentTimeFilter) {
TimeFilter.MONTH -> { TimeFilter.MONTH -> {
@ -201,7 +202,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
// Manage time queryWith // Manage time queryWith
filterTimeMonth.setOnCheckedChangeListener { _, isChecked -> filterTimeMonth.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) { if (isChecked) {
currentTimeFilter = TimeFilter.MONTH currentTimeFilter =
TimeFilter.MONTH
filterTimeYear.isChecked = false filterTimeYear.isChecked = false
displayData() displayData()
} else if (currentTimeFilter == TimeFilter.MONTH) { } else if (currentTimeFilter == TimeFilter.MONTH) {
@ -211,7 +213,8 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
} }
filterTimeYear.setOnCheckedChangeListener { _, isChecked -> filterTimeYear.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) { if (isChecked) {
currentTimeFilter = TimeFilter.YEAR currentTimeFilter =
TimeFilter.YEAR
filterTimeMonth.isChecked = false filterTimeMonth.isChecked = false
displayData() displayData()
} else if (currentTimeFilter == TimeFilter.YEAR) { } else if (currentTimeFilter == TimeFilter.YEAR) {
@ -374,7 +377,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
val row = CustomizableRowRepresentable( val row = CustomizableRowRepresentable(
customViewType = RowViewType.TITLE_VALUE_ARROW, customViewType = RowViewType.TITLE_VALUE_ARROW,
title = date.getDateMonth(), title = date.getDateMonth(),
computedStat = computedStat, valueTextFormat = computedStat.textFormat,
isSelectable = true isSelectable = true
) )
@ -391,7 +394,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
val row = CustomizableRowRepresentable( val row = CustomizableRowRepresentable(
customViewType = RowViewType.TITLE_VALUE_ARROW, customViewType = RowViewType.TITLE_VALUE_ARROW,
title = date.getDateYear(), title = date.getDateYear(),
computedStat = computedStat, valueTextFormat = computedStat.textFormat,
isSelectable = true 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.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import net.pokeranalytics.android.R 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.realm.Bankroll
import net.pokeranalytics.android.model.retrofit.CurrencyConverterValue
import net.pokeranalytics.android.ui.activity.CurrenciesActivity import net.pokeranalytics.android.ui.activity.CurrenciesActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource 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.UserDefaults
import net.pokeranalytics.android.util.extensions.toCurrency import net.pokeranalytics.android.util.extensions.toCurrency
import net.pokeranalytics.android.util.extensions.toRate import net.pokeranalytics.android.util.extensions.toRate
import retrofit2.Call
import retrofit2.Response
import java.util.* import java.util.*
/** /**
* Custom EditableDataFragment to manage the Bankroll data * Custom EditableDataFragment to manage the Bankroll data
*/ */
@ -37,7 +34,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
// Return the item as a Bankroll object // Return the item as a Bankroll object
private val bankroll: Bankroll private val bankroll: Bankroll
get() { get() {
return this.viewModel.item as Bankroll return this.model.item as Bankroll
} }
private lateinit var defaultCurrency: Currency private lateinit var defaultCurrency: Currency
@ -83,7 +80,11 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
return rows return rows
} }
override fun stringForRow(row: RowRepresentable): String { override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) { return when (row) {
SimpleRow.NAME -> if (bankroll.name.isNotEmpty()) bankroll.name else NULL_TEXT SimpleRow.NAME -> if (bankroll.name.isNotEmpty()) bankroll.name else NULL_TEXT
BankrollRow.CURRENCY -> { BankrollRow.CURRENCY -> {
@ -104,7 +105,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
val rate = this.bankroll.currency?.rate ?: 1.0 val rate = this.bankroll.currency?.rate ?: 1.0
rate.toRate() 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) { return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.bankroll.name)) SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.bankroll.name))
BankrollRow.INITIAL_VALUE -> { 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) { when (row) {
BankrollRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@BankrollDataFragment, BankrollRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@BankrollDataFragment,
RequestCode.CURRENCY.value RequestCode.CURRENCY.value
) )
BankrollRow.REFRESH_RATE -> refreshRate() 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) { if (System.currentTimeMillis() - lastRefreshRateCall < 10 * 1000 || isRefreshingRate) {
return return
} }
lastRefreshRateCall = System.currentTimeMillis() this.lastRefreshRateCall = System.currentTimeMillis()
val currenciesConverterValue = "${bankroll.currency?.code}_${defaultCurrency.currencyCode}" 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) { FreeConverterApi.currencyRate(currenciesConverterValue, requireContext()) { rate ->
isRefreshingRate = false onRowValueChanged(rate, BankrollRow.RATE)
rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE) isRefreshingRate = false
} rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE)
}) }
isRefreshingRate = true // val call = CurrencyConverterApi.getApi(requireContext())?.convert(currenciesConverterValue)
rowRepresentableAdapter.refreshRow(BankrollRow.REFRESH_RATE) // 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -33,7 +34,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
// Return the item as a Custom TypedCSVField object // Return the item as a Custom TypedCSVField object
private val customField: CustomField private val customField: CustomField
get() { get() {
return this.viewModel.item as CustomField return this.model.item as CustomField
} }
private val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() { private val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
@ -120,10 +121,14 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
return customField.adapterRows() return customField.adapterRows()
} }
override fun stringForRow(row: RowRepresentable): String { override fun charSequenceForRow(
row: RowRepresentable,
context: Context,
tag: Int
): CharSequence {
return when (row) { return when (row) {
SimpleRow.NAME -> if (customField.name.isNotEmpty()) customField.name else NULL_TEXT 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) { return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.customField.name)) SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.customField.name))
is CustomFieldEntry -> row.editingDescriptors(mapOf("defaultValue" to row.value)) 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) { when (row) {
is CustomFieldEntry -> { is CustomFieldEntry -> {
val data = customField.editDescriptors(row) 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.app.Activity
import android.content.Intent import android.content.Intent
@ -13,14 +13,13 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.ConfigurationException import net.pokeranalytics.android.exceptions.ConfigurationException
import net.pokeranalytics.android.model.interfaces.Savable import net.pokeranalytics.android.model.interfaces.Savable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus 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.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel import net.pokeranalytics.android.ui.viewmodel.DataManagerViewModel
open class DataManagerFragment : RealmFragment() { open class DataManagerFragment : RealmFragment() {
protected val viewModel: DataManagerViewModel by lazy { protected val model: DataManagerViewModel by lazy {
ViewModelProviders.of(this).get(DataManagerViewModel::class.java) ViewModelProviders.of(this).get(DataManagerViewModel::class.java)
} }
@ -41,7 +40,6 @@ open class DataManagerFragment : RealmFragment() {
this.updateMenuUI() this.updateMenuUI()
} }
private var editableMenu: Menu? = null private var editableMenu: Menu? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -49,24 +47,24 @@ open class DataManagerFragment : RealmFragment() {
loadItem() loadItem()
} }
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu?.clear() menu.clear()
inflater?.inflate(R.menu.toolbar_editable_data, menu) inflater.inflate(R.menu.toolbar_editable_data, menu)
this.editableMenu = menu this.editableMenu = menu
updateMenuUI() updateMenuUI()
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }
/** /**
* Update menu UI * Update menu UI
*/ */
private fun updateMenuUI() { private fun updateMenuUI() {
editableMenu?.findItem(R.id.delete)?.isVisible = this.deleteButtonShouldAppear editableMenu?.findItem(R.id.delete)?.isVisible = this.deleteButtonShouldAppear
editableMenu?.findItem(R.id.save)?.isVisible = this.saveButtonShouldAppear editableMenu?.findItem(R.id.save)?.isVisible = this.saveButtonShouldAppear
} }
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item!!.itemId) { when (item.itemId) {
R.id.save -> saveData() R.id.save -> saveData()
R.id.delete -> deleteData() R.id.delete -> deleteData()
} }
@ -79,7 +77,7 @@ open class DataManagerFragment : RealmFragment() {
private fun loadItem() { private fun loadItem() {
// this.item = this.liveDataType.updateOrCreate(this.getRealm(), primaryKey) // 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() { protected open fun saveData() {
val savable = this.viewModel.item val savable = this.model.item
this.willSaveData() this.willSaveData()
when (savable) { when (savable) {
@ -97,7 +95,7 @@ open class DataManagerFragment : RealmFragment() {
when (status) { when (status) {
SaveValidityStatus.VALID -> { SaveValidityStatus.VALID -> {
this.getRealm().executeTransaction { this.getRealm().executeTransaction {
val managedItem = it.copyToRealmOrUpdate(this.viewModel.item) val managedItem = it.copyToRealmOrUpdate(this.model.item)
if (managedItem is Savable) { if (managedItem is Savable) {
val uniqueIdentifier = managedItem.id val uniqueIdentifier = managedItem.id
finishActivityWithResult(uniqueIdentifier) finishActivityWithResult(uniqueIdentifier)
@ -135,14 +133,14 @@ open class DataManagerFragment : RealmFragment() {
val realm = this.getRealm() val realm = this.getRealm()
if (this.viewModel.item.isValidForDelete(realm)) { if (this.model.item.isValidForDelete(realm)) {
val intent = Intent() 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?.setResult(Activity.RESULT_OK, intent)
activity?.finish() activity?.finish()
} else { } else {
val status = this.viewModel.item.getDeleteStatus(requireContext(), realm) val status = this.model.item.getDeleteStatus(requireContext(), realm)
val message = this.viewModel.item.getFailedDeleteMessage(status) val message = this.model.item.getFailedDeleteMessage(status)
val builder = AlertDialog.Builder(requireContext()) val builder = AlertDialog.Builder(requireContext())
.setMessage(message) .setMessage(message)
.setNegativeButton(R.string.ok, null) .setNegativeButton(R.string.ok, null)
@ -157,7 +155,7 @@ open class DataManagerFragment : RealmFragment() {
*/ */
private fun finishActivityWithResult(uniqueIdentifier: String) { private fun finishActivityWithResult(uniqueIdentifier: String) {
val intent = Intent() 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) intent.putExtra(EditableDataActivity.IntentKey.PRIMARY_KEY.keyName, uniqueIdentifier)
activity?.setResult(Activity.RESULT_OK, intent) activity?.setResult(Activity.RESULT_OK, intent)
activity?.finish() 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.Context
import android.content.Intent import android.content.Intent
@ -7,7 +7,6 @@ import androidx.fragment.app.Fragment
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.components.MediaActivity import net.pokeranalytics.android.ui.activity.components.MediaActivity
import net.pokeranalytics.android.ui.fragment.data.*
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -22,9 +21,9 @@ class EditableDataActivity : MediaActivity() {
/** /**
* Default constructor * 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) val intent = Intent(context, EditableDataActivity::class.java)
intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType) intent.putExtra(IntentKey.DATA_TYPE.keyName, dataType.ordinal)
primaryKey?.let { primaryKey?.let {
intent.putExtra(IntentKey.PRIMARY_KEY.keyName, it) 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