Merge branch 'dev' of gitlab.com:stax-river/poker-analytics into dev

dev
Laurent 6 years ago
commit b1fce6a99d
  1. 17
      app/build.gradle
  2. 1
      app/proguard-rules.pro
  3. 8
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/BankrollInstrumentedUnitTest.kt
  4. 40
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/CustomFieldFilterInstrumentedUnitTest.kt
  5. 12
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/filter/TransactionFilterInstrumentedUnitTest.kt
  6. 26
      app/src/main/AndroidManifest.xml
  7. 6
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  8. 5
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  9. 34
      app/src/main/java/net/pokeranalytics/android/calculus/ComputedStat.kt
  10. 14
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  11. 61
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  12. 13
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt
  13. 87
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt
  14. 118
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReportManager.kt
  15. 3
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  16. 3
      app/src/main/java/net/pokeranalytics/android/model/Criteria.kt
  17. 42
      app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt
  18. 3
      app/src/main/java/net/pokeranalytics/android/model/filter/Query.kt
  19. 81
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  20. 4
      app/src/main/java/net/pokeranalytics/android/model/interfaces/CountableUsage.kt
  21. 7
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Dated.kt
  22. 13
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt
  23. 7
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  24. 29
      app/src/main/java/net/pokeranalytics/android/model/migrations/Patcher.kt
  25. 7
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  26. 28
      app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt
  27. 6
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  28. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt
  29. 99
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt
  30. 31
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomFieldEntry.kt
  31. 101
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  32. 27
      app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt
  33. 5
      app/src/main/java/net/pokeranalytics/android/model/realm/Location.kt
  34. 4
      app/src/main/java/net/pokeranalytics/android/model/realm/ReportSetup.kt
  35. 5
      app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt
  36. 176
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  37. 11
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  38. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt
  39. 23
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt
  40. 18
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentName.kt
  41. 26
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  42. 63
      app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt
  43. 39
      app/src/main/java/net/pokeranalytics/android/model/utils/DataUtils.kt
  44. 22
      app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt
  45. 31
      app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt
  46. 7
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt
  47. 21
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionUtils.kt
  48. 48
      app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt
  49. 38
      app/src/main/java/net/pokeranalytics/android/ui/activity/BillingActivity.kt
  50. 2
      app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt
  51. 2
      app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersActivity.kt
  52. 58
      app/src/main/java/net/pokeranalytics/android/ui/activity/FiltersListActivity.kt
  53. 88
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  54. 86
      app/src/main/java/net/pokeranalytics/android/ui/activity/ImportActivity.kt
  55. 27
      app/src/main/java/net/pokeranalytics/android/ui/activity/NewDataMenuActivity.kt
  56. 27
      app/src/main/java/net/pokeranalytics/android/ui/activity/SessionActivity.kt
  57. 33
      app/src/main/java/net/pokeranalytics/android/ui/activity/Top10Activity.kt
  58. 9
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt
  59. 2
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/PokerAnalyticsActivity.kt
  60. 114
      app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedSessionRowRepresentableAdapter.kt
  61. 10
      app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedTransactionRowRepresentableAdapter.kt
  62. 4
      app/src/main/java/net/pokeranalytics/android/ui/adapter/HomePagerAdapter.kt
  63. 5
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableAdapter.kt
  64. 22
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt
  65. 163
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollDetailsFragment.kt
  66. 148
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt
  67. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt
  68. 9
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CurrenciesFragment.kt
  69. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
  70. 200
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt
  71. 38
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt
  72. 36
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt
  73. 112
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersListFragment.kt
  74. 19
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  75. 71
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt
  76. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/MoreFragment.kt
  77. 63
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt
  78. 34
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  79. 5
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt
  80. 34
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SubscriptionFragment.kt
  81. 175
      app/src/main/java/net/pokeranalytics/android/ui/fragment/Top10Fragment.kt
  82. 11
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/DeletableItemFragment.kt
  83. 7
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/RealmFragment.kt
  84. 48
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/bottomsheet/BottomSheetType.kt
  85. 30
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputDoubleEditTextFragment.kt
  86. 24
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputEditTextFragment.kt
  87. 19
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputEditTextMultiLinesFragment.kt
  88. 62
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputFragment.kt
  89. 50
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputFragmentType.kt
  90. 22
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputListFragment.kt
  91. 17
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputListGameFragment.kt
  92. 8
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputMultiSelectionFragment.kt
  93. 26
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputNumericTextFragment.kt
  94. 17
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputStaticListFragment.kt
  95. 19
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputSumFragment.kt
  96. 10
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/input/InputTableSizeGridFragment.kt
  97. 37
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/BankrollDataFragment.kt
  98. 25
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt
  99. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/DataManagerFragment.kt
  100. 3
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -29,8 +29,8 @@ android {
applicationId "net.pokeranalytics.android"
minSdkVersion 23
targetSdkVersion 28
versionCode 30
versionName "2.0"
versionCode 50
versionName "2.1.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -39,6 +39,7 @@ android {
ext.enableCrashlytics = false
}
release {
useProguard false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
applicationVariants.all { variant ->
@ -78,7 +79,7 @@ dependencies {
// Android
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.2.0-alpha01'
implementation 'androidx.core:core-ktx:1.2.0-alpha02'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
@ -99,7 +100,7 @@ dependencies {
implementation 'com.android.billingclient:billing:1.2.2'
// Firebase
implementation 'com.google.firebase:firebase-core:16.0.9'
implementation 'com.google.firebase:firebase-core:17.0.0'
// Crashlytics
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
@ -114,10 +115,10 @@ dependencies {
implementation 'org.apache.commons:commons-csv:1.6'
// Instrumented Tests
androidTestImplementation 'androidx.test:core:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
// Test
testImplementation 'junit:junit:4.12'

@ -29,6 +29,7 @@
-dontwarn javax.**
-dontwarn io.realm.**
-keep class net.pokeranalytics.android.model.** { *; }
-keep class net.pokeranalytics.android.ui.fragment.** { *; }
# Retrofit

@ -19,7 +19,7 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
TransactionType.Value.values().forEachIndexed { index, value ->
val type = TransactionType()
type.additive = value.additive
type.kind = index
type.kind = value.uniqueIdentifier
type.lock = true
realm.insertOrUpdate(type)
}
@ -72,12 +72,12 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
}
val br1 = realm.where(Bankroll::class.java).equalTo("name", "br1").findFirst()
val brSetup1 = BankrollReportSetup(br1)
val brSetup1 = BankrollReportSetup(br1?.id)
val report1 = BankrollCalculator.computeReport(realm, brSetup1)
Assert.assertEquals(400.0, report1.total, EPSILON)
val br2 = realm.where(Bankroll::class.java).equalTo("name", "br2").findFirst()
val brSetup2 = BankrollReportSetup(br2)
val brSetup2 = BankrollReportSetup(br2?.id)
val report2 = BankrollCalculator.computeReport(realm, brSetup2)
Assert.assertEquals(2000.0, report2.total, EPSILON)
@ -116,7 +116,7 @@ class BankrollInstrumentedUnitTest : SessionInstrumentedUnitTest() {
}
val brSetup1 = BankrollReportSetup(br1)
val brSetup1 = BankrollReportSetup(br1?.id)
val report1 = BankrollCalculator.computeReport(realm, brSetup1)
Assert.assertEquals(400.0, report1.total, EPSILON)

@ -22,12 +22,11 @@ class CustomFieldFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val realm = this.mockRealm
realm.beginTransaction()
val cf1 = CustomField()
cf1.id = "1"
val cf1 = realm.createObject(CustomField::class.java, "1")
cf1.type = CustomField.Type.LIST.ordinal
val cfe1 = CustomFieldEntry()
val cfe2 = CustomFieldEntry()
val cfe1 = realm.createObject(CustomFieldEntry::class.java, "9")
val cfe2 = realm.createObject(CustomFieldEntry::class.java, "8")
cfe1.value = "super"
cfe2.value = "nul"
@ -51,35 +50,38 @@ class CustomFieldFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
@Test
fun testCustomFieldAmountFilter() {
val realm = this.mockRealm
realm.beginTransaction()
val cfId = "1234"
var s2Id = ""
val cf1 = CustomField()
cf1.id = "1234"
cf1.type = CustomField.Type.AMOUNT.ordinal
val realm = this.mockRealm
realm.executeTransaction {
val cf = realm.createObject(CustomField::class.java, cfId)
cf.type = CustomField.Type.AMOUNT.ordinal
val cfe1 = CustomFieldEntry()
cfe1.id = "999"
cf1.entries.add(cfe1)
val cfe1 = realm.createObject(CustomFieldEntry::class.java, "999")
cf.entries.add(cfe1)
cfe1.numericValue = 30.0
val cfe2 = CustomFieldEntry()
cfe2.id = "888"
cf1.entries.add(cfe2)
val cfe2 = realm.createObject(CustomFieldEntry::class.java, "888")
cf.entries.add(cfe2)
cfe2.numericValue = 100.0
val s1 = Session.testInstance(100.0, false, Date(), 1)
s1.customFieldEntries.add(cfe1)
val s2 = Session.testInstance(100.0, true, Date(), 1)
s2.customFieldEntries.add(cfe2)
realm.commitTransaction()
s2Id = s2.id
}
val sessions = Filter.queryOn<Session>(realm, Query(QueryCondition.CustomFieldNumberQuery(cf1.id, 100.0)))
val condition = QueryCondition.CustomFieldNumberQuery(cfId, 100.0)
val sessions = Filter.queryOn<Session>(realm, Query(condition))
Assert.assertEquals(1, sessions.size)
sessions[0]?.run {
Assert.assertEquals(s2.id, (this).id)
sessions.first()?.run {
Assert.assertEquals(s2Id, this.id)
}
}
}

@ -2,18 +2,16 @@ package net.pokeranalytics.android.unitTests.filter
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.realm.RealmList
import io.realm.RealmResults
import net.pokeranalytics.android.R
import net.pokeranalytics.android.components.BaseFilterInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.model.realm.TransactionType
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import java.util.*
@RunWith(AndroidJUnit4::class)
class TransactionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
@ -29,7 +27,7 @@ class TransactionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val name = "test"
type.name = name
type.additive = value.additive
type.kind = index
type.kind = value.uniqueIdentifier
type.lock = true
realm.insertOrUpdate(type)
}

@ -32,6 +32,13 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.ImportActivity"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@ -45,23 +52,16 @@
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.ImportActivity"
android:screenOrientation="portrait"
android:launchMode="singleTop">
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.SessionActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing" />
<!-- No screenOrientation="portrait" to fix Oreo crash -->
<activity
android:name="net.pokeranalytics.android.ui.activity.NewDataMenuActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/PokerAnalyticsTheme.MenuDialog" />
<activity
@ -74,6 +74,11 @@
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.Top10Activity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.SettingsActivity"
android:launchMode="singleTop"
@ -109,6 +114,11 @@
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FiltersListActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.EditableDataActivity"
android:launchMode="singleTop"

@ -33,7 +33,7 @@ class PokerAnalyticsApplication : Application() {
Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(6)
.schemaVersion(7)
.migration(PokerAnalyticsMigration())
.initialData(Seed(this))
.build()
@ -72,10 +72,10 @@ class PokerAnalyticsApplication : Application() {
private fun createFakeSessions() {
val realm = Realm.getDefaultInstance()
val sessionsCount = realm.where<Session>().findAll().size
val sessionsCount = realm.where<Session>().count()
realm.close()
if (sessionsCount < 1) {
if (sessionsCount < 10) {
GlobalScope.launch {
FakeDataManager.createFakeSessions(200)
}

@ -4,6 +4,7 @@ import android.content.Context
import io.realm.Realm
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat.*
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.extensions.hourlyDuration
@ -95,7 +96,7 @@ class Calculator {
TABLE -> TableReportActivity::class.java
PROGRESS -> ProgressReportActivity::class.java
COMPARISON -> ComparisonReportActivity::class.java
else -> throw IllegalStateException("undefined activity for report display")
else -> throw PAIllegalStateException("undefined activity for report display")
// MAP -> R.string.map
// POLYNOMIAL -> null
@ -378,7 +379,7 @@ class Calculator {
}
val session =
computable.session ?: throw IllegalStateException("Computing lone ComputableResult")
computable.session ?: throw PAIllegalStateException("Computing lone ComputableResult")
results.addEvolutionValue(tSum, stat = NET_RESULT, data = session)
results.addEvolutionValue(tSum / index, stat = AVERAGE, data = session)
results.addEvolutionValue(index.toDouble(), stat = NUMBER_OF_GAMES, data = session)

@ -0,0 +1,34 @@
package net.pokeranalytics.android.calculus
import net.pokeranalytics.android.util.TextFormat
import java.util.*
/**
* ComputedStat contains a [stat] and their associated [value]
*/
class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? = null, var currency: Currency? = null) {
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) {
if (previousValue != null) {
this.variation = (value - previousValue) / previousValue
}
}
/**
* The value used to get evolution dataset
*/
var progressValue: Double? = null
/**
* The variation of the stat
*/
var variation: Double? = null
/**
* Formats the value of the stat to be suitable for display
*/
fun format(): TextFormat {
return this.stat.format(this.value, this.secondValue, this.currency)
}
}

@ -5,9 +5,11 @@ import com.github.mikephil.charting.data.*
import io.realm.Realm
import io.realm.RealmResults
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.model.interfaces.GraphIdentifiableEntry
//import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.SessionSet
@ -173,7 +175,8 @@ class ComputableGroup(var query: Query, var stats: List<Stat>? = null) {
}
class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) : GraphUnderlyingEntry {
class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) :
GraphUnderlyingEntry {
/**
* The session group used to computed the statIds
@ -195,7 +198,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
/**
* Adds a value to the evolution values
*/
fun addEvolutionValue(value: Double, duration: Double? = null, stat: Stat, data: Timed) {
fun addEvolutionValue(value: Double, duration: Double? = null, stat: Stat, data: GraphIdentifiableEntry) {
val point = if (duration != null) {
Point(duration, y = value, data = data.objectIdentifier)
@ -322,7 +325,8 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
computedStat.progressValue = Stat.returnOnInvestment(netResult, totalBuyin)
}
}
else -> {}
else -> {
}
}
}
@ -437,7 +441,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
this.computedStat(stat)?.let {
return it.format()
} ?: run {
throw IllegalStateException("Missing stat in results")
throw PAIllegalStateException("Missing stat in results")
}
}

@ -3,6 +3,7 @@ package net.pokeranalytics.android.calculus
import android.content.Context
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FormattingException
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
@ -14,6 +15,7 @@ import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
import net.pokeranalytics.android.util.extensions.toCurrency
import java.util.*
import kotlin.math.exp
import kotlin.math.pow
class StatFormattingException(message: String) : Exception(message) {
@ -91,7 +93,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
}
val numerator = -2 * hourlyRate * bankrollValue
val denominator = Math.pow(hourlyStandardDeviation, 2.0)
val denominator = hourlyStandardDeviation.pow(2.0)
val ratio = numerator / denominator
return exp(ratio)
@ -127,7 +129,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
MAXIMUM_DURATION -> R.string.longest_session
DAYS_PLAYED -> R.string.days_played
TOTAL_BUYIN -> R.string.total_buyin
else -> throw IllegalStateException("Stat ${this.name} name required but undefined")
else -> throw PAIllegalStateException("Stat ${this.name} name required but undefined")
}
}
@ -159,12 +161,17 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> {
return TextFormat(value.formattedHourlyDuration())
} // red/green percentages
WIN_RATIO, ROI, RISK_OF_RUIN -> {
WIN_RATIO, ROI -> {
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr
}
RISK_OF_RUIN -> {
val color = if (value * 100 <= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color)
}
// white amountsr
AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS -> {
STANDARD_DEVIATION_BB_PER_100_HANDS, TOTAL_BUYIN -> {
return TextFormat(value.toCurrency(currency))
}
LONGEST_STREAKS -> {
@ -177,6 +184,7 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
private val threshold: Double
get() {
return when (this) {
RISK_OF_RUIN -> 5.0
WIN_RATIO -> 50.0
else -> 0.0
}
@ -235,13 +243,24 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
}
}
val legendHideRightValue: Boolean
get() {
return when (this) {
AVERAGE, NUMBER_OF_SETS, NUMBER_OF_GAMES, WIN_RATIO,
HOURLY_DURATION, AVERAGE_HOURLY_DURATION -> true
else -> false
}
}
/**
* Returns if the stat has a significant value to display in a progress graph
*/
val graphSignificantIndividualValue: Boolean
get() {
return when (this) {
WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false
AVERAGE, WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES,
STANDARD_DEVIATION, HOURLY_DURATION -> false
else -> true
}
}
@ -294,33 +313,3 @@ enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepres
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
}
/**
* ComputedStat contains a [stat] and their associated [value]
*/
class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? = null, var currency: Currency? = null) {
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) {
if (previousValue != null) {
this.variation = (value - previousValue) / previousValue
}
}
/**
* The value used to get evolution dataset
*/
var progressValue: Double? = null
/**
* The variation of the stat
*/
var variation: Double? = null
/**
* Formats the value of the stat to be suitable for display
*/
fun format(): TextFormat {
return this.stat.format(this.value, this.secondValue, this.currency)
}
}

@ -5,7 +5,9 @@ import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.util.extensions.findById
class BankrollCalculator {
@ -13,12 +15,15 @@ class BankrollCalculator {
fun computeReport(realm: Realm, setup: BankrollReportSetup) : BankrollReport {
//val realm = Realm.getDefaultInstance()
val report = BankrollReport(setup)
realm.refresh() // fixes an issue where a newly created bankroll is not found, throwing an exception
val bankrolls: List<Bankroll> =
if (setup.bankroll != null) listOf(setup.bankroll)
if (setup.bankrollId != null) {
val bankroll = realm.findById<Bankroll>(setup.bankrollId) ?: throw PAIllegalStateException("Bankroll not found with id=${setup.bankrollId}")
report.currency = bankroll.utilCurrency
listOf(bankroll)
}
else realm.where(Bankroll::class.java).findAll()
var initialValue = 0.0
@ -41,7 +46,7 @@ class BankrollCalculator {
report.transactionsNet = transactionNet
report.initial = initialValue
val query = setup.query
val query = setup.query(realm)
val transactions = Filter.queryOn<Transaction>(realm, query)
report.addDatedItems(transactions)

@ -3,54 +3,28 @@ package net.pokeranalytics.android.calculus.bankroll
import android.content.Context
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineDataSet
import io.realm.Realm
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.DatedValue
import net.pokeranalytics.android.model.interfaces.DatedBankrollGraphEntry
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.graph.DataSetFactory
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.extensions.findById
import java.util.*
import kotlin.collections.HashMap
//object BankrollReportManager {
//
// var mainReport: BankrollReport? = null
// var reports: MutableMap<String, BankrollReport> = mutableMapOf()
//
// fun udpateBankrolls(bankrolls: List<Bankroll>) {
// this.invalidateMainReport()
// bankrolls.forEach {
// this.reports.remove(it.id)
// }
// }
//
// fun deleteBankrolls(bankrolls: List<Bankroll>) {
// this.invalidateMainReport()
// bankrolls.forEach {
// this.reports.remove(it.id)
// }
// }
//
// private fun invalidateMainReport() {
// this.mainReport = null
// }
//
// private fun launchReports(bankrolls: List<Bankroll>) {
//
// this.mainReport = BankrollCalculator.computeReport()
//
//
// }
//
//}
/**
* This class holds the results from the BankrollCalculator computations
* It has all the information required for the Bankroll various displays
*/
class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable {
class BankrollReport(var setup: BankrollReportSetup) {
/**
* The java.util.Currency
*/
var currency: Currency? = null
/**
* The value of the bankroll
@ -148,21 +122,12 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable {
/**
* The list of dated items used for the graph
*/
private var evolutionItems: MutableList<DatedValue> = mutableListOf()
override val viewType: Int
get() {
return if (setup.bankroll == null) {
RowViewType.LEGEND_DEFAULT.ordinal
} else {
RowViewType.TITLE_VALUE_ARROW.ordinal
}
}
private var evolutionItems: MutableList<DatedBankrollGraphEntry> = mutableListOf()
/**
* Adds a list of dated items to the evolution items used to get the bankroll graph
*/
fun addDatedItems(items: Collection<DatedValue>) {
fun addDatedItems(items: Collection<DatedBankrollGraphEntry>) {
this.evolutionItems.addAll(items)
}
@ -176,7 +141,7 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable {
var bucket = this.transactionBuckets[type.id]
if (bucket == null) {
val b = TransactionBucket(this.setup.virtualBankroll)
val b = TransactionBucket(type.name, this.setup.virtualBankroll)
this.transactionBuckets[type.id] = b
bucket = b
}
@ -184,7 +149,7 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable {
bucket.addTransaction(transaction)
} ?: run {
throw IllegalStateException("Transaction has no type")
throw PAIllegalStateException("Transaction has no type")
}
}
@ -200,10 +165,14 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable {
this.evolutionItems.sortBy { it.date }
var total = 0.0
var total = this.initial
this.evolutionItems.forEach {
total += it.amount
val point = BRGraphPoint(total, it.date, it)
val rate = it.bankroll?.rate ?: 1.0
// Timber.d("rate = $rate, amount = ${it.amount}")
total += it.amount * rate
// Timber.d("total = $total")
val point = BRGraphPoint(total, it.date, it.objectIdentifier)
this.evolutionPoints.add(point)
}
@ -228,7 +197,7 @@ class BankrollReport(var setup: BankrollReportSetup) : RowRepresentable {
* A class describing the parameters required to launch a bankroll report
*
*/
class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null, val to: Date? = null) {
class BankrollReportSetup(val bankrollId: String? = null, val from: Date? = null, val to: Date? = null) {
/**
* Returns whether the setup concerns the virtual bankroll,
@ -236,17 +205,17 @@ class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null
*/
val virtualBankroll: Boolean
get() {
return this.bankroll == null
return this.bankrollId == null
}
/**
* the query used to get bankroll transactions
*/
val query: Query
get() {
fun query(realm: Realm): Query {
val query = Query()
this.bankroll?.let {
this.bankrollId?.let {
val bankroll = realm.findById<Bankroll>(it) ?: throw PAIllegalStateException("Bankroll not found with id $it")
val bankrollCondition = QueryCondition.AnyBankroll(bankroll)
query.add(bankrollCondition)
}
@ -276,7 +245,7 @@ class BankrollReportSetup(val bankroll: Bankroll? = null, val from: Date? = null
/**
* A TransactionBucket holds a list of _transactions and computes its amount sum
*/
class TransactionBucket(useRate: Boolean = false) {
class TransactionBucket(var name: String, useRate: Boolean = false) {
/**
* Whether the bankroll rate should be used
@ -317,6 +286,6 @@ class TransactionBucket(useRate: Boolean = false) {
data class BRGraphPoint(var value: Double, var date: Date, var data: Any? = null) {
var variation: Double = 0.0
// var variation: Double = 0.0
}

@ -0,0 +1,118 @@
package net.pokeranalytics.android.calculus.bankroll
import io.realm.Realm
import io.realm.RealmResults
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Transaction
import timber.log.Timber
import java.util.*
import kotlin.coroutines.CoroutineContext
object BankrollReportManager {
val coroutineContext: CoroutineContext
get() = Dispatchers.Main
private var reports: MutableMap<String?, BankrollReport> = mutableMapOf()
private var computableResults: RealmResults<ComputableResult>
private var bankrolls: RealmResults<Bankroll>
private var transactions: RealmResults<Transaction>
init {
val realm = Realm.getDefaultInstance()
computableResults = realm.where(ComputableResult::class.java).findAll()
bankrolls = realm.where(Bankroll::class.java).findAll()
transactions = realm.where(Transaction::class.java).findAll()
initializeListeners()
realm.close()
}
/**
* Listens to all objects that might have an impact on any bankroll report
*/
private fun initializeListeners() {
this.computableResults.addChangeListener { t, changeSet ->
val indexes = changeSet.changes.plus(changeSet.insertions).toList()
val bankrolls = indexes.mapNotNull { t[it]?.session?.bankroll }.toSet()
this.updateBankrolls(bankrolls)
}
this.bankrolls.addChangeListener { t, changeSet ->
val indexes = changeSet.changes.plus(changeSet.insertions).toList()
val bankrolls = indexes.mapNotNull { t[it] }.toSet()
this.updateBankrolls(bankrolls)
}
this.transactions.addChangeListener { t, changeSet ->
val indexes = changeSet.changes.plus(changeSet.insertions).toList()
val bankrolls = indexes.mapNotNull { t[it]?.bankroll }.toSet()
this.updateBankrolls(bankrolls)
}
}
fun reportForBankroll(bankrollId: String?, handler: (BankrollReport) -> Unit) {
Timber.d("Request bankroll report for bankrollId = $bankrollId")
// if the report exists, return it
val existingReport: BankrollReport? = this.reports[bankrollId]
if (existingReport != null) {
handler(existingReport)
return
}
// otherwise compute it
GlobalScope.launch(coroutineContext) {
var report: BankrollReport? = null
val coroutine = GlobalScope.async {
val s = Date()
Timber.d(">>>>> start computing bankroll...")
val realm = Realm.getDefaultInstance()
val setup = BankrollReportSetup(bankrollId)
report = BankrollCalculator.computeReport(realm, setup)
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>>>> ended in $duration seconds")
}
coroutine.await()
report?.let {
handler(it)
}
}
}
/**
* Notifies the manager of cases not managed by RealmResults listener, such as deletions
*/
fun notifyBankrollReportImpact(bankrollId: String) {
this.reports.remove(bankrollId)
this.reports.remove(null)
}
private fun updateBankrolls(bankrolls: Set<Bankroll>) {
this.invalidateReport(bankrolls)
}
private fun invalidateReport(bankrolls: Set<Bankroll>) {
this.reports.remove(null)
bankrolls.forEach { br ->
this.reports.remove(br.id)
}
}
}

@ -10,6 +10,7 @@ class ConfigurationException(message: String) : Exception(message)
class EnumIdentifierNotFoundException(message: String) : Exception(message)
class MisconfiguredSavableEnumException(message: String) : Exception(message)
class PAIllegalStateException(message: String) : Exception(message)
sealed class PokerAnalyticsException(message: String) : Exception(message) {
object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition")
@ -20,6 +21,8 @@ sealed class PokerAnalyticsException(message: String) : Exception(message) {
object QueryTypeUnhandled: PokerAnalyticsException(message = "queryWith type not handled")
object QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected")
object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "queryWith is empty or null")
object InputFragmentException : PokerAnalyticsException(message = "RowEditableDelegate must be a Fragment")
object DateTimePickerException: PokerAnalyticsException(message = "DataSource is not a DateRowEditableDescriptor")
data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "queryWith element '$filterElementRow' type is missing")
data class QueryValueMapMissingKeys(val missingKeys: List<String>) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys")
data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow")

@ -4,6 +4,7 @@ import io.realm.Realm
import io.realm.Sort
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.Criteria.Bankrolls.comparison
import net.pokeranalytics.android.model.Criteria.Blinds.comparison
@ -329,7 +330,7 @@ interface CustomFieldCriteria {
var customFieldId: String
fun customField(realm: Realm) : CustomField {
return realm.findById(this.customFieldId) ?: throw IllegalStateException("Custom field not found")
return realm.findById(this.customFieldId) ?: throw PAIllegalStateException("Custom field not found")
}
fun customFieldType(realm: Realm): Int {

@ -1,6 +1,10 @@
package net.pokeranalytics.android.model.extensions
import android.content.Context
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.extensions.toCurrency
import java.util.*
enum class SessionState {
@ -39,7 +43,45 @@ fun Session.getState(): SessionState {
return SessionState.STARTED
}
}
}
/**
* Formate the session game type
*/
fun Session.getFormattedGameType(context: Context): String {
var parameters = mutableListOf<String>()
if (isTournament()) {
tournamentEntryFee?.let {
parameters.add(it.toCurrency(currency))
}
tournamentName?.let {
parameters.add(it.name)
} ?: run {
parameters.add(getFormattedGame())
tournamentType?.let { type ->
parameters.add(TournamentType.values()[type].localizedTitle(context))
}
}
if (parameters.size == 0) {
parameters.add(context.getString(R.string.tournament).capitalize())
}
} else {
if (cgSmallBlind != null && cgBigBlind != null) {
parameters.add(getFormattedBlinds())
}
game?.let {
parameters.add(getFormattedGame())
}
if (parameters.size == 0) {
parameters.add(context.getString(R.string.cash_game).capitalize())
}
}
return parameters.joinToString(separator = " ")
}
val AbstractList<Session>.hourlyDuration: Double

@ -2,7 +2,6 @@ package net.pokeranalytics.android.model.filter
import android.content.Context
import io.realm.RealmQuery
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.util.NULL_TEXT
@ -77,7 +76,7 @@ class Query {
}
}
//println("<<<<<< ${realmQuery}")
// println("<<<<<< ${realmQuery.description}")
val queryLast = this.conditions.filter {
it is QueryCondition.Last
}.firstOrNull()

@ -15,7 +15,7 @@ import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.fragment.components.input.InputFragmentType
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterSectionRow
@ -257,7 +257,7 @@ sealed class QueryCondition : FilterElementRow {
override fun getDisplayName(context: Context): String {
val realm = Realm.getDefaultInstance()
val entityName = entityName(realm)
val entityName = entityName(realm, context)
val completeLabel = when (listOfValues.size) {
0 -> NULL_TEXT
1, 2 -> {
@ -269,8 +269,8 @@ sealed class QueryCondition : FilterElementRow {
return completeLabel
}
open fun entityName(realm: Realm): String {
return baseId
open fun entityName(realm: Realm, context: Context): String {
return entityName(context)
}
private fun labelForValue(realm: Realm, value: String): String {
@ -320,6 +320,10 @@ sealed class QueryCondition : FilterElementRow {
constructor(bankroll: Bankroll) : this() {
this.setObject(bankroll)
}
override fun entityName(context: Context): String {
return context.getString(R.string.bankrolls)
}
}
class AnyGame() : QueryDataCondition<Game>() {
@ -328,6 +332,10 @@ sealed class QueryCondition : FilterElementRow {
constructor(game: Game) : this() {
this.setObject(game)
}
override fun entityName(context: Context): String {
return context.getString(R.string.games)
}
}
class AnyTournamentName() : QueryDataCondition<TournamentName>() {
@ -336,6 +344,10 @@ sealed class QueryCondition : FilterElementRow {
constructor(tournamentName: TournamentName) : this() {
this.setObject(tournamentName)
}
override fun entityName(context: Context): String {
return context.getString(R.string.tournament_names)
}
}
class AnyTournamentFeature() : QueryDataCondition<TournamentFeature>() {
@ -344,6 +356,10 @@ sealed class QueryCondition : FilterElementRow {
constructor(tournamentFeature: TournamentFeature) : this() {
this.setObject(tournamentFeature)
}
override fun entityName(context: Context): String {
return context.getString(R.string.tournament_features)
}
}
class AllTournamentFeature() : QueryDataCondition<TournamentFeature>() {
@ -353,6 +369,10 @@ sealed class QueryCondition : FilterElementRow {
constructor(tournamentFeature: TournamentFeature) : this() {
this.setObject(tournamentFeature)
}
override fun entityName(context: Context): String {
return context.getString(R.string.tournament_features)
}
}
class AnyLocation() : QueryDataCondition<Location>() {
@ -361,6 +381,10 @@ sealed class QueryCondition : FilterElementRow {
constructor(location: Location) : this() {
this.setObject(location)
}
override fun entityName(context: Context): String {
return context.getString(R.string.locations)
}
}
class AnyTransactionType() : QueryDataCondition<TransactionType>() {
@ -369,27 +393,47 @@ sealed class QueryCondition : FilterElementRow {
constructor(transactionType: TransactionType) : this() {
this.setObject(transactionType)
}
override fun entityName(context: Context): String {
return context.getString(R.string.operation_types)
}
}
class AnyLimit : ListOfInt() {
override fun labelForValue(value: Int, context: Context): String {
return Limit.values()[value].getDisplayName(context)
}
override fun entityName(context: Context): String {
return context.getString(R.string.limits)
}
}
class AnyTableSize : ListOfInt() {
override fun labelForValue(value: Int, context: Context): String {
return TableSize(value).getDisplayName(context)
}
override fun entityName(context: Context): String {
return context.getString(R.string.table_sizes)
}
}
class AnyTournamentType : ListOfInt() {
override fun labelForValue(value: Int, context: Context): String {
return TournamentType.values()[value].getDisplayName(context)
}
override fun entityName(context: Context): String {
return context.getString(R.string.tournament_types)
}
}
class AnyBlind : ListOfString()
class AnyBlind : ListOfString() {
override fun entityName(context: Context): String {
return context.getString(R.string.blinds)
}
}
object Last : SingleInt() {
override var operator = Operator.EQUALS
@ -549,7 +593,7 @@ sealed class QueryCondition : FilterElementRow {
}
override val viewType: Int = RowViewType.TITLE_VALUE_CHECK.ordinal
override val bottomSheetType: BottomSheetType = BottomSheetType.DOUBLE_EDIT_TEXT
override val inputFragmentType: InputFragmentType = InputFragmentType.DOUBLE_EDIT_TEXT
override fun labelForValue(value: Int, context: Context): String {
return value.toMinutes(context)
@ -645,11 +689,11 @@ sealed class QueryCondition : FilterElementRow {
constructor(customFieldEntry: CustomFieldEntry) : this() {
this.setObject(customFieldEntry)
this.customFieldId = customFieldEntry.customFields?.firstOrNull()?.id
this.customFieldId = customFieldEntry.customField?.id
?: throw PokerAnalyticsException.QueryValueMapUnexpectedValue
}
override fun entityName(realm: Realm): String {
override fun entityName(realm: Realm, context: Context): String {
return customFieldName(realm)
}
@ -668,6 +712,7 @@ sealed class QueryCondition : FilterElementRow {
realmQuery: RealmQuery<T>,
otherQueryCondition: QueryCondition? = null
): RealmQuery<T> {
val fieldName = FilterHelper.fieldNameForQueryType<T>(this::class.java)
if (BuildConfig.DEBUG) {
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
@ -720,12 +765,14 @@ sealed class QueryCondition : FilterElementRow {
return realmQuery
}
is DuringThisWeek -> {
val startDate = Date()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.set(Calendar.DAY_OF_WEEK_IN_MONTH, Calendar.SUNDAY)
calendar.set(Calendar.HOUR_OF_DAY, 0)
calendar.clear(Calendar.MINUTE)
calendar.clear(Calendar.SECOND)
calendar.clear(Calendar.MILLISECOND)
calendar.set(Calendar.DAY_OF_WEEK, calendar.firstDayOfWeek)
return realmQuery.greaterThanOrEqualTo(fieldName, calendar.time.startOfDay()).and()
.lessThanOrEqualTo(fieldName, startDate.endOfDay())
.lessThanOrEqualTo(fieldName, Date().endOfDay())
}
is DuringThisMonth -> {
val startDate = Date()
@ -905,15 +952,15 @@ sealed class QueryCondition : FilterElementRow {
}
}
override val bottomSheetType: BottomSheetType
override val inputFragmentType: InputFragmentType
get() {
return when (this) {
is PastDay -> BottomSheetType.EDIT_TEXT
is PastDay -> InputFragmentType.EDIT_TEXT
else -> {
when (this.operator) {
Operator.MORE -> BottomSheetType.EDIT_TEXT
Operator.LESS -> BottomSheetType.EDIT_TEXT
else -> BottomSheetType.NONE
Operator.MORE -> InputFragmentType.EDIT_TEXT
Operator.LESS -> InputFragmentType.EDIT_TEXT
else -> InputFragmentType.NONE
}
}
}

@ -1,5 +1,7 @@
package net.pokeranalytics.android.model.interfaces
import io.realm.RealmModel
/**
* An interface to be able to track the usage of an object
*/
@ -7,4 +9,6 @@ interface CountableUsage : Identifiable {
var useCount: Int
get() { return 0 }
set(_) {}
val ownerClass: Class<out RealmModel>
}

@ -1,5 +1,6 @@
package net.pokeranalytics.android.model.interfaces
import net.pokeranalytics.android.model.realm.Bankroll
import java.util.*
interface Dated {
@ -13,3 +14,9 @@ interface DatedValue : Dated {
var amount: Double
}
interface DatedBankrollGraphEntry : DatedValue, GraphIdentifiableEntry {
var bankroll: Bankroll?
}

@ -45,6 +45,7 @@ interface NameManageable : Manageable {
}
}
class ObjectIdentifier(var id: String, var clazz: Class<out Identifiable>)
/**
* An interface associate a unique uniqueIdentifier to an object
@ -55,6 +56,16 @@ interface Identifiable : RealmModel {
* A unique uniqueIdentifier getter
*/
var id: String
/**
* required because "this.class" returns the proxy class, making where<class> queries crash
*/
val realmObjectClass: Class<out Identifiable>
val objectIdentifier: ObjectIdentifier
get() {
return ObjectIdentifier(this.id, this.realmObjectClass)
}
}
/**
@ -125,6 +136,6 @@ interface Deletable : Identifiable {
/**
* A method to override if we need to delete linked objects or other stuff
*/
fun deleteDependencies() {}
fun deleteDependencies(realm: Realm) {}
}

@ -1,10 +1,11 @@
package net.pokeranalytics.android.model.interfaces
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import java.util.*
interface Timed : GraphUnderlyingEntry, Identifiable {
interface GraphIdentifiableEntry : GraphUnderlyingEntry, Identifiable
interface Timed : GraphIdentifiableEntry {
fun startDate() : Date?
@ -30,6 +31,4 @@ interface Timed : GraphUnderlyingEntry, Identifiable {
val hourlyDuration: Double
get() = this.netDuration / 3600000.0
val objectIdentifier : ObjectIdentifier
}

@ -5,6 +5,7 @@ import io.realm.Realm
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.utils.Seed
import net.pokeranalytics.android.util.Preferences
class Patcher {
@ -19,7 +20,23 @@ class Patcher {
Preferences.executeOnce(Preferences.Keys.PATCH_TRANSACTION_TYPES_NAMES, context) {
patchDefaultTransactionTypes(context)
}
Preferences.executeOnce(Preferences.Keys.PATCH_BLINDS_FORMAT, context) {
patchBlindFormat()
}
val realm = Realm.getDefaultInstance()
val lockedTypes = realm.where(TransactionType::class.java).equalTo("lock", true).findAll()
if (lockedTypes.size == 3) {
Preferences.executeOnce(Preferences.Keys.ADD_NEW_TRANSACTION_TYPES, context) {
val newTypes = arrayOf(TransactionType.Value.STACKING_INCOMING, TransactionType.Value.STACKING_OUTGOING)
realm.executeTransaction {
Seed.createDefaultTransactionTypes(newTypes, context, realm)
}
}
}
realm.close()
}
private fun patchBreaks() {
@ -40,6 +57,7 @@ class Patcher {
it.computeNumberOfRebuy()
}
}
realm.close()
}
@ -58,6 +76,17 @@ class Patcher {
realm.close()
}
private fun patchBlindFormat() {
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
val sessions = realm.where(Session::class.java).findAll()
sessions.forEach { session ->
session.formatBlinds()
}
}
realm.close()
}
}
}

@ -144,6 +144,13 @@ class PokerAnalyticsMigration : RealmMigration {
schema.get("Filter")?.removeField("usageCount")
currentVersion++
}
// Migrate to version 7
if (currentVersion == 6) {
Timber.d("*** Running migration ${currentVersion + 1}")
schema.get("TransactionType")?.addField("useCount", Int::class.java)
currentVersion++
}
}
override fun equals(other: Any?): Boolean {

@ -4,16 +4,18 @@ import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.util.UserDefaults
import java.util.*
open class Bankroll : RealmObject(), NameManageable, RowRepresentable {
@ -40,7 +42,16 @@ open class Bankroll : RealmObject(), NameManageable, RowRepresentable {
val rate: Double
get() {
return this.currency?.rate ?: 1.0
return this.currency?.rate ?: Currency.DEFAULT_RATE
}
val javaCurrency: java.util.Currency
get() {
return currency?.code?.let {
java.util.Currency.getInstance(it)
} ?: run {
UserDefaults.currency
}
}
override fun getDisplayName(context: Context): String {
@ -49,7 +60,7 @@ open class Bankroll : RealmObject(), NameManageable, RowRepresentable {
override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) {
SimpleRow.NAME -> this.name = value as String? ?: ""
BankrollRow.NAME -> this.name = value as String? ?: ""
BankrollRow.LIVE -> {
this.live = if (value is Boolean) !value else false
}
@ -118,4 +129,15 @@ open class Bankroll : RealmObject(), NameManageable, RowRepresentable {
}
val utilCurrency: java.util.Currency
get() {
this.currency?.code?.let {
return java.util.Currency.getInstance(it)
}
return UserDefaults.currency
}
@Ignore
override val realmObjectClass: Class<out Identifiable> = Bankroll::class.java
}

@ -20,14 +20,10 @@ open class ComputableResult() : RealmObject(), Filterable {
var bbPer100Hands: BB = 0.0
// var sessionSet: SessionSet? = null
var session: Session? = null
fun updateWith(session: Session) {
// this.sessionSet = session.sessionSet
val rate = session.bankroll?.currency?.rate ?: 1.0
session.result?.let { result ->
@ -50,7 +46,7 @@ open class ComputableResult() : RealmObject(), Filterable {
IS_POSITIVE("isPositive"),
RATED_BUYIN("ratedBuyin"),
ESTIMATED_HANDS("estimatedHands"),
BB_PER100HANDS("bbPer100Hands")
// BB_PER100HANDS("bbPer100Hands")
}
companion object {

@ -8,8 +8,12 @@ import java.util.*
open class Currency : RealmObject() {
companion object {
@Ignore
val DEFAULTRATE: Double = 1.0
val DEFAULT_RATE: Double = 1.0
}
@PrimaryKey
var id = UUID.randomUUID().toString()
@ -22,11 +26,11 @@ open class Currency : RealmObject() {
/**
* The rate of the currency with the main currency
*/
var rate: Double? = DEFAULTRATE
var rate: Double? = DEFAULT_RATE
fun refreshRelatedRatedValues() {
val rate = this.rate ?: DEFAULTRATE
val rate = this.rate ?: DEFAULT_RATE
val query = this.realm.where(ComputableResult::class.java)
query.`in`("session.bankroll.currency.id", arrayOf(this.id))
val cResults = query.findAll()

@ -2,6 +2,7 @@ package net.pokeranalytics.android.model.realm
import android.content.Context
import android.text.InputType
import androidx.fragment.app.Fragment
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmObject
@ -11,16 +12,18 @@ import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.fragment.components.input.InputFragment
import net.pokeranalytics.android.ui.fragment.components.input.InputFragmentType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.RowEditableDataSource
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomFieldRow
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import java.util.*
import kotlin.collections.ArrayList
@ -28,6 +31,9 @@ import kotlin.collections.ArrayList
open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable {
@Ignore
override val realmObjectClass: Class<out Identifiable> = CustomField::class.java
/**
* The custom field type: a list of items, a number or an amont
*/
@ -59,7 +65,8 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
var type: Int = Type.LIST.uniqueIdentifier
set(value) {
if (field == Type.LIST.uniqueIdentifier && value != Type.LIST.uniqueIdentifier) {
this.removeListEntries()
this.entriesToDelete.addAll(this.entries)
this.entries.clear()
}
field = value
@ -120,9 +127,23 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
return rowRepresentation
}
override fun startEditing(dataSource: Any?, parent: Fragment?) {
if (dataSource == null) return
if (dataSource !is Session) return
if (parent == null) return
if (parent !is RowRepresentableDelegate) return
val data = RowEditableDataSource()
when (type) {
Type.LIST.uniqueIdentifier -> data.append(dataSource.customFieldEntries.find { it.customField?.id == id }?.value, staticData = entries)
else -> data.append(dataSource.customFieldEntries.find { it.customField?.id == dataSource.id }?.numericValue, inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED)
}
InputFragment.buildAndShow(this, parent, data)
}
override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) {
SimpleRow.NAME -> this.name = value as String? ?: ""
CustomFieldRow.NAME -> this.name = value as String? ?: ""
CustomFieldRow.TYPE -> this.type = (value as Type?)?.uniqueIdentifier ?: Type.LIST.uniqueIdentifier
CustomFieldRow.COPY_ON_DUPLICATE -> this.duplicateValue = value as Boolean? ?: false
}
@ -146,8 +167,9 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
}
override fun isValidForDelete(realm: Realm): Boolean {
val sessions = realm.where<Session>().contains("customFieldEntries.customField.id", id).findAll()
return sessions.isEmpty()
return true
// val sessions = realm.where<Session>().contains("customFieldEntries.customFields.id", id).findAll()
// return sessions.isEmpty()
}
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
@ -155,60 +177,27 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
return R.string.cf_entry_delete_popup_message
}
override val bottomSheetType: BottomSheetType
override val inputFragmentType: InputFragmentType
get() {
return when (type) {
Type.LIST.uniqueIdentifier -> BottomSheetType.LIST_STATIC
else -> BottomSheetType.NUMERIC_TEXT
Type.LIST.uniqueIdentifier -> InputFragmentType.LIST_STATIC
else -> InputFragmentType.NUMERIC_TEXT
}
}
override fun deleteDependencies() {
override fun deleteDependencies(realm: Realm) {
if (isValid) {
val entries = realm.where<CustomFieldEntry>().equalTo("customField.id", id).findAll()
val entries = realm.where<CustomFieldEntry>().equalTo("customFields.id", id).findAll()
entries.deleteAllFromRealm()
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) {
is CustomFieldEntry -> row.editingDescriptors(
mapOf(
"defaultValue" to row.value
)
)
else -> null
}
}
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
return when (type) {
Type.LIST.uniqueIdentifier -> {
val defaultValue: Any? by map
val data: RealmList<CustomFieldEntry>? by map
arrayListOf(
RowRepresentableEditDescriptor(defaultValue, staticData = data)
)
}
else -> {
val defaultValue: Double? by map
arrayListOf(
RowRepresentableEditDescriptor(
defaultValue, inputType = InputType.TYPE_CLASS_NUMBER
or InputType.TYPE_NUMBER_FLAG_DECIMAL
or InputType.TYPE_NUMBER_FLAG_SIGNED
)
)
}
}
}
/**
* Update the row representation
*/
private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> {
val rows = ArrayList<RowRepresentable>()
rows.add(SimpleRow.NAME)
rows.add(CustomFieldRow.NAME)
rows.add(CustomFieldRow.TYPE)
if (type == Type.LIST.uniqueIdentifier && entries.size >= 0) {
@ -257,26 +246,24 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
* Delete an entry
*/
fun deleteEntry(entry: CustomFieldEntry) {
entries.remove(entry)
entriesToDelete.add(entry)
entries.remove(entry)
sortEntries()
updateRowRepresentation()
}
private fun removeListEntries() {
fun cleanupEntries() { // called when saving the custom field
this.entriesToDelete.addAll(entries)
this.entries.clear()
val realm = Realm.getDefaultInstance()
if (realm != null) {
realm.executeTransaction {
this.entriesToDelete.forEach {
if (it.isManaged) {
it.deleteFromRealm()
}
}
this.entriesToDelete.forEach { // entries are out of realm
realm.where<CustomFieldEntry>().equalTo("id", it.id).findFirst()?.deleteFromRealm()
}
}
realm.close()
this.entriesToDelete.clear()
}
/**

@ -2,6 +2,7 @@ package net.pokeranalytics.android.model.realm
import android.content.Context
import android.text.InputType
import androidx.fragment.app.Fragment
import io.realm.Realm
import io.realm.RealmObject
import io.realm.RealmResults
@ -12,11 +13,14 @@ import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.input.InputFragment
import net.pokeranalytics.android.ui.fragment.components.input.InputFragmentType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.RowEditableDataSource
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.toCurrency
@ -27,6 +31,9 @@ import java.util.Currency
open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable {
@Ignore
override val realmObjectClass: Class<out Identifiable> = CustomFieldEntry::class.java
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -77,7 +84,7 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable {
}
@Ignore
override val bottomSheetType: BottomSheetType = BottomSheetType.EDIT_TEXT
override val inputFragmentType: InputFragmentType = InputFragmentType.EDIT_TEXT
override fun localizedTitle(context: Context): String {
return context.getString(R.string.value)
@ -87,13 +94,17 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable {
return if (value.isNotEmpty()) value else NULL_TEXT
}
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
val defaultValue: Any? by map
return arrayListOf(
RowRepresentableEditDescriptor(defaultValue, R.string.value, InputType.TYPE_CLASS_TEXT)
)
override fun startEditing(dataSource: Any?, parent: Fragment?) {
if (parent == null) return
if (parent !is RowRepresentableDelegate) return
val data = RowEditableDataSource()
data.append(this.value, R.string.value, InputType.TYPE_CLASS_TEXT)
InputFragment.buildAndShow(this, parent, data, isDeletable = true)
}
override val valueCanBeClearedWhenEditing: Boolean
get() = false
override fun isValidForSave(): Boolean {
return true
}
@ -110,9 +121,9 @@ open class CustomFieldEntry : RealmObject(), NameManageable, RowRepresentable {
return R.string.cf_entry_delete_popup_message
}
override fun deleteDependencies() {
override fun deleteDependencies(realm: Realm) {
if (isValid) {
val entries = realm.where<CustomFieldEntry>().equalTo("customField.id", id).findAll()
val entries = realm.where<Session>().contains("customFieldEntries.id", id).findAll()
entries.deleteAllFromRealm()
}
}

@ -1,21 +1,23 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import androidx.fragment.app.Fragment
import io.realm.*
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.CountableUsage
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.*
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.input.InputFragment
import net.pokeranalytics.android.ui.fragment.components.input.InputFragmentType
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.view.ImageDecorator
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.*
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import timber.log.Timber
import java.util.*
/**
@ -23,7 +25,10 @@ import java.util.*
* It contains a list of [FilterCondition] describing the complete query to launch
* The [Filter] is working closely with a [Filterable] interface providing the entity we want the query being launched on
*/
open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, CountableUsage, ImageDecorator {
open class Filter : RealmObject(), RowRepresentable, Editable, Deletable, CountableUsage, ImageDecorator {
@Ignore
override val realmObjectClass: Class<out Identifiable> = Filter::class.java
companion object {
@ -35,21 +40,27 @@ open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, Co
//return realm.copyToRealm(filter)
}
// Get a queryWith by its id
fun getFilterBydId(realm: Realm, filterId: String): Filter? {
return realm.where<Filter>().equalTo("id", filterId).findFirst()
}
inline fun <reified T : Filterable> queryOn(realm: Realm, query: Query, sortField: String? = null): RealmResults<T> {
val realmQuery = realm.where<T>()
val rootQuery = realm.where<T>()
var realmQuery = query.queryWith(rootQuery)
sortField?.let {
return query.queryWith(realmQuery).sort(it).findAll()
} ?: run {
return query.queryWith(realmQuery).findAll()
realmQuery = realmQuery.sort(it)
}
// val desc = realmQuery.description
return realmQuery.findAll()
}
}
override val viewType: Int
get() = RowViewType.TITLE_VALUE_ACTION.ordinal
override val imageRes: Int?
get() = R.drawable.ic_outline_settings
override val imageTint: Int?
get() = R.color.green
override val imageClickable: Boolean?
get() = true
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -64,6 +75,9 @@ open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, Co
override var useCount: Int = 0
@Ignore
override val ownerClass: Class<out RealmModel> = Session::class.java
var filterConditions: RealmList<FilterCondition> = RealmList()
private set
@ -78,8 +92,8 @@ open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, Co
}
fun createOrUpdateFilterConditions(filterConditionRows: ArrayList<QueryCondition>) {
println("list of querys saving: ${filterConditionRows.map { it.id }}")
println("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}")
Timber.d("list of querys saving: ${filterConditionRows.map { it.id }}")
Timber.d("list of querys previous: ${this.filterConditions.map { it.queryCondition.id }}")
filterConditionRows
.map {
it.groupId
@ -92,7 +106,7 @@ open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, Co
}
.apply {
println("list of querys: ${this.map { it.id }}")
Timber.d("list of querys: ${this.map { it.id }}")
val casted = arrayListOf<QueryCondition>()
casted.addAll(this)
val newFilterCondition = FilterCondition(casted)
@ -113,17 +127,17 @@ open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, Co
fun countBy(filterCategoryRow: FilterCategoryRow): Int {
val sections = filterCategoryRow.filterSectionRows.map { it.name }
println("list of sections $sections")
Timber.d("list of sections $sections")
val savedSections = filterConditions.filter { sections.contains(it.sectionName) }.flatMap { it.queryCondition.id }
println("list of savedSections $savedSections")
Timber.d("list of savedSections $savedSections")
return savedSections.size
}
fun contains(filterElementRow: QueryCondition): Boolean {
println("list of saved queries ${filterConditions.map { it.queryCondition.id }}")
println("list of contains ${filterElementRow.id}")
Timber.d("list of saved queries ${filterConditions.map { it.queryCondition.id }}")
Timber.d("list of contains ${filterElementRow.id}")
val contained = filterConditions.flatMap { it.queryCondition.id }.contains(filterElementRow.id.first())
println("list of : $contained")
Timber.d("list of : $contained")
return contained
}
@ -139,19 +153,22 @@ open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, Co
}
}
inline fun <reified T : Filterable> results(firstField: String? = null, secondField: String? = null): RealmResults<T> {
inline fun <reified T : Filterable> query(firstField: String? = null, secondField: String? = null): RealmQuery<T> {
val realmQuery = realm.where<T>()
if (firstField != null && secondField != null) {
return this.query.queryWith(realmQuery).distinct(firstField, secondField).findAll()
return this.query.queryWith(realmQuery).distinct(firstField, secondField)
}
if (firstField != null) {
return this.query.queryWith(realmQuery).distinct(firstField).findAll()
return this.query.queryWith(realmQuery).distinct(firstField)
}
return this.query.queryWith(realmQuery).findAll()
return this.query.queryWith(realmQuery)
}
inline fun <reified T : Filterable> results(firstField: String? = null, secondField: String? = null): RealmResults<T> {
return this.query<T>(firstField, secondField).findAll()
}
val query: Query
@ -175,4 +192,30 @@ open class Filter : RealmObject(), RowRepresentable, Identifiable, Deletable, Co
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return R.string.relationship_error
}
override val inputFragmentType: InputFragmentType
get() {
return InputFragmentType.EDIT_TEXT
}
override fun localizedTitle(context: Context): String {
return context.getString(R.string.name)
}
override fun updateValue(value: Any?, row: RowRepresentable) {
realm.executeTransaction {
val newName = value as String? ?: ""
if (newName.isNotEmpty()) {
name = newName
}
}
}
override fun startEditing(dataSource: Any?, parent: Fragment?) {
if (parent == null) return
if (parent !is RowRepresentableDelegate) return
val data = RowEditableDataSource()
data.append(this.name)
InputFragment.buildAndShow(this, parent, data, isDeletable = true, valueHasPlaceholder = false)
}
}

@ -2,28 +2,32 @@ package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rowrepresentable.GameRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.util.NULL_TEXT
import java.util.*
import kotlin.collections.ArrayList
open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable, CountableUsage {
@Ignore
override val realmObjectClass: Class<out Identifiable> = Game::class.java
companion object {
val rowRepresentation : List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.add(SimpleRow.NAME)
rows.add(GameRow.NAME)
// rows.addAll(GameRow.values())
rows
}
@ -41,6 +45,9 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
// CountableUsage
override var useCount: Int = 0
@Ignore
override val ownerClass: Class<out RealmModel> = Session::class.java
fun getNotNullShortName() : String {
this.shortName?.let {
return it
@ -53,28 +60,20 @@ open class Game : RealmObject(), NameManageable, StaticRowRepresentableDataSourc
}
override fun adapterRows(): List<RowRepresentable>? {
return Game.rowRepresentation
return rowRepresentation
}
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
GameRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
GameRow.SHORT_NAME -> this.shortName ?: NULL_TEXT
else -> return super.stringForRow(row)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.name))
GameRow.SHORT_NAME -> row.editingDescriptors(mapOf("defaultValue" to this.shortName))
else -> null
}
}
override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) {
SimpleRow.NAME -> this.name = value as String? ?: ""
GameRow.NAME -> this.name = value as String? ?: ""
GameRow.SHORT_NAME -> this.shortName = value as String? ?: ""
}
}

@ -4,9 +4,11 @@ import android.content.Context
import com.google.android.libraries.places.api.model.Place
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.view.RowRepresentable
@ -16,6 +18,9 @@ import java.util.*
open class Location : RealmObject(), NameManageable, RowRepresentable {
@Ignore
override val realmObjectClass: Class<out Identifiable> = Location::class.java
@PrimaryKey
override var id = UUID.randomUUID().toString()

@ -11,6 +11,7 @@ import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.extensions.findById
@ -19,6 +20,9 @@ import java.util.*
open class ReportSetup : RealmObject(), RowRepresentable, Deletable {
@Ignore
override val realmObjectClass: Class<out Identifiable> = ReportSetup::class.java
@PrimaryKey
override var id = UUID.randomUUID().toString()

@ -6,6 +6,7 @@ import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
import io.realm.annotations.RealmClass
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
@ -52,10 +53,10 @@ open class Result : RealmObject(), Filterable {
this.session?.bankroll?.let { bankroll ->
if (bankroll.live) {
throw IllegalStateException("Can't set net result on a live bankroll")
throw PAIllegalStateException("Can't set net result on a live bankroll")
}
} ?: run {
throw IllegalStateException("Session doesn't have any bankroll")
throw PAIllegalStateException("Session doesn't have any bankroll")
}
field = value

@ -16,6 +16,7 @@ import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.TournamentType
@ -29,7 +30,6 @@ import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import net.pokeranalytics.android.ui.view.*
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
@ -46,7 +46,7 @@ import kotlin.collections.ArrayList
typealias BB = Double
open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed,
TimeFilterable, Filterable, DatedValue {
TimeFilterable, Filterable, DatedBankrollGraphEntry {
enum class Type {
CASH_GAME,
@ -67,7 +67,8 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
companion object {
fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session {
fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null, managed: Boolean = true): Session {
val session = Session()
session.result = Result()
if (bankroll != null) {
@ -76,7 +77,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
session.bankroll = realm.where<Bankroll>().findFirst()
}
session.type = if (isTournament) Session.Type.TOURNAMENT.ordinal else Session.Type.CASH_GAME.ordinal
return realm.copyToRealm(session)
return if (managed) {
realm.copyToRealm(session)
} else {
session
}
}
fun fieldNameForQueryType(queryCondition: Class < out QueryCondition >): String? {
@ -235,9 +241,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
var creationDate: Date = Date()
// The bankroll hosting the results
var bankroll: Bankroll? = null
override var bankroll: Bankroll? = null
set(value) {
field = value
this.formatBlinds()
this.updateRowRepresentation()
}
@ -399,7 +406,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
@Ignore
override var amount: Double = 0.0
get() {
return this.computableResult?.ratedNet ?: 0.0
return this.result?.net ?: 0.0
}
/**
@ -420,7 +427,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
this.computableResults?.forEachIndexed { index, computableResult ->
computableResult.updateWith(this)
if (index > 0) {
throw IllegalStateException("Session cannot have more than one computable result")
throw PAIllegalStateException("Session cannot have more than one computable result")
}
}
this.sessionSet?.computeStats()
@ -498,12 +505,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
if (pauseDate != null) {
this.breakDuration += Date().time - pauseDate.time
} else {
throw IllegalStateException("When resuming, the pause date must be set")
throw PAIllegalStateException("When resuming, the pause date must be set")
}
this.pauseDate = null
}
else -> {
throw IllegalStateException("unmanaged session state")
throw PAIllegalStateException("unmanaged session state")
}
}
}
@ -524,7 +531,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
SessionState.STARTED -> {
this.pauseDate = Date()
}
else -> throw IllegalStateException("Pausing a session in an unmanaged state")
else -> throw PAIllegalStateException("Pausing a session in an unmanaged state")
}
}
}
@ -613,7 +620,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
if (cgBigBlind == null) return
cgBigBlind?.let { bb ->
val sb = cgSmallBlind ?: bb / 2.0
blinds = "${currency.symbol} ${sb.formatted()}/${bb.round()}"
val preFormattedBlinds = "${sb.formatted()}/${bb.round()}"
println("<<<<<< bb.toCurrency(currency) : ${bb.toCurrency(currency)}")
println("<<<<<< preFormattedBlinds : $preFormattedBlinds")
val regex = Regex("-?\\d+(\\.\\d+)?")
blinds = bb.toCurrency(currency).replace(regex, preFormattedBlinds)
println("<<<<<< blinds = $blinds")
}
}
@ -646,6 +658,24 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
fun duplicate() : Session {
val copy = Session.newInstance(this.realm, this.isTournament(), this.bankroll)
copy.game = this.game
copy.limit = this.limit
copy.cgSmallBlind = this.cgSmallBlind
copy.cgBigBlind = this.cgBigBlind
copy.tournamentEntryFee = this.tournamentEntryFee
copy.tournamentFeatures = this.tournamentFeatures
copy.tournamentName = this.tournamentName
copy.tournamentType = this.tournamentType
copy.tableSize = this.tableSize
copy.numberOfTables = this.numberOfTables
return copy
}
@Ignore
override val viewType: Int = RowViewType.ROW_SESSION.ordinal
@ -791,7 +821,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
return NULL_TEXT
}
else -> throw UnmanagedRowRepresentableException("Unmanaged row = ${row}")
else -> throw UnmanagedRowRepresentableException("Unmanaged row = $row")
}
}
@ -804,117 +834,6 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) {
SessionRow.BANKROLL -> row.editingDescriptors(
mapOf(
"defaultValue" to this.bankroll,
"data" to realm.sorted<Bankroll>() // LiveData.Bankroll.items(realm)
)
)
SessionRow.GAME -> row.editingDescriptors(
mapOf(
"limit" to this.limit,
"defaultValue" to this.game,
"data" to realm.sorted<Game>() //LiveData.Game.items(realm)
)
)
SessionRow.LOCATION -> row.editingDescriptors(
mapOf(
"defaultValue" to this.location,
"data" to realm.sorted<Location>() // LiveData.Location.items(realm)
)
)
SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentFeatures,
"data" to realm.sorted<TournamentFeature>() //LiveData.TournamentFeature.items(realm)
)
)
SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentName,
"data" to realm.sorted<TournamentName>() //LiveData.TournamentName.items(realm)
)
)
SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentType
)
)
SessionRow.TABLE_SIZE -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tableSize
)
)
SessionRow.BLINDS -> row.editingDescriptors(
mapOf(
"sb" to cgSmallBlind?.round(),
"bb" to cgBigBlind?.round()
)
)
SessionRow.BUY_IN -> row.editingDescriptors(
mapOf(
"bb" to cgBigBlind,
"fee" to this.tournamentEntryFee,
"ratedBuyin" to result?.buyin
)
)
SessionRow.BREAK_TIME -> row.editingDescriptors(mapOf())
SessionRow.CASHED_OUT, SessionRow.PRIZE -> row.editingDescriptors(
mapOf(
"defaultValue" to result?.cashout
)
)
SessionRow.NET_RESULT -> row.editingDescriptors(
mapOf(
"defaultValue" to result?.netResult
)
)
SessionRow.COMMENT -> row.editingDescriptors(
mapOf(
"defaultValue" to this.comment
)
)
SessionRow.INITIAL_BUY_IN -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentEntryFee
)
)
SessionRow.PLAYERS -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentNumberOfPlayers
)
)
SessionRow.POSITION -> row.editingDescriptors(
mapOf(
"defaultValue" to this.result?.tournamentFinalPosition
)
)
SessionRow.TIPS -> row.editingDescriptors(
mapOf(
"sb" to cgSmallBlind?.round(),
"bb" to cgBigBlind?.round(),
"tips" to result?.tips
)
)
is CustomField -> {
row.editingDescriptors(
when (row.type) {
CustomField.Type.LIST.uniqueIdentifier -> mapOf(
"defaultValue" to customFieldEntries.find { it.customField?.id == row.id }?.value,
"data" to row.entries
)
else -> mapOf(
"defaultValue" to customFieldEntries.find { it.customField?.id == row.id }?.numericValue
)
}
)
}
else -> null
}
}
override fun updateValue(value: Any?, row: RowRepresentable) {
realm.executeTransaction {
@ -1098,7 +1017,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
} ?: run {
throw java.lang.IllegalStateException("Asking for statIds on Session without Result")
throw PAIllegalStateException("Asking for statIds on Session without Result")
}
}
@ -1138,7 +1057,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
DefaultLegendValues(this.entryTitle(context), left, right)
}
else -> {
super.legendValues(stat, entry, style, groupName, context)
super<Timed>.legendValues(stat, entry, style, groupName, context)
}
}
}
@ -1147,10 +1066,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
// Timed
override val objectIdentifier: ObjectIdentifier
get() = ObjectIdentifier(this.id, Session::class.java)
@Ignore
override val realmObjectClass: Class<out Identifiable> = Session::class.java
}

@ -4,16 +4,17 @@ import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatFormattingException
import net.pokeranalytics.android.util.TextFormat
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import java.text.DateFormat
import java.util.*
@ -138,10 +139,8 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
}
}
// Timed
override val objectIdentifier: ObjectIdentifier
get() = ObjectIdentifier(this.id, SessionSet::class.java)
@Ignore
override val realmObjectClass: Class<out Identifiable> = SessionSet::class.java
}

@ -233,7 +233,7 @@
//
// /**
// * Multiple session sets update:
// * Merges all sets into one (delete all then create a new one)
// * Merges all sets into one (delete all then buildAndShow a new one)
// */
// private fun mergeSessionGroups(owner: Session, sessionSets: RealmResults<SessionSet>) {
//

@ -2,17 +2,18 @@ package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.ui.view.rowrepresentable.TournamentFeatureRow
import net.pokeranalytics.android.util.NULL_TEXT
import java.util.*
@ -24,12 +25,14 @@ open class TournamentFeature : RealmObject(), NameManageable, StaticRowRepresent
companion object {
val rowRepresentation : List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.add(SimpleRow.NAME)
rows.addAll(TournamentFeatureRow.values())
rows
}
}
@Ignore
override val realmObjectClass: Class<out Identifiable> = TournamentFeature::class.java
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -39,29 +42,27 @@ open class TournamentFeature : RealmObject(), NameManageable, StaticRowRepresent
// CountableUsage
override var useCount: Int = 0
@Ignore
override val ownerClass: Class<out RealmModel> = Session::class.java
override fun getDisplayName(context: Context): String {
return this.name
}
override fun adapterRows(): List<RowRepresentable>? {
return TournamentFeature.rowRepresentation
return rowRepresentation
}
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
TournamentFeatureRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
else -> return super.stringForRow(row)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf(
"defaultValue" to this.name))
}
override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) {
SimpleRow.NAME -> this.name = value as String? ?: ""
TournamentFeatureRow.NAME -> this.name = value as String? ?: ""
}
}

@ -3,15 +3,15 @@ package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.ui.view.rowrepresentable.TournamentNameRow
import net.pokeranalytics.android.util.NULL_TEXT
import java.util.*
@ -22,12 +22,14 @@ open class TournamentName : RealmObject(), NameManageable, StaticRowRepresentabl
companion object {
val rowRepresentation : List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.add(SimpleRow.NAME)
rows.addAll(TournamentNameRow.values())
rows
}
}
@Ignore
override val realmObjectClass: Class<out Identifiable> = TournamentName::class.java
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -40,25 +42,21 @@ open class TournamentName : RealmObject(), NameManageable, StaticRowRepresentabl
override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) {
SimpleRow.NAME -> this.name = value as String? ?: ""
TournamentNameRow.NAME -> this.name = value as String? ?: ""
}
}
override fun adapterRows(): List<RowRepresentable>? {
return TournamentName.rowRepresentation
return rowRepresentation
}
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
SimpleRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
TournamentNameRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
else -> return super.stringForRow(row)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf("defaultValue" to this.name))
}
override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
return when (status) {
SaveValidityStatus.DATA_INVALID -> R.string.tournament_name_empty_field_error

@ -13,7 +13,6 @@ import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.view.DefaultLegendValues
import net.pokeranalytics.android.ui.view.LegendContent
import net.pokeranalytics.android.ui.view.RowRepresentable
@ -26,11 +25,22 @@ import java.util.*
import kotlin.collections.ArrayList
open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, TimeFilterable, Filterable, DatedValue,
GraphUnderlyingEntry {
open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, TimeFilterable,
Filterable, DatedBankrollGraphEntry {
companion object {
fun newInstance(realm: Realm, bankroll: Bankroll, date: Date? = null, type: TransactionType, amount: Double): Transaction {
val transaction = realm.copyToRealm(Transaction())
transaction.date = date ?: Date()
transaction.amount = amount
transaction.type = type
transaction.bankroll = bankroll
return transaction
}
val rowRepresentation: List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.addAll(TransactionRow.values())
@ -60,7 +70,7 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
override var id = UUID.randomUUID().toString()
// The bankroll of the transaction
var bankroll: Bankroll? = null
override var bankroll: Bankroll? = null
// The amount of the transaction
override var amount: Double = 0.0
@ -134,14 +144,17 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
return SaveValidityStatus.VALID
}
// GraphUnderlyingEntry
// GraphIdentifiableEntry
@Ignore
override val realmObjectClass: Class<out Identifiable> = Transaction::class.java
override fun entryTitle(context: Context): String {
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.date)
}
override fun formattedValue(stat: Stat): TextFormat {
return stat.format(this.amount)
return stat.format(this.amount, currency = this.bankroll?.utilCurrency)
}
override fun legendValues(
@ -158,4 +171,5 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
return DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName)
}
}

@ -2,27 +2,40 @@ package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.Localizable
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionTypeRow
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import net.pokeranalytics.android.util.enumerations.IntSearchable
import java.util.*
import kotlin.collections.ArrayList
open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable {
open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable,
CountableUsage {
enum class Value(val additive: Boolean) : Localizable {
WITHDRAWAL(false),
DEPOSIT(true),
BONUS(true);
enum class Value(override var uniqueIdentifier: Int, val additive: Boolean) : IntIdentifiable, Localizable {
WITHDRAWAL(0, false),
DEPOSIT(1, true),
BONUS(2, true),
STACKING_INCOMING(3, true),
STACKING_OUTGOING(4, false);
companion object : IntSearchable<Value> {
override fun valuesInternal(): Array<Value> {
return values()
}
}
override val resId: Int?
get() {
@ -30,28 +43,33 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
WITHDRAWAL -> R.string.withdrawal
DEPOSIT -> R.string.deposit
BONUS -> R.string.bonus
STACKING_INCOMING -> R.string.stacking_incoming
STACKING_OUTGOING -> R.string.stacking_outgoing
}
}
}
companion object {
val rowRepresentation: List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.add(SimpleRow.NAME)
rows.addAll(TransactionTypeRow.values())
rows
}
fun getByValue(value: Value, realm: Realm): TransactionType {
val type = realm.where(TransactionType::class.java).equalTo("kind", value.ordinal).findFirst()
val type = realm.where(TransactionType::class.java).equalTo("kind", value.uniqueIdentifier).findFirst()
type?.let {
return it
}
throw IllegalStateException("Transaction type ${value.name} should exist in database!")
throw PAIllegalStateException("Transaction type ${value.name} should exist in database!")
}
}
@Ignore
override val realmObjectClass: Class<out Identifiable> = TransactionType::class.java
@PrimaryKey
override var id = UUID.randomUUID().toString()
@ -72,6 +90,11 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
// The predefined kind, if necessary, like: Withdrawal, deposit, or tips
var kind: Int? = null
override var useCount: Int = 0
@Ignore
override val ownerClass: Class<out RealmModel> = Transaction::class.java
override fun getDisplayName(context: Context): String {
return this.name
}
@ -82,7 +105,7 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
SimpleRow.NAME -> this.name
TransactionTypeRow.NAME -> this.name
else -> return super.stringForRow(row)
}
}
@ -94,13 +117,9 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf("defaultValue" to this.name))
}
override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) {
SimpleRow.NAME -> this.name = value as String? ?: ""
TransactionTypeRow.NAME -> this.name = value as String? ?: ""
TransactionTypeRow.TRANSACTION_ADDITIVE -> this.additive = value as Boolean? ?: false
}
}
@ -114,5 +133,13 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
return R.string.transaction_relationship_error
}
override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
return when (status) {
SaveValidityStatus.DATA_INVALID -> R.string.operation_type_empty_field_error
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_operation_type_error
else -> super.getFailedSaveMessage(status)
}
}
}

@ -0,0 +1,39 @@
package net.pokeranalytics.android.model.utils
import io.realm.Realm
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.model.realm.TransactionType
import java.util.*
class DataUtils {
companion object {
/**
* Returns true if the provided parameters doesn't correspond to an existing session
*/
fun sessionCount(realm: Realm, startDate: Date, endDate: Date, net: Double): Int {
val sessions = realm.where(Session::class.java)
.equalTo("startDate", startDate)
.equalTo("endDate", endDate)
.equalTo("result.net", net)
.findAll()
return sessions.size
}
/**
* Returns true if the provided parameters doesn't correspond to an existing transaction
*/
fun transactionUnicityCheck(realm: Realm, date: Date, amount: Double, type: TransactionType): Boolean {
val transactions = realm.where(Transaction::class.java)
.equalTo("date", date)
.equalTo("amount", amount)
.equalTo("type.id", type.id)
.findAll()
return transactions.isEmpty()
}
}
}

@ -71,27 +71,27 @@ class FavoriteSessionFinder {
private const val FAVORITE_SIGNIFICANT_SESSIONS = 15L
/**
* Copies the favorite session parameters on the [newSession]
* Copies the favorite session parameters on the [session]
*/
fun copyParametersFromFavoriteSession(newSession: Session, location: Location?, context: Context) {
fun copyParametersFromFavoriteSession(session: Session, location: Location?, context: Context) {
val favoriteSession =
favoriteSession(newSession.type, location, newSession.realm, context)
favoriteSession(session.type, location, session.realm, context)
favoriteSession?.let { fav ->
newSession.limit = fav.limit
newSession.game = fav.game
newSession.bankroll = fav.bankroll
newSession.tableSize = fav.tableSize
session.limit = fav.limit
session.game = fav.game
session.bankroll = fav.bankroll
session.tableSize = fav.tableSize
when (newSession.type) {
when (session.type) {
Session.Type.CASH_GAME.ordinal -> {
newSession.cgSmallBlind = fav.cgSmallBlind
newSession.cgBigBlind = fav.cgBigBlind
session.cgSmallBlind = fav.cgSmallBlind
session.cgBigBlind = fav.cgBigBlind
}
Session.Type.TOURNAMENT.ordinal -> {
newSession.tournamentEntryFee = fav.tournamentEntryFee
session.tournamentEntryFee = fav.tournamentEntryFee
}
}
}

@ -12,11 +12,29 @@ import java.util.*
class Seed(var context:Context) : Realm.Transaction {
companion object {
fun createDefaultTransactionTypes(values: Array<TransactionType.Value>, context: Context, realm: Realm) {
values.forEach { value ->
val existing = realm.where(TransactionType::class.java).equalTo("kind", value.uniqueIdentifier).findAll()
if (existing.isEmpty()) {
val type = TransactionType()
type.name = value.localizedTitle(context)
type.additive = value.additive
type.kind = value.uniqueIdentifier
type.lock = true
realm.insertOrUpdate(type)
}
}
}
}
override fun execute(realm: Realm) {
this.createDefaultGames(realm)
this.createDefaultTournamentFeatures(realm)
this.createDefaultCurrencyAndBankroll(realm)
this.createDefaultTransactionTypes(realm)
createDefaultTransactionTypes(TransactionType.Value.values(), context, realm)
}
private fun createDefaultTournamentFeatures(realm: Realm) {
@ -56,15 +74,4 @@ class Seed(var context:Context) : Realm.Transaction {
}
}
private fun createDefaultTransactionTypes(realm: Realm) {
TransactionType.Value.values().forEachIndexed { index, value ->
val type = TransactionType()
type.name = value.localizedTitle(context)
type.additive = value.additive
type.kind = index
type.lock = true
realm.insertOrUpdate(type)
}
}
}

@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.utils
import io.realm.RealmQuery
import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import kotlin.math.max
@ -23,7 +24,7 @@ class SessionSetManager {
fun updateTimeline(session: Session) {
if (!session.realm.isInTransaction) {
throw IllegalStateException("realm should be in transaction at this point")
throw PAIllegalStateException("realm should be in transaction at this point")
}
if (session.startDate == null) {
@ -96,7 +97,7 @@ class SessionSetManager {
/**
* Multiple session sets update:
* Merges all sets into one (delete all then create a new one)
* Merges all sets into one (delete all then buildAndShow a new one)
*/
private fun mergeSessionGroups(session: Session, sessionSets: RealmResults<SessionSet>) {
@ -154,7 +155,7 @@ class SessionSetManager {
fun removeFromTimeline(session: Session) {
if (!session.realm.isInTransaction) {
throw IllegalStateException("realm should be in transaction at this point")
throw PAIllegalStateException("realm should be in transaction at this point")
}
val sessionSet = session.sessionSet

@ -1,21 +0,0 @@
package net.pokeranalytics.android.model.utils
import io.realm.Realm
import net.pokeranalytics.android.model.realm.Session
import java.util.*
class SessionUtils {
companion object {
/**
* Returns true if the provided parameters doesn't correspond to an existing session
*/
fun unicityCheck(realm: Realm, startDate: Date, endDate: Date, net: Double) : Boolean {
val sessions = realm.where(Session::class.java).equalTo("startDate", startDate).equalTo("endDate", endDate).equalTo("result.net", net).findAll()
return sessions.isEmpty()
}
}
}

@ -37,54 +37,6 @@ class BankrollActivity : PokerAnalyticsActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bankroll)
// this.computableResults = getRealm().where(ComputableResult::class.java).findAll() // ComputableResult are existing only if sessions are ended
// this.computableResults.addChangeListener { t, changeSet ->
//
// val bankrolls = mutableSetOf<Bankroll>()
// val indexes = mutableSetOf<Int>()
// indexes.addAll(changeSet.changes.toList())
// indexes.addAll(changeSet.insertions.toList())
// indexes.addAll(changeSet.deletions.toList())
// indexes.forEach { index ->
// t[index]?.session?.bankroll?.let { br ->
// bankrolls.add(br)
// }
// }
// this.computeBankrollReports(bankrolls)
// }
// this.bankrolls = getRealm().where(Bankroll::class.java).findAll() // ComputableResult are existing only if sessions are ended
// this.bankrolls.addChangeListener { _, changeSet ->
//
//
//
//
//
// }
// this.transactions = getRealm().where(Transaction::class.java).findAll() // ComputableResult are existing only if sessions are ended
// this.transactions.addChangeListener { t, changeSet ->
//
// val bankrolls = mutableSetOf<Bankroll>()
// val indexes = mutableSetOf<Int>()
// indexes.addAll(changeSet.changes.toList())
// indexes.addAll(changeSet.insertions.toList())
// indexes.addAll(changeSet.deletions.toList())
// indexes.forEach { index ->
// if (t.isNotEmpty()) {
// t[index]?.bankroll?.let { br ->
// bankrolls.add(br)
// }
// }
// }
// this.computeBankrollReports(bankrolls)
// }
}
fun computeBankrollReports(bankrolls: Collection<Bankroll>) {
}
}

@ -1,28 +1,52 @@
package net.pokeranalytics.android.ui.activity
import android.content.Context
import android.app.Activity
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.PokerAnalyticsActivity
import net.pokeranalytics.android.util.billing.AppGuard
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.fragment.SubscriptionFragment
class BillingActivity : PokerAnalyticsActivity() {
private enum class IntentKey(val keyName: String) {
SHOW_MESSAGE("showMessage"),
}
companion object {
fun newInstance(context: Context) {
val intent = Intent(context, BillingActivity::class.java)
context.startActivity(intent)
fun newInstanceForResult(activity: Activity, showSessionMessage: Boolean) {
val intent = Intent(activity, BillingActivity::class.java)
intent.putExtra(IntentKey.SHOW_MESSAGE.keyName, showSessionMessage)
activity.startActivityForResult(intent, RequestCode.SUBSCRIPTION.value)
}
fun newInstanceForResult(fragment: Fragment, showSessionMessage: Boolean) {
val intent = Intent(fragment.requireContext(), BillingActivity::class.java)
intent.putExtra(IntentKey.SHOW_MESSAGE.keyName, showSessionMessage)
fragment.startActivityForResult(intent, RequestCode.SUBSCRIPTION.value)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_billing)
initUI()
}
override fun onResume() {
super.onResume()
private fun initUI() {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment = SubscriptionFragment()
val showSessionMessage = intent.getBooleanExtra(IntentKey.SHOW_MESSAGE.keyName, false)
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
fragment.setData(showSessionMessage)
}

@ -67,10 +67,10 @@ class EditableDataActivity : PokerAnalyticsActivity() {
LiveData.TRANSACTION_TYPE.ordinal -> TransactionTypeDataFragment()
else -> EditableDataFragment()
}
fragment.setData(dataType, primaryKey)
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
fragment.setData(dataType, primaryKey)
}
}

@ -64,9 +64,9 @@ class FiltersActivity : PokerAnalyticsActivity() {
val filterableType = FilterableType.valueByIdentifier(uniqueIdentifier)
fragment = FiltersFragment()
fragment.setData(filterId, filterableType)
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
fragment.setData(filterId, filterableType)
fragment.updateMostUsedFiltersVisibility(!hideMostUsedFilters)
}

@ -0,0 +1,58 @@
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.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.FiltersListFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
class FiltersListActivity : PokerAnalyticsActivity() {
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)
}
}

@ -3,7 +3,6 @@ package net.pokeranalytics.android.ui.activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView
@ -13,12 +12,8 @@ import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.activity.components.ResultCode
import net.pokeranalytics.android.ui.adapter.HomePagerAdapter
import net.pokeranalytics.android.ui.extensions.showAlertDialog
import net.pokeranalytics.android.util.billing.AppGuard
import timber.log.Timber
class HomeActivity : PokerAnalyticsActivity() {
@ -48,7 +43,7 @@ class HomeActivity : PokerAnalyticsActivity() {
R.id.navigation_reports -> {
displayFragment(3)
}
R.id.navigation_more -> {
R.id.navigation_settings -> {
displayFragment(4)
}
}
@ -80,39 +75,50 @@ class HomeActivity : PokerAnalyticsActivity() {
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let {
when (intent.action) {
"android.intent.action.VIEW" -> { // import
val data = it.data
if (data != null) {
this.requestImportConfirmation(data)
} else {
throw IllegalStateException("URI null on import")
}
}
else -> {
Timber.d("Intent ${intent.action} unmanaged")
}
}
}
// override fun onNewIntent(intent: Intent?) {
// super.onNewIntent(intent)
//
// setIntent(intent)
// intent?.let {
//
// when (intent.action) {
// "android.intent.action.VIEW" -> { // import
// val data = it.data
// if (data != null) {
// this.requestImportConfirmation(data)
// } else {
// throw PAIllegalStateException("URI null on import")
// }
// }
// else -> {
// Timber.w("Intent ${intent.action} unmanaged")
// }
// }
// }
//
// }
}
// override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// super.onActivityResult(requestCode, resultCode, data)
//
// when (requestCode) {
// RequestCode.IMPORT.value -> {
// if (resultCode == ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) {
// showAlertDialog(context = this, message = R.string.unknown_import_format_popup_message)
// }
// }
// }
// }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Import
when (requestCode) {
RequestCode.IMPORT.value -> {
if (resultCode == ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) {
showAlertDialog(context = this, message = R.string.unknown_import_format_popup_message)
}
}
}
}
// private fun requestImportConfirmation(uri: Uri) {
//
// showAlertDialog(context = this, title = R.string.import_confirmation, showCancelButton = true, positiveAction = {
// ImportActivity.newInstanceForResult(this, uri)
// })
//
// }
private fun observeRealmObjects() {
@ -164,14 +170,4 @@ class HomeActivity : PokerAnalyticsActivity() {
viewPager.setCurrentItem(index, false)
}
// Import
private fun requestImportConfirmation(uri: Uri) {
showAlertDialog(context = this, title = R.string.import_confirmation, showCancelButton = true, positiveAction = {
ImportActivity.newInstanceForResult(this, uri)
})
}
}

@ -4,12 +4,18 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import io.realm.Realm
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.activity.components.ResultCode
import net.pokeranalytics.android.ui.extensions.showAlertDialog
import net.pokeranalytics.android.ui.fragment.ImportFragment
import net.pokeranalytics.android.util.billing.AppGuard
import net.pokeranalytics.android.util.extensions.count
import timber.log.Timber
class ImportActivity : PokerAnalyticsActivity() {
@ -39,20 +45,12 @@ class ImportActivity : PokerAnalyticsActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.fileURI = intent.getParcelableExtra(ImportActivity.IntentKey.URI.keyName)
setContentView(R.layout.activity_import)
initUI()
intent?.data?.let {
this.fileURI = it
}
override fun onStop() {
super.onStop()
// Updates the main thread instance with newly inserted data
val realm = Realm.getDefaultInstance()
realm.refresh()
realm.close()
setContentView(R.layout.activity_import)
requestImportConfirmation()
}
private fun initUI() {
@ -71,16 +69,66 @@ class ImportActivity : PokerAnalyticsActivity() {
}
// private fun requestPermission() {
// if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
// ActivityCompat.requestPermissions(
// this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PERMISSION_REQUEST_ACCESS_FINE_LOCATION
// )
// override fun onNewIntent(intent: Intent?) {
// super.onNewIntent(intent)
//
// Timber.d("++++++ data = ${intent?.data}")
//
// setIntent(intent)
// intent?.let {
//
// when (intent.action) {
// "android.intent.action.VIEW" -> { // import
// val data = it.data
// if (data != null) {
// this.requestImportConfirmation(data)
// } else {
// throw PAIllegalStateException("URI null on import")
// }
// }
// else -> {
// Timber.w("Intent ${intent.action} unmanaged")
// }
// }
// }
//
// override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
RequestCode.IMPORT.value -> {
if (resultCode == ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) {
showAlertDialog(context = this, message = R.string.unknown_import_format_popup_message, positiveAction = {
finish()
})
}
}
}
}
// Import
private fun requestImportConfirmation() {
val realm = Realm.getDefaultInstance()
val sessionCount = realm.count(Session::class.java)
realm.close()
if (!AppGuard.isProUser && sessionCount >= AppGuard.MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION) { // && !BuildConfig.DEBUG
Toast.makeText(this, "Please subscribe!", Toast.LENGTH_LONG).show()
BillingActivity.newInstanceForResult(this, true)
return
}
showAlertDialog(context = this, title = R.string.import_confirmation, showCancelButton = true, positiveAction = {
initUI()
}, negativeAction = {
finish()
})
}
}

@ -4,6 +4,8 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewAnimationUtils
@ -12,6 +14,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.extensions.px
@ -29,11 +32,18 @@ class NewDataMenuActivity : PokerAnalyticsActivity() {
}
}
private var choiceSelected = false
private var menuWillBeHidden = false
private val fabSize = 48.px
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(net.pokeranalytics.android.R.layout.activity_new_data)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // used to fix Oreo crash
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
setContentView(R.layout.activity_new_data)
initUI()
}
@ -78,12 +88,18 @@ class NewDataMenuActivity : PokerAnalyticsActivity() {
* Set the result and hide menu
*/
private fun finishWithResult(choice: Int) {
if (choiceSelected) {
return
}
choiceSelected = true
val intent = Intent()
intent.putExtra(IntentKey.CHOICE.keyName, choice)
setResult(RESULT_OK, intent)
GlobalScope.launch(Dispatchers.Main) {
delay(200)
hideMenu(true)
hideMenu()
}
}
@ -105,7 +121,12 @@ class NewDataMenuActivity : PokerAnalyticsActivity() {
/**
* Hide menu
*/
private fun hideMenu(hideQuickly: Boolean = false) {
private fun hideMenu() {
if (menuWillBeHidden) {
return
}
menuWillBeHidden = true
val cx = menuContainer.measuredWidth - fabSize / 2
val cy = menuContainer.measuredHeight - fabSize / 2

@ -14,32 +14,32 @@ class SessionActivity: PokerAnalyticsActivity() {
enum class IntentKey(val keyName : String) {
IS_TOURNAMENT("IS_TOURNAMENT"),
DUPLICATE("DUPLICATE"),
SESSION_ID("SESSION_ID");
}
companion object {
fun newInstance(context: Context, isTournament: Boolean? = false, sessionId: String? = "") {
val intent = Intent(context, SessionActivity::class.java)
isTournament?.let {
intent.putExtra(IntentKey.IS_TOURNAMENT.keyName, isTournament)
}
sessionId?.let {
intent.putExtra(IntentKey.SESSION_ID.keyName, sessionId)
}
fun newInstance(context: Context, isTournament: Boolean? = false, sessionId: String? = "", duplicate: Boolean = false) {
val intent = this.intent(context, isTournament, sessionId, duplicate)
context.startActivity(intent)
}
fun newInstanceforResult(fragment: Fragment, isTournament: Boolean? = false, sessionId: String? = "", requestCode: Int) {
val intent = Intent(fragment.requireContext(), SessionActivity::class.java)
fun newInstanceforResult(fragment: Fragment, isTournament: Boolean? = false, sessionId: String? = "", duplicate: Boolean = false, requestCode: Int) {
val intent = this.intent(fragment.requireContext(), isTournament, sessionId, duplicate)
fragment.startActivityForResult(intent, requestCode)
}
private fun intent(context: Context, isTournament: Boolean? = false, sessionId: String? = "", duplicate: Boolean = false) : Intent {
val intent = Intent(context, SessionActivity::class.java)
isTournament?.let {
intent.putExtra(IntentKey.IS_TOURNAMENT.keyName, isTournament)
}
intent.putExtra(IntentKey.DUPLICATE.keyName, duplicate)
sessionId?.let {
intent.putExtra(IntentKey.SESSION_ID.keyName, sessionId)
}
fragment.startActivityForResult(intent, requestCode)
return intent
}
}
@ -62,8 +62,9 @@ class SessionActivity: PokerAnalyticsActivity() {
private fun initUI() {
val sessionId = intent.getStringExtra(IntentKey.SESSION_ID.keyName)
val isTournament = intent.getBooleanExtra(IntentKey.IS_TOURNAMENT.keyName, false)
val duplicate = intent.getBooleanExtra(IntentKey.DUPLICATE.keyName, false)
val fragment = sessionFragment as SessionFragment
fragment.setData(isTournament, sessionId)
fragment.setData(isTournament, sessionId, duplicate)
}
}

@ -0,0 +1,33 @@
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.PokerAnalyticsActivity
class Top10Activity : PokerAnalyticsActivity() {
companion object {
fun newInstance(context: Context) {
val intent = Intent(context, Top10Activity::class.java)
context.startActivity(intent)
}
/**
* Create a new instance for result
*/
fun newInstanceForResult(fragment: Fragment, requestCode: Int) {
val intent = Intent(fragment.requireContext(), Top10Activity::class.java)
fragment.startActivityForResult(intent, requestCode)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_top_10)
}
}

@ -2,10 +2,17 @@ package net.pokeranalytics.android.ui.activity.components
enum class RequestCode(var value: Int) {
DEFAULT(1),
FEED_MENU(100),
FEED_TRANSACTION_DETAILS(101),
BANKROLL_DETAILS(700),
BANKROLL_CREATE(701),
BANKROLL_EDIT(702),
NEW_SESSION(800),
NEW_TRANSACTION(801),
NEW_REPORT(802),
IMPORT(900)
IMPORT(900),
SUBSCRIPTION(901),
CURRENCY(902)
}
enum class ResultCode(var value: Int) {

@ -1,6 +1,7 @@
package net.pokeranalytics.android.ui.activity.components
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.PersistableBundle
@ -33,6 +34,7 @@ open class PokerAnalyticsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
Crashlytics.log("$this.localClassName onCreate")
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT // fixes crash
}
override fun onResume() {

@ -6,13 +6,17 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import io.realm.kotlin.where
import kotlinx.android.synthetic.main.row_feed_session.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.view.BindableHolder
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.getMonthAndYear
import timber.log.Timber
import java.util.*
@ -21,28 +25,54 @@ import kotlin.collections.HashMap
/**
* An adapter capable of displaying a list of RowRepresentables
* @param dataSource the datasource providing rows
* @param delegate the delegate, notified of UI actions
* The [delegate] is an object notified of UI actions
*/
class FeedSessionRowRepresentableAdapter(
var delegate: RowRepresentableDelegate? = null,
var realmResults: RealmResults<Session>,
var pendingRealmResults: RealmResults<Session>,
var distinctHeaders: RealmResults<Session>
private var realm: Realm,
var delegate: RowRepresentableDelegate? = null
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var headersPositions = HashMap<Int, Date?>()
private lateinit var startedSessions: RealmResults<Session>
private lateinit var pendingSessions: RealmResults<Session>
private lateinit var sortedHeaders: SortedMap<Int, Date?>
private var allSessions = mutableListOf<Session>()
var filter: Filter? = null
set(value) {
field = value
defineSessions()
refreshData()
}
init {
defineSessions()
refreshData()
}
private fun defineSessions() {
this.startedSessions = requestNewQuery().isNotNull("startDate").findAll()
.sort("startDate", Sort.DESCENDING)
this.pendingSessions = requestNewQuery().isNull("startDate").findAll()
.sort("creationDate", Sort.DESCENDING)
}
private fun requestNewQuery() : RealmQuery<Session> {
this.filter?.let {
return it.query()
} ?: run {
return realm.where()
}
}
/**
* Display a session view
*/
inner class RowSessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, row: Session?, adapter: FeedSessionRowRepresentableAdapter) {
itemView.sessionRow.setData(row as Session)
@ -50,6 +80,10 @@ class FeedSessionRowRepresentableAdapter(
adapter.delegate?.onRowSelected(position, row)
}
itemView.sessionRow.setOnClickListener(listener)
itemView.sessionRow.setOnLongClickListener {
itemView.showContextMenu()
}
}
}
@ -59,7 +93,7 @@ class FeedSessionRowRepresentableAdapter(
inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(title: String) {
// Title
itemView.findViewById<AppCompatTextView>(R.id.title)?.let {
itemView.findViewById<AppCompatTextView>(net.pokeranalytics.android.R.id.title)?.let {
it.text = title
}
}
@ -67,25 +101,24 @@ class FeedSessionRowRepresentableAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == RowViewType.ROW_SESSION.ordinal) {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_feed_session, parent, false)
val layout = LayoutInflater.from(parent.context).inflate(net.pokeranalytics.android.R.layout.row_feed_session, parent, false)
RowSessionViewHolder(layout)
} else {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_header_title, parent, false)
val layout = LayoutInflater.from(parent.context).inflate(net.pokeranalytics.android.R.layout.row_header_title, parent, false)
HeaderTitleViewHolder(layout)
}
}
override fun getItemViewType(position: Int): Int {
if (sortedHeaders.containsKey(position)) {
return RowViewType.HEADER_TITLE.ordinal
return if (sortedHeaders.containsKey(position)) {
RowViewType.HEADER_TITLE.ordinal
} else {
return RowViewType.ROW_SESSION.ordinal
RowViewType.ROW_SESSION.ordinal
}
}
override fun getItemCount(): Int {
return realmResults.size + pendingRealmResults.size + distinctHeaders.size
return allSessions.size + sortedHeaders.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
@ -104,25 +137,23 @@ class FeedSessionRowRepresentableAdapter(
// If the header has no date, it's a pending session
return if (sortedHeaders[position] == null) {
context.getString(R.string.pending)
context.getString(net.pokeranalytics.android.R.string.pending)
} else {
// Else, return the formatted date
val realmHeaderPosition = if (pendingRealmResults.size > 0) sortedHeaders.keys.indexOf(position) - 1 else sortedHeaders.keys.indexOf(position)
distinctHeaders[realmHeaderPosition]?.startDate?.getMonthAndYear() ?: ""
sortedHeaders[position]?.getMonthAndYear() ?: throw PAIllegalStateException("Null date should not happen there")
}
}
throw PAIllegalStateException("Any position should always have a header, position = $position")
}
return NULL_TEXT
fun sessionIdForPosition(position: Int): String? {
return this.getSessionForPosition(position)?.id
}
/**
* Get real index
*/
private fun getSessionForPosition(position: Int): Session? {
return if (pendingRealmResults.size > 0 && position < pendingRealmResults.size + 1) {
// If we have pending session & the position is between these sessions
pendingRealmResults[position - 1]
} else {
// Else, return the correct session
// Row position
var headersBefore = 0
@ -133,9 +164,7 @@ class FeedSessionRowRepresentableAdapter(
break
}
}
realmResults[position - headersBefore - pendingRealmResults.size]
}
return allSessions[position - headersBefore]
}
/**
@ -143,12 +172,12 @@ class FeedSessionRowRepresentableAdapter(
*/
fun refreshData() {
headersPositions.clear()
allSessions.clear()
allSessions.addAll(this.pendingSessions)
allSessions.addAll(this.startedSessions)
Timber.d("Update session list, total count = ${allSessions.size}")
// If we have pending sessions, set the first header to null
if (pendingRealmResults.size > 0) {
headersPositions[0] = null
}
val headersPositions = HashMap<Int, Date?>()
val start = System.currentTimeMillis()
@ -158,28 +187,31 @@ class FeedSessionRowRepresentableAdapter(
val calendar = Calendar.getInstance()
// Add headers if the date doesn't exist yet
for ((index, session) in realmResults.withIndex()) {
calendar.time = session.startDate ?: session.creationDate
for ((index, session) in allSessions.withIndex()) {
val startDate = session.startDate
if (startDate == null) {
headersPositions[0] = null
} else {
calendar.time = startDate
if (checkHeaderCondition(calendar, previousYear, previousMonth)) {
headersPositions[index + headersPositions.size + pendingRealmResults.size] = session.startDate ?: session.creationDate
headersPositions[index + headersPositions.size] = startDate
previousYear = calendar.get(Calendar.YEAR)
previousMonth = calendar.get(Calendar.MONTH)
}
}
}
sortedHeaders = headersPositions.toSortedMap()
Timber.d("Create viewTypesPositions in: ${System.currentTimeMillis() - start}ms")
}
/**
* Check if we need to add a header
* Can be change to manage different condition
* Can be changed 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)
}
}

@ -40,6 +40,7 @@ class FeedTransactionRowRepresentableAdapter(
* Display a transaction view
*/
inner class RowTransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, row: Transaction?, adapter: FeedTransactionRowRepresentableAdapter) {
itemView.transactionRow.setData(row as Transaction)
@ -75,10 +76,10 @@ class FeedTransactionRowRepresentableAdapter(
override fun getItemViewType(position: Int): Int {
if (sortedHeaders.containsKey(position)) {
return RowViewType.HEADER_TITLE.ordinal
return if (sortedHeaders.containsKey(position)) {
RowViewType.HEADER_TITLE.ordinal
} else {
return RowViewType.ROW_TRANSACTION.ordinal
RowViewType.ROW_TRANSACTION.ordinal
}
}
@ -130,8 +131,6 @@ class FeedTransactionRowRepresentableAdapter(
headersPositions.clear()
val start = System.currentTimeMillis()
var previousYear = Int.MAX_VALUE
var previousMonth = Int.MAX_VALUE
@ -148,6 +147,7 @@ class FeedTransactionRowRepresentableAdapter(
}
sortedHeaders = headersPositions.toSortedMap()
}
/**

@ -21,7 +21,7 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
1 -> StatisticsFragment.newInstance()
2 -> CalendarFragment.newInstance()
3 -> ReportsFragment.newInstance()
4 -> MoreFragment.newInstance()
4 -> SettingsFragment.newInstance()
else -> FeedFragment.newInstance()
}
}
@ -47,7 +47,7 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
StatisticsFragment::class.java -> 1
CalendarFragment::class.java -> 2
ReportsFragment::class.java -> 3
MoreFragment::class.java -> 4
SettingsFragment::class.java -> 4
else -> -1
}
}

@ -7,8 +7,11 @@ import net.pokeranalytics.android.ui.view.BindableHolder
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
interface RowRepresentableDelegate {
interface RowRepresentableDelegate: RowEditableDelegate {
fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean = false) {}
}
interface RowEditableDelegate {
fun onRowValueChanged(value: Any?, row: RowRepresentable) {}
fun onRowDeleted(row: RowRepresentable) {}
}

@ -1,14 +1,14 @@
package net.pokeranalytics.android.ui.adapter
import android.content.Context
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.util.TextFormat
/**
* Base Interface to provide the RowRepresentable to the adapter
*/
interface RowRepresentableDataSource: EditableDataSource, DisplayableDataSource, SelectableDataSource {
interface RowRepresentableDataSource: DisplayableDataSource, SelectableDataSource {
/**
* Returns a prebuild list of rows
@ -42,21 +42,21 @@ interface StaticRowRepresentableDataSource: RowRepresentableDataSource {
this.adapterRows()?.let {
return it[position]
}
throw IllegalStateException("Need to implement Data Source")
throw PAIllegalStateException("Need to implement Data Source")
}
override fun numberOfRows(): Int {
this.adapterRows()?.let {
return it.size
}
throw IllegalStateException("Need to implement Data Source")
throw PAIllegalStateException("Need to implement Data Source")
}
override fun viewTypeForPosition(position:Int): Int {
this.rowRepresentableForPosition(position)?.let {
return it.viewType
}
throw IllegalStateException("Need to implement Data Source")
throw PAIllegalStateException("Need to implement Data Source")
}
}
@ -160,18 +160,6 @@ interface DisplayableDataSource {
}
}
/**
* An interface providing a way to describe how the edition of a [RowRepresentable] will be displayed
*/
interface EditableDataSource {
/**
* A list of [RowRepresentableEditDescriptor] object specifying the way the edition will be handled
*/
fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return null
}
}
/**
* An interface providing a way to select a row

@ -10,114 +10,83 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.bankroll.BankrollReport
import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.util.extensions.findById
import timber.log.Timber
class BankrollDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
class BankrollDetailsFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_EDIT = 1000
/**
* Create new instance
*/
fun newInstance(bankrollReport: BankrollReport): BankrollDetailsFragment {
val fragment = BankrollDetailsFragment()
fragment.bankrollReport = bankrollReport
fragment.bankrollId = bankrollReport.setup.bankrollId
return fragment
}
}
private var bankrollId: String? = null
private lateinit var bankroll: Bankroll
private var rows: ArrayList<RowRepresentable> = ArrayList()
private lateinit var bankrollAdapter: RowRepresentableAdapter
private lateinit var bankrollReport: BankrollReport
private var bankrollDetailsMenu: Menu? = null
private var rows: ArrayList<RowRepresentable> = ArrayList()
// Life Cycle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_bankroll_details, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
initData()
initUI()
updateUI()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_EDIT && resultCode == RESULT_OK) {
if (requestCode == RequestCode.BANKROLL_EDIT.value && resultCode == RESULT_OK) {
if (data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) != null) {
activity?.setResult(RESULT_OK, data)
activity?.finish()
} else {
updateMenuUI()
updateUI()
}
}
}
override fun adapterRows(): List<RowRepresentable>? {
return rows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
menu?.clear()
inflater?.inflate(R.menu.toolbar_comparison_chart, menu)
inflater?.inflate(R.menu.toolbar_comparison_chart, menu) // TODO R.menu.toolbar_comparison_chart?
this.bankrollDetailsMenu = menu
updateMenuUI()
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
R.id.settings -> editBankroll()
}
return true
}
// Business
/**
* Init data
*/
private fun initData() {
rows.clear()
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.global))
val totalComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total)
val netComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netResult)
val netBankedComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netBanked)
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, resId = R.string.bankroll, computedStat = totalComputedStat))
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, resId = R.string.net_result, computedStat = netComputedStat))
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, resId = R.string.net_banked, computedStat = netBankedComputedStat))
if (bankrollReport.transactionBuckets.isNotEmpty()) {
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.operations))
bankrollReport.transactionBuckets.keys.forEach { key ->
bankrollReport.transactionBuckets[key]?.let { transactionBucket ->
val typeName = transactionBucket.transactions.firstOrNull()?.type?.getDisplayName(requireContext())
val computedStat = ComputedStat(Stat.NET_RESULT, transactionBucket.total)
rows.add(CustomizableRowRepresentable(RowViewType.TITLE_VALUE, title = typeName, computedStat = computedStat))
}
}
this.bankrollId?.let { id ->
this.bankroll = getRealm().findById(id) ?: throw PAIllegalStateException("Bankroll not found, id=$id")
}
}
@ -128,8 +97,7 @@ class BankrollDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
private fun initUI() {
setDisplayHomeAsUpEnabled(true)
updateMenuUI()
this.updateMenuUI()
bankrollAdapter = RowRepresentableAdapter(this, this)
@ -142,26 +110,107 @@ class BankrollDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresentable
}
}
private fun updateUI() {
this.updateMenuUI()
Timber.d("BankrollDetailsFragment > updateUI > reportForBankroll")
BankrollReportManager.reportForBankroll(this.bankrollId) { bankrollReport ->
this.initRows(bankrollReport)
this.bankrollAdapter.notifyDataSetChanged()
}
}
private fun initRows(bankrollReport: BankrollReport) {
rows.clear()
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.global))
val currency = if (this.bankrollId != null) { this.bankroll.utilCurrency } else { null }
val totalComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total, currency = currency)
val netComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netResult, currency = currency)
val netBankedComputedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.netBanked, currency = currency)
rows.add(
CustomizableRowRepresentable(
RowViewType.TITLE_VALUE,
resId = R.string.bankroll,
computedStat = totalComputedStat
)
)
rows.add(
CustomizableRowRepresentable(
RowViewType.TITLE_VALUE,
resId = R.string.net_result,
computedStat = netComputedStat
)
)
rows.add(
CustomizableRowRepresentable(
RowViewType.TITLE_VALUE,
resId = R.string.net_banked,
computedStat = netBankedComputedStat
)
)
if (bankrollReport.transactionBuckets.isNotEmpty()) {
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.operations))
bankrollReport.transactionBuckets.keys.forEach { key ->
bankrollReport.transactionBuckets[key]?.let { transactionBucket ->
val typeName = transactionBucket.name
val computedStat = ComputedStat(Stat.NET_RESULT, transactionBucket.total, currency = currency)
rows.add(
CustomizableRowRepresentable(
RowViewType.TITLE_VALUE,
title = typeName,
computedStat = computedStat
)
)
}
}
}
}
/**
* Update menu UI
*/
private fun updateMenuUI() {
if (bankrollReport.setup.virtualBankroll) {
if (this.bankrollId == null) {
setToolbarTitle(getString(R.string.total))
bankrollDetailsMenu?.findItem(R.id.settings)?.isVisible = false
} else {
setToolbarTitle(bankrollReport.setup.bankroll?.name)
setToolbarTitle(this.bankroll.name)
bankrollDetailsMenu?.findItem(R.id.settings)?.isVisible = true
}
}
// StaticRowRepresentableDataSource
override fun adapterRows(): List<RowRepresentable>? {
return rows
}
// RowRepresentableDelegate
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.settings -> editBankroll()
}
return true
}
// Business
/**
* Open Bankroll edit activity
*/
private fun editBankroll() {
EditableDataActivity.newInstanceForResult(this, LiveData.BANKROLL, bankrollReport.setup.bankroll?.id, REQUEST_CODE_EDIT)
EditableDataActivity.newInstanceForResult(this, LiveData.BANKROLL, this.bankrollId, RequestCode.BANKROLL_EDIT.value)
}
}

@ -8,7 +8,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.mikephil.charting.data.LineDataSet
import io.realm.RealmObject
import io.realm.RealmResults
import kotlinx.android.synthetic.main.fragment_bankroll.*
import kotlinx.coroutines.Dispatchers
@ -16,39 +15,38 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator
import net.pokeranalytics.android.calculus.bankroll.BankrollReport
import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup
import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.GraphActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollGraphRow
import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollMainRow
import net.pokeranalytics.android.ui.view.rowrepresentable.BankrollTotalRow
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.GraphRow
import net.pokeranalytics.android.util.extensions.sorted
import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList
interface BankrollRowRepresentable : RowRepresentable {
var bankrollId: String?
}
class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_DETAILS = 100
const val REQUEST_CODE_CREATE = 101
/**
* Create new instance
*/
@ -61,10 +59,7 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour
}
private var rows: ArrayList<RowRepresentable> = ArrayList()
private var bankrollReportForRow: HashMap<RowRepresentable, BankrollReport> = HashMap()
private var lastItemClickedPosition: Int = 0
private var lastItemClickedId: String = ""
private var deletedRow: RowRepresentable? = null
private var bankrollRowRepresentables: HashMap<String?, List<BankrollRowRepresentable>> = HashMap()
private lateinit var bankrolls: RealmResults<Bankroll>
@ -85,102 +80,69 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour
initData()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_DETAILS && resultCode == Activity.RESULT_OK) {
if (requestCode == RequestCode.BANKROLL_DETAILS.value && resultCode == Activity.RESULT_OK) {
val itemToDeleteId = data?.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName)
itemToDeleteId?.let { id ->
GlobalScope.launch(Dispatchers.Main) {
delay(300)
deleteItem(dataListAdapter, bankrolls, id)
// update view
BankrollReportManager.notifyBankrollReportImpact(id)
dataListAdapter.notifyDataSetChanged()
}
}
} else if (requestCode == REQUEST_CODE_CREATE && resultCode == Activity.RESULT_OK) {
} else if (requestCode == RequestCode.BANKROLL_CREATE.value && resultCode == Activity.RESULT_OK) {
//TODO: Refresh bankrolls
initData()
} else {
initData()
}
}
override fun adapterRows(): List<RowRepresentable>? {
return rows
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
is GraphRow -> {
val lineDataSet = row.dataSet as LineDataSet
GraphActivity.newInstance(requireContext(), listOf(lineDataSet), title = getString(R.string.bankroll))
}
else -> {
if (bankrollReportForRow.containsKey(row)) {
bankrollReportForRow[row]?.let { bankrollReport ->
lastItemClickedPosition = position
lastItemClickedId = (row as? Identifiable)?.id ?: ""
BankrollDetailsActivity.newInstanceForResult(this, bankrollReport, REQUEST_CODE_DETAILS)
}
}
}
}
}
// Business
/**
* Init data
*/
private fun initData() {
val realm = getRealm()
this.bankrolls = realm.sorted()
rows.clear()
bankrollReportForRow.clear()
this.bankrolls = getRealm().sorted()
this.bankrolls.addChangeListener { _, _ ->
this.createRowRepresentables()
}
GlobalScope.launch {
this.createRowRepresentables()
}
launch(Dispatchers.Main) {
private fun createRowRepresentables() {
// TODO: Improve that
// We are in the main thread...
rows.clear()
bankrollRowRepresentables.clear()
val startDate = Date()
// Virtual bankroll
val graphRow = BankrollGraphRow()
rows.add(0, graphRow)
val mainRow = BankrollMainRow()
rows.add(mainRow)
// Graph
val globalBankrollReportSetup = BankrollReportSetup()
val globalBankrollReport = BankrollCalculator.computeReport(getRealm(), globalBankrollReportSetup)
rows.add(0, GraphRow(dataSet = globalBankrollReport.lineDataSet(requireContext())))
rows.add(globalBankrollReport)
bankrollReportForRow[globalBankrollReport] = globalBankrollReport
bankrollRowRepresentables[null] = listOf(graphRow, mainRow)
// Bankrolls
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, resId = R.string.bankrolls))
Timber.d("initData: ${System.currentTimeMillis() - startDate.time}ms")
// val bankrolls = LiveData.Bankroll.items(getRealm()) as RealmResults<Bankroll>
bankrolls.forEach { bankroll ->
val bankrollReportSetup = BankrollReportSetup(bankroll)
val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup)
val computedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total)
val row =
CustomizableRowRepresentable(RowViewType.TITLE_VALUE_ARROW, title = bankroll.name, computedStat = computedStat, isSelectable = true)
row.id = bankroll.id
Timber.d("Creating row for br : ${bankroll.id}, name= ${bankroll.name}, isManaged = ${bankroll.isManaged()}, isValid = ${bankroll.isValid}")
val row = BankrollTotalRow(bankroll.id, bankroll.name)
rows.add(row)
bankrollReportForRow[row] = bankrollReport
bankrollRowRepresentables[bankroll.id] = listOf(row)
}
if (!isDetached) {
dataListAdapter.notifyDataSetChanged()
}
}
}
}
/**
* Init UI
@ -200,29 +162,31 @@ class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSour
}
addButton.setOnClickListener {
EditableDataActivity.newInstanceForResult(this@BankrollFragment, dataType = LiveData.BANKROLL, primaryKey = null, requestCode = REQUEST_CODE_CREATE)
EditableDataActivity.newInstanceForResult(
this@BankrollFragment,
dataType = LiveData.BANKROLL,
primaryKey = null,
requestCode = RequestCode.BANKROLL_CREATE.value
)
}
}
override fun updateUIAfterDeletion(itemPosition: Int) {
lastItemClickedPosition = rows.indexOfFirst { if (it is Identifiable) it.id == lastItemClickedId else false }
deletedRow = rows.find { if (it is Identifiable) it.id == lastItemClickedId else false }
rows.removeAt(lastItemClickedPosition)
dataListAdapter.notifyItemRemoved(lastItemClickedPosition)
override fun adapterRows(): List<RowRepresentable>? {
return rows
}
override fun updateUIAfterUndoDeletion(newItem: RealmObject) {
// TODO: Improve that
// We are recreating a Bankroll report because the last one is invalid => the bankroll of the setup has been deleted
deletedRow?.let { row ->
val bankrollReportSetup = BankrollReportSetup(newItem as Bankroll)
val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup)
bankrollReportForRow[row] = bankrollReport
rows.add(lastItemClickedPosition, row)
dataListAdapter.notifyItemInserted(lastItemClickedPosition)
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
is BankrollGraphRow -> {
val lineDataSet = row.dataSet as LineDataSet
GraphActivity.newInstance(requireContext(), listOf(lineDataSet), title = getString(R.string.bankroll))
}
is BankrollRowRepresentable -> {
Timber.d("BankrollFragment > onRowSelected > reportForBankroll")
BankrollReportManager.reportForBankroll(row.bankrollId) { bankrollReport ->
BankrollDetailsActivity.newInstanceForResult(this, bankrollReport, RequestCode.BANKROLL_DETAILS.value)
}
}
}
}

@ -8,6 +8,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.tabs.TabLayout
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmResults
import kotlinx.android.synthetic.main.fragment_calendar.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -84,7 +85,10 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
super.onViewCreated(view, savedInstanceState)
initData()
initUI()
}
override fun onResume() {
super.onResume()
launchStatComputation()
}
@ -117,7 +121,7 @@ class CalendarFragment : RealmFragment(), CoroutineScope, StaticRowRepresentable
override val observedEntities: List<Class<out RealmModel>> = listOf(ComputableResult::class.java)
override fun entitiesChanged(clazz: Class<out RealmModel>) {
override fun entitiesChanged(clazz: Class<out RealmModel>, results: RealmResults<out RealmModel>) {
launchStatComputation()
}

@ -17,6 +17,7 @@ import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
import net.pokeranalytics.android.util.UserDefaults
import java.util.*
class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
@ -47,12 +48,8 @@ class CurrenciesFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataS
private val availableCurrencies = this.systemCurrencies.filter {
!mostUsedCurrencyCodes.contains(it.currencyCode)
}.filter {
Locale.getAvailableLocales().filter { locale ->
try {
Currency.getInstance(locale).currencyCode == it.currencyCode
} catch (e: Exception) {
false
}
UserDefaults.availableCurrencyLocales.filter { currencyLocale ->
Currency.getInstance(currencyLocale).currencyCode == it.currencyCode
}.isNotEmpty()
}.sortedBy {
it.displayName

@ -13,6 +13,7 @@ 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
@ -24,7 +25,6 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.extensions.sorted
@ -44,7 +44,7 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
/**
* Set fragment data
*/
fun setData(dataType: Int) {
open fun setData(dataType: Int) {
this.dataType = LiveData.values()[dataType]
this.identifiableClass = this.dataType.relatedEntity
@ -87,7 +87,7 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
val itemId = item.id
deleteItem(dataListAdapter, items, itemId)
} else {
throw IllegalStateException("Item with position $position not found")
throw PAIllegalStateException("Item with position $position not found")
}
}

@ -3,9 +3,7 @@ package net.pokeranalytics.android.ui.fragment
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.widget.Toast
import androidx.core.app.ActivityOptionsCompat
import androidx.core.view.isVisible
@ -17,6 +15,7 @@ import io.realm.Sort
import io.realm.kotlin.where
import kotlinx.android.synthetic.main.fragment_feed.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Editable
import net.pokeranalytics.android.model.realm.Filter
@ -30,13 +29,15 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.FilterableFragment
import net.pokeranalytics.android.ui.interfaces.FilterActivityRequestCode
import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.view.ContextMenuRecyclerView
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.billing.AppGuard
import net.pokeranalytics.android.util.extensions.count
import java.text.SimpleDateFormat
import java.util.*
class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
private enum class Tab {
@ -46,20 +47,20 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_MENU = 100
const val REQUEST_CODE_TRANSACTION_DETAILS = 101
fun newInstance(): FeedFragment {
val fragment = FeedFragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
}
}
private var currentTab = Tab.SESSIONS
private lateinit var feedSessionAdapter: FeedSessionRowRepresentableAdapter
private lateinit var feedTransactionAdapter: FeedTransactionRowRepresentableAdapter
private lateinit var realmSessions: RealmResults<Session>
private lateinit var realmTransactions: RealmResults<Transaction>
private lateinit var betaLimitDate: Date
@ -70,8 +71,8 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
override val observedEntities: List<Class<out RealmModel>> = listOf(Session::class.java, Transaction::class.java)
override fun entitiesChanged(clazz: Class<out RealmModel>) {
super.entitiesChanged(clazz)
override fun entitiesChanged(clazz: Class<out RealmModel>, results: RealmResults<out RealmModel>) {
super.entitiesChanged(clazz, results)
when (clazz.kotlin) {
Session::class -> {
@ -91,6 +92,34 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
return inflater.inflate(R.layout.fragment_feed, container, false)
}
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
if (v?.id == R.id.menuRecyclerView) {
activity?.menuInflater?.inflate(R.menu.menu_session, menu)
}
}
override fun onContextItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.duplicate -> {
val info = item.menuInfo as ContextMenuRecyclerView.RecyclerViewContextMenuInfo
val sessionId = this.feedSessionAdapter.sessionIdForPosition(info.position)
if (sessionId != null) {
createNewSession(true, sessionId = sessionId, duplicate = true)
} else {
throw PAIllegalStateException("Session not found for duplicate at position: ${info.position}")
}
}
else -> {
}
}
return true
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
@ -99,13 +128,13 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_MENU && resultCode == RESULT_OK && data != null) {
if (requestCode == RequestCode.FEED_MENU.value && resultCode == RESULT_OK && data != null) {
when (data.getIntExtra(NewDataMenuActivity.IntentKey.CHOICE.keyName, -1)) {
0 -> createNewSession(false)
1 -> createNewSession(true)
2 -> createNewTransaction()
}
} else if (requestCode == REQUEST_CODE_TRANSACTION_DETAILS && resultCode == RESULT_OK && data != null) {
} else if (requestCode == RequestCode.FEED_TRANSACTION_DETAILS.value && resultCode == RESULT_OK && data != null) {
if (data.getStringExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName) != null) {
deleteSelectedTransaction()
}
@ -123,22 +152,9 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
override fun onDestroyView() {
super.onDestroyView()
realmSessions.removeAllChangeListeners()
realmTransactions.removeAllChangeListeners()
}
/*
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser && view != null) {
if (FilterHandler.filterWasUpdated) {
applyFilter()
FilterHandler.filterWasUpdated = false
}
}
}
*/
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
is Session -> SessionActivity.newInstance(requireContext(), sessionId = (row as Editable).id)
@ -149,7 +165,7 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
this,
LiveData.TRANSACTION,
row.id,
REQUEST_CODE_TRANSACTION_DETAILS
RequestCode.FEED_TRANSACTION_DETAILS.value
)
}
}
@ -160,37 +176,49 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
*/
private fun initUI() {
disclaimerContainer.isVisible = Preferences.shouldShowDisclaimer(requireContext())
this.feedSessionAdapter = FeedSessionRowRepresentableAdapter(getRealm(), this)
registerForContextMenu(this.menuRecyclerView)
val messageToShow: Preferences.FeedMessage? = Preferences.feedMessageToShow(requireContext())
disclaimerDismiss.setOnClickListener {
Preferences.setStopShowingDisclaimer(requireContext())
if (messageToShow != null) {
messageBox.isVisible = true
message.text = getString(messageToShow.resId)
disclaimerContainer.animate().translationY(disclaimerContainer.height.toFloat())
messageBoxDismiss.setOnClickListener {
Preferences.setStopShowingMessage(messageToShow, requireContext())
messageBox.animate().translationY(messageBox.height.toFloat())
.setInterpolator(FastOutSlowInInterpolator())
.withEndAction { disclaimerContainer?.isVisible = false }
.withEndAction { messageBox?.isVisible = false }
.start()
}
} else {
messageBox.isVisible = false
}
// Add button
addButton.setOnClickListener {
activity?.let {
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(it)
val intent = Intent(requireContext(), NewDataMenuActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_MENU, options.toBundle())
startActivityForResult(intent, RequestCode.FEED_MENU.value, options.toBundle())
}
}
// Tabs
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
when (tab.position) {
0 -> {
Tab.SESSIONS.ordinal -> {
currentFilterable = FilterableType.SESSION
recyclerView.adapter = feedSessionAdapter
}
1 -> {
Tab.TRANSACTIONS.ordinal -> {
currentFilterable = FilterableType.TRANSACTION
recyclerView.adapter = feedTransactionAdapter
}
}
tabChanged(tab.position)
}
override fun onTabUnselected(tab: TabLayout.Tab) {
@ -212,7 +240,7 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
this.currentFilterable = FilterableType.SESSION
val viewManager = SmoothScrollLinearLayoutManager(requireContext())
recyclerView.apply {
menuRecyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
}
@ -221,30 +249,15 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
}
private fun loadSessions(filter: Filter? = null) {
val sessionFilter: Filter? = filter?.let {
if (it.filterableType == FilterableType.SESSION) {
it
} else {
null
}
}
// Sessions
this.realmSessions =
sessionFilter?.results() ?: run { getRealm().where<Session>().isNotNull("startDate").findAll() }
this.realmSessions = this.realmSessions.sort("startDate", Sort.DESCENDING)
val pendingSessions = sessionFilter?.let {
getRealm().where<Session>().alwaysFalse().findAll()
} ?: run {
getRealm().where<Session>().isNull("year").isNull("month").findAll().sort("startDate", Sort.DESCENDING)
when (filter?.filterableType) {
FilterableType.SESSION -> {
this.feedSessionAdapter.filter = filter
}
else -> {
this.feedSessionAdapter.filter = null
}
var distinctDateSessions = sessionFilter?.results("year", "month") ?: run {
getRealm().where<Session>().distinct("year", "month").findAll()
}
distinctDateSessions = distinctDateSessions.sort("startDate", Sort.DESCENDING)
this.feedSessionAdapter =
FeedSessionRowRepresentableAdapter(this, realmSessions, pendingSessions, distinctDateSessions)
}
private fun loadTransactions(filter: Filter? = null) {
@ -272,21 +285,27 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
/**
* Create a new cash game
*/
private fun createNewSession(isTournament: Boolean) {
// val sessionCount = this.feedSessionAdapter.realmResults.size
// if (!AppGuard.isProUser && sessionCount >= AppGuard.MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION) { // && !BuildConfig.DEBUG
// Toast.makeText(context, "Please subscribe!", Toast.LENGTH_LONG).show()
// BillingActivity.newInstanceForResult(requireContext())
// return
// }
private fun createNewSession(isTournament: Boolean, sessionId: String? = null, duplicate: Boolean = false) {
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
val sessionCount = getRealm().count(Session::class.java)
if (!AppGuard.isProUser && sessionCount >= AppGuard.MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION) { // && !BuildConfig.DEBUG
BillingActivity.newInstanceForResult(this, true)
return
}
SessionActivity.newInstanceforResult(this, isTournament, requestCode = RequestCode.NEW_SESSION.value)
// Keep commented code for special versions
// if (Date().after(betaLimitDate)) {
// this.showEndOfBetaMessage()
// return
// }
SessionActivity.newInstanceforResult(
this,
isTournament,
sessionId = sessionId,
duplicate = duplicate,
requestCode = RequestCode.NEW_SESSION.value
)
newSessionCreated = true
}
@ -295,38 +314,36 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
*/
private fun createNewTransaction() {
if (Date().after(betaLimitDate)) {
this.showEndOfBetaMessage()
return
}
// if (Date().after(betaLimitDate)) {
// this.showEndOfBetaMessage()
// return
// }
EditableDataActivity.newInstanceForResult(this, LiveData.TRANSACTION, null, RequestCode.NEW_TRANSACTION.value)
// EditableDataActivity.newInstance(requireContext(), LiveData.TRANSACTION.ordinal)
}
/**
* Delete selected transaction
*/
private fun deleteSelectedTransaction() {
val realm = getRealm()
realm.beginTransaction()
getRealm().executeTransaction {
selectedTransaction?.deleteFromRealm()
realm.commitTransaction()
}
selectedTransactionPosition = -1
}
/**
* Show end of beta message
* Keep for possible future uses
*/
private fun showEndOfBetaMessage() {
Toast.makeText(
context,
"Beta has ended. Thanks a lot for your participation! Please update with the Google Play version to continue using the app",
"App version has ended. Thanks a lot for using it! Please update with the Google Play version to continue using the app.",
Toast.LENGTH_LONG
).show()
}
// Filter Handler
override fun applyFilter() {
@ -339,11 +356,9 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
filter?.let {
when (it.filterableType) {
FilterableType.SESSION -> {
recyclerView.adapter = feedSessionAdapter
this.selectTab(Tab.SESSIONS)
}
FilterableType.TRANSACTION -> {
recyclerView.adapter = feedTransactionAdapter
this.selectTab(Tab.TRANSACTIONS)
}
else -> {
@ -354,24 +369,33 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate {
if (!adapterHasBeenSet) {
adapterHasBeenSet = true
recyclerView.adapter = feedSessionAdapter
this.setAdapter()
}
}
override fun removeFilter() {
super.removeFilter()
this.loadSessions()
this.loadTransactions()
if (currentFilterable == FilterableType.SESSION) {
recyclerView.adapter = feedSessionAdapter
} else {
recyclerView.adapter = feedTransactionAdapter
}
this.setAdapter()
}
private fun selectTab(tab: Tab) {
this.currentTab = tab
this.tabs.getTabAt(tab.ordinal)?.select()
setAdapter()
}
private fun tabChanged(index: Int) {
this.currentTab = Tab.values()[index]
setAdapter()
}
private fun setAdapter() {
when (this.currentTab) {
Tab.SESSIONS -> menuRecyclerView.adapter = feedSessionAdapter
Tab.TRANSACTIONS -> menuRecyclerView.adapter = feedTransactionAdapter
}
}
}

@ -18,8 +18,6 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.helpers.DateTimePickerManager
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
@ -65,35 +63,7 @@ open class FilterDetailsFragment : RealmFragment(), StaticRowRepresentableDataSo
updateRowsSelection(row)
return
}
when (row) {
is QueryCondition.DateQuery -> DateTimePickerManager.create(
requireContext(),
row,
this,
row.singleValue,
onlyDate = !row.showTime,
onlyTime = row.showTime
)
is QueryCondition.Duration -> {
var hours: String? = null
var minutes: String? = null
row.minutes?.let {
hours = if (it / 60 > 0) (it / 60).toString() else null
minutes = if (it % 60 > 0) (it % 60).toString() else null
}
val data = row.editingDescriptors(mapOf("hours" to hours, "minutes" to minutes))
BottomSheetFragment.create(fragmentManager, row, this, data, true)
}
is QueryCondition.ListOfValues<*> -> {
var valueAsString: String? = null
row.listOfValues.firstOrNull()?.let {
valueAsString = row.listOfValues.firstOrNull()?.toString()
}
val data = row.editingDescriptors(mapOf("valueAsString" to valueAsString))
BottomSheetFragment.create(fragmentManager, row, this, data, true)
}
}
row.startEditing(null, this)
}
override fun stringForRow(row: RowRepresentable, context: Context): String {
@ -243,12 +213,10 @@ open class FilterDetailsFragment : RealmFragment(), StaticRowRepresentableDataSo
Timber.d("Selected rows: $it")
}
val realm = getRealm()
realm.beginTransaction()
getRealm().executeTransaction {
currentFilter?.remove(filterCategoryRow)
currentFilter?.createOrUpdateFilterConditions(selectedRows)
realm.commitTransaction()
}
currentFilter?.filterConditions?.forEach {
Timber.d("Condition: $it")
}

@ -12,11 +12,12 @@ import kotlinx.android.synthetic.main.fragment_editable_data.recyclerView
import kotlinx.android.synthetic.main.fragment_filters.*
import kotlinx.android.synthetic.main.fragment_filters.view.toolbar
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.FilterDetailsActivity
import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.activity.FiltersListActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -27,6 +28,7 @@ import net.pokeranalytics.android.ui.interfaces.FilterableType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterCategoryRow
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.sorted
import timber.log.Timber
@ -54,6 +56,14 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource,
private var isUpdating = false
private var showMostUsedFiltersLayout = true
/**
* Set fragment data
*/
fun setData(primaryKey: String?, filterableType: FilterableType) {
this.primaryKey = primaryKey
this.filterableType = filterableType
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_filters, container, false)
@ -159,7 +169,7 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource,
moreFilters.setOnClickListener {
LiveData.FILTER.subType = filterableType.uniqueIdentifier
DataListActivity.newSelectInstance(this, LiveData.FILTER.ordinal, false)
FiltersListActivity.newSelectInstance(this, LiveData.FILTER.ordinal, false)
}
mostUsedFiltersLayout.isVisible = showMostUsedFiltersLayout
@ -173,7 +183,9 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource,
val realm = getRealm()
primaryKey?.let {
currentFilter = realm.copyFromRealm(Filter.getFilterBydId(realm, it))
val filter = realm.findById<Filter>(it) ?: throw PAIllegalStateException("Can't find filter with id=$it")
currentFilter = realm.copyFromRealm(filter)
isUpdating = true
} ?: run {
currentFilter = Filter.newInstance(this.filterableType.uniqueIdentifier) //realm.copyFromRealm(Filter.newInstanceForResult(realm, this.filterableType.ordinal))
@ -251,13 +263,12 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource,
* Validate the updates of the queryWith
*/
private fun validateUpdates() {
val realm = getRealm()
realm.beginTransaction()
getRealm().executeTransaction { realm ->
currentFilter?.let {
it.name = it.query.getName(requireContext())
realm.copyToRealmOrUpdate(it)
}
realm.commitTransaction()
}
val filterId = currentFilter?.id ?: ""
finishActivityWithResult(filterId)
@ -268,12 +279,11 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource,
*/
private fun cancelUpdates() {
val filterId = filterCopy?.id ?: ""
val realm = getRealm()
realm.beginTransaction()
getRealm().executeTransaction { realm ->
filterCopy?.let {
realm.copyToRealmOrUpdate(it)
}
realm.commitTransaction()
}
finishActivityWithResult(filterId)
}
@ -287,14 +297,6 @@ open class FiltersFragment : RealmFragment(), StaticRowRepresentableDataSource,
activity?.finish()
}
/**
* Set fragment data
*/
fun setData(primaryKey: String?, filterableType: FilterableType) {
this.primaryKey = primaryKey
this.filterableType = filterableType
}
/**
* Update the most used filters visibility
*/

@ -0,0 +1,112 @@
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.interfaces.FilterHandler.Companion.INTENT_FILTER_UPDATE_FILTER_UI
import net.pokeranalytics.android.ui.view.RowRepresentable
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 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) {
row.startEditing(null, this)
} 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)
}
}
}

@ -14,13 +14,15 @@ import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import kotlinx.android.synthetic.main.fragment_graph.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.interfaces.ObjectIdentifier
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.graph.AxisFormatting
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.graph.ObjectIdentifier
import net.pokeranalytics.android.ui.graph.setStyle
import net.pokeranalytics.android.ui.view.LegendView
import net.pokeranalytics.android.ui.view.MultiLineLegendView
import net.pokeranalytics.android.util.extensions.findById
import timber.log.Timber
class GraphFragment : RealmFragment(), OnChartValueSelectedListener {
@ -150,10 +152,12 @@ class GraphFragment : RealmFragment(), OnChartValueSelectedListener {
barChart.data = barData
dataSets.firstOrNull()?.let {
if (it.entryCount > 0) {
val entry = it.getEntryForIndex(0)
this.chartView?.highlightValue(entry.x, 0)
}
}
}
this.chartContainer.addView(this.chartView)
@ -179,18 +183,23 @@ class GraphFragment : RealmFragment(), OnChartValueSelectedListener {
val statEntry = when (entry.data) {
is ObjectIdentifier -> {
val identifier = entry.data as ObjectIdentifier
getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull()
getRealm().findById(identifier.clazz, identifier.id)
}
is GraphUnderlyingEntry -> entry.data as GraphUnderlyingEntry?
is GraphUnderlyingEntry -> entry.data
else -> null
}
statEntry?.let {
if (statEntry is GraphUnderlyingEntry) {
val groupName = dataSet?.label ?: ""
val color = dataSet?.color
val legendValue = it.legendValues(stat, entry, this.style, groupName, requireContext())
val legendValue = statEntry.legendValues(stat, entry, this.style, groupName, requireContext())
this.legendView.setItemData(legendValue, color)
} else {
Timber.w("Data $statEntry should implement GraphUnderlyingEntry")
}
}
}

@ -4,27 +4,30 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_import.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.ResultCode
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.util.csv.CSVImporter
import net.pokeranalytics.android.util.csv.ImportDelegate
import net.pokeranalytics.android.util.csv.ImportException
import timber.log.Timber
import java.io.InputStream
import java.text.NumberFormat
import java.util.*
import kotlin.coroutines.CoroutineContext
class ImportFragment : RealmFragment() {
class ImportFragment : RealmFragment(), ImportDelegate {
val coroutineContext: CoroutineContext
get() = Dispatchers.Main
private lateinit var filePath: String
private lateinit var inputStream: InputStream
private lateinit var importer: CSVImporter
fun setData(path: String) {
this.filePath = path
@ -42,12 +45,34 @@ class ImportFragment : RealmFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
this.initUI()
this.startImport()
}
fun startImport() {
private fun initUI() {
this.imported.text = requireContext().getString(R.string.imported)
this.total.text = requireContext().getString(R.string.total)
this.save.isEnabled = false
this.save.setOnClickListener {
this.end()
}
this.cancel.setOnClickListener {
this.cancel()
this.end()
}
}
private fun startImport() {
var shouldDismissActivity = false
// var shouldDismissActivity = false
this.importer = CSVImporter(inputStream)
this.importer.delegate = this
GlobalScope.launch(coroutineContext) {
@ -56,10 +81,9 @@ class ImportFragment : RealmFragment() {
Timber.d(">>> Start Import...")
try {
val csv = CSVImporter(inputStream)
csv.start()
importer.start()
} catch (e: ImportException) {
shouldDismissActivity = true
// shouldDismissActivity = true
}
val e = Date()
val duration = (e.time - s.time) / 1000.0
@ -68,17 +92,42 @@ class ImportFragment : RealmFragment() {
}
test.await()
if (shouldDismissActivity) {
// if (shouldDismissActivity) {
//
// activity?.let {
// it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value)
// it.finish()
// }
//
// } else {
// }
importDidFinish()
}
activity?.let {
it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value)
it.finish()
}
private fun cancel() {
this.importer.cancel(getRealm())
}
private fun importDidFinish() {
this.save.isEnabled = true
}
private fun end() {
activity?.finish()
}
val numberFormatter = NumberFormat.getNumberInstance()
// ImportDelegate
override fun parsingCountUpdate(importedCount: Int, totalCount: Int) {
this.counter.text = this.numberFormatter.format(importedCount)
this.totalCounter.text = this.numberFormatter.format(totalCount)
}
}

@ -9,6 +9,7 @@ import kotlinx.android.synthetic.main.fragment_more.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.BankrollActivity
import net.pokeranalytics.android.ui.activity.SettingsActivity
import net.pokeranalytics.android.ui.activity.Top10Activity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -62,6 +63,7 @@ class MoreFragment : PokerAnalyticsFragment(), StaticRowRepresentableDataSource,
super.onRowSelected(position, row, fromAction)
when(row) {
MoreTabRow.BANKROLL -> BankrollActivity.newInstance(requireContext())
MoreTabRow.TOP_10 -> Top10Activity.newInstance(requireContext())
MoreTabRow.SETTINGS -> SettingsActivity.newInstance(requireContext())
}
}

@ -10,6 +10,8 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.recyclerview.widget.DiffUtil
import kotlinx.android.synthetic.main.fragment_session.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.extensions.SessionState
import net.pokeranalytics.android.model.extensions.getState
@ -21,8 +23,6 @@ import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.helpers.DateTimePickerManager
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableDiffCallback
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
@ -59,28 +59,41 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
/**
* Set fragment data
*/
fun setData(isTournament: Boolean, sessionId: String) {
fun setData(isTournament: Boolean, sessionId: String? = null, duplicate: Boolean) {
val realm = getRealm()
if (sessionId != null) {
val sessionRealm = realm.findById<Session>(sessionId)
if (sessionRealm != null) {
if (duplicate) { // duplicate session
realm.executeTransaction {
val session = sessionRealm.duplicate()
currentSession = session
}
sessionHasBeenCustomized = false
} else { // show existing session
currentSession = sessionRealm
sessionHasBeenCustomized = true
}
} else {
realm.beginTransaction()
currentSession = Session.newInstance(realm, isTournament)
throw PAIllegalStateException("Session cannot be null here, session id = $sessionId")
}
} else { // buildAndShow new session
realm.executeTransaction { executeRealm ->
currentSession = Session.newInstance(executeRealm, isTournament)
FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, null, requireContext())
realm.commitTransaction()
}
// Find the nearest location around the user
parentActivity?.findNearestLocation {
it?.let { location ->
realm.beginTransaction()
val realmLocation = realm.findById<Location>(location.id)
realm.executeTransaction { executeRealm ->
val realmLocation = executeRealm.findById<Location>(location.id)
FavoriteSessionFinder.copyParametersFromFavoriteSession(currentSession, realmLocation, requireContext())
currentSession.location = realmLocation
realm.commitTransaction()
}
updateSessionUI(true)
}
}
@ -143,37 +156,14 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
Toast.makeText(requireContext(), "Action for row: $row", Toast.LENGTH_SHORT).show()
return
}
val data = currentSession.editDescriptors(row)
when (row) {
SessionRow.START_DATE -> DateTimePickerManager.create(requireContext(), row, this, currentSession.startDate)
SessionRow.END_DATE -> {
if (this.currentSession.startDate == null) {
Toast.makeText(context, R.string.session_missing_start_date, Toast.LENGTH_SHORT).show()
} else {
DateTimePickerManager.create(
requireContext(),
row,
this,
currentSession.endDate ?: currentSession.startDate ?: Date(),
currentSession.startDate
)
}
}
SessionRow.BANKROLL -> {
BottomSheetFragment.create(fragmentManager, row, this, data, false, currentSession.currency)
}
else -> BottomSheetFragment.create(fragmentManager, row, this, data, currentCurrency = currentSession.currency)
}
row.startEditing(currentSession, this)
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
sessionHasBeenCustomized = true
try {
currentSession.updateValue(value, row)
} catch (e: IllegalStateException) {
} catch (e: PAIllegalStateException) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
return
}
@ -345,6 +335,9 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
* Delete a session
*/
private fun deleteSession() {
currentSession.bankroll?.id?.let { id ->
BankrollReportManager.notifyBankrollReportImpact(id)
}
currentSession.delete()
activity?.finish()
}

@ -13,11 +13,10 @@ import io.realm.Realm
import kotlinx.android.synthetic.main.fragment_settings.*
import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.activity.BillingActivity
import net.pokeranalytics.android.ui.activity.CurrenciesActivity
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.activity.GDPRActivity
import net.pokeranalytics.android.ui.activity.*
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -57,7 +56,6 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
rows
}
const val REQUEST_CODE_CURRENCY: Int = 100
}
private lateinit var settingsAdapterRow: RowRepresentableAdapter
@ -73,13 +71,20 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_CURRENCY && resultCode == Activity.RESULT_OK) {
when (requestCode) {
RequestCode.CURRENCY.value -> {
if (resultCode == Activity.RESULT_OK) {
data?.let {
Preferences.setCurrencyCode(data.getStringExtra(CurrenciesFragment.INTENT_CURRENCY_CODE), requireContext())
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
it.where(Session::class.java).isNull("bankroll.currency.code").findAll().forEach {
it.bankrollHasBeenUpdated()
realm.where(Currency::class.java).isNull("code").or().equalTo("code", UserDefaults.currency.currencyCode).findAll().forEach { currency ->
currency.rate = Currency.DEFAULT_RATE
}
realm.where(Session::class.java).isNull("bankroll.currency.code").findAll().forEach { session ->
session.bankrollHasBeenUpdated()
}
}
realm.close()
@ -87,6 +92,11 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
}
}
}
RequestCode.SUBSCRIPTION.value -> {
settingsAdapterRow.refreshRow(SettingRow.SUBSCRIPTION)
}
}
}
override fun adapterRows(): List<RowRepresentable>? {
return rowRepresentation
@ -103,9 +113,11 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
SettingRow.BANKROLL_REPORT -> BankrollActivity.newInstance(requireContext())
SettingRow.TOP_10 -> Top10Activity.newInstance(requireContext())
SettingRow.SUBSCRIPTION -> {
if (!AppGuard.isProUser) {
BillingActivity.newInstance(requireContext())
BillingActivity.newInstanceForResult(this, false)
} else {
this.openPlaystoreAccount()
}
@ -113,7 +125,7 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
SettingRow.RATE_APP -> parentActivity?.openPlayStorePage()
SettingRow.CONTACT_US -> parentActivity?.openContactMail(R.string.contact)
SettingRow.BUG_REPORT -> parentActivity?.openContactMail(R.string.bug_report_subject, Realm.getDefaultInstance().path)
SettingRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@SettingsFragment, REQUEST_CODE_CURRENCY)
SettingRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@SettingsFragment, RequestCode.CURRENCY.value)
SettingRow.FOLLOW_US -> {
when (position) {
0 -> parentActivity?.openUrl(URL.BLOG.value)
@ -137,6 +149,8 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
*/
private fun initUI() {
setToolbarTitle(getString(R.string.more))
setDisplayHomeAsUpEnabled(true)
val viewManager = LinearLayoutManager(requireContext())

@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmResults
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
@ -32,7 +33,7 @@ import kotlin.coroutines.CoroutineContext
class StatisticsFragment : FilterableFragment() {
val coroutineContext: CoroutineContext
private val coroutineContext: CoroutineContext
get() = Dispatchers.Main
private lateinit var tableReportFragment: ComposableTableReportFragment
@ -74,7 +75,7 @@ class StatisticsFragment : FilterableFragment() {
override val observedEntities: List<Class<out RealmModel>> = listOf(ComputableResult::class.java)
override fun entitiesChanged(clazz: Class<out RealmModel>) {
override fun entitiesChanged(clazz: Class<out RealmModel>, results: RealmResults<out RealmModel>) {
this.launchStatComputation()
}

@ -1,6 +1,9 @@
package net.pokeranalytics.android.ui.fragment
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.os.Build
import android.os.Bundle
import android.text.SpannableStringBuilder
@ -24,6 +27,7 @@ import com.android.billingclient.api.SkuDetails
import com.android.billingclient.api.SkuDetailsResponseListener
import kotlinx.android.synthetic.main.fragment_subscription.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.ui.extensions.px
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.fragment.components.ScreenSlidePageFragment
@ -42,10 +46,20 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene
private var pagerAdapter: ScreenSlidePagerAdapter? = null
private var selectedProduct: SkuDetails? = null
private var showSessionMessage = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val cm = requireContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork: NetworkInfo? = cm.activeNetworkInfo
val isConnected: Boolean = activeNetwork?.isConnected == true
if (!isConnected) {
Toast.makeText(requireContext(), R.string.billingclient_unavailable, Toast.LENGTH_LONG).show()
return
}
this.showLoader(R.string.loading_please_wait)
if (!AppGuard.requestProducts(this)) {
this.hideLoader()
@ -53,6 +67,10 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene
}
}
fun setData(showSessionMessage: Boolean) {
this.showSessionMessage = showSessionMessage
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_subscription, container, false)
}
@ -88,6 +106,10 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene
this.title.text = upgradeString
}
if (showSessionMessage) {
this.message.text = getString(R.string.iap_session_message)
}
// Pager
// The pager adapter, which provides the pages to the view pager widget.
@ -101,7 +123,7 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene
this.selectedProduct?.let {
AppGuard.initiatePurchase(this.requireActivity(), it, this)
} ?: run {
throw IllegalStateException("Attempt to initiate purchase while no product has been chosen")
throw PAIllegalStateException("Attempt to initiate purchase while no product has been chosen")
}
}
@ -109,7 +131,7 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene
for (i in 1..count) {
val view = View(requireContext())
view.background = requireContext().getDrawable(R.drawable.circle_green)
val layoutParam = LinearLayout.LayoutParams(10.px, 10.px)
val layoutParam = LinearLayout.LayoutParams(8.px, 8.px)
layoutParam.setMargins(6.px)
this.pageIndicator.addView(view, layoutParam)
}
@ -201,12 +223,8 @@ class SubscriptionFragment : PokerAnalyticsFragment(), SkuDetailsResponseListene
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
pagerAdapter?.getFragment(position)?.let {
it.updateViewsPosition(-positionOffset * parallax)
}
pagerAdapter?.getFragment(position + 1)?.let {
it.updateViewsPosition((1 - positionOffset) * parallax)
}
pagerAdapter?.getFragment(position)?.updateViewsPosition(-positionOffset * parallax)
pagerAdapter?.getFragment(position + 1)?.updateViewsPosition((1 - positionOffset) * parallax)
}
override fun onPageSelected(position: Int) {

@ -0,0 +1,175 @@
package net.pokeranalytics.android.ui.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.tabs.TabLayout
import io.realm.RealmResults
import io.realm.kotlin.where
import kotlinx.android.synthetic.main.fragment_top_10.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
class Top10Fragment : RealmFragment(), RowRepresentableDataSource, RowRepresentableDelegate {
private enum class Tab {
CASH_GAMES,
TOURNAMENTS
}
companion object {
fun newInstance(): Top10Fragment {
val fragment = Top10Fragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
}
}
private lateinit var positiveSessions: RealmResults<Session>
private lateinit var dataListAdapter: RowRepresentableAdapter
private var realmCashGames: List<Session> = mutableListOf()
private var realmTournaments: List<Session> = mutableListOf()
private var currentTab: Tab = Tab.CASH_GAMES
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_top_10, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
initData()
}
/**
* Init UI
*/
private fun initUI() {
dataListAdapter = RowRepresentableAdapter(this, this)
recyclerView.adapter = dataListAdapter
setDisplayHomeAsUpEnabled(true)
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
when (tab.position) {
0 -> {
currentTab = Tab.CASH_GAMES
dataListAdapter.notifyDataSetChanged()
}
1 -> {
currentTab = Tab.TOURNAMENTS
}
}
dataListAdapter.notifyDataSetChanged()
}
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
val viewManager = SmoothScrollLinearLayoutManager(requireContext())
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
}
}
/**
* Init data
*/
private fun initData() {
this.positiveSessions = getRealm().where<Session>()
.isNotNull("endDate")
.greaterThan("result.net", 0.0)
.findAll()
updateTop10()
}
private fun updateTop10() {
val cashGames = mutableListOf<Session>()
val tournaments = mutableListOf<Session>()
// filter by type: cash game or tournament
this.positiveSessions.forEach {
when (it.type) {
Session.Type.CASH_GAME.ordinal -> {
cashGames.add(it)
}
else -> {
tournaments.add(it)
}
}
}
// Sort by rated net
val sortedCashGames = cashGames.sortedByDescending {
it.computableResult?.ratedNet
}.toMutableList()
val sortedTournaments = tournaments.sortedByDescending {
it.computableResult?.ratedNet
}.toMutableList()
// Keep 10 items
if (sortedCashGames.size > 10) {
sortedCashGames.subList(10, sortedCashGames.size).clear()
}
if (sortedTournaments.size > 10) {
sortedTournaments.subList(10, sortedTournaments.size).clear()
}
this.realmCashGames = sortedCashGames
this.realmTournaments = sortedTournaments
dataListAdapter.notifyDataSetChanged()
}
override fun adapterRows(): List<RowRepresentable>? {
return when (currentTab) {
Tab.CASH_GAMES -> realmCashGames
Tab.TOURNAMENTS -> realmTournaments
}
}
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return when (currentTab) {
Tab.CASH_GAMES -> realmCashGames[position]
Tab.TOURNAMENTS -> realmTournaments[position]
}
}
override fun numberOfRows(): Int {
return when (currentTab) {
Tab.CASH_GAMES -> realmCashGames.size
Tab.TOURNAMENTS -> realmTournaments.size
}
}
override fun viewTypeForPosition(position: Int): Int {
return RowViewType.ROW_TOP_10.ordinal
}
}

@ -13,6 +13,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.ui.activity.DataListActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -77,7 +78,7 @@ abstract class DeletableItemFragment : RealmFragment() {
this.dataListAdapter = dataListAdapter
// Save the delete position & create a copy of the object
// Save the delete position & buildAndShow a copy of the object
val itemPosition = items.indexOfFirst { it.id == itemId }
val itemToDelete = items.find { it.id == itemId }
@ -88,11 +89,11 @@ abstract class DeletableItemFragment : RealmFragment() {
deletedItem = getRealm().copyFromRealm(itemToDelete)
lastDeletedItemPosition = itemPosition
getRealm().executeTransaction {
itemToDelete.deleteDependencies()
itemToDelete.deleteDependencies(it)
itemToDelete.deleteFromRealm()
}
itemHasBeenReInserted = false
updateUIAfterDeletion(itemPosition)
updateUIAfterDeletion(itemId, itemPosition)
showUndoSnackBar()
} else {
dataListAdapter.notifyItemChanged(itemPosition)
@ -126,14 +127,14 @@ abstract class DeletableItemFragment : RealmFragment() {
}
snackBar?.show()
} ?: run {
throw IllegalStateException("mainLayout is not defined")
throw PAIllegalStateException("mainLayout is not defined")
}
}
/**
* Called once the object has been deleted
*/
open fun updateUIAfterDeletion(itemPosition: Int) {
open fun updateUIAfterDeletion(itemId: String, itemPosition: Int) {
dataListAdapter.notifyItemRemoved(itemPosition)
}

@ -7,6 +7,7 @@ import android.view.ViewGroup
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmResults
import timber.log.Timber
open class RealmFragment : PokerAnalyticsFragment() {
@ -26,8 +27,8 @@ open class RealmFragment : PokerAnalyticsFragment() {
this.observedEntities.forEach {
val realmResults = realm.where(it).findAll()
realmResults.addChangeListener { _,_ ->
this.entitiesChanged(it)
realmResults.addChangeListener { t, _ ->
this.entitiesChanged(it, t)
}
this.observedRealmResults.add(realmResults)
@ -61,6 +62,6 @@ open class RealmFragment : PokerAnalyticsFragment() {
/**
* The method called when a change happened in any RealmResults
*/
open fun entitiesChanged(clazz: Class<out RealmModel>) {}
open fun entitiesChanged(clazz: Class<out RealmModel>, results: RealmResults<out RealmModel>) {}
}

@ -1,48 +0,0 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
enum class BottomSheetType {
NONE,
LIST,
LIST_STATIC,
LIST_GAME,
DOUBLE_LIST,
MULTI_SELECTION,
GRID,
EDIT_TEXT,
EDIT_TEXT_MULTI_LINES,
DOUBLE_EDIT_TEXT,
NUMERIC_TEXT,
SUM;
fun newInstance(): BottomSheetFragment {
return when (this) {
NONE -> BottomSheetFragment()
LIST -> BottomSheetListFragment()
LIST_STATIC -> BottomSheetStaticListFragment()
LIST_GAME -> BottomSheetListGameFragment()
DOUBLE_LIST -> BottomSheetListGameFragment()
MULTI_SELECTION -> BottomSheetMultiSelectionFragment()
GRID -> BottomSheetTableSizeGridFragment()
EDIT_TEXT -> BottomSheetEditTextFragment()
EDIT_TEXT_MULTI_LINES -> BottomSheetEditTextMultiLinesFragment()
DOUBLE_EDIT_TEXT -> BottomSheetDoubleEditTextFragment()
NUMERIC_TEXT -> BottomSheetNumericTextFragment()
SUM -> BottomSheetSumFragment()
}
}
val validationRequired: Boolean
get() = when (this) {
LIST, LIST_GAME, LIST_STATIC, GRID, DOUBLE_LIST -> false
else -> true
}
val clearRequired: Boolean
get() = true
val addRequired: Boolean
get() = when (this) {
EDIT_TEXT, NUMERIC_TEXT, DOUBLE_EDIT_TEXT, EDIT_TEXT_MULTI_LINES, GRID, LIST_STATIC, SUM -> false
else -> true
}
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.os.Bundle
import android.text.InputType
@ -10,11 +10,12 @@ import kotlinx.android.synthetic.main.bottom_sheet_double_edit_text.*
import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.extensions.round
class BottomSheetDoubleEditTextFragment : BottomSheetFragment() {
class InputDoubleEditTextFragment(row: RowRepresentable) : InputFragment(row) {
private val values = ArrayList<String>()
private var isEditingBlinds: Boolean = false
@ -30,11 +31,13 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() {
if (isEditingBlinds) {
editText2.requestFocus()
} else {
editText1.requestFocus()
editText.requestFocus()
}
}
override fun getValue(): Any? {
if (values.isEmpty()) { return null }
if (values.all { it.isEmpty() }) { return null }
return values
}
@ -42,7 +45,6 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() {
* Init data
*/
private fun initData() {
valueHasPlaceholder = true
isEditingBlinds = row == SessionRow.BLINDS
}
@ -50,9 +52,9 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() {
* Init UI
*/
private fun initUI() {
val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (data.size != 2) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
values.add(0, "")
@ -64,20 +66,20 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() {
values[0] = (data[0].defaultValue ?: "").toString()
values[1] = (data[1].defaultValue ?: "").toString()
data[0].hint?.let { editText1.hint = getString(it) }
editText1.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
data[0].hint?.let { editText.hint = getString(it) }
editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
data[1].hint?.let { editText2.hint = getString(it) }
editText2.inputType = data[1].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
if (valueHasPlaceholder) {
editText1.hint = values[0]
editText2.hint = values[1]
if (values[0].isNotBlank()) { editText.hint = values[0] }
if (values[1].isNotBlank()) { editText2.hint = values[1] }
} else {
editText1.setText(values[0])
editText.setText(values[0])
editText2.setText(values[1])
}
editText1.addTextChangedListener {
editText.addTextChangedListener {
values[0] = it?.toString() ?: ""
}
@ -86,9 +88,9 @@ class BottomSheetDoubleEditTextFragment : BottomSheetFragment() {
if (isEditingBlinds) {
try {
val bigBlind = values[1].toDouble()
editText1.setText((bigBlind / 2.0).round())
editText.setText((bigBlind / 2.0).round())
} catch (e: Exception) {
editText1.setText("")
editText.setText("")
}
}
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.os.Bundle
import android.text.InputType
@ -10,9 +10,10 @@ import kotlinx.android.synthetic.main.bottom_sheet_edit_text.*
import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException
import net.pokeranalytics.android.ui.view.RowRepresentable
class BottomSheetEditTextFragment : BottomSheetFragment() {
class InputEditTextFragment(row: RowRepresentable) : InputFragment(row) {
private var value: String? = null
@ -24,7 +25,7 @@ class BottomSheetEditTextFragment : BottomSheetFragment() {
override fun onStart() {
super.onStart()
editText1.requestFocus()
editText.requestFocus()
}
override fun getValue(): Any? {
@ -43,7 +44,6 @@ class BottomSheetEditTextFragment : BottomSheetFragment() {
* Init data
*/
private fun initData() {
valueHasPlaceholder = true
}
/**
@ -51,28 +51,28 @@ class BottomSheetEditTextFragment : BottomSheetFragment() {
*/
private fun initUI() {
val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (data.size != 1) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_edit_text, view?.bottomSheetContainer, true)
data[0].hint?.let { editText1.hint = getString(it) }
editText1.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText1.addTextChangedListener {
data[0].hint?.let { editText.hint = getString(it) }
editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText.addTextChangedListener {
this.value = it?.toString()
}
data[0].defaultValue?.let {
if (valueHasPlaceholder) {
this.value = it.toString()
editText1.hint = it.toString()
editText.hint = it.toString()
} else {
editText1.setText(it.toString())
editText.setText(it.toString())
}
}
editText1.setOnEditorActionListener { _, actionId, _ ->
editText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
delegate.onRowValueChanged(getValue(), row)
dismiss()

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.os.Bundle
import android.text.InputType
@ -8,9 +8,10 @@ import androidx.core.widget.addTextChangedListener
import kotlinx.android.synthetic.main.bottom_sheet_edit_text_multi_lines.*
import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.*
import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException
import net.pokeranalytics.android.ui.view.RowRepresentable
class BottomSheetEditTextMultiLinesFragment : BottomSheetFragment() {
class InputEditTextMultiLinesFragment(row: RowRepresentable) : InputFragment(row) {
private var value: String? = null
@ -22,7 +23,7 @@ class BottomSheetEditTextMultiLinesFragment : BottomSheetFragment() {
override fun onStart() {
super.onStart()
editText1.requestFocus()
editText.requestFocus()
}
override fun getValue(): Any? {
@ -42,18 +43,18 @@ class BottomSheetEditTextMultiLinesFragment : BottomSheetFragment() {
* Init UI
*/
private fun initUI() {
val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (data.size != 1) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
LayoutInflater.from(requireContext()).inflate(net.pokeranalytics.android.R.layout.bottom_sheet_edit_text_multi_lines, view?.bottomSheetContainer, true)
data[0].hint?.let { editText1.hint = getString(it) }
editText1.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText1.addTextChangedListener { this.value = it?.toString() }
data[0].hint?.let { editText.hint = getString(it) }
editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText.addTextChangedListener { this.value = it?.toString() }
data[0].defaultValue?.let {
editText1.setText(it.toString())
editText.setText(it.toString())
}
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
@ -10,56 +10,58 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.view.ContextThemeWrapper
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.Fragment
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.realm.RealmModel
import kotlinx.android.synthetic.main.fragment_bottom_sheet.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.adapter.RowEditableDelegate
import net.pokeranalytics.android.ui.helpers.DateTimePickerManager
import net.pokeranalytics.android.ui.view.*
import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionRow
import java.util.*
open class BottomSheetFragment : BottomSheetDialogFragment() {
open class InputFragment(val row: RowRepresentable) : BottomSheetDialogFragment() {
lateinit var row: RowRepresentable
lateinit var delegate: RowRepresentableDelegate
// lateinit var row: RowRepresentable
lateinit var delegate: RowEditableDelegate
var currentCurrency: Currency? = null
var valueHasPlaceholder: Boolean = false
private var isClearable: Boolean = true
private var isDeletable: Boolean = false
private var rowRepresentableEditDescriptors: ArrayList<RowRepresentableEditDescriptor>? = null
private var rowEditableDescriptors: ArrayList<RowEditableDescriptor>? = null
companion object {
const val REQUEST_CODE_ADD_NEW_OBJECT = 100
fun create(
fragmentManager: FragmentManager?,
fun buildAndShow(
row: RowRepresentable,
delegate: RowRepresentableDelegate,
rowRepresentableEditDescriptors: ArrayList<RowRepresentableEditDescriptor>?,
isClearable: Boolean? = true,
currentCurrency: Currency? = null,
delegate: RowEditableDelegate,
dataSource: RowEditableDataSource?,
isDeletable: Boolean? = false,
valueHasPlaceholder: Boolean? = false
): BottomSheetFragment {
val bottomSheetFragment = row.bottomSheetType.newInstance()
bottomSheetFragment.show(fragmentManager, "bottomSheet")
bottomSheetFragment.row = row
valueHasPlaceholder: Boolean? = null
) {
if (delegate !is Fragment) throw PokerAnalyticsException.InputFragmentException
if (dataSource?.descriptorType == RowEditableDescriptorType.DATE) {
DateTimePickerManager.buildAndShow(row, delegate, dataSource.descriptors.first())
} else {
val bottomSheetFragment = row.inputFragmentType.newInstance(row)
bottomSheetFragment.show(delegate.fragmentManager, "bottomSheet")
bottomSheetFragment.delegate = delegate
bottomSheetFragment.rowRepresentableEditDescriptors = rowRepresentableEditDescriptors
bottomSheetFragment.isClearable = isClearable ?: true
bottomSheetFragment.rowEditableDescriptors = dataSource?.descriptors
bottomSheetFragment.isClearable = row.valueCanBeClearedWhenEditing
bottomSheetFragment.isDeletable = isDeletable ?: true
bottomSheetFragment.valueHasPlaceholder = valueHasPlaceholder ?: true
bottomSheetFragment.currentCurrency = currentCurrency
return bottomSheetFragment
bottomSheetFragment.currentCurrency = dataSource?.currency
}
}
}
@ -107,9 +109,9 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
bottomSheetToolbar.setOnMenuItemClickListener {
false
}
bottomSheetToolbar.menu.findItem(R.id.actionCheck).isVisible = row.bottomSheetType.validationRequired
bottomSheetToolbar.menu.findItem(R.id.actionClear).isVisible = row.bottomSheetType.clearRequired
bottomSheetToolbar.menu.findItem(R.id.actionAdd).isVisible = row.bottomSheetType.addRequired
bottomSheetToolbar.menu.findItem(R.id.actionCheck).isVisible = row.inputFragmentType.validationRequired
bottomSheetToolbar.menu.findItem(R.id.actionClear).isVisible = row.inputFragmentType.clearRequired
bottomSheetToolbar.menu.findItem(R.id.actionAdd).isVisible = row.inputFragmentType.addRequired
// Menu
bottomSheetToolbar.menu.findItem(R.id.actionClear).setOnMenuItemClickListener {
@ -132,7 +134,7 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
SessionRow.TOURNAMENT_NAME -> LiveData.TOURNAMENT_NAME
SessionRow.TOURNAMENT_FEATURE -> LiveData.TOURNAMENT_FEATURE
TransactionRow.TYPE -> LiveData.TRANSACTION_TYPE
else -> throw IllegalStateException("row $row does not have an associated LiveData value")
else -> throw PAIllegalStateException("row $row does not have an associated LiveData value")
}
EditableDataActivity.newInstanceForResult(
@ -157,8 +159,8 @@ open class BottomSheetFragment : BottomSheetDialogFragment() {
/**
* Return the data list
*/
fun getData(): ArrayList<RowRepresentableEditDescriptor>? {
return this.rowRepresentableEditDescriptors
fun getData(): ArrayList<RowEditableDescriptor>? {
return this.rowEditableDescriptors
}
open fun getValue(): Any? {

@ -0,0 +1,50 @@
package net.pokeranalytics.android.ui.fragment.components.input
import net.pokeranalytics.android.ui.view.RowRepresentable
enum class InputFragmentType {
NONE,
LIST,
LIST_STATIC,
LIST_GAME,
DOUBLE_LIST,
MULTI_SELECTION,
GRID,
EDIT_TEXT,
EDIT_TEXT_MULTI_LINES,
DOUBLE_EDIT_TEXT,
NUMERIC_TEXT,
SUM;
fun newInstance(row: RowRepresentable): InputFragment {
return when (this) {
NONE -> InputFragment(row)
LIST -> InputListFragment(row)
LIST_STATIC -> InputStaticListFragment(row)
LIST_GAME -> InputListGameFragment(row)
DOUBLE_LIST -> InputListGameFragment(row)
MULTI_SELECTION -> InputMultiSelectionFragment(row)
GRID -> InputTableSizeGridFragment(row)
EDIT_TEXT -> InputEditTextFragment(row)
EDIT_TEXT_MULTI_LINES -> InputEditTextMultiLinesFragment(row)
DOUBLE_EDIT_TEXT -> InputDoubleEditTextFragment(row)
NUMERIC_TEXT -> InputNumericTextFragment(row)
SUM -> InputSumFragment(row)
}
}
val validationRequired: Boolean
get() = when (this) {
LIST, LIST_GAME, LIST_STATIC, GRID, DOUBLE_LIST -> false
else -> true
}
val clearRequired: Boolean
get() = true
val addRequired: Boolean
get() = when (this) {
EDIT_TEXT, NUMERIC_TEXT, DOUBLE_EDIT_TEXT, EDIT_TEXT_MULTI_LINES, GRID, LIST_STATIC, SUM -> false
else -> true
}
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.os.Bundle
import android.view.LayoutInflater
@ -8,14 +8,16 @@ import io.realm.RealmResults
import kotlinx.android.synthetic.main.bottom_sheet_list.*
import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException
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.view.DataRowEditableDescriptor
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
open class InputListFragment(row: RowRepresentable) : InputFragment(row), LiveRowRepresentableDataSource, RowRepresentableDelegate {
lateinit var dataAdapter: RowRepresentableAdapter
var realmData: RealmResults<RowRepresentable>? = null
@ -39,14 +41,14 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable
realmData?.let {
return it[position] as RowRepresentable
}
throw IllegalStateException("Need to implement Data Source")
throw PAIllegalStateException("Need to implement Data Source")
}
override fun numberOfRows(): Int {
realmData?.let {
return it.size
}
throw IllegalStateException("Need to implement Data Source")
throw PAIllegalStateException("Need to implement Data Source")
}
override fun viewTypeForPosition(position: Int): Int {
@ -68,14 +70,14 @@ open class BottomSheetListFragment : BottomSheetFragment(), LiveRowRepresentable
* Init data
*/
open fun initData() {
val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (bottomSheetData.size != 1) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
if (bottomSheetData.first().data == null) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
}
this.realmData = bottomSheetData.first().data as RealmResults<RowRepresentable>
val dataList = bottomSheetData[0]
if (dataList !is DataRowEditableDescriptor) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") }
if (dataList.data == null) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") }
this.realmData = dataList.data
}
/**

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.os.Bundle
import android.view.LayoutInflater
@ -13,13 +13,14 @@ import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorExcep
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.extensions.px
import net.pokeranalytics.android.ui.view.DataRowEditableDescriptor
import net.pokeranalytics.android.ui.view.RowRepresentable
/**
* Bottom Sheet List Game Fragment
* Display a list of game + chips to choose the game limit
*/
class BottomSheetListGameFragment : BottomSheetListFragment() {
class InputListGameFragment(row: RowRepresentable) : InputListFragment(row) {
private var limit: Int? = 0
private val values = ArrayList<Any?>()
@ -49,15 +50,15 @@ class BottomSheetListGameFragment : BottomSheetListFragment() {
* Init data
*/
override fun initData() {
val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (bottomSheetData.size != 2) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
}
if (bottomSheetData[1].data == null) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
val dataList = bottomSheetData[1]
if (dataList !is DataRowEditableDescriptor) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") }
if (dataList.data == null) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") }
this.realmData = dataList.data
this.limit = bottomSheetData[0].defaultValue as Int?
this.realmData = bottomSheetData[1].data
}
/**

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.app.Activity
import android.content.Intent
@ -14,7 +14,7 @@ import net.pokeranalytics.android.ui.view.RowViewType
/**
* Manage multiple items selection in a bottom sheet list
*/
open class BottomSheetMultiSelectionFragment : BottomSheetListFragment() {
open class InputMultiSelectionFragment(row: RowRepresentable) : InputListFragment(row) {
private val selectedRows: ArrayList<RowRepresentable> = ArrayList()
@ -54,9 +54,9 @@ open class BottomSheetMultiSelectionFragment : BottomSheetListFragment() {
override fun initData() {
super.initData()
val bottomSheetData =
getData() ?: throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
getData() ?: throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (bottomSheetData.size != 1) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
bottomSheetData.first().defaultValue?.let {
(it as RealmList<*>).forEach { row ->

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.os.Bundle
import android.text.InputType
@ -10,10 +10,11 @@ import kotlinx.android.synthetic.main.bottom_sheet_edit_text.*
import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException
import net.pokeranalytics.android.ui.view.RowRepresentable
import java.text.NumberFormat
class BottomSheetNumericTextFragment : BottomSheetFragment() {
class InputNumericTextFragment(row: RowRepresentable) : InputFragment(row) {
private var value: Double? = null
@ -25,7 +26,7 @@ class BottomSheetNumericTextFragment : BottomSheetFragment() {
override fun onStart() {
super.onStart()
editText1.requestFocus()
editText.requestFocus()
}
override fun getValue(): Any? {
@ -36,26 +37,25 @@ class BottomSheetNumericTextFragment : BottomSheetFragment() {
* Init data
*/
private fun initData() {
valueHasPlaceholder = true
}
/**
* Init UI
*/
private fun initUI() {
val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (data.size != 1) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_edit_text, view?.bottomSheetContainer, true)
data[0].hint?.let { editText1.hint = getString(it) }
editText1.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText1.addTextChangedListener {
data[0].hint?.let { editText.hint = getString(it) }
editText.inputType = data[0].inputType ?: InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
editText.addTextChangedListener {
this.value = try {
editText1.text.toString().toDouble()
editText.text.toString().toDouble()
} catch (e: Exception) {
null
}
@ -73,13 +73,13 @@ class BottomSheetNumericTextFragment : BottomSheetFragment() {
if (valueHasPlaceholder) {
this.value = it.toString().toDoubleOrNull()
editText1.hint = valueString
editText.hint = valueString
} else {
editText1.setText(valueString)
editText.setText(valueString)
}
}
editText1.setOnEditorActionListener { _, actionId, _ ->
editText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
delegate.onRowValueChanged(getValue(), row)
dismiss()

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.os.Bundle
import android.view.LayoutInflater
@ -12,8 +12,9 @@ import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.StaticDataRowEditableDescriptor
class BottomSheetStaticListFragment : BottomSheetFragment(), StaticRowRepresentableDataSource,
class InputStaticListFragment(row: RowRepresentable) : InputFragment(row), StaticRowRepresentableDataSource,
RowRepresentableDelegate {
private var staticRows: List<RowRepresentable> = emptyList()
@ -44,14 +45,14 @@ class BottomSheetStaticListFragment : BottomSheetFragment(), StaticRowRepresenta
* Init data
*/
private fun initData() {
val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (bottomSheetData.size != 1) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
if (bottomSheetData.first().staticData == null) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
}
this.staticRows = bottomSheetData.first().staticData as List<RowRepresentable>
val dataList = bottomSheetData[0]
if (dataList !is StaticDataRowEditableDescriptor) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") }
if (dataList.staticData == null) { throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency") }
this.staticRows= dataList.staticData as List<RowRepresentable>
}
/**

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.os.Bundle
import android.text.InputType
@ -10,12 +10,13 @@ import kotlinx.android.synthetic.main.bottom_sheet_sum.*
import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.RowRepresentableEditDescriptorException
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.util.extensions.round
import net.pokeranalytics.android.util.extensions.toCurrency
import java.text.NumberFormat
class BottomSheetSumFragment : BottomSheetFragment() {
class InputSumFragment(row: RowRepresentable) : InputFragment(row) {
private var value = 0.0
private var currentDefaultValue = 0.0
@ -28,7 +29,7 @@ class BottomSheetSumFragment : BottomSheetFragment() {
override fun onStart() {
super.onStart()
editText1.requestFocus()
editText.requestFocus()
}
override fun getValue(): Any? {
@ -49,9 +50,9 @@ class BottomSheetSumFragment : BottomSheetFragment() {
* Init UI
*/
private fun initUI() {
val data = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val data = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (data.size != 5) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_sum, view?.bottomSheetContainer, true)
@ -98,11 +99,11 @@ class BottomSheetSumFragment : BottomSheetFragment() {
}
// First edit text
data[3].hint?.let { editText1.hint = getString(it) }
editText1.inputType = data[3].inputType ?: InputType.TYPE_CLASS_TEXT
editText1.addTextChangedListener {
data[3].hint?.let { editText.hint = getString(it) }
editText.inputType = data[3].inputType ?: InputType.TYPE_CLASS_TEXT
editText.addTextChangedListener {
val valueToAdd = try {
editText1.text.toString().toDouble()
editText.text.toString().toDouble()
} catch (e: Exception) {
0.0
}

@ -1,4 +1,4 @@
package net.pokeranalytics.android.ui.fragment.components.bottomsheet
package net.pokeranalytics.android.ui.fragment.components.input
import android.os.Bundle
import android.view.LayoutInflater
@ -11,11 +11,11 @@ import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.extensions.px
import net.pokeranalytics.android.ui.view.GridSpacingItemDecoration
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.extensions.px
class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
class InputTableSizeGridFragment(row: RowRepresentable) : InputFragment(row), StaticRowRepresentableDataSource, RowRepresentableDelegate {
private lateinit var dataAdapter: RowRepresentableAdapter
private var defaultSize: Int? = null
@ -39,9 +39,9 @@ class BottomSheetTableSizeGridFragment : BottomSheetFragment(), StaticRowReprese
* Init data
*/
private fun initData() {
val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor not found")
val bottomSheetData = getData()?:throw RowRepresentableEditDescriptorException("RowEditableDescriptor not found")
if (bottomSheetData.size != 1) {
throw RowRepresentableEditDescriptorException("RowRepresentableEditDescriptor inconsistency")
throw RowRepresentableEditDescriptorException("RowEditableDescriptor inconsistency")
}
defaultSize = bottomSheetData.first().defaultValue as Int?
}

@ -9,15 +9,14 @@ import net.pokeranalytics.android.api.CurrencyConverterApi
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.retrofit.CurrencyConverterValue
import net.pokeranalytics.android.ui.activity.CurrenciesActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.CurrenciesFragment
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.ui.view.rowrepresentable.BankrollRow
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.toCurrency
@ -33,10 +32,6 @@ import java.util.*
*/
class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataSource {
companion object {
const val REQUEST_CODE_CURRENCY: Int = 100
}
// Return the item as a Bankroll object
private val bankroll: Bankroll
get() {
@ -67,7 +62,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_CURRENCY && resultCode == RESULT_OK) {
if (requestCode == RequestCode.CURRENCY.value && resultCode == RESULT_OK) {
data?.let {
val currencyCode = it.getStringExtra(CurrenciesFragment.INTENT_CURRENCY_CODE)
onRowValueChanged(currencyCode, BankrollRow.CURRENCY)
@ -88,7 +83,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
SimpleRow.NAME -> if (bankroll.name.isNotEmpty()) bankroll.name else NULL_TEXT
BankrollRow.NAME -> if (bankroll.name.isNotEmpty()) bankroll.name else NULL_TEXT
BankrollRow.CURRENCY -> {
bankroll.currency?.code?.let { code ->
Currency.getInstance(code).currencyCode
@ -119,24 +114,10 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.bankroll.name))
BankrollRow.INITIAL_VALUE -> {
row.editingDescriptors(mapOf("defaultValue" to this.bankroll.initialValue))
}
BankrollRow.RATE -> {
val rate = this.bankroll.currency?.rate
row.editingDescriptors(mapOf("defaultValue" to rate))
}
else -> null
}
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
BankrollRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@BankrollDataFragment,
REQUEST_CODE_CURRENCY
RequestCode.CURRENCY.value
)
BankrollRow.REFRESH_RATE -> refreshRate()
else -> super.onRowSelected(position, row, fromAction)
@ -146,9 +127,11 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
super.onRowValueChanged(value, row)
// Clear the value when the currency has been updated
if (row == BankrollRow.CURRENCY) {
when (row) {
BankrollRow.CURRENCY -> { // Clear the value when the currency has been updated
this.lastRefreshRateCall = 0
this.rowRepresentableAdapter.notifyDataSetChanged()
}
}
updateAdapterUI()
@ -175,7 +158,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
private fun refreshRows() {
rows.clear()
rows.add(SimpleRow.NAME)
rows.add(BankrollRow.NAME)
rows.add(BankrollRow.LIVE)
rows.add(BankrollRow.INITIAL_VALUE)
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.currency))
@ -213,7 +196,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
}
lastRefreshRateCall = System.currentTimeMillis()
val currenciesConverterValue = "${defaultCurrency.currencyCode}_${bankroll.currency?.code}"
val currenciesConverterValue = "${bankroll.currency?.code}_${defaultCurrency.currencyCode}"
val call = CurrencyConverterApi.getApi(requireContext())?.convert(currenciesConverterValue)
call?.enqueue(object : retrofit2.Callback<Map<String, CurrencyConverterValue>> {

@ -17,11 +17,8 @@ import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.extensions.ChipGroupExtension
import net.pokeranalytics.android.ui.extensions.px
import net.pokeranalytics.android.ui.extensions.showAlertDialog
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomFieldRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.util.NULL_TEXT
import java.util.*
@ -122,7 +119,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
SimpleRow.NAME -> if (customField.name.isNotEmpty()) customField.name else NULL_TEXT
CustomFieldRow.NAME -> if (customField.name.isNotEmpty()) customField.name else NULL_TEXT
else -> super.stringForRow(row)
}
}
@ -142,20 +139,19 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
/*
override fun editDescriptors(row: RowRepresentable): ArrayList<RowEditableDescriptor>? {
return when (row) {
SimpleRow.NAME -> row.editingDescriptors(mapOf("defaultValue" to this.customField.name))
is CustomFieldEntry -> row.editingDescriptors(mapOf("defaultValue" to row.value))
else -> null
}
}
*/
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
is CustomFieldEntry -> {
val data = customField.editDescriptors(row)
BottomSheetFragment.create(fragmentManager, row, this, data, isClearable = false, isDeletable = true)
}
is CustomFieldEntry -> row.startEditing(null, this)
else -> super.onRowSelected(position, row, fromAction)
}
}
@ -187,13 +183,13 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
customField.deleteEntry(row)
rowRepresentableAdapter.notifyDataSetChanged()
})
return
}
} else {
customField.deleteEntry(row)
rowRepresentableAdapter.notifyDataSetChanged()
}
}
}
}
/**
* Init UI
@ -271,4 +267,11 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
}
}
override fun onDataSaved() {
super.onDataSaved()
this.customField.cleanupEntries()
}
}

@ -128,6 +128,8 @@ open class DataManagerFragment : RealmFragment() {
*/
protected open fun deleteData() {
this.willDeleteData()
val realm = this.getRealm()
if (this.item.isValidForDelete(realm)) {
@ -145,6 +147,8 @@ open class DataManagerFragment : RealmFragment() {
}
}
open fun willDeleteData() { }
/**
* Finish the activity with a result
*/

@ -14,7 +14,6 @@ import net.pokeranalytics.android.model.interfaces.Editable
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
@ -62,7 +61,7 @@ open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegat
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
BottomSheetFragment.create(fragmentManager, row, this, getDataSource().editDescriptors(row))
row.startEditing(this.item, this)
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {

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

Loading…
Cancel
Save