diff --git a/app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt index 4b834a9f..5afe3fd0 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt @@ -1,6 +1,7 @@ package net.pokeranalytics.android import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.realm.RealmResults import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.ComputedResults import net.pokeranalytics.android.calculus.SessionGroup @@ -37,13 +38,6 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() { val realm = this.mockRealm 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 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) s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) -// var s1: Session = Session.newInstance() -// var s2: Session = Session.newInstance() + s1.result?.buyin = 100.0 // net result = -100 + s2.result?.buyin = 200.0 + s2.result?.cashout = 500.0 // net result = 300 - s1.result?.netResult = -100.0 - s2.result?.netResult = 300.0 + s1.cgBigBlind = 0.5 // bb net result = -200bb + s2.cgBigBlind = 2.0 // bb net result = 150bb realm.insert(s1) realm.insert(s2) realm.commitTransaction() + val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") + + val sd1 = sdf.parse("01/1/2019 10:00") + val ed1 = sdf.parse("01/1/2019 11:00") + val sd2 = sdf.parse("02/1/2019 08:00") + val ed2 = sdf.parse("02/1/2019 11:00") + realm.beginTransaction() - s1.timeFrame?.setDate(sd1, ed1) - 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(s2) @@ -75,7 +78,10 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() { val sessions = realm.where(Session::class.java).findAll() 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 sum = results.computedStat(Stat.NETRESULT) @@ -99,8 +105,315 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() { 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? = 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") + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt index ede13e9f..293d6a4e 100644 --- a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt +++ b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt @@ -3,13 +3,29 @@ package net.pokeranalytics.android import android.app.Application import io.realm.Realm import io.realm.RealmConfiguration +import io.realm.RealmResults +import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.util.PokerAnalyticsLogs import timber.log.Timber class PokerAnalyticsApplication: Application() { -// var timeFrames: RealmResults? = null + var sessions: RealmResults? = null + +// private val listener: OrderedRealmCollectionChangeListener> = +// OrderedRealmCollectionChangeListener() { realmResults: RealmResults, changeSet: OrderedCollectionChangeSet -> +// +// if (changeSet == null) { +// return@OrderedRealmCollectionChangeListener +// } +// +// val realm: Realm = Realm.getDefaultInstance() +// +// val deletedSessions = realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll() +// deletedSessions.forEach { it.cleanup() } +// +// } override fun onCreate() { super.onCreate() @@ -22,12 +38,15 @@ class PokerAnalyticsApplication: Application() { .build() Realm.setDefaultConfiguration(realmConfiguration) -// val realm: Realm = Realm.getDefaultInstance() -// // Add observer on session time frame changes -// this.timeFrames = realm.where(Session::class.java).findAllAsync() -// this.timeFrames?.addChangeListener { t, changeSet -> // @todo check if main thread has running Looper, cf Realm doc -// changeSet.deletions -// } + val realm: Realm = Realm.getDefaultInstance() + // Add observer on session time frame changes + this.sessions = realm.where(Session::class.java).findAll() // monitor session deletions + this.sessions?.addChangeListener { t, changeSet -> + + val deletedSessions = realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll() + deletedSessions.forEach { it.cleanup() } + + } if (BuildConfig.DEBUG) { // Logs diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt index c28b04b1..348d4312 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt @@ -3,11 +3,19 @@ package net.pokeranalytics.android.calculus import net.pokeranalytics.android.calculus.Stat.* import net.pokeranalytics.android.model.realm.SessionSet - +/** + * The class performing stats computation + */ class Calculator { + /** + * The options used for calculations or display + */ class Options { + /** + * The way the stats are going to be displayed + */ enum class Display { TABLE, EVOLUTION, @@ -16,6 +24,9 @@ class Calculator { POLYNOMIAL } + /** + * The type of evolution values + */ enum class EvolutionValues { NONE, STANDARD, @@ -26,6 +37,9 @@ class Calculator { var evolutionValues: EvolutionValues = EvolutionValues.NONE var displayedStats: List = listOf() + /** + * This function determines whether the standard deviation should be computed + */ fun shouldComputeStandardDeviation() : Boolean { this.displayedStats.forEach { stat -> return when (stat) { @@ -41,14 +55,16 @@ class Calculator { companion object { - fun computePreAggregation(sets: List, options: Options): List { + fun computePreAggregation(sets: List, options: Options): List { return listOf() } - // Computes all stats for list of Session sessionGroup - fun computeGroups(groups: List, options: Options): List { + /** + * Computes all stats for list of Session sessionGroup + */ + fun computeGroups(groups: List, options: Options): List { - var computedGroups: MutableList = mutableListOf() + var computedResults: MutableList = mutableListOf() groups.forEach { group -> // Computes actual sessionGroup stats val results: ComputedResults = Calculator.compute(group, options = options) @@ -57,19 +73,20 @@ class Calculator { val comparedGroup = group.comparedSessions if (comparedGroup != null) { val comparedResults = Calculator.compute(comparedGroup, options = options) - group.comparedComputedGroup = ComputedGroup(comparedGroup, comparedResults) - + group.comparedComputedResults = comparedResults results.computeStatVariations(comparedResults) } - results.finalize(options) - computedGroups.add(ComputedGroup(group, results)) + results.finalize(options) // later treatment, such as evolution values sorting + computedResults.add(results) } - return computedGroups + return computedResults } - // Computes stats for a SessionSet + /** + * Computes stats for a SessionSet + */ fun compute(sessionGroup: SessionGroup, options: Options) : ComputedResults { val sessions: List = sessionGroup.sessions @@ -124,15 +141,15 @@ class Calculator { gTotalHands += sessionSet.estimatedHands gBBSum += sessionSet.bbNetResult - hourlyRate = gSum / duration * 3600.0 - hourlyRateBB = gBBSum / duration * 3600.0 + hourlyRate = gSum / duration + hourlyRateBB = gBBSum / duration if (options.evolutionValues == Options.EvolutionValues.DATED) { 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(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(duration / gIndex, duration, AVERAGE_DURATION) results.addEvolutionValue(hourlyRateBB, duration, HOURLY_RATE_BB) @@ -147,11 +164,11 @@ class Calculator { ComputedStat(HOURLY_RATE, hourlyRate), ComputedStat(AVERAGE, average), 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(AVERAGE_DURATION, (duration / 3600.0) / sessions.size), + ComputedStat(AVERAGE_DURATION, duration / sessions.size), 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(WIN_RATIO, (winningSessionCount / sessions.size).toDouble()), ComputedStat(AVERAGE_BUYIN, totalBuyin / sessions.size), @@ -160,7 +177,6 @@ class Calculator { )) - // Standard Deviation if (options.shouldComputeStandardDeviation()) { @@ -178,7 +194,7 @@ class Calculator { results.addStats(setOf( ComputedStat(STANDARD_DEVIATION, standardDeviation), - ComputedStat(Stat.STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) + ComputedStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) )) } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt index 0452cc90..0f01a82a 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt @@ -32,20 +32,7 @@ class SessionGroup(name: String, sessions: List) { var comparedSessions: SessionGroup? = null // The computed stats of the comparable sessionGroup - var comparedComputedGroup: ComputedGroup? = 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 - } + var comparedComputedResults: ComputedResults? = null } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt index 60744e73..680f1060 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt @@ -4,16 +4,15 @@ import net.pokeranalytics.android.R import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType -interface AnyStat { - -} - -enum class Stat : AnyStat, RowRepresentable { +/** + * An enum representing all the types of Session statistics + */ +enum class Stat : RowRepresentable { NETRESULT, HOURLY_RATE, AVERAGE, - NUMBER_OF_GROUPS, + NUMBER_OF_SETS, NUMBER_OF_GAMES, DURATION, AVERAGE_DURATION, @@ -28,12 +27,9 @@ enum class Stat : AnyStat, RowRepresentable { STANDARD_DEVIATION_BB_PER_100_HANDS, HANDS_PLAYED; - fun label() : String = when (this) { - NETRESULT -> "" - HOURLY_RATE -> "" - else -> throw IllegalArgumentException("Label not defined") - } - + /** + * Returns whether the stat evolution values requires a distribution sorting + */ fun hasDistributionSorting() : Boolean { when (this) { STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> return true @@ -59,7 +55,7 @@ enum class Stat : AnyStat, RowRepresentable { NETRESULT -> R.string.net_result HOURLY_RATE -> R.string.average_hour_rate 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 DURATION -> R.string.duration AVERAGE_DURATION -> R.string.average_duration @@ -79,11 +75,9 @@ enum class Stat : AnyStat, RowRepresentable { override val viewType: Int = RowViewType.TITLE_VALUE.ordinal } -enum class CashSessionStat : AnyStat { - NETBB, - AVERAGEBB -} - +/** + * ComputedStat contains a [stat] and their associated [value] + */ class ComputedStat(stat: Stat, value: Double) { 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 - // The stat value + /** + * The stat value + */ var value: Double = value - // The variation of the stat + /** + * The variation of the stat + */ var variation: Double? = null - // The data points leading to the current stat value -// var points: List = 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 { return StatFormat() } + /** + * Returns a StatFormat instance for an evolution value located at the specified [index] + */ fun evolutionValueFormat(index: Int) : StatFormat { return StatFormat() } diff --git a/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt b/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt index f54b5fd8..f4cc0770 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt @@ -4,14 +4,25 @@ import io.realm.Realm import io.realm.RealmObject import io.realm.RealmResults import io.realm.Sort +import net.pokeranalytics.android.R 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, GAME, LOCATION, - TOURNAMENT_TYPE, + TOURNAMENT_FEATURE, TRANSACTION_TYPE; fun items(realm: Realm, fieldName: String? = null, sortOrder: Sort? = null): RealmResults<*> { @@ -28,11 +39,21 @@ enum class LiveData { BANKROLL -> Bankroll::class.java GAME -> Game::class.java LOCATION -> Location::class.java - TOURNAMENT_TYPE -> TournamentFeature::class.java + TOURNAMENT_FEATURE -> TournamentFeature::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? { var proxyItem: RealmObject? = null primaryKey?.let { @@ -49,13 +70,24 @@ enum class LiveData { proxyItem?.let { return realm.copyFromRealm(it) } ?: run { - realm.beginTransaction() + return this.newEntity() +/* realm.beginTransaction() val t = realm.createObject(this.relatedEntity, UUID.randomUUID().toString()) 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 { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt index bf23ca9c..d33a9174 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt @@ -4,6 +4,7 @@ import android.text.InputType import io.realm.RealmList import io.realm.RealmObject 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.RowRepresentableDataSource import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData @@ -15,7 +16,7 @@ import java.util.* import kotlin.collections.ArrayList open class Bankroll(name: String = "") : RealmObject(), RowRepresentableDataSource, LiveDataDataSource, - RowEditable { + RowEditable, ObjectSavable { companion object { fun newInstance() : Bankroll { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt index c0a419a1..e83f7c77 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Game.kt @@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.realm import android.text.InputType import io.realm.RealmObject import io.realm.annotations.PrimaryKey +import net.pokeranalytics.android.model.ObjectSavable import net.pokeranalytics.android.ui.adapter.components.* import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetData import net.pokeranalytics.android.ui.view.RowEditable @@ -11,7 +12,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.SimpleRow import java.util.* -open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource, RowEditable { +open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource, RowEditable, ObjectSavable { @PrimaryKey var id = UUID.randomUUID().toString() @@ -35,6 +36,7 @@ open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource, override fun stringForRow(row: RowRepresentable): String { return when (row) { SimpleRow.NAME -> this.name + GameRow.SHORT_NAME -> this.shortName?:"" else -> return super.stringForRow(row) } } @@ -43,6 +45,7 @@ open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource, val data = java.util.ArrayList() when (row) { SimpleRow.NAME -> data.add(BottomSheetData(this.name, SimpleRow.NAME.resId, InputType.TYPE_CLASS_TEXT)) + GameRow.SHORT_NAME -> data.add(BottomSheetData(this.shortName, GameRow.SHORT_NAME.resId, InputType.TYPE_CLASS_TEXT)) } return data } @@ -50,6 +53,11 @@ open class Game : RealmObject(), RowRepresentableDataSource, LiveDataDataSource, override fun updateValue(value: Any?, row: RowRepresentable) { when (row) { SimpleRow.NAME -> this.name = value as String? ?: "" + GameRow.SHORT_NAME -> this.shortName = value as String } } + + override fun isValidForSave(): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index e3bce209..390fac79 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -164,7 +164,7 @@ open class Session : RealmObject(), SessionInterface, RowRepresentableDataSource } @Ignore - override var estimatedHands: Double = 0.0 + override var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0) @Ignore override var bbNetResult: Double = 0.0 @@ -191,6 +191,32 @@ open class Session : RealmObject(), SessionInterface, RowRepresentableDataSource 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 { val rows = ArrayList() rows.addAll(SessionRow.getRowsForState(getState())) diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt index 071a31a0..d05fad74 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt @@ -1,18 +1,25 @@ package net.pokeranalytics.android.model.realm import io.realm.Realm -import io.realm.RealmList import io.realm.RealmObject +import io.realm.RealmResults import io.realm.annotations.Ignore +import io.realm.annotations.LinkingObjects 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 - // The list of Session played within the set, i.e. played within the same time frame - var sessions: RealmList = RealmList() + /** + * The list of sessions associated with this set + */ + @LinkingObjects("sessionSet") + val sessions: RealmResults? = null @Ignore // a duration shortcut var duration: Long = 0L @@ -28,12 +35,15 @@ open class SessionSet() : RealmObject() { @Ignore // a netResult shortcut var netResult: Double = 0.0 + get () { + return this.sessions?.sumByDouble { it.value } ?: 0.0 + } @Ignore // a duration shortcut var hourlyRate: Double = 0.0 @Ignore - var estimatedHands: Double = 0.0 + var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0) @Ignore var bbNetResult: Double = 0.0 diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt index f5c55284..40dae22f 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt @@ -7,6 +7,7 @@ import io.realm.RealmResults import io.realm.annotations.Ignore import io.realm.annotations.LinkingObjects import net.pokeranalytics.android.exceptions.ModelException +import timber.log.Timber import java.util.* open class TimeFrame : RealmObject() { @@ -81,24 +82,26 @@ open class TimeFrame : RealmObject() { this.duration = netDuration } - private fun notifySessionDateChange() { + fun notifySessionDateChange() { val realm = Realm.getDefaultInstance() var query: RealmQuery = realm.where(SessionSet::class.java) query.isNotNull("timeFrame") +// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}") + if (this.endDate == null) { query.greaterThan("timeFrame.startDate", this.startDate.time).or().greaterThan("timeFrame.endDate", this.startDate.time) } else { val endDate = this.endDate!! query - .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) + .greaterThan("timeFrame.endDate", this.startDate) + .or() + .lessThan("timeFrame.startDate", endDate) .greaterThan("timeFrame.endDate", endDate) + .or() + .greaterThan("timeFrame.startDate", this.startDate) + .lessThan("timeFrame.endDate", endDate) } val sessionGroups = query.findAll() @@ -127,7 +130,6 @@ open class TimeFrame : RealmObject() { private fun createSessionGroup() { val realm = Realm.getDefaultInstance() -// realm.beginTransaction() val set: SessionSet = SessionSet.newInstance(realm) set.timeFrame?.let { @@ -142,9 +144,9 @@ open class TimeFrame : RealmObject() { } ?: run { throw ModelException("Session should never be null here") } -// this.session?.sessionSet = set -// set.sessions.add(this.session) -// realm.commitTransaction() + + Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}") + } /** @@ -165,14 +167,7 @@ open class TimeFrame : RealmObject() { groupTimeFrame.endDate = null } - // Realm Update -// val realm = Realm.getDefaultInstance() - realm.beginTransaction() - if (!sessionSet.sessions.contains(this.session)) { - sessionSet.sessions.add(this.session) - } -// realm.copyToRealmOrUpdate(groupTimeFrame) -// realm.commitTransaction() + this.session?.sessionSet = sessionSet } @@ -185,6 +180,7 @@ open class TimeFrame : RealmObject() { var startDate: Date = this.startDate var endDate: Date? = this.endDate + // find earlier and later dates from all sets val timeFrames = sessionSets.mapNotNull { it.timeFrame } timeFrames.forEach { tf -> if (tf.startDate.before(startDate)) { @@ -204,11 +200,8 @@ open class TimeFrame : RealmObject() { } // get all sessions from sets - var sessions = sessionSets.flatMap { it.sessions } - - // Start Realm updates -// val realm = Realm.getDefaultInstance() -// realm.beginTransaction() + var sessions = mutableSetOf() + sessionSets.forEach { it.sessions?.asIterable()?.let { it1 -> sessions.addAll(it1) } } // delete all sets sessionSets.deleteAllFromRealm() @@ -224,15 +217,14 @@ open class TimeFrame : RealmObject() { // Add the session linked to this timeframe to the new sessionGroup this.sessions?.first()?.let { - set.sessions.add(it) + it.sessionSet = set } ?: run { throw ModelException("TimeFrame should never be null here") } // Add all orphan sessions - set.sessions.addAll(sessions) + sessions.forEach { it.sessionSet = set } -// realm.commitTransaction() } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/RowRepresentableAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/RowRepresentableAdapter.kt index 7106449e..343dfcd9 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/RowRepresentableAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/RowRepresentableAdapter.kt @@ -3,22 +3,37 @@ package net.pokeranalytics.android.ui.adapter.components import android.view.View import android.view.ViewGroup 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.RowViewType +/** + * An interface used to provide RowRepresentableAdapter content and value in the form of rows + */ interface RowRepresentableDataSource { + /** + * Returns a list of rows + */ fun adapterRows(): ArrayList + /** + * Returns a boolean for a specific row + */ fun boolForRow(row: RowRepresentable): Boolean { return false } + /** + * Returns a string for a specific row + */ fun stringForRow(row: RowRepresentable): String { return "" } + /** + * Returns an action icon identifier for a specific row + */ fun actionIconForRow(row: RowRepresentable): Int? { return 0 } @@ -34,14 +49,25 @@ interface RowRepresentableDataSource { } +/** + * A delegate used to propagate UI actions + */ interface RowRepresentableDelegate { fun onRowSelected(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) : RecyclerView.Adapter() { + /** + * The list of rows to display + */ private var rows: ArrayList = ArrayList() init { @@ -72,7 +98,7 @@ class RowRepresentableAdapter(var rowRepresentableDataSource: RowRepresentableDa rowRepresentableDelegate?.onActionSelected(dynamicRow) } - (holder as DynamicHolder).bind(dynamicRow, this.rowRepresentableDataSource, listener, actionListener) + (holder as BindableHolder).bind(dynamicRow, this.rowRepresentableDataSource, listener, actionListener) } /** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt index 7a1041b0..97bc80e7 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt @@ -1,17 +1,19 @@ package net.pokeranalytics.android.ui.fragment +import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.LinearLayoutManager -import io.realm.Realm import io.realm.RealmObject import kotlinx.android.synthetic.main.fragment_editable_data.* import kotlinx.android.synthetic.main.fragment_editable_data.view.* import net.pokeranalytics.android.R 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.adapter.components.* import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment @@ -78,26 +80,56 @@ class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, setHasFixedSize(true) 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 */ fun setData(dataType: Int, primaryKey: String?) { - this.liveDataType = LiveData.values()[dataType] - val realm = Realm.getDefaultInstance() - var proxyItem : RealmObject? = this.liveDataType.getData(realm, primaryKey) + var proxyItem : RealmObject? = this.liveDataType.getData(this.getRealm(), primaryKey) proxyItem?.let { this.appBar.toolbar.title = "Update ${this.liveDataType.name.toLowerCase().capitalize()}" } ?: run { this.appBar.toolbar.title = "New ${this.liveDataType.name.toLowerCase().capitalize()}" } - - this.item = this.liveDataType.updateOrCreate(realm, primaryKey) - + this.item = this.liveDataType.updateOrCreate(this.getRealm(), primaryKey) this.rowRepresentableAdapter = RowRepresentableAdapter((this.item as RowRepresentableDataSource), this) this.recyclerView.adapter = rowRepresentableAdapter - } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt index ad03ba40..0858657e 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt @@ -7,11 +7,9 @@ import net.pokeranalytics.android.model.extensions.SessionState import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType /** - * An interface used so that enums values can be represented visually - * as rows in RecyclerViews + * An interface to easily localize any object */ -interface RowRepresentable { - +interface Localizable { /** * The resource identifier of the localized title */ @@ -29,7 +27,13 @@ interface RowRepresentable { } 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 */ @@ -190,9 +194,53 @@ enum class BankrollRow : 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 { + 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 { @@ -205,17 +253,15 @@ enum class SettingRow : RowRepresentable { BANKROLL, GAME, LOCATION, - TOURNAMENT_TYPE, + TOURNAMENT_FEATURE, TRANSACTION_TYPE; override val resId: Int? get() { - return when (this) { - BANKROLL -> R.string.bankroll - GAME -> R.string.game - LOCATION -> R.string.location - TOURNAMENT_TYPE -> R.string.tournament_type - TRANSACTION_TYPE -> R.string.operation_types + this.relatedResultsRepresentable?. let { + return it.resId + } ?: run { + return super.resId } } @@ -227,7 +273,7 @@ enum class SettingRow : RowRepresentable { BANKROLL -> LiveData.BANKROLL GAME -> LiveData.GAME LOCATION -> LiveData.LOCATION - TOURNAMENT_TYPE -> LiveData.TOURNAMENT_TYPE + TOURNAMENT_FEATURE -> LiveData.TOURNAMENT_FEATURE TRANSACTION_TYPE -> LiveData.TRANSACTION_TYPE } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt index 02d34b2f..b5f8e226 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt @@ -8,7 +8,10 @@ import kotlinx.android.synthetic.main.row_title_value_action.view.* import net.pokeranalytics.android.R 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) {} @@ -22,13 +25,13 @@ enum class RowViewType { TITLE_VALUE_ACTION; inner class FakeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), - DynamicHolder { + BindableHolder { override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) { } } inner class TitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), - DynamicHolder { + BindableHolder { override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) { itemView.title.text = row.localizedTitle(itemView.context) itemView.container.setOnClickListener(listener) @@ -36,7 +39,7 @@ enum class RowViewType { } inner class TitleValueViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), - DynamicHolder { + BindableHolder { override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) { itemView.title.text = row.localizedTitle(itemView.context) @@ -48,7 +51,7 @@ enum class RowViewType { } inner class TitleValueActionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), - DynamicHolder { + BindableHolder { override fun bind(row: RowRepresentable, rowRepresentableDataSource: RowRepresentableDataSource?, listener: View.OnClickListener, actionListener: View.OnClickListener?) { itemView.title.text = row.localizedTitle(itemView.context) rowRepresentableDataSource?.let { rowDelegate -> diff --git a/app/src/main/res/layout/fragment_editable_data.xml b/app/src/main/res/layout/fragment_editable_data.xml index d072701e..ed7b8bf4 100644 --- a/app/src/main/res/layout/fragment_editable_data.xml +++ b/app/src/main/res/layout/fragment_editable_data.xml @@ -9,7 +9,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" - app:layout_behavior="@string/appbar_scrolling_view_behavior"> + app:layout_behavior="@string/appbar_scrolling_view_behavior" android:id="@+id/nestedScrollView"> + app:layout_constraintTop_toTopOf="parent"/> @@ -51,10 +51,24 @@ android:layout_height="?attr/actionBarSize" app:title="Poker Analytics" app:titleTextColor="@color/white" - app:layout_collapseMode="pin" /> + app:layout_collapseMode="pin"/> +