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

dev_raz_wip
Aurelien Hubert 7 years ago
commit 02471ed095
  1. 341
      app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt
  2. 33
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  3. 56
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  4. 15
      app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt
  5. 52
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  6. 46
      app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
  7. 3
      app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt
  8. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt
  9. 28
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  10. 20
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  11. 46
      app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt
  12. 30
      app/src/main/java/net/pokeranalytics/android/ui/adapter/components/RowRepresentableAdapter.kt
  13. 48
      app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt
  14. 70
      app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt
  15. 13
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  16. 20
      app/src/main/res/layout/fragment_editable_data.xml
  17. 2
      app/src/main/res/values/strings.xml

@ -1,6 +1,7 @@
package net.pokeranalytics.android package net.pokeranalytics.android
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.RealmResults
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputedResults import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.SessionGroup import net.pokeranalytics.android.calculus.SessionGroup
@ -37,13 +38,6 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() {
val realm = this.mockRealm val realm = this.mockRealm
realm.beginTransaction() realm.beginTransaction()
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val sd1 = sdf.parse("01/1/2019 10:00")
val ed1 = sdf.parse("01/1/2019 11:00")
val sd2 = sdf.parse("02/1/2019 08:00")
val ed2 = sdf.parse("02/1/2019 11:00")
var s1 = realm.createObject(Session::class.java, "1") var s1 = realm.createObject(Session::class.java, "1")
var s2 = realm.createObject(Session::class.java, "2") var s2 = realm.createObject(Session::class.java, "2")
@ -53,19 +47,28 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() {
s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
// var s1: Session = Session.newInstance() s1.result?.buyin = 100.0 // net result = -100
// var s2: Session = Session.newInstance() s2.result?.buyin = 200.0
s2.result?.cashout = 500.0 // net result = 300
s1.result?.netResult = -100.0 s1.cgBigBlind = 0.5 // bb net result = -200bb
s2.result?.netResult = 300.0 s2.cgBigBlind = 2.0 // bb net result = 150bb
realm.insert(s1) realm.insert(s1)
realm.insert(s2) realm.insert(s2)
realm.commitTransaction() realm.commitTransaction()
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val sd1 = sdf.parse("01/1/2019 10:00")
val ed1 = sdf.parse("01/1/2019 11:00")
val sd2 = sdf.parse("02/1/2019 08:00")
val ed2 = sdf.parse("02/1/2019 11:00")
realm.beginTransaction() realm.beginTransaction()
s1.timeFrame?.setDate(sd1, ed1)
s2.timeFrame?.setDate(sd2, ed2) s1.timeFrame?.setDate(sd1, ed1) // duration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
s2.timeFrame?.setDate(sd2, ed2) // duration = 3h, hourly = 100, bb100 = 150 / 75 * 100 = +200
realm.copyToRealmOrUpdate(s1) realm.copyToRealmOrUpdate(s1)
realm.copyToRealmOrUpdate(s2) realm.copyToRealmOrUpdate(s2)
@ -75,7 +78,10 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() {
val sessions = realm.where(Session::class.java).findAll() val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions) val group = SessionGroup(name = "test", sessions = sessions)
val results: ComputedResults = Calculator.compute(group, Calculator.Options()) var options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
val results: ComputedResults = Calculator.compute(group, options)
val delta = 0.01 val delta = 0.01
val sum = results.computedStat(Stat.NETRESULT) val sum = results.computedStat(Stat.NETRESULT)
@ -99,8 +105,315 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() {
Assert.fail("No duration stat") Assert.fail("No duration stat")
} }
val hourlyRate = results.computedStat(Stat.HOURLY_RATE)
if (hourlyRate != null) {
assertEquals(50.0, hourlyRate.value, delta)
} else {
Assert.fail("No houry rate stat")
}
val handsPlayed = results.computedStat(Stat.HANDS_PLAYED)
if (handsPlayed != null) {
assertEquals(100.0, handsPlayed.value, delta)
} else {
Assert.fail("No hands played stat")
}
val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES)
if (numberOfGames != null) {
assertEquals(2, numberOfGames.value.toInt())
} else {
Assert.fail("No numberOfGames stat")
}
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS)
if (numberOfSets != null) {
assertEquals(2, numberOfSets.value.toInt())
} else {
Assert.fail("No numberOfSets stat")
}
val avgBuyin = results.computedStat(Stat.AVERAGE_BUYIN)
if (avgBuyin != null) {
assertEquals(150.0, avgBuyin.value, delta)
} else {
Assert.fail("No avgBuyin stat")
}
val avgDuration = results.computedStat(Stat.AVERAGE_DURATION)
if (avgDuration != null) {
assertEquals(2.0, avgDuration.value, delta)
} else {
Assert.fail("No avgDuration stat")
}
val roi = results.computedStat(Stat.ROI)
if (roi != null) {
assertEquals(200 / 300.0, roi.value, delta)
} else {
Assert.fail("No roi stat")
}
val avgBBNet = results.computedStat(Stat.AVERAGE_NET_BB)
if (avgBBNet != null) {
assertEquals(-25.0, avgBBNet.value, delta)
} else {
Assert.fail("No avgBBNet stat")
}
val bbHourlyRate = results.computedStat(Stat.HOURLY_RATE_BB)
if (bbHourlyRate != null) {
assertEquals(-12.5, bbHourlyRate.value, delta)
} else {
Assert.fail("No bbHourlyRate stat")
}
val netbbPer100Hands = results.computedStat(Stat.NET_BB_PER_100_HANDS)
if (netbbPer100Hands != null) {
assertEquals(-50.0, netbbPer100Hands.value, delta)
} else {
Assert.fail("No netbbPer100Hands stat")
}
// val stdHourly = results.computedStat(Stat.STANDARD_DEVIATION_HOURLY)
// if (stdHourly != null) {
// assertEquals(111.8, stdHourly.value, delta)
// } else {
// Assert.fail("No stdHourly stat")
// }
//
// val std = results.computedStat(Stat.STANDARD_DEVIATION)
// if (std != null) {
// assertEquals(200.0, std.value, delta)
// } else {
// Assert.fail("No std stat")
// }
//
// val std100 = results.computedStat(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS)
// if (std100 != null) {
// assertEquals(503.12, std100.value, delta)
// } else {
// Assert.fail("No std100 stat")
// }
}
@Test
fun testOverlappingSessions1() {
val realm = this.mockRealm
realm.beginTransaction()
var s1 = realm.createObject(Session::class.java, "1")
var s2 = realm.createObject(Session::class.java, "2")
s1.timeFrame = realm.createObject(TimeFrame::class.java)
s2.timeFrame = realm.createObject(TimeFrame::class.java)
s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
realm.insert(s1)
realm.insert(s2)
realm.commitTransaction()
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val sd1 = sdf.parse("01/1/2019 09:00")
val ed1 = sdf.parse("01/1/2019 10:00")
val sd2 = sdf.parse("01/1/2019 08:00")
val ed2 = sdf.parse("01/1/2019 11:00")
realm.beginTransaction()
s1.timeFrame?.setDate(sd1, ed1) // duration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
s2.timeFrame?.setDate(sd2, ed2) // duration = 4h, hourly = 100, bb100 = 150 / 75 * 100 = +200
realm.copyToRealmOrUpdate(s1)
realm.copyToRealmOrUpdate(s2)
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
var options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
val results: ComputedResults = Calculator.compute(group, options)
val delta = 0.01
val duration = results.computedStat(Stat.DURATION)
if (duration != null) {
assertEquals(3.0, duration.value, delta)
} else {
Assert.fail("No Net result stat")
}
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS)
if (numberOfSets != null) {
assertEquals(1, numberOfSets.value.toInt())
} else {
Assert.fail("No numberOfSets stat")
}
val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES)
if (numberOfGames != null) {
assertEquals(2, numberOfGames.value.toInt())
} else {
Assert.fail("No numberOfSets stat")
}
}
@Test
fun testOverlappingSessions2() {
val realm = this.mockRealm
realm.beginTransaction()
var s1 = realm.createObject(Session::class.java, "1")
var s2 = realm.createObject(Session::class.java, "2")
var s3 = realm.createObject(Session::class.java, "3")
s1.timeFrame = realm.createObject(TimeFrame::class.java)
s2.timeFrame = realm.createObject(TimeFrame::class.java)
s3.timeFrame = realm.createObject(TimeFrame::class.java)
realm.insert(s1)
realm.insert(s2)
realm.insert(s3)
realm.commitTransaction()
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val sd1 = sdf.parse("01/1/2019 05:00")
val ed1 = sdf.parse("01/1/2019 09:00")
val sd2 = sdf.parse("01/1/2019 07:00")
val ed2 = sdf.parse("01/1/2019 11:00")
val sd3 = sdf.parse("01/1/2019 03:00")
val ed3 = sdf.parse("01/1/2019 06:00")
realm.beginTransaction()
s1.timeFrame?.setDate(sd1, ed1) // duration = 4h
s2.timeFrame?.setDate(sd2, ed2) // duration = 4h
s3.timeFrame?.setDate(sd3, ed3) // duration = 3h
realm.copyToRealmOrUpdate(s1)
realm.copyToRealmOrUpdate(s2)
realm.copyToRealmOrUpdate(s3)
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
var options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
val results: ComputedResults = Calculator.compute(group, options)
val delta = 0.01
val duration = results.computedStat(Stat.DURATION)
if (duration != null) {
assertEquals(8.0, duration.value, delta)
} else {
Assert.fail("No Net result stat")
}
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS)
if (numberOfSets != null) {
assertEquals(1, numberOfSets.value.toInt())
} else {
Assert.fail("No numberOfSets stat")
}
val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES)
if (numberOfGames != null) {
assertEquals(3, numberOfGames.value.toInt())
} else {
Assert.fail("No numberOfSets stat")
}
}
var sessions: RealmResults<Session>? = null
@Test
fun testOverlappingSessionDeletion() {
val realm = this.mockRealm
// this.sessions = realm.where(Session::class.java).findAll() // monitor session deletions
// Looper.prepare()
// this.sessions?.addChangeListener { t, changeSet ->
//
// val deletedSessions = realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll()
// deletedSessions.forEach { it.cleanup() }
//
// }
// Looper.loop()
realm.beginTransaction()
var s1 = realm.createObject(Session::class.java, "1")
var s2 = realm.createObject(Session::class.java, "2")
var s3 = realm.createObject(Session::class.java, "3")
s1.timeFrame = realm.createObject(TimeFrame::class.java)
s2.timeFrame = realm.createObject(TimeFrame::class.java)
s3.timeFrame = realm.createObject(TimeFrame::class.java)
realm.insert(s1)
realm.insert(s2)
realm.insert(s3)
realm.commitTransaction()
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val sd1 = sdf.parse("01/1/2019 05:00")
val ed1 = sdf.parse("01/1/2019 09:00")
val sd2 = sdf.parse("01/1/2019 07:00")
val ed2 = sdf.parse("01/1/2019 11:00")
val sd3 = sdf.parse("01/1/2019 03:00")
val ed3 = sdf.parse("01/1/2019 06:00")
realm.beginTransaction()
s1.timeFrame?.setDate(sd1, ed1) // duration = 4h
s2.timeFrame?.setDate(sd2, ed2) // duration = 4h
s3.timeFrame?.setDate(sd3, ed3) // duration = 3h
realm.copyToRealmOrUpdate(s1)
realm.copyToRealmOrUpdate(s2)
realm.copyToRealmOrUpdate(s3)
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
var options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
val results: ComputedResults = Calculator.compute(group, options)
val delta = 0.01
val duration = results.computedStat(Stat.DURATION)
if (duration != null) {
assertEquals(8.0, duration.value, delta)
} else {
Assert.fail("No duration stat")
} }
realm.beginTransaction()
s1.deleteFromRealm()
realm.commitTransaction()
// realm.executeTransaction {
// s1.deleteFromRealm()
// }
val group2 = SessionGroup(name = "test", sessions = sessions)
val results2: ComputedResults = Calculator.compute(group2, options)
val duration2 = results2.computedStat(Stat.DURATION)
if (duration2 != null) {
assertEquals(7.0, duration2.value, delta)
} else {
Assert.fail("No duration2 stat")
}
}
} }

@ -3,13 +3,29 @@ package net.pokeranalytics.android
import android.app.Application import android.app.Application
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.RealmResults
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.PokerAnalyticsLogs import net.pokeranalytics.android.util.PokerAnalyticsLogs
import timber.log.Timber import timber.log.Timber
class PokerAnalyticsApplication: Application() { class PokerAnalyticsApplication: Application() {
// var timeFrames: RealmResults<Session>? = null var sessions: RealmResults<Session>? = null
// private val listener: OrderedRealmCollectionChangeListener<RealmResults<Session>> =
// OrderedRealmCollectionChangeListener() { realmResults: RealmResults<Session>, changeSet: OrderedCollectionChangeSet ->
//
// if (changeSet == null) {
// return@OrderedRealmCollectionChangeListener
// }
//
// val realm: Realm = Realm.getDefaultInstance()
//
// val deletedSessions = realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll()
// deletedSessions.forEach { it.cleanup() }
//
// }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -22,12 +38,15 @@ class PokerAnalyticsApplication: Application() {
.build() .build()
Realm.setDefaultConfiguration(realmConfiguration) Realm.setDefaultConfiguration(realmConfiguration)
// val realm: Realm = Realm.getDefaultInstance() val realm: Realm = Realm.getDefaultInstance()
// // Add observer on session time frame changes // Add observer on session time frame changes
// this.timeFrames = realm.where(Session::class.java).findAllAsync() this.sessions = realm.where(Session::class.java).findAll() // monitor session deletions
// this.timeFrames?.addChangeListener { t, changeSet -> // @todo check if main thread has running Looper, cf Realm doc this.sessions?.addChangeListener { t, changeSet ->
// changeSet.deletions
// } val deletedSessions = realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll()
deletedSessions.forEach { it.cleanup() }
}
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
// Logs // Logs

@ -3,11 +3,19 @@ package net.pokeranalytics.android.calculus
import net.pokeranalytics.android.calculus.Stat.* import net.pokeranalytics.android.calculus.Stat.*
import net.pokeranalytics.android.model.realm.SessionSet import net.pokeranalytics.android.model.realm.SessionSet
/**
* The class performing stats computation
*/
class Calculator { class Calculator {
/**
* The options used for calculations or display
*/
class Options { class Options {
/**
* The way the stats are going to be displayed
*/
enum class Display { enum class Display {
TABLE, TABLE,
EVOLUTION, EVOLUTION,
@ -16,6 +24,9 @@ class Calculator {
POLYNOMIAL POLYNOMIAL
} }
/**
* The type of evolution values
*/
enum class EvolutionValues { enum class EvolutionValues {
NONE, NONE,
STANDARD, STANDARD,
@ -26,6 +37,9 @@ class Calculator {
var evolutionValues: EvolutionValues = EvolutionValues.NONE var evolutionValues: EvolutionValues = EvolutionValues.NONE
var displayedStats: List<Stat> = listOf() var displayedStats: List<Stat> = listOf()
/**
* This function determines whether the standard deviation should be computed
*/
fun shouldComputeStandardDeviation() : Boolean { fun shouldComputeStandardDeviation() : Boolean {
this.displayedStats.forEach { stat -> this.displayedStats.forEach { stat ->
return when (stat) { return when (stat) {
@ -41,14 +55,16 @@ class Calculator {
companion object { companion object {
fun computePreAggregation(sets: List<SessionSet>, options: Options): List<ComputedGroup> { fun computePreAggregation(sets: List<SessionSet>, options: Options): List<ComputedResults> {
return listOf() return listOf()
} }
// Computes all stats for list of Session sessionGroup /**
fun computeGroups(groups: List<SessionGroup>, options: Options): List<ComputedGroup> { * Computes all stats for list of Session sessionGroup
*/
fun computeGroups(groups: List<SessionGroup>, options: Options): List<ComputedResults> {
var computedGroups: MutableList<ComputedGroup> = mutableListOf() var computedResults: MutableList<ComputedResults> = mutableListOf()
groups.forEach { group -> groups.forEach { group ->
// Computes actual sessionGroup stats // Computes actual sessionGroup stats
val results: ComputedResults = Calculator.compute(group, options = options) val results: ComputedResults = Calculator.compute(group, options = options)
@ -57,19 +73,20 @@ class Calculator {
val comparedGroup = group.comparedSessions val comparedGroup = group.comparedSessions
if (comparedGroup != null) { if (comparedGroup != null) {
val comparedResults = Calculator.compute(comparedGroup, options = options) val comparedResults = Calculator.compute(comparedGroup, options = options)
group.comparedComputedGroup = ComputedGroup(comparedGroup, comparedResults) group.comparedComputedResults = comparedResults
results.computeStatVariations(comparedResults) results.computeStatVariations(comparedResults)
} }
results.finalize(options) results.finalize(options) // later treatment, such as evolution values sorting
computedGroups.add(ComputedGroup(group, results)) computedResults.add(results)
} }
return computedGroups return computedResults
} }
// Computes stats for a SessionSet /**
* Computes stats for a SessionSet
*/
fun compute(sessionGroup: SessionGroup, options: Options) : ComputedResults { fun compute(sessionGroup: SessionGroup, options: Options) : ComputedResults {
val sessions: List<SessionInterface> = sessionGroup.sessions val sessions: List<SessionInterface> = sessionGroup.sessions
@ -124,15 +141,15 @@ class Calculator {
gTotalHands += sessionSet.estimatedHands gTotalHands += sessionSet.estimatedHands
gBBSum += sessionSet.bbNetResult gBBSum += sessionSet.bbNetResult
hourlyRate = gSum / duration * 3600.0 hourlyRate = gSum / duration
hourlyRateBB = gBBSum / duration * 3600.0 hourlyRateBB = gBBSum / duration
if (options.evolutionValues == Options.EvolutionValues.DATED) { if (options.evolutionValues == Options.EvolutionValues.DATED) {
results.addEvolutionValue(gSum, duration, NETRESULT) results.addEvolutionValue(gSum, duration, NETRESULT)
results.addEvolutionValue(gSum / duration * 3600.0, duration, HOURLY_RATE) results.addEvolutionValue(gSum / duration, duration, HOURLY_RATE)
results.addEvolutionValue(Stat.netBBPer100Hands(gBBSum, gTotalHands), duration, NET_BB_PER_100_HANDS) results.addEvolutionValue(Stat.netBBPer100Hands(gBBSum, gTotalHands), duration, NET_BB_PER_100_HANDS)
results.addEvolutionValue(hourlyRate, duration, HOURLY_RATE) results.addEvolutionValue(hourlyRate, duration, HOURLY_RATE)
results.addEvolutionValue(gIndex.toDouble(), duration, NUMBER_OF_GROUPS) results.addEvolutionValue(gIndex.toDouble(), duration, NUMBER_OF_SETS)
results.addEvolutionValue(sessionSet.duration.toDouble(), duration, DURATION) results.addEvolutionValue(sessionSet.duration.toDouble(), duration, DURATION)
results.addEvolutionValue(duration / gIndex, duration, AVERAGE_DURATION) results.addEvolutionValue(duration / gIndex, duration, AVERAGE_DURATION)
results.addEvolutionValue(hourlyRateBB, duration, HOURLY_RATE_BB) results.addEvolutionValue(hourlyRateBB, duration, HOURLY_RATE_BB)
@ -147,11 +164,11 @@ class Calculator {
ComputedStat(HOURLY_RATE, hourlyRate), ComputedStat(HOURLY_RATE, hourlyRate),
ComputedStat(AVERAGE, average), ComputedStat(AVERAGE, average),
ComputedStat(DURATION, duration), ComputedStat(DURATION, duration),
ComputedStat(NUMBER_OF_GROUPS, sessionSets.size.toDouble()), ComputedStat(NUMBER_OF_SETS, sessionSets.size.toDouble()),
ComputedStat(NUMBER_OF_GAMES, sessions.size.toDouble()), ComputedStat(NUMBER_OF_GAMES, sessions.size.toDouble()),
ComputedStat(AVERAGE_DURATION, (duration / 3600.0) / sessions.size), ComputedStat(AVERAGE_DURATION, duration / sessions.size),
ComputedStat(NET_BB_PER_100_HANDS, Stat.netBBPer100Hands(bbSum, totalHands)), ComputedStat(NET_BB_PER_100_HANDS, Stat.netBBPer100Hands(bbSum, totalHands)),
ComputedStat(HOURLY_RATE_BB, bbSum / duration * 3600.0), ComputedStat(HOURLY_RATE_BB, bbSum / duration),
ComputedStat(AVERAGE_NET_BB, bbSum / bbSessionCount), ComputedStat(AVERAGE_NET_BB, bbSum / bbSessionCount),
ComputedStat(WIN_RATIO, (winningSessionCount / sessions.size).toDouble()), ComputedStat(WIN_RATIO, (winningSessionCount / sessions.size).toDouble()),
ComputedStat(AVERAGE_BUYIN, totalBuyin / sessions.size), ComputedStat(AVERAGE_BUYIN, totalBuyin / sessions.size),
@ -160,7 +177,6 @@ class Calculator {
)) ))
// Standard Deviation // Standard Deviation
if (options.shouldComputeStandardDeviation()) { if (options.shouldComputeStandardDeviation()) {
@ -178,7 +194,7 @@ class Calculator {
results.addStats(setOf( results.addStats(setOf(
ComputedStat(STANDARD_DEVIATION, standardDeviation), ComputedStat(STANDARD_DEVIATION, standardDeviation),
ComputedStat(Stat.STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) ComputedStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation)
)) ))
} }

@ -32,20 +32,7 @@ class SessionGroup(name: String, sessions: List<SessionInterface>) {
var comparedSessions: SessionGroup? = null var comparedSessions: SessionGroup? = null
// The computed stats of the comparable sessionGroup // The computed stats of the comparable sessionGroup
var comparedComputedGroup: ComputedGroup? = null var comparedComputedResults: ComputedResults? = null
}
class ComputedGroup(sessionGroup: SessionGroup, computedResults: ComputedResults) {
// A computable sessionGroup
var sessionGroup: SessionGroup = sessionGroup
// The computed stats of the sessionGroup
var computedResults: ComputedResults = computedResults
fun statValue(stat: Stat) : Double? {
return computedResults.computedStat(stat)?.value
}
} }

@ -4,16 +4,15 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
interface AnyStat { /**
* An enum representing all the types of Session statistics
} */
enum class Stat : RowRepresentable {
enum class Stat : AnyStat, RowRepresentable {
NETRESULT, NETRESULT,
HOURLY_RATE, HOURLY_RATE,
AVERAGE, AVERAGE,
NUMBER_OF_GROUPS, NUMBER_OF_SETS,
NUMBER_OF_GAMES, NUMBER_OF_GAMES,
DURATION, DURATION,
AVERAGE_DURATION, AVERAGE_DURATION,
@ -28,12 +27,9 @@ enum class Stat : AnyStat, RowRepresentable {
STANDARD_DEVIATION_BB_PER_100_HANDS, STANDARD_DEVIATION_BB_PER_100_HANDS,
HANDS_PLAYED; HANDS_PLAYED;
fun label() : String = when (this) { /**
NETRESULT -> "" * Returns whether the stat evolution values requires a distribution sorting
HOURLY_RATE -> "" */
else -> throw IllegalArgumentException("Label not defined")
}
fun hasDistributionSorting() : Boolean { fun hasDistributionSorting() : Boolean {
when (this) { when (this) {
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> return true STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> return true
@ -59,7 +55,7 @@ enum class Stat : AnyStat, RowRepresentable {
NETRESULT -> R.string.net_result NETRESULT -> R.string.net_result
HOURLY_RATE -> R.string.average_hour_rate HOURLY_RATE -> R.string.average_hour_rate
AVERAGE -> R.string.average AVERAGE -> R.string.average
NUMBER_OF_GROUPS -> R.string.number_of_groups NUMBER_OF_SETS -> R.string.number_of_groups
NUMBER_OF_GAMES -> R.string.number_of_games NUMBER_OF_GAMES -> R.string.number_of_games
DURATION -> R.string.duration DURATION -> R.string.duration
AVERAGE_DURATION -> R.string.average_duration AVERAGE_DURATION -> R.string.average_duration
@ -79,11 +75,9 @@ enum class Stat : AnyStat, RowRepresentable {
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
} }
enum class CashSessionStat : AnyStat { /**
NETBB, * ComputedStat contains a [stat] and their associated [value]
AVERAGEBB */
}
class ComputedStat(stat: Stat, value: Double) { class ComputedStat(stat: Stat, value: Double) {
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) {
@ -92,23 +86,31 @@ class ComputedStat(stat: Stat, value: Double) {
} }
} }
// The statistic type /**
* The statistic type
*/
var stat: Stat = stat var stat: Stat = stat
// The stat value /**
* The stat value
*/
var value: Double = value var value: Double = value
// The variation of the stat /**
* The variation of the stat
*/
var variation: Double? = null var variation: Double? = null
// The data points leading to the current stat value /**
// var points: List<Point> = mutableListOf() * Formats the value of the stat to be suitable for display
*/
// Formats the value of the stat to be suitable for display
fun format() : StatFormat { fun format() : StatFormat {
return StatFormat() return StatFormat()
} }
/**
* Returns a StatFormat instance for an evolution value located at the specified [index]
*/
fun evolutionValueFormat(index: Int) : StatFormat { fun evolutionValueFormat(index: Int) : StatFormat {
return StatFormat() return StatFormat()
} }

@ -4,14 +4,25 @@ import io.realm.Realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.Sort import io.realm.Sort
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import java.util.* import net.pokeranalytics.android.ui.view.Localizable
enum class LiveData { /**
* An interface to easily handle the validity of any object we want to save
*/
interface ObjectSavable {
fun isValidForSave(): Boolean { return true }
}
/**
* An enum managing the business objects related to a realm results
*/
enum class LiveData : Localizable {
BANKROLL, BANKROLL,
GAME, GAME,
LOCATION, LOCATION,
TOURNAMENT_TYPE, TOURNAMENT_FEATURE,
TRANSACTION_TYPE; TRANSACTION_TYPE;
fun items(realm: Realm, fieldName: String? = null, sortOrder: Sort? = null): RealmResults<*> { fun items(realm: Realm, fieldName: String? = null, sortOrder: Sort? = null): RealmResults<*> {
@ -28,11 +39,21 @@ enum class LiveData {
BANKROLL -> Bankroll::class.java BANKROLL -> Bankroll::class.java
GAME -> Game::class.java GAME -> Game::class.java
LOCATION -> Location::class.java LOCATION -> Location::class.java
TOURNAMENT_TYPE -> TournamentFeature::class.java TOURNAMENT_FEATURE -> TournamentFeature::class.java
TRANSACTION_TYPE -> TransactionType::class.java TRANSACTION_TYPE -> TransactionType::class.java
} }
} }
fun newEntity(): RealmObject {
return when (this) {
BANKROLL -> Bankroll()
GAME -> Game()
LOCATION -> Location()
TOURNAMENT_FEATURE -> TournamentFeature()
TRANSACTION_TYPE -> TransactionType()
}
}
fun getData(realm:Realm, primaryKey:String?): RealmObject? { fun getData(realm:Realm, primaryKey:String?): RealmObject? {
var proxyItem: RealmObject? = null var proxyItem: RealmObject? = null
primaryKey?.let { primaryKey?.let {
@ -49,13 +70,24 @@ enum class LiveData {
proxyItem?.let { proxyItem?.let {
return realm.copyFromRealm(it) return realm.copyFromRealm(it)
} ?: run { } ?: run {
realm.beginTransaction() return this.newEntity()
/* realm.beginTransaction()
val t = realm.createObject(this.relatedEntity, UUID.randomUUID().toString()) val t = realm.createObject(this.relatedEntity, UUID.randomUUID().toString())
realm.commitTransaction() realm.commitTransaction()
return realm.copyFromRealm(t) return realm.copyFromRealm(t)*/
} }
} }
}
override val resId: Int?
get() {
return when (this) {
BANKROLL -> R.string.bankroll
GAME -> R.string.game
LOCATION -> R.string.location
TOURNAMENT_FEATURE -> R.string.tournament_type
TRANSACTION_TYPE -> R.string.operation_types
}
}}
/* /*
interface ListableDataSource { interface ListableDataSource {

@ -4,6 +4,7 @@ import android.text.InputType
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.model.ObjectSavable
import net.pokeranalytics.android.ui.adapter.components.LiveDataDataSource import net.pokeranalytics.android.ui.adapter.components.LiveDataDataSource
import net.pokeranalytics.android.ui.adapter.components.RowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.components.RowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData
@ -15,7 +16,7 @@ import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
open class Bankroll(name: String = "") : RealmObject(), RowRepresentableDataSource, LiveDataDataSource, open class Bankroll(name: String = "") : RealmObject(), RowRepresentableDataSource, LiveDataDataSource,
RowEditable { RowEditable, ObjectSavable {
companion object { companion object {
fun newInstance() : Bankroll { fun newInstance() : Bankroll {

@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.realm
import android.text.InputType import android.text.InputType
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.model.ObjectSavable
import net.pokeranalytics.android.ui.adapter.components.* import net.pokeranalytics.android.ui.adapter.components.*
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData
import net.pokeranalytics.android.ui.view.RowEditable import net.pokeranalytics.android.ui.view.RowEditable
@ -11,7 +12,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SimpleRow import net.pokeranalytics.android.ui.view.SimpleRow
import java.util.* import java.util.*
open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource, RowEditable { open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource, RowEditable, ObjectSavable {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() var id = UUID.randomUUID().toString()
@ -35,6 +36,7 @@ open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource,
override fun stringForRow(row: RowRepresentable): String { override fun stringForRow(row: RowRepresentable): String {
return when (row) { return when (row) {
SimpleRow.NAME -> this.name SimpleRow.NAME -> this.name
GameRow.SHORT_NAME -> this.shortName?:""
else -> return super.stringForRow(row) else -> return super.stringForRow(row)
} }
} }
@ -43,6 +45,7 @@ open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource,
val data = java.util.ArrayList<BottomSheetData>() val data = java.util.ArrayList<BottomSheetData>()
when (row) { when (row) {
SimpleRow.NAME -> data.add(BottomSheetData(this.name, SimpleRow.NAME.resId, InputType.TYPE_CLASS_TEXT)) SimpleRow.NAME -> data.add(BottomSheetData(this.name, SimpleRow.NAME.resId, InputType.TYPE_CLASS_TEXT))
GameRow.SHORT_NAME -> data.add(BottomSheetData(this.shortName, GameRow.SHORT_NAME.resId, InputType.TYPE_CLASS_TEXT))
} }
return data return data
} }
@ -50,6 +53,11 @@ open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource,
override fun updateValue(value: Any?, row: RowRepresentable) { override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) { when (row) {
SimpleRow.NAME -> this.name = value as String? ?: "" SimpleRow.NAME -> this.name = value as String? ?: ""
GameRow.SHORT_NAME -> this.shortName = value as String
} }
} }
override fun isValidForSave(): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
} }

@ -164,7 +164,7 @@ open class Session : RealmObject(), SessionInterface, RowRepresentableDataSource
} }
@Ignore @Ignore
override var estimatedHands: Double = 0.0 override var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0)
@Ignore @Ignore
override var bbNetResult: Double = 0.0 override var bbNetResult: Double = 0.0
@ -191,6 +191,32 @@ open class Session : RealmObject(), SessionInterface, RowRepresentableDataSource
return 0.0 return 0.0
} }
/**
* This method is called whenever a session is about to be deleted
*/
fun cleanup() {
this.sessionSet?.let { set ->
// get all sessions part of the deleted session set
val sessionsFromSet = set.sessions
// cleanup unecessary related objects
set.deleteFromRealm()
this.timeFrame?.deleteFromRealm()
this.result?.deleteFromRealm()
// make sessions recreate/find their session set
sessionsFromSet?.let { sessions ->
sessions.forEach { session ->
session.timeFrame?.notifySessionDateChange()
}
}
}
}
override fun adapterRows(): ArrayList<RowRepresentable> { override fun adapterRows(): ArrayList<RowRepresentable> {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
rows.addAll(SessionRow.getRowsForState(getState())) rows.addAll(SessionRow.getRowsForState(getState()))

@ -1,18 +1,25 @@
package net.pokeranalytics.android.model.realm package net.pokeranalytics.android.model.realm
import io.realm.Realm import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
open class SessionSet() : RealmObject() { open class SessionSet() : RealmObject() {
// The timeframe of the set, i.e. its start & end date /**
* The timeframe of the set, i.e. its start & end date
*/
var timeFrame: TimeFrame? = null var timeFrame: TimeFrame? = null
// The list of Session played within the set, i.e. played within the same time frame /**
var sessions: RealmList<Session> = RealmList() * The list of sessions associated with this set
*/
@LinkingObjects("sessionSet")
val sessions: RealmResults<Session>? = null
@Ignore // a duration shortcut @Ignore // a duration shortcut
var duration: Long = 0L var duration: Long = 0L
@ -28,12 +35,15 @@ open class SessionSet() : RealmObject() {
@Ignore // a netResult shortcut @Ignore // a netResult shortcut
var netResult: Double = 0.0 var netResult: Double = 0.0
get () {
return this.sessions?.sumByDouble { it.value } ?: 0.0
}
@Ignore // a duration shortcut @Ignore // a duration shortcut
var hourlyRate: Double = 0.0 var hourlyRate: Double = 0.0
@Ignore @Ignore
var estimatedHands: Double = 0.0 var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0)
@Ignore @Ignore
var bbNetResult: Double = 0.0 var bbNetResult: Double = 0.0

@ -7,6 +7,7 @@ import io.realm.RealmResults
import io.realm.annotations.Ignore import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import net.pokeranalytics.android.exceptions.ModelException import net.pokeranalytics.android.exceptions.ModelException
import timber.log.Timber
import java.util.* import java.util.*
open class TimeFrame : RealmObject() { open class TimeFrame : RealmObject() {
@ -81,24 +82,26 @@ open class TimeFrame : RealmObject() {
this.duration = netDuration this.duration = netDuration
} }
private fun notifySessionDateChange() { fun notifySessionDateChange() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
var query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java) var query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java)
query.isNotNull("timeFrame") query.isNotNull("timeFrame")
// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}")
if (this.endDate == null) { if (this.endDate == null) {
query.greaterThan("timeFrame.startDate", this.startDate.time).or().greaterThan("timeFrame.endDate", this.startDate.time) query.greaterThan("timeFrame.startDate", this.startDate.time).or().greaterThan("timeFrame.endDate", this.startDate.time)
} else { } else {
val endDate = this.endDate!! val endDate = this.endDate!!
query query
.greaterThan("timeFrame.startDate", this.startDate)
.lessThan("timeFrame.endDate", this.startDate)
.or()
.greaterThan("timeFrame.startDate", endDate)
.lessThan("timeFrame.endDate", endDate)
.or()
.lessThan("timeFrame.startDate", this.startDate) .lessThan("timeFrame.startDate", this.startDate)
.greaterThan("timeFrame.endDate", this.startDate)
.or()
.lessThan("timeFrame.startDate", endDate)
.greaterThan("timeFrame.endDate", endDate) .greaterThan("timeFrame.endDate", endDate)
.or()
.greaterThan("timeFrame.startDate", this.startDate)
.lessThan("timeFrame.endDate", endDate)
} }
val sessionGroups = query.findAll() val sessionGroups = query.findAll()
@ -127,7 +130,6 @@ open class TimeFrame : RealmObject() {
private fun createSessionGroup() { private fun createSessionGroup() {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
// realm.beginTransaction()
val set: SessionSet = SessionSet.newInstance(realm) val set: SessionSet = SessionSet.newInstance(realm)
set.timeFrame?.let { set.timeFrame?.let {
@ -142,9 +144,9 @@ open class TimeFrame : RealmObject() {
} ?: run { } ?: run {
throw ModelException("Session should never be null here") throw ModelException("Session should never be null here")
} }
// this.session?.sessionSet = set
// set.sessions.add(this.session) Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}")
// realm.commitTransaction()
} }
/** /**
@ -165,14 +167,7 @@ open class TimeFrame : RealmObject() {
groupTimeFrame.endDate = null groupTimeFrame.endDate = null
} }
// Realm Update this.session?.sessionSet = sessionSet
// val realm = Realm.getDefaultInstance()
realm.beginTransaction()
if (!sessionSet.sessions.contains(this.session)) {
sessionSet.sessions.add(this.session)
}
// realm.copyToRealmOrUpdate(groupTimeFrame)
// realm.commitTransaction()
} }
@ -185,6 +180,7 @@ open class TimeFrame : RealmObject() {
var startDate: Date = this.startDate var startDate: Date = this.startDate
var endDate: Date? = this.endDate var endDate: Date? = this.endDate
// find earlier and later dates from all sets
val timeFrames = sessionSets.mapNotNull { it.timeFrame } val timeFrames = sessionSets.mapNotNull { it.timeFrame }
timeFrames.forEach { tf -> timeFrames.forEach { tf ->
if (tf.startDate.before(startDate)) { if (tf.startDate.before(startDate)) {
@ -204,11 +200,8 @@ open class TimeFrame : RealmObject() {
} }
// get all sessions from sets // get all sessions from sets
var sessions = sessionSets.flatMap { it.sessions } var sessions = mutableSetOf<Session>()
sessionSets.forEach { it.sessions?.asIterable()?.let { it1 -> sessions.addAll(it1) } }
// Start Realm updates
// val realm = Realm.getDefaultInstance()
// realm.beginTransaction()
// delete all sets // delete all sets
sessionSets.deleteAllFromRealm() sessionSets.deleteAllFromRealm()
@ -224,15 +217,14 @@ open class TimeFrame : RealmObject() {
// Add the session linked to this timeframe to the new sessionGroup // Add the session linked to this timeframe to the new sessionGroup
this.sessions?.first()?.let { this.sessions?.first()?.let {
set.sessions.add(it) it.sessionSet = set
} ?: run { } ?: run {
throw ModelException("TimeFrame should never be null here") throw ModelException("TimeFrame should never be null here")
} }
// Add all orphan sessions // Add all orphan sessions
set.sessions.addAll(sessions) sessions.forEach { it.sessionSet = set }
// realm.commitTransaction()
} }
} }

@ -3,22 +3,37 @@ package net.pokeranalytics.android.ui.adapter.components
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import net.pokeranalytics.android.ui.view.DynamicHolder import net.pokeranalytics.android.ui.view.BindableHolder
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
/**
* An interface used to provide RowRepresentableAdapter content and value in the form of rows
*/
interface RowRepresentableDataSource { interface RowRepresentableDataSource {
/**
* Returns a list of rows
*/
fun adapterRows(): ArrayList<RowRepresentable> fun adapterRows(): ArrayList<RowRepresentable>
/**
* Returns a boolean for a specific row
*/
fun boolForRow(row: RowRepresentable): Boolean { fun boolForRow(row: RowRepresentable): Boolean {
return false return false
} }
/**
* Returns a string for a specific row
*/
fun stringForRow(row: RowRepresentable): String { fun stringForRow(row: RowRepresentable): String {
return "" return ""
} }
/**
* Returns an action icon identifier for a specific row
*/
fun actionIconForRow(row: RowRepresentable): Int? { fun actionIconForRow(row: RowRepresentable): Int? {
return 0 return 0
} }
@ -34,14 +49,25 @@ interface RowRepresentableDataSource {
} }
/**
* A delegate used to propagate UI actions
*/
interface RowRepresentableDelegate { interface RowRepresentableDelegate {
fun onRowSelected(row: RowRepresentable) {} fun onRowSelected(row: RowRepresentable) {}
fun onActionSelected(row: RowRepresentable) {} fun onActionSelected(row: RowRepresentable) {}
} }
/**
* An adapter capable of displaying a list of RowRepresentables
* @param rowRepresentableDataSource the datasource providing rows
* @param rowRepresentableDelegate the delegate, notified of UI actions
*/
class RowRepresentableAdapter(var rowRepresentableDataSource: RowRepresentableDataSource, var rowRepresentableDelegate: RowRepresentableDelegate? = null) : class RowRepresentableAdapter(var rowRepresentableDataSource: RowRepresentableDataSource, var rowRepresentableDelegate: RowRepresentableDelegate? = null) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
/**
* The list of rows to display
*/
private var rows: ArrayList<RowRepresentable> = ArrayList() private var rows: ArrayList<RowRepresentable> = ArrayList()
init { init {
@ -72,7 +98,7 @@ class RowRepresentableAdapter(var rowRepresentableDataSource: RowRepresentableDa
rowRepresentableDelegate?.onActionSelected(dynamicRow) rowRepresentableDelegate?.onActionSelected(dynamicRow)
} }
(holder as DynamicHolder).bind(dynamicRow, this.rowRepresentableDataSource, listener, actionListener) (holder as BindableHolder).bind(dynamicRow, this.rowRepresentableDataSource, listener, actionListener)
} }
/** /**

@ -1,17 +1,19 @@
package net.pokeranalytics.android.ui.fragment package net.pokeranalytics.android.ui.fragment
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm
import io.realm.RealmObject import io.realm.RealmObject
import kotlinx.android.synthetic.main.fragment_editable_data.* import kotlinx.android.synthetic.main.fragment_editable_data.*
import kotlinx.android.synthetic.main.fragment_editable_data.view.* import kotlinx.android.synthetic.main.fragment_editable_data.view.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.ObjectSavable
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.components.* import net.pokeranalytics.android.ui.adapter.components.*
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
@ -78,26 +80,56 @@ class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate,
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = viewManager layoutManager = viewManager
} }
this.saveButton.text = this.saveButton.context.getString(R.string.save)
this.saveButton.setOnClickListener {
if ((this.item as ObjectSavable).isValidForSave()) {
this.getRealm().executeTransaction {
it.copyToRealmOrUpdate(this.item)
}
this.activity?.let {
it.finish()
}
} else {
val builder = AlertDialog.Builder(it.context)
builder.setTitle(R.string.warning)
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
this.deleteButton.text = this.deleteButton.context.getString(R.string.delete)
this.deleteButton.setOnClickListener {
val builder = AlertDialog.Builder(it.context)
builder.setTitle(R.string.warning)
.setMessage(R.string.are_you_sure_you_want_to_do_that_)
.setNeutralButton(R.string.no, null)
.setNegativeButton(R.string.yes, DialogInterface.OnClickListener { dialog, id ->
this.getRealm().executeTransaction {
this.item.deleteFromRealm()
}
this.activity?.let {
it.finish()
}
})
builder.show()
}
} }
/** /**
* Set fragment data * Set fragment data
*/ */
fun setData(dataType: Int, primaryKey: String?) { fun setData(dataType: Int, primaryKey: String?) {
this.liveDataType = LiveData.values()[dataType] this.liveDataType = LiveData.values()[dataType]
val realm = Realm.getDefaultInstance() var proxyItem : RealmObject? = this.liveDataType.getData(this.getRealm(), primaryKey)
var proxyItem : RealmObject? = this.liveDataType.getData(realm, primaryKey)
proxyItem?.let { proxyItem?.let {
this.appBar.toolbar.title = "Update ${this.liveDataType.name.toLowerCase().capitalize()}" this.appBar.toolbar.title = "Update ${this.liveDataType.name.toLowerCase().capitalize()}"
} ?: run { } ?: run {
this.appBar.toolbar.title = "New ${this.liveDataType.name.toLowerCase().capitalize()}" this.appBar.toolbar.title = "New ${this.liveDataType.name.toLowerCase().capitalize()}"
} }
this.item = this.liveDataType.updateOrCreate(this.getRealm(), primaryKey)
this.item = this.liveDataType.updateOrCreate(realm, primaryKey)
this.rowRepresentableAdapter = RowRepresentableAdapter((this.item as RowRepresentableDataSource), this) this.rowRepresentableAdapter = RowRepresentableAdapter((this.item as RowRepresentableDataSource), this)
this.recyclerView.adapter = rowRepresentableAdapter this.recyclerView.adapter = rowRepresentableAdapter
} }
} }

@ -7,11 +7,9 @@ import net.pokeranalytics.android.model.extensions.SessionState
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
/** /**
* An interface used so that enums values can be represented visually * An interface to easily localize any object
* as rows in RecyclerViews
*/ */
interface RowRepresentable { interface Localizable {
/** /**
* The resource identifier of the localized title * The resource identifier of the localized title
*/ */
@ -29,7 +27,13 @@ interface RowRepresentable {
} }
return "LOCALISATION NOT FOUND" return "LOCALISATION NOT FOUND"
} }
}
/**
* An interface used so that enums values can be represented visually
* as rows in RecyclerViews
*/
interface RowRepresentable : Localizable {
/** /**
* The type of view associated with the row * The type of view associated with the row
*/ */
@ -190,9 +194,53 @@ enum class BankrollRow : RowRepresentable {
} }
enum class GameRow : RowRepresentable { enum class GameRow : RowRepresentable {
SHORT_NAME;
override val resId: Int?
get() {
return when (this) {
SHORT_NAME -> R.string.short_name
}
}
override val viewType: Int
get() {
return when (this) {
SHORT_NAME -> RowViewType.TITLE_VALUE.ordinal
}
}
override val bottomSheetType: BottomSheetType
get() {
return when (this) {
SHORT_NAME -> BottomSheetType.EDIT_TEXT
}
}
} }
enum class LocationRow : RowRepresentable { enum class LocationRow : RowRepresentable {
LOCATION_STATUS;
override val resId: Int?
get() {
return when (this) {
LOCATION_STATUS -> R.string.short_name
}
}
override val viewType: Int
get() {
return when (this) {
LOCATION_STATUS -> RowViewType.TITLE.ordinal
}
}
override val bottomSheetType: BottomSheetType
get() {
return when (this) {
LOCATION_STATUS -> BottomSheetType.NONE
}
}
} }
enum class TransactionTypeRow : RowRepresentable { enum class TransactionTypeRow : RowRepresentable {
@ -205,17 +253,15 @@ enum class SettingRow : RowRepresentable {
BANKROLL, BANKROLL,
GAME, GAME,
LOCATION, LOCATION,
TOURNAMENT_TYPE, TOURNAMENT_FEATURE,
TRANSACTION_TYPE; TRANSACTION_TYPE;
override val resId: Int? override val resId: Int?
get() { get() {
return when (this) { this.relatedResultsRepresentable?. let {
BANKROLL -> R.string.bankroll return it.resId
GAME -> R.string.game } ?: run {
LOCATION -> R.string.location return super.resId
TOURNAMENT_TYPE -> R.string.tournament_type
TRANSACTION_TYPE -> R.string.operation_types
} }
} }
@ -227,7 +273,7 @@ enum class SettingRow : RowRepresentable {
BANKROLL -> LiveData.BANKROLL BANKROLL -> LiveData.BANKROLL
GAME -> LiveData.GAME GAME -> LiveData.GAME
LOCATION -> LiveData.LOCATION LOCATION -> LiveData.LOCATION
TOURNAMENT_TYPE -> LiveData.TOURNAMENT_TYPE TOURNAMENT_FEATURE -> LiveData.TOURNAMENT_FEATURE
TRANSACTION_TYPE -> LiveData.TRANSACTION_TYPE TRANSACTION_TYPE -> LiveData.TRANSACTION_TYPE
} }
} }

@ -8,7 +8,10 @@ import kotlinx.android.synthetic.main.row_title_value_action.view.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.adapter.components.RowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.components.RowRepresentableDataSource
interface DynamicHolder { /**
* An interface used to factor the configuration of RecyclerView.ViewHolder
*/
interface BindableHolder {
fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource? = null, listener: View.OnClickListener, actionListener: View.OnClickListener? = null) {} fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource? = null, listener: View.OnClickListener, actionListener: View.OnClickListener? = null) {}
@ -22,13 +25,13 @@ enum class RowViewType {
TITLE_VALUE_ACTION; TITLE_VALUE_ACTION;
inner class FakeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), inner class FakeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
DynamicHolder { BindableHolder {
override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) { override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) {
} }
} }
inner class TitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), inner class TitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
DynamicHolder { BindableHolder {
override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) { override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) {
itemView.title.text = row.localizedTitle(itemView.context) itemView.title.text = row.localizedTitle(itemView.context)
itemView.container.setOnClickListener(listener) itemView.container.setOnClickListener(listener)
@ -36,7 +39,7 @@ enum class RowViewType {
} }
inner class TitleValueViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), inner class TitleValueViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
DynamicHolder { BindableHolder {
override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) { override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) {
itemView.title.text = row.localizedTitle(itemView.context) itemView.title.text = row.localizedTitle(itemView.context)
@ -48,7 +51,7 @@ enum class RowViewType {
} }
inner class TitleValueActionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), inner class TitleValueActionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
DynamicHolder { BindableHolder {
override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) { override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) {
itemView.title.text = row.localizedTitle(itemView.context) itemView.title.text = row.localizedTitle(itemView.context)
rowRepresentableDataSource?.let { rowDelegate -> rowRepresentableDataSource?.let { rowDelegate ->

@ -9,7 +9,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fillViewport="true" android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior" android:id="@+id/nestedScrollView">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -22,7 +22,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@ -51,10 +51,24 @@
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:title="Poker Analytics" app:title="Poker Analytics"
app:titleTextColor="@color/white" app:titleTextColor="@color/white"
app:layout_collapseMode="pin" /> app:layout_collapseMode="pin"/>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<Button
tools:text="Save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/saveButton"
app:layout_anchorGravity="right|top"
app:layout_anchor="@+id/appBar"/>
<Button
tools:text="Delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/deleteButton"
app:layout_anchorGravity="bottom|right"
app:layout_anchor="@+id/nestedScrollView"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -18,6 +18,8 @@
<string name="standard_deviation_hourly">Standard deviation hourly</string> <string name="standard_deviation_hourly">Standard deviation hourly</string>
<string name="hands_played">Hands played</string> <string name="hands_played">Hands played</string>
<string name="save">Save</string>
<!-- <!--
<string name="bankroll">Bankroll</string> <string name="bankroll">Bankroll</string>
<string name="blinds">Blinds</string> <string name="blinds">Blinds</string>

Loading…
Cancel
Save