fix conflicts

feature/top10
Razmig Sarkissian 7 years ago
commit adcc2ead57
  1. 82
      app/src/androidTest/java/net/pokeranalytics/android/BankrollInstrumentedUnitTest.kt
  2. 518
      app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt
  3. 4
      app/src/androidTest/java/net/pokeranalytics/android/FavoriteSessionUnitTest.kt
  4. 573
      app/src/androidTest/java/net/pokeranalytics/android/StatsInstrumentedUnitTest.kt
  5. 281
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  6. 25
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  7. 4
      app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt
  8. 10
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  9. 4
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  10. 41
      app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt
  11. 33
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  12. 9
      app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt
  13. 12
      app/src/main/java/net/pokeranalytics/android/model/realm/Location.kt
  14. 335
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  15. 79
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  16. 568
      app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt
  17. 4
      app/src/main/java/net/pokeranalytics/android/model/utils/FavoriteSessionFinder.kt
  18. 161
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt
  19. 7
      app/src/main/java/net/pokeranalytics/android/ui/adapter/RowRepresentableDataSource.kt
  20. 61
      app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt
  21. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt
  22. 10
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt
  23. 10
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt
  24. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/SessionObserverFragment.kt
  25. 30
      app/src/main/java/net/pokeranalytics/android/ui/helpers/DateTimePickerManager.kt
  26. 8
      app/src/main/java/net/pokeranalytics/android/ui/helpers/PlacePickerManager.kt
  27. 6
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  28. 26
      app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt
  29. 2
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/BankrollRow.kt
  30. 2
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt
  31. 2
      app/src/main/java/net/pokeranalytics/android/util/DateExtension.kt
  32. 21
      app/src/main/res/layout/row_button.xml
  33. 3
      app/src/main/res/values/strings.xml

@ -0,0 +1,82 @@
package net.pokeranalytics.android
import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.SessionGroup
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.model.realm.Session
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import java.util.*
@RunWith(AndroidJUnit4::class)
class BankrollInstrumentedUnitTest : RealmInstrumentedUnitTest() {
// convenience extension
fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session {
val session: Session = Session.newInstance(super.mockRealm, false)
session.result?.netResult = netResult
session.startDate = startDate
session.endDate = endDate
return session
}
@Test
fun testSessionStats() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val s2 = realm.createObject(Session::class.java, "2")
val br1 = realm.createObject(Bankroll::class.java, "1")
val br2 = realm.createObject(Bankroll::class.java, "2")
val c1 = realm.createObject(Currency::class.java, "1")
val c2 = realm.createObject(Currency::class.java, "2")
c1.rate = 0.1
c2.rate = 2.0
br1.currency = c1
br2.currency = c2
s1.bankroll = br1
s2.bankroll = br2
s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
s1.result?.netResult = 100.0
s2.result?.netResult = 200.0
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
val options = Calculator.Options()
val results: ComputedResults = Calculator.compute(group, options)
val delta = 0.01
val sum = results.computedStat(Stat.NETRESULT)
if (sum != null) {
Assert.assertEquals(410.0, sum.value, delta)
} else {
Assert.fail("No Net result stat")
}
val average = results.computedStat(Stat.AVERAGE)
if (average != null) {
Assert.assertEquals(205.0, average.value, delta)
} else {
Assert.fail("No AVERAGE stat")
}
}
}

@ -1,518 +0,0 @@
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
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.model.realm.TimeFrame
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import java.text.SimpleDateFormat
import java.util.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() {
// convenience extension
fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session {
val session: Session = Session.newInstance(super.mockRealm, false)
session.result?.netResult = netResult
session.timeFrame?.setDate(startDate, endDate)
return session
}
@Test
fun testSessionStats() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val 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)
s1.result?.buyin = 100.0 // net result = -100
s2.result?.buyin = 200.0
s2.result?.cashout = 500.0 // net result = 300
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) // netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
s2.timeFrame?.setDate(sd2, ed2) // netDuration = 3h, 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)
val 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)
if (sum != null) {
assertEquals(200.0, sum.value, delta)
} else {
Assert.fail("No Net result stat")
}
val average = results.computedStat(Stat.AVERAGE)
if (average != null) {
assertEquals(100.0, average.value, delta)
} else {
Assert.fail("No AVERAGE stat")
}
val duration = results.computedStat(Stat.DURATION)
if (duration != null) {
assertEquals(4.0, duration.value, delta)
} else {
Assert.fail("No netDuration 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 std = results.computedStat(Stat.STANDARD_DEVIATION)
if (std != null) {
assertEquals(200.0, std.value, delta)
} else {
Assert.fail("No std 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 std100 = results.computedStat(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS)
if (std100 != null) {
assertEquals(559.01, std100.value, delta)
} else {
Assert.fail("No std100 stat")
}
}
@Test
fun testOverlappingSessions1() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val 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)
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")
s1.timeFrame?.setDate(sd1, ed1) // netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
s2.timeFrame?.setDate(sd2, ed2) // netDuration = 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)
val 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()
val s1 = realm.createObject(Session::class.java, "1")
val s2 = realm.createObject(Session::class.java, "2")
val 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)
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")
s1.timeFrame?.setDate(sd1, ed1) // netDuration = 4h
s2.timeFrame?.setDate(sd2, ed2) // netDuration = 4h
s3.timeFrame?.setDate(sd3, ed3) // netDuration = 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)
val 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
this.sessions?.addChangeListener { t, changeSet ->
val deletedSessions = realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll()
deletedSessions.forEach { it.cleanup() }
}
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val s2 = realm.createObject(Session::class.java, "2")
val 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) // netDuration = 4h
s2.timeFrame?.setDate(sd2, ed2) // netDuration = 4h
s3.timeFrame?.setDate(sd3, ed3) // netDuration = 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)
val 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 netDuration stat")
}
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")
}
}
@Test
fun testSessionSetCount() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
s1.timeFrame = realm.createObject(TimeFrame::class.java)
s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
realm.insert(s1)
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")
s1.timeFrame?.setDate(sd1, ed1) // netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
realm.copyToRealmOrUpdate(s1)
realm.commitTransaction()
val sets = realm.where(SessionSet::class.java).findAll()
Assert.assertEquals(1, sets.size)
val set = sets.first()
if (set != null) {
Assert.assertEquals(sd1.time, set.timeFrame?.startDate?.time)
Assert.assertEquals(ed1.time, set.timeFrame?.endDate?.time)
} else {
Assert.fail("No set")
}
}
@Test
fun testSessionSetCount2() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val 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)
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/2/2018 09:00")
val ed2 = sdf.parse("01/2/2018 10:00")
s1.timeFrame?.let {
it.setStart(sd1)
it.setEnd(ed1)
}
s2.timeFrame?.let {
it.setStart(sd2)
it.setEnd(ed2)
}
// s1.timeFrame?.setDate(sd1, ed1) // netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
// s2.timeFrame?.setDate(sd2, ed2) // netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
realm.copyToRealmOrUpdate(s1)
realm.copyToRealmOrUpdate(s2)
realm.commitTransaction()
val sets = realm.where(SessionSet::class.java).findAll()
Assert.assertEquals(2, sets.size)
// val set = sets.first()
// if (set != null) {
// Assert.assertEquals(sd1.time, set.timeFrame?.startDate?.time)
// Assert.assertEquals(ed1.time, set.timeFrame?.endDate?.time)
// } else {
// Assert.fail("No set")
// }
}
//
// @Test
// fun testDurationConversion() {
//
// val duration = 6.7555561274509826
// val longDuration = duration.toLong()
// val formatted = longDuration.toMinutes()
//
// assert(formatted == "11:00")
// }
}

@ -32,7 +32,7 @@ class FavoriteSessionUnitTest : RealmInstrumentedUnitTest() {
realm.insert(s3) realm.insert(s3)
realm.commitTransaction() realm.commitTransaction()
val favSession = FavoriteSessionFinder.favoriteSession(Session.Type.CASH_GAME, null, realm, InstrumentationRegistry.getInstrumentation().targetContext) val favSession = FavoriteSessionFinder.favoriteSession(Session.Type.CASH_GAME.ordinal, null, realm, InstrumentationRegistry.getInstrumentation().targetContext)
if (favSession != null) { if (favSession != null) {
Assert.assertEquals(4.0, favSession.cgBigBlind) Assert.assertEquals(4.0, favSession.cgBigBlind)
@ -68,7 +68,7 @@ class FavoriteSessionUnitTest : RealmInstrumentedUnitTest() {
realm.insert(s3) realm.insert(s3)
realm.commitTransaction() realm.commitTransaction()
val favSession = FavoriteSessionFinder.favoriteSession(Session.Type.CASH_GAME, loc2, realm, InstrumentationRegistry.getInstrumentation().targetContext) val favSession = FavoriteSessionFinder.favoriteSession(Session.Type.CASH_GAME.ordinal, loc2, realm, InstrumentationRegistry.getInstrumentation().targetContext)
if (favSession != null) { if (favSession != null) {
Assert.assertEquals(1.0, favSession.cgBigBlind) Assert.assertEquals(1.0, favSession.cgBigBlind)

@ -0,0 +1,573 @@
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
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import java.text.SimpleDateFormat
import java.util.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
// convenience extension
fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session {
val session: Session = Session.newInstance(super.mockRealm, false)
session.result?.netResult = netResult
session.startDate = startDate
session.endDate = endDate
return session
}
@Test
fun testSessionStats() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val s2 = realm.createObject(Session::class.java, "2")
s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
s1.result?.buyin = 100.0 // net result = -100
s2.result?.buyin = 200.0
s2.result?.cashout = 500.0 // net result = 300
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.startDate = sd1
s1.endDate = ed1
s2.startDate = sd2
s2.endDate = ed2
realm.copyToRealmOrUpdate(s1)
realm.copyToRealmOrUpdate(s2)
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
val 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)
if (sum != null) {
assertEquals(200.0, sum.value, delta)
} else {
Assert.fail("No Net result stat")
}
val average = results.computedStat(Stat.AVERAGE)
if (average != null) {
assertEquals(100.0, average.value, delta)
} else {
Assert.fail("No AVERAGE stat")
}
val duration = results.computedStat(Stat.DURATION)
if (duration != null) {
assertEquals(4.0, duration.value, delta)
} else {
Assert.fail("No netDuration 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 std = results.computedStat(Stat.STANDARD_DEVIATION)
if (std != null) {
assertEquals(200.0, std.value, delta)
} else {
Assert.fail("No std 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 std100 = results.computedStat(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS)
if (std100 != null) {
assertEquals(559.01, std100.value, delta)
} else {
Assert.fail("No std100 stat")
}
}
@Test
fun testOverlappingSessions1() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val s2 = realm.createObject(Session::class.java, "2")
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)
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")
s1.startDate = sd1
s1.endDate = ed1
s2.startDate = sd2
s2.endDate = ed2
// netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
// netDuration = 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)
val 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()
val s1 = realm.createObject(Session::class.java, "1")
val s2 = realm.createObject(Session::class.java, "2")
val s3 = realm.createObject(Session::class.java, "3")
realm.insert(s1)
realm.insert(s2)
realm.insert(s3)
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") // 4h
val sd2 = sdf.parse("01/1/2019 07:00")
val ed2 = sdf.parse("01/1/2019 11:00") // 4h
val sd3 = sdf.parse("01/1/2019 03:00")
val ed3 = sdf.parse("01/1/2019 06:00") // 3h
s1.startDate = sd1
s1.endDate = ed1
s2.startDate = sd2
s2.endDate = ed2
s3.startDate = sd3
s3.endDate = ed3
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)
val 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")
}
}
private var sessions: RealmResults<Session>? = null
// @Test
fun testOverlappingSessionDeletion() {
val realm = this.mockRealm
this.sessions = realm.where(Session::class.java).findAll() // monitor session deletions
this.sessions?.addChangeListener { _, changeSet ->
val deletedSessions =
realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll()
deletedSessions.forEach { it.cleanup() }
}
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val s2 = realm.createObject(Session::class.java, "2")
val s3 = realm.createObject(Session::class.java, "3")
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.startDate = sd1
s1.endDate = ed1
s2.startDate = sd2
s2.endDate = ed2
s3.startDate = sd3
s3.endDate = ed3
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)
val 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 netDuration stat")
}
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")
}
}
@Test
fun testSessionSetCount() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java)
realm.insert(s1)
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")
s1.startDate = sd1 // timeFrame?.setDate(sd1, ed1) // netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
s1.endDate = ed1
realm.copyToRealmOrUpdate(s1)
realm.commitTransaction()
val sets = realm.where(SessionSet::class.java).findAll()
Assert.assertEquals(1, sets.size)
val set = sets.first()
if (set != null) {
Assert.assertEquals(sd1.time, set.startDate.time)
Assert.assertEquals(ed1.time, set.endDate.time)
} else {
Assert.fail("No set")
}
}
@Test
fun testSessionSetCount2() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val s2 = realm.createObject(Session::class.java, "2")
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)
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/2/2018 09:00")
val ed2 = sdf.parse("01/2/2018 10:00")
s1.startDate = sd1
s1.endDate = ed1
s2.startDate = sd2
s2.endDate = ed2
// s1.timeFrame?.setDate(sd1, ed1) // netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
// s2.timeFrame?.setDate(sd2, ed2) // netDuration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800
realm.copyToRealmOrUpdate(s1)
realm.copyToRealmOrUpdate(s2)
realm.commitTransaction()
val sets = realm.where(SessionSet::class.java).findAll()
Assert.assertEquals(2, sets.size)
// val set = sets.first()
// if (set != null) {
// Assert.assertEquals(sd1.time, set.timeFrame?.startDate?.time)
// Assert.assertEquals(ed1.time, set.timeFrame?.endDate?.time)
// } else {
// Assert.fail("No set")
// }
}
//
// @Test
// fun testDurationConversion() {
//
// val duration = 6.7555561274509826
// val longDuration = duration.toLong()
// val formatted = longDuration.toMinutes()
//
// assert(formatted == "11:00")
// }
@Test
fun testSessionRestartInOverlappingSessions() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = realm.createObject(Session::class.java, "1")
val s2 = realm.createObject(Session::class.java, "2")
val s3 = realm.createObject(Session::class.java, "3")
realm.insert(s1)
realm.insert(s2)
realm.insert(s3)
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") // 4h
val sd2 = sdf.parse("01/1/2019 07:00")
val ed2 = sdf.parse("01/1/2019 11:00") // 4h
val sd3 = sdf.parse("01/1/2019 03:00")
val ed3 = sdf.parse("01/1/2019 06:00") // 3h
s1.startDate = sd1
s1.endDate = ed1
s2.startDate = sd2
s2.endDate = ed2
s3.startDate = sd3
s3.endDate = ed3
realm.copyToRealmOrUpdate(s1)
realm.copyToRealmOrUpdate(s2)
realm.copyToRealmOrUpdate(s3)
realm.commitTransaction()
realm.executeTransaction {
s1.endDate = null
}
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
val 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(7.0, duration.value, delta)
} else {
Assert.fail("No Net result stat")
}
val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS)
if (numberOfSets != null) {
assertEquals(2, numberOfSets.value.toInt())
} else {
Assert.fail("No numberOfSets stat")
}
}
}

@ -16,7 +16,7 @@ import java.util.*
class PokerAnalyticsApplication : Application() { class PokerAnalyticsApplication : Application() {
var sessions: RealmResults<Session>? = null var sessions: RealmResults<Session>? = null
// private val listener: OrderedRealmCollectionChangeListener<RealmResults<Session>> = // private val listener: OrderedRealmCollectionChangeListener<RealmResults<Session>> =
// OrderedRealmCollectionChangeListener() { realmResults: RealmResults<Session>, changeSet: OrderedCollectionChangeSet -> // OrderedRealmCollectionChangeListener() { realmResults: RealmResults<Session>, changeSet: OrderedCollectionChangeSet ->
@ -32,162 +32,171 @@ class PokerAnalyticsApplication : Application() {
// //
// } // }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
// Realm // Realm
Realm.init(this) Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder() val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME) .name(Realm.DEFAULT_REALM_NAME)
.deleteRealmIfMigrationNeeded() .deleteRealmIfMigrationNeeded()
.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.sessions = realm.where(Session::class.java).findAll() // monitor session deletions this.sessions = realm.where(Session::class.java).findAll() // monitor session deletions
// this.sessions?.addChangeListener { _, changeSet -> // this.endedSessions?.addChangeListener { _, changeSet ->
/* /*
val deletedSessions = val deletedSessions =
realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll() realm.where(Session::class.java).`in`("id", changeSet.deletions.toTypedArray()).findAll()
deletedSessions.forEach { it.cleanup() } deletedSessions.forEach { it.cleanup() }
*/ */
// } // }
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
// Logs // Logs
Timber.plant(PokerAnalyticsLogs()) Timber.plant(PokerAnalyticsLogs())
} else { } else {
//Fabric.with(this, Crashlytics()) //Fabric.with(this, Crashlytics())
} }
if (BuildConfig.DEBUG) { this.createDefaultData()
createDefaultData() if (BuildConfig.DEBUG) {
} // this.createFakeSessions() // debug
}
}
}
/**
* Create default data /**
*/ * Create default data
private fun createDefaultData() { */
private fun createDefaultData() {
val realm = Realm.getDefaultInstance()
val realm = Realm.getDefaultInstance()
// Games
val gamesName = resources.getStringArray(R.array.game_name) // Games
val gamesShortName = resources.getStringArray(R.array.game_short_name) val gamesName = resources.getStringArray(R.array.game_name)
if (realm.where<Game>().findAll().isEmpty()) { val gamesShortName = resources.getStringArray(R.array.game_short_name)
realm.executeTransaction { if (realm.where<Game>().findAll().isEmpty()) {
for ((index, name) in gamesName.withIndex()) { realm.executeTransaction {
val game = Game() for ((index, name) in gamesName.withIndex()) {
game.id = UUID.randomUUID().toString() val game = Game()
game.name = name game.id = UUID.randomUUID().toString()
game.shortName = gamesShortName[index] game.name = name
realm.copyToRealmOrUpdate(game) game.shortName = gamesShortName[index]
} realm.copyToRealmOrUpdate(game)
} }
} }
}
// Tournament types
val tournamentFakeName = resources.getStringArray(R.array.tournament_fake_name) // Tournament types
if (realm.where<TournamentName>().findAll().isEmpty()) { val tournamentFakeName = resources.getStringArray(R.array.tournament_fake_name)
realm.executeTransaction { if (realm.where<TournamentName>().findAll().isEmpty()) {
for (type in tournamentFakeName) { realm.executeTransaction {
val tournament = TournamentName() for (type in tournamentFakeName) {
tournament.id = UUID.randomUUID().toString() val tournament = TournamentName()
tournament.name = type tournament.id = UUID.randomUUID().toString()
realm.copyToRealmOrUpdate(tournament) tournament.name = type
} realm.copyToRealmOrUpdate(tournament)
} }
} }
}
// Currency
if (realm.where<Currency>().findAll().isEmpty()) { // Currency
realm.executeTransaction { if (realm.where<Currency>().findAll().isEmpty()) {
val localeCurrency = java.util.Currency.getInstance(Locale.getDefault()) realm.executeTransaction {
val defaultCurrency = Currency() val localeCurrency = java.util.Currency.getInstance(Locale.getDefault())
defaultCurrency.code = localeCurrency.currencyCode val defaultCurrency = Currency()
realm.copyToRealmOrUpdate(defaultCurrency) defaultCurrency.code = localeCurrency.currencyCode
} realm.copyToRealmOrUpdate(defaultCurrency)
} }
}
// Bankroll
if (realm.where<Bankroll>().findAll().isEmpty()) { // Bankroll
realm.executeTransaction { if (realm.where<Bankroll>().findAll().isEmpty()) {
val localeCurrency = java.util.Currency.getInstance(Preferences.getCurrencyLocale(this)) realm.executeTransaction {
val bankroll = Bankroll() val localeCurrency = java.util.Currency.getInstance(Preferences.getCurrencyLocale(this))
bankroll.name = "Live" val bankroll = Bankroll()
bankroll.live = true bankroll.name = "Live"
bankroll.currency = realm.where<Currency>().equalTo("code", localeCurrency.currencyCode).findFirst() bankroll.live = true
realm.copyToRealmOrUpdate(bankroll) bankroll.currency = realm.where<Currency>().equalTo("code", localeCurrency.currencyCode).findFirst()
} realm.copyToRealmOrUpdate(bankroll)
} }
}
// Test sessions
if (BuildConfig.DEBUG) { realm.close()
val sessions = realm.where<Session>().findAll()
if (sessions.size < 10) { }
for (index in 0..50) {
private fun createFakeSessions() {
realm.executeTransaction {
val session = Session.newInstance(realm, false) val realm = Realm.getDefaultInstance()
// Test endedSessions
val sessions = realm.where<Session>().findAll()
if (sessions.size < 10) {
for (index in 0..50) {
realm.executeTransaction {
val session = Session.newInstance(realm, false)
// session.id = UUID.randomUUID().toString() // session.id = UUID.randomUUID().toString()
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
calendar.set( calendar.set(
(2017..2018).random(), (2017..2018).random(),
(0..11).random(), (0..11).random(),
(0..28).random(), (0..28).random(),
(0..23).random(), (0..23).random(),
(0..59).random() (0..59).random()
) )
val startDate = calendar.time val startDate = calendar.time
calendar.add(Calendar.HOUR_OF_DAY, (2..12).random()) calendar.add(Calendar.HOUR_OF_DAY, (2..12).random())
calendar.add(Calendar.MINUTE, (0..59).random()) calendar.add(Calendar.MINUTE, (0..59).random())
val endDate = calendar.time val endDate = calendar.time
// val timeFrame = TimeFrame() // val timeFrame = TimeFrame()
session.timeFrame?.let { session.startDate = startDate
// it.startDate = startDate session.endDate = endDate
// it.endDate = endDate
it.setDate(startDate, endDate)
}
// session.timeFrame = timeFrame // session.timeFrame?.let {
session.creationDate = startDate // // it.startDate = startDate
//// it.endDate = endDate
// it.setDate(startDate, endDate)
// }
session.limit = Limit.values().random().ordinal // session.timeFrame = timeFrame
session.game = realm.where<Game>().findAll().random() session.creationDate = startDate
session.result?.let { result -> session.limit = Limit.values().random().ordinal
result.buyin = arrayListOf(100, 200, 300, 500, 1000, 2000).random().toDouble() session.game = realm.where<Game>().findAll().random()
result.netResult = arrayListOf(
-2500.0, -2000.0, -1500.0, -1000.0, -500.0, 200.0, 1000.0, 1500.0,
2500.0
).random()
}
realm.copyToRealmOrUpdate(session) session.result?.let { result ->
} result.buyin = arrayListOf(100, 200, 300, 500, 1000, 2000).random().toDouble()
} result.netResult = arrayListOf(
} -2500.0, -2000.0, -1500.0, -1000.0, -500.0, 200.0, 1000.0, 1500.0,
} 2500.0
).random()
}
realm.close() realm.copyToRealmOrUpdate(session)
}
}
}
val sets = realm.where(SessionSet::class.java).findAll()
// val sets = realm.where(SessionSet::class.java).findAll()
//
// Timber.d("sets = ${sets.size}") // Timber.d("sets = ${sets.size}")
// //
// sets.forEach { set -> // sets.forEach { set ->
// Timber.d("set sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}") // Timber.d("set sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}")
// } // }
realm.close()
}
}
} }

@ -64,7 +64,7 @@ class Calculator {
*/ */
fun computeGroups(groups: List<SessionGroup>, options: Options): List<ComputedResults> { fun computeGroups(groups: List<SessionGroup>, options: Options): List<ComputedResults> {
var computedResults: MutableList<ComputedResults> = mutableListOf() val computedResults = mutableListOf<ComputedResults>()
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)
@ -90,9 +90,9 @@ class Calculator {
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
var sessionSets = sessionGroup.sessions.mapNotNull { it.sessionSet }.toHashSet() val sessionSets = sessionGroup.sessions.mapNotNull { it.sessionSet }.toHashSet()
var results: ComputedResults = ComputedResults(sessionGroup) val results: ComputedResults = ComputedResults(sessionGroup)
var sum: Double = 0.0 var sum: Double = 0.0
var totalHands: Double = 0.0 var totalHands: Double = 0.0
@ -106,7 +106,7 @@ class Calculator {
sessions.forEach { s -> sessions.forEach { s ->
index++; index++;
sum += s.value sum += s.ratedNet
bbSum += s.bbNetResult bbSum += s.bbNetResult
bbSessionCount += s.bigBlindSessionCount bbSessionCount += s.bigBlindSessionCount
if (s.value >= 0) { if (s.value >= 0) {
@ -147,7 +147,7 @@ class Calculator {
sessionSets.forEach { sessionSet -> sessionSets.forEach { sessionSet ->
gIndex++ gIndex++
duration += sessionSet.hourlyDuration duration += sessionSet.hourlyDuration
gSum += sessionSet.netResult gSum += sessionSet.ratedNet
gTotalHands += sessionSet.estimatedHands gTotalHands += sessionSet.estimatedHands
gBBSum += sessionSet.bbNetResult gBBSum += sessionSet.bbNetResult
@ -159,7 +159,7 @@ class Calculator {
results.addEvolutionValue(gSum / duration, duration, HOURLY_RATE) results.addEvolutionValue(gSum / duration, duration, HOURLY_RATE)
results.addEvolutionValue(hourlyRate, duration, HOURLY_RATE) results.addEvolutionValue(hourlyRate, duration, HOURLY_RATE)
results.addEvolutionValue(gIndex.toDouble(), duration, NUMBER_OF_SETS) results.addEvolutionValue(gIndex.toDouble(), duration, NUMBER_OF_SETS)
results.addEvolutionValue(sessionSet.duration.toDouble(), duration, DURATION) results.addEvolutionValue(sessionSet.netDuration.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)
} }
@ -175,19 +175,24 @@ class Calculator {
var average = 0.0 var average = 0.0
if (sessions.size > 0) { if (sessions.size > 0) {
average = sum / sessions.size.toDouble() average = sum / sessions.size.toDouble()
val avgDuration = duration / sessions.size
val winRatio = (winningSessionCount / sessions.size).toDouble() val winRatio = (winningSessionCount / sessions.size).toDouble()
val avgBuyin = totalBuyin / sessions.size val avgBuyin = totalBuyin / sessions.size
results.addStats(setOf( results.addStats(setOf(
ComputedStat(AVERAGE, average), ComputedStat(AVERAGE, average),
ComputedStat(AVERAGE_DURATION, avgDuration),
ComputedStat(WIN_RATIO, winRatio), ComputedStat(WIN_RATIO, winRatio),
ComputedStat(AVERAGE_BUYIN, avgBuyin) ComputedStat(AVERAGE_BUYIN, avgBuyin)
)) ))
} }
// Create stats if (sessionSets.size > 0) {
val avgDuration = duration / sessionSets.size
results.addStats(setOf(
ComputedStat(AVERAGE_DURATION, avgDuration)
))
}
// Create stats
results.addStats(setOf( results.addStats(setOf(
ComputedStat(NETRESULT, sum), ComputedStat(NETRESULT, sum),
ComputedStat(HOURLY_RATE, hourlyRate), ComputedStat(HOURLY_RATE, hourlyRate),
@ -219,7 +224,7 @@ class Calculator {
var stdSum: Double = 0.0 var stdSum: Double = 0.0
var stdBBper100HandsSum: Double = 0.0 var stdBBper100HandsSum: Double = 0.0
sessions.forEach { session -> sessions.forEach { session ->
stdSum += Math.pow(session.value - average, 2.0) stdSum += Math.pow(session.ratedNet - average, 2.0)
stdBBper100HandsSum += Math.pow(session.bbPer100Hands - bbPer100Hands, 2.0) stdBBper100HandsSum += Math.pow(session.bbPer100Hands - bbPer100Hands, 2.0)
} }
val standardDeviation: Double = Math.sqrt(stdSum / sessions.size) val standardDeviation: Double = Math.sqrt(stdSum / sessions.size)

@ -19,7 +19,7 @@ interface SessionInterface : Summable {
var bigBlindSessionCount: Int // 0 or 1 var bigBlindSessionCount: Int // 0 or 1
var buyin: Double var buyin: Double
var bbPer100Hands: Double var bbPer100Hands: Double
var ratedNet: Double
} }
/** /**
@ -33,7 +33,7 @@ class SessionGroup(name: String, sessions: List<SessionInterface>, stats: List<S
var name: String = name var name: String = name
/** /**
* The list of sessions to compute * The list of endedSessions to compute
*/ */
var sessions: List<SessionInterface> = sessions var sessions: List<SessionInterface> = sessions

@ -37,9 +37,9 @@ enum class Stat : RowRepresentable {
* Returns whether the stat evolution values requires a distribution sorting * Returns whether the stat evolution values requires a distribution sorting
*/ */
fun hasDistributionSorting() : Boolean { fun hasDistributionSorting() : Boolean {
when (this) { return 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 -> true
else -> return false else -> false
} }
} }
@ -146,8 +146,8 @@ class ComputedStat(stat: Stat, value: Double) {
return TextFormat(value.formattedHourlyDuration()) return TextFormat(value.formattedHourlyDuration())
} // red/green percentages } // red/green percentages
Stat.WIN_RATIO, Stat.ROI -> { Stat.WIN_RATIO, Stat.ROI -> {
val color = if (value >= this.stat.threshold) R.color.green else R.color.red val color = if (value * 100 >= this.stat.threshold) R.color.green else R.color.red
return TextFormat("${value.formatted()}%", color) return TextFormat("${(value * 100).formatted()}%", color)
} // white amounts } // white amounts
Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY, Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY,
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {

@ -6,4 +6,8 @@ class ModelException(message: String) : Exception(message) {
class FormattingException(message: String) : Exception(message) { class FormattingException(message: String) : Exception(message) {
}
class TypeException(message: String) : Exception(message) {
} }

@ -8,8 +8,15 @@ enum class SessionState {
PLANNED, PLANNED,
STARTED, STARTED,
PAUSED, PAUSED,
FINISHED, FINISHED;
INVALID;
var hasStarted: Boolean = false
get() {
return when (this) {
STARTED, PAUSED, FINISHED -> true
else -> false
}
}
} }
/** /**
@ -18,24 +25,22 @@ enum class SessionState {
*/ */
fun Session.getState(): SessionState { fun Session.getState(): SessionState {
if (timeFrame == null) { // if (timeFrame == null) {
// return SessionState.PENDING
// }
val start = this.startDate
if (start == null) {
return SessionState.PENDING return SessionState.PENDING
} } else {
if (start > Date()) {
val endDate = timeFrame?.endDate return SessionState.PLANNED
timeFrame?.let {sessionTimeFrame -> } else if (this.endDate != null) {
timeFrame?.startDate?.let {startDate -> return SessionState.FINISHED
if (startDate > Date()) { } else if (this.pauseDate != null) {
return SessionState.PLANNED return SessionState.PAUSED
} else if (endDate != null) { } else {
return SessionState.FINISHED return SessionState.STARTED
} else if (sessionTimeFrame.paused) {
return SessionState.PAUSED
} else {
return SessionState.STARTED
}
} }
} }
return SessionState.INVALID
} }

@ -0,0 +1,33 @@
package net.pokeranalytics.android.model.interfaces
import java.util.*
interface Timed {
fun startDate() : Date?
fun endDate() : Date
var breakDuration: Long
var pauseDate: Date?
var netDuration: Long
/**
* Computes the net netDuration of the session
*/
fun computeNetDuration() {
this.startDate()?.let { start ->
this.netDuration = this.endDate().time - start.time - this.breakDuration
} ?: run {
this.netDuration = 0L
}
}
var hourlyDuration: Double
get() = this.netDuration / 3600000.0
set(value) = TODO()
}

@ -37,8 +37,6 @@ open class Bankroll(name: String = "") : RealmObject(), Savable,
// The currency of the bankroll // The currency of the bankroll
var currency: Currency? = null var currency: Currency? = null
// @todo rate management
override fun getDisplayName(): String { override fun getDisplayName(): String {
return this.name return this.name
} }
@ -65,7 +63,7 @@ open class Bankroll(name: String = "") : RealmObject(), Savable,
override fun boolForRow(row: RowRepresentable): Boolean { override fun boolForRow(row: RowRepresentable): Boolean {
when (row) { when (row) {
BankrollRow.LIVE -> return this.live BankrollRow.LIVE -> return !this.live
else -> return super.boolForRow(row) else -> return super.boolForRow(row)
} }
} }
@ -86,8 +84,9 @@ open class Bankroll(name: String = "") : RealmObject(), Savable,
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? ?: ""
BankrollRow.LIVE -> this.live = value as Boolean? ?: false BankrollRow.LIVE -> {
this.live = if (value is Boolean) !value else false
}
} }
} }

@ -2,6 +2,7 @@ package net.pokeranalytics.android.model.realm
import com.google.android.libraries.places.api.model.Place import com.google.android.libraries.places.api.model.Place
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.model.interfaces.Savable import net.pokeranalytics.android.model.interfaces.Savable
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -30,6 +31,9 @@ open class Location : RealmObject(), Savable, StaticRowRepresentableDataSource,
// the latitude of the location // the latitude of the location
var latitude: Double? = null var latitude: Double? = null
@Ignore
var isLookingForPlaces = false
override fun getDisplayName(): String { override fun getDisplayName(): String {
return this.name return this.name
} }
@ -53,6 +57,13 @@ open class Location : RealmObject(), Savable, StaticRowRepresentableDataSource,
} }
} }
override fun boolForRow(row: RowRepresentable): Boolean {
return when(row) {
LocationRow.LOCATE_ME -> return isLookingForPlaces
else -> super.boolForRow(row)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor> { override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor> {
val data = java.util.ArrayList<RowRepresentableEditDescriptor>() val data = java.util.ArrayList<RowRepresentableEditDescriptor>()
when (row) { when (row) {
@ -78,6 +89,7 @@ open class Location : RealmObject(), Savable, StaticRowRepresentableDataSource,
SimpleRow.NAME -> this.name = value as String? ?: "" SimpleRow.NAME -> this.name = value as String? ?: ""
LocationRow.ADDRESS -> this.address = value as String? ?: "" LocationRow.ADDRESS -> this.address = value as String? ?: ""
LocationRow.LOCATE_ME -> { LocationRow.LOCATE_ME -> {
isLookingForPlaces = false
if (value is Place) { if (value is Place) {
setPlace(value) setPlace(value)
} }

@ -12,6 +12,7 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedStat import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.calculus.SessionInterface import net.pokeranalytics.android.calculus.SessionInterface
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.TableSize import net.pokeranalytics.android.model.TableSize
@ -19,7 +20,10 @@ import net.pokeranalytics.android.model.TournamentKind
import net.pokeranalytics.android.model.extensions.SessionState import net.pokeranalytics.android.model.extensions.SessionState
import net.pokeranalytics.android.model.extensions.getState import net.pokeranalytics.android.model.extensions.getState
import net.pokeranalytics.android.model.interfaces.Savable import net.pokeranalytics.android.model.interfaces.Savable
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
@ -28,10 +32,11 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRowRepresent
import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.* import net.pokeranalytics.android.util.*
import java.util.* import java.util.*
import java.util.Currency
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
open class Session : RealmObject(), SessionInterface, Savable, open class Session : RealmObject(), SessionInterface, Savable, StaticRowRepresentableDataSource, RowRepresentable,
StaticRowRepresentableDataSource, RowRepresentable { Timed {
enum class Type { enum class Type {
CASH_GAME, CASH_GAME,
@ -59,14 +64,59 @@ open class Session : RealmObject(), SessionInterface, Savable,
// The result of the main user // The result of the main user
var result: Result? = null var result: Result? = null
// The time frame of the Session, i.e. the start & end date // Timed interface
var timeFrame: TimeFrame? = null
/**
* The start date of the session
*/
var startDate: Date? = null
set(value) { set(value) {
field = value field = value
// value?.let { it.notifySessionDateChange(this) } this.computeNetDuration()
// nullifies endate when setting the start date after the end date
if (value != null && this.endDate != null && value.after(this.endDate)) {
this.endDate = null
}
this.dateChanged()
}
/**
* the end date of the session
*/
var endDate: Date? = null
set(value) {
field = value
this.computeNetDuration()
this.dateChanged()
}
/**
* The break duration of the session
*/
override var breakDuration: Long = 0L
set(value) {
field = value
this.computeNetDuration()
} }
// The time frame sessionGroup, which can contain multiple sessions /**
* the net duration of the session, automatically calculated
*/
override var netDuration: Long = 0L
/**
* The start date of the break
*/
override var pauseDate: Date? = null
// The time frame of the Session, i.e. the start & end date
// var timeFrame: TimeFrame? = null
// set(value) {
// field = value
// value?.let { it.notifySessionDateChange(this) }
// }
// The time frame sessionGroup, which can contain multiple endedSessions
override var sessionSet: SessionSet? = null override var sessionSet: SessionSet? = null
// the date of creation of the app // the date of creation of the app
@ -124,20 +174,110 @@ open class Session : RealmObject(), SessionInterface, Savable,
// The features of the tournament, like Knockout, Shootout, Turbo... // The features of the tournament, like Knockout, Shootout, Turbo...
var tournamentFeatures: RealmList<TournamentFeature> = RealmList() var tournamentFeatures: RealmList<TournamentFeature> = RealmList()
/**
* Manages impacts on SessionSets
* Should be called when the start / end date are changed
*/
private fun dateChanged() {
if (this.endDate != null) {
SessionSetManager.updateTimeline(this)
} else if (this.sessionSet != null) {
SessionSetManager.removeFromTimeline(this)
}
}
/**
* Returns a non-null date for the session
*/
override fun endDate(): Date {
return this.endDate ?: Date()
}
/**
* Returns the nullable date
*/
override fun startDate(): Date? {
return this.startDate
}
/** /**
* Return if this session is a tournament * Return if this session is a tournament
*/ */
fun isTournament(): Boolean { fun isTournament(): Boolean {
return type == Type.TOURNAMENT.ordinal return this.type == Type.TOURNAMENT.ordinal
} }
/** /**
* Return if this session is a cash game * Return if this session is a cash game
*/ */
fun isCashGame(): Boolean { fun isCashGame(): Boolean {
return type == Type.CASH_GAME.ordinal return this.type == Type.CASH_GAME.ordinal
} }
// Stats
@Ignore // SessionInterface value
override var value: Double = 0.0
get() {
return this.result?.net ?: 0.0
}
@Ignore
override var estimatedHands: Double = 25.0 * this.hourlyDuration
@Ignore
override var bbNetResult: Double = 0.0
get() {
this.cgBigBlind?.let { bb ->
this.result?.let { result ->
return result.net / bb
}
}
return 0.0
}
@Ignore
override var bbPer100Hands: Double = 0.0
get() {
return this.bbNetResult / this.estimatedHands * 100.0
}
@Ignore
override var bigBlindSessionCount: Int = if (this.cgBigBlind != null) 1 else 0
@Ignore
override var buyin: Double = 0.0
get() {
this.result?.let { result ->
result.buyin?.let {
return it
}
}
return 0.0
}
val hourlyRate: Double
get() {
this.result?.let { result ->
return result.net / this.hourlyDuration
}
throw ModelException("Session should have an existing Result relationship")
}
override var ratedNet: Double = 0.0
get() {
this.result?.net?.let { net ->
this.bankroll?.currency?.rate?.let { rate ->
return net * rate
} ?: run {
return net
}
}
return 0.0
}
// States
/** /**
* Start or continue a session * Start or continue a session
*/ */
@ -145,16 +285,22 @@ open class Session : RealmObject(), SessionInterface, Savable,
realm.executeTransaction { realm.executeTransaction {
when (getState()) { when (getState()) {
SessionState.PENDING, SessionState.PLANNED -> { SessionState.PENDING, SessionState.PLANNED -> {
val sessionTimeFrame = this.timeFrame ?: realm.createObject(TimeFrame::class.java) this.startDate = Date()
sessionTimeFrame.setStart(Date()) if (this.tournamentEntryFee != null) {
// sessionTimeFrame.setDate(Date(), null) this.result?.buyin = this.tournamentEntryFee
this.timeFrame = sessionTimeFrame }
} }
SessionState.PAUSED -> { SessionState.PAUSED -> {
this.timeFrame?.paused = false val pauseDate = this.pauseDate
this.timeFrame?.pauseDate = null if (pauseDate != null) {
this.breakDuration += Date().time - pauseDate.time
} else {
throw IllegalStateException("When resuming, the pause date must be set")
}
this.pauseDate = null
} }
else -> { else -> {
throw IllegalStateException("unmanaged session state")
} }
} }
} }
@ -167,9 +313,9 @@ open class Session : RealmObject(), SessionInterface, Savable,
realm.executeTransaction { realm.executeTransaction {
when (getState()) { when (getState()) {
SessionState.STARTED -> { SessionState.STARTED -> {
this.timeFrame?.paused = true this.pauseDate = Date()
this.timeFrame?.pauseDate = Date()
} }
else -> throw IllegalStateException("Pausing a session in an unmanaged state")
} }
} }
} }
@ -181,10 +327,7 @@ open class Session : RealmObject(), SessionInterface, Savable,
realm.executeTransaction { realm.executeTransaction {
when (getState()) { when (getState()) {
SessionState.STARTED, SessionState.PAUSED -> { SessionState.STARTED, SessionState.PAUSED -> {
this.timeFrame?.paused = false this.end()
this.timeFrame?.pauseDate = null
this.timeFrame?.setEnd(Date())
// this.timeFrame?.setDate(null, Date())
} }
else -> throw Exception("Stopping session in unmanaged state") else -> throw Exception("Stopping session in unmanaged state")
} }
@ -196,27 +339,43 @@ open class Session : RealmObject(), SessionInterface, Savable,
*/ */
fun restart() { fun restart() {
realm.executeTransaction { realm.executeTransaction {
this.timeFrame?.paused = false // this.timeFrame?.paused = false
this.timeFrame?.pauseDate = null this.pauseDate = null
this.timeFrame?.setDate(Date(), null) this.startDate = Date() // timeFrame?.setDate(Date(), null)
this.timeFrame?.breakDuration = 0L this.breakDuration = 0L
} }
} }
/**
* Utility method to cleanly end a session
*/
private fun end() {
this.pauseDate = null
if (this.endDate == null) {
this.endDate = Date()
}
}
// Formatters
/** /**
* Return the netDuration of the current session * Return the netDuration of the current session
*/ */
fun getDuration(): String { fun getFormattedDuration(): String {
val startDate = timeFrame?.startDate ?: Date() this.startDate?.let { start ->
val enDate = timeFrame?.endDate ?: Date() val netDuration = this.endDate().time - start.time - this.breakDuration
return startDate.getDuration(enDate) return netDuration.toMinutes()
}
return NULL_TEXT
} }
/** /**
* Return the formatted blinds * Return the formatted blinds
*/ */
fun getBlinds(): String { fun getBlinds(): String {
return if (cgSmallBlind == null) NULL_TEXT else "${cgSmallBlind?.toCurrency()}/${cgBigBlind?.round()}" val currencyCode = bankroll?.currency?.code ?: Currency.getInstance(Locale.getDefault()).currencyCode
val currencySymbol = Currency.getInstance(currencyCode).symbol
return if (cgSmallBlind == null) NULL_TEXT else "$currencySymbol ${cgSmallBlind?.formatted()}/${cgBigBlind?.round()}"
} }
/** /**
@ -233,6 +392,7 @@ open class Session : RealmObject(), SessionInterface, Savable,
return if (gameTitle.isNotBlank()) gameTitle else NULL_TEXT return if (gameTitle.isNotBlank()) gameTitle else NULL_TEXT
} }
// LifeCycle
/** /**
* Delete the object from realm * Delete the object from realm
@ -252,62 +412,15 @@ open class Session : RealmObject(), SessionInterface, Savable,
this.sessionSet?.let { set -> this.sessionSet?.let { set ->
// get all sessions part of the deleted session set
val sessionsFromSet = set.sessions
// cleanup unnecessary related objects // cleanup unnecessary related objects
set.deleteFromRealm() set.deleteFromRealm()
this.timeFrame?.deleteFromRealm()
this.result?.deleteFromRealm() this.result?.deleteFromRealm()
// make sessions recreate/find their session set // Updates the timeline
sessionsFromSet?.let { sessions -> SessionSetManager.removeFromTimeline(this)
sessions.forEach { session ->
session.timeFrame?.notifySessionDateChange(session)
}
}
}
}
@Ignore // SessionInterface value
override var value: Double = 0.0
get() {
return this.result?.net ?: 0.0
}
@Ignore
override var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration ?: 0.0)
@Ignore
override var bbNetResult: Double = 0.0
get() {
this.cgBigBlind?.let { bb ->
this.result?.let { result ->
return result.net / bb
}
}
return 0.0
}
@Ignore
override var bbPer100Hands: Double = 0.0
get() {
return this.bbNetResult / this.estimatedHands * 100.0
}
@Ignore
override var bigBlindSessionCount: Int = if (this.cgBigBlind != null) 1 else 0
@Ignore
override var buyin: Double = 0.0
get() {
this.result?.let {
it.buyin?.let {
return it
}
}
return 0.0
} }
}
@Ignore @Ignore
override val viewType: Int = RowViewType.ROW_SESSION.ordinal override val viewType: Int = RowViewType.ROW_SESSION.ordinal
@ -329,7 +442,7 @@ open class Session : RealmObject(), SessionInterface, Savable,
rows.add( rows.add(
HeaderRowRepresentable( HeaderRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getDuration(), title = getFormattedDuration(),
computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0) computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0)
) )
) )
@ -349,7 +462,7 @@ open class Session : RealmObject(), SessionInterface, Savable,
rows.add( rows.add(
HeaderRowRepresentable( HeaderRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getDuration(), title = getFormattedDuration(),
computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0) computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0)
) )
) )
@ -357,7 +470,7 @@ open class Session : RealmObject(), SessionInterface, Savable,
HeaderRowRepresentable( HeaderRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT, RowViewType.HEADER_TITLE_AMOUNT,
resId = R.string.hour_rate_without_pauses, resId = R.string.hour_rate_without_pauses,
computedStat = ComputedStat(Stat.NETRESULT, this.sessionSet?.hourlyRate ?: 0.0) computedStat = ComputedStat(Stat.HOURLY_RATE, this.hourlyRate)
) )
) )
@ -393,19 +506,17 @@ open class Session : RealmObject(), SessionInterface, Savable,
return when (row) { return when (row) {
SessionRow.BANKROLL -> bankroll?.name ?: NULL_TEXT SessionRow.BANKROLL -> bankroll?.name ?: NULL_TEXT
SessionRow.BLINDS -> getBlinds() SessionRow.BLINDS -> getBlinds()
SessionRow.BREAK_TIME -> timeFrame?.breakDuration?.toMinutes() ?: NULL_TEXT SessionRow.BREAK_TIME -> if (this.breakDuration > 0.0) this.breakDuration.toMinutes() else NULL_TEXT
SessionRow.BUY_IN -> buyin.toCurrency() SessionRow.BUY_IN -> this.result?.buyin?.toCurrency() ?: NULL_TEXT
SessionRow.CASHED_OUT, SessionRow.PRIZE, SessionRow.NET_RESULT -> result?.cashout?.toCurrency() ?: NULL_TEXT SessionRow.CASHED_OUT, SessionRow.PRIZE, SessionRow.NET_RESULT -> this.result?.cashout?.toCurrency() ?: NULL_TEXT
SessionRow.COMMENT -> if (comment.isNotEmpty()) comment else NULL_TEXT SessionRow.COMMENT -> if (this.comment.isNotEmpty()) this.comment else NULL_TEXT
SessionRow.END_DATE -> if (timeFrame != null) timeFrame?.endDate?.shortDateTime() SessionRow.END_DATE -> this.endDate?.shortDateTime() ?: NULL_TEXT
?: NULL_TEXT else NULL_TEXT
SessionRow.GAME -> getGameTitle() SessionRow.GAME -> getGameTitle()
SessionRow.INITIAL_BUY_IN -> tournamentEntryFee?.toCurrency() ?: NULL_TEXT SessionRow.INITIAL_BUY_IN -> tournamentEntryFee?.toCurrency() ?: NULL_TEXT
SessionRow.LOCATION -> location?.name ?: NULL_TEXT SessionRow.LOCATION -> location?.name ?: NULL_TEXT
SessionRow.PLAYERS -> tournamentNumberOfPlayers?.toString() ?: NULL_TEXT SessionRow.PLAYERS -> tournamentNumberOfPlayers?.toString() ?: NULL_TEXT
SessionRow.POSITION -> result?.tournamentFinalPosition?.toString() ?: NULL_TEXT SessionRow.POSITION -> result?.tournamentFinalPosition?.toString() ?: NULL_TEXT
SessionRow.START_DATE -> if (timeFrame != null) timeFrame?.startDate?.shortDateTime() SessionRow.START_DATE -> this.startDate?.shortDateTime() ?: NULL_TEXT
?: NULL_TEXT else NULL_TEXT
SessionRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: NULL_TEXT SessionRow.TABLE_SIZE -> this.tableSize?.let { TableSize(it).localizedTitle(context) } ?: NULL_TEXT
SessionRow.TIPS -> result?.tips?.toCurrency() ?: NULL_TEXT SessionRow.TIPS -> result?.tips?.toCurrency() ?: NULL_TEXT
SessionRow.TOURNAMENT_KIND -> this.tournamentKind?.let { SessionRow.TOURNAMENT_KIND -> this.tournamentKind?.let {
@ -419,8 +530,7 @@ open class Session : RealmObject(), SessionInterface, Savable,
} }
} }
SessionRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT SessionRow.TOURNAMENT_NAME -> tournamentName?.name ?: NULL_TEXT
else -> NULL_TEXT else -> throw UnmanagedRowRepresentableException("Unmanaged row = ${row.getDisplayName()}")
} }
} }
@ -468,12 +578,12 @@ open class Session : RealmObject(), SessionInterface, Savable,
} }
SessionRow.BUY_IN -> { SessionRow.BUY_IN -> {
// Add first & second buttons values, current value & set the 2 edit texts // Add first & second buttons values, current value & set the 2 edit texts
if (cgBigBlind != null) { if (this.cgBigBlind != null) {
data.add(RowRepresentableEditDescriptor(100.0 * (cgBigBlind ?: 0.0))) data.add(RowRepresentableEditDescriptor(100.0 * (this.cgBigBlind ?: 0.0)))
data.add(RowRepresentableEditDescriptor(200.0 * (cgBigBlind ?: 0.0))) data.add(RowRepresentableEditDescriptor(200.0 * (this.cgBigBlind ?: 0.0)))
} else if (tournamentEntryFee != null) { } else if (this.tournamentEntryFee != null) {
data.add(RowRepresentableEditDescriptor((tournamentEntryFee ?: 0.0) * 1.0)) data.add(RowRepresentableEditDescriptor((this.tournamentEntryFee ?: 0.0) * 1.0))
data.add(RowRepresentableEditDescriptor((tournamentEntryFee ?: 0.0) * 2.0)) data.add(RowRepresentableEditDescriptor((this.tournamentEntryFee ?: 0.0) * 2.0))
} else { } else {
data.add(RowRepresentableEditDescriptor(0)) data.add(RowRepresentableEditDescriptor(0))
data.add(RowRepresentableEditDescriptor(0)) data.add(RowRepresentableEditDescriptor(0))
@ -593,10 +703,7 @@ open class Session : RealmObject(), SessionInterface, Savable,
cgBigBlind = null cgBigBlind = null
} }
SessionRow.BREAK_TIME -> { SessionRow.BREAK_TIME -> {
val timeFrameToUpdate = this.breakDuration = if (value != null) (value as String).toLong() * 60 * 1000 else 0
if (timeFrame != null) timeFrame as TimeFrame else realm.createObject(TimeFrame::class.java)
timeFrameToUpdate.breakDuration = if (value != null) (value as String).toLong() * 60 * 1000 else 0
timeFrame = timeFrameToUpdate
} }
SessionRow.BUY_IN -> { SessionRow.BUY_IN -> {
val localResult = if (result != null) result as Result else realm.createObject(Result::class.java) val localResult = if (result != null) result as Result else realm.createObject(Result::class.java)
@ -610,10 +717,7 @@ open class Session : RealmObject(), SessionInterface, Savable,
localResult.cashout = null localResult.cashout = null
} else { } else {
localResult.cashout = (value as String).toDouble() localResult.cashout = (value as String).toDouble()
val timeFrameToUpdate = this.end()
if (timeFrame != null) timeFrame as TimeFrame else realm.createObject(TimeFrame::class.java)
timeFrameToUpdate.setEnd(Date())
timeFrame = timeFrameToUpdate
} }
result = localResult result = localResult
@ -621,11 +725,8 @@ open class Session : RealmObject(), SessionInterface, Savable,
SessionRow.COMMENT -> comment = value as String? ?: "" SessionRow.COMMENT -> comment = value as String? ?: ""
SessionRow.END_DATE -> if (value is Date?) { SessionRow.END_DATE -> if (value is Date?) {
val timeFrameToUpdate = this.endDate = value
if (timeFrame != null) timeFrame as TimeFrame else realm.createObject(TimeFrame::class.java)
// timeFrameToUpdate.setDate(null, value)
timeFrameToUpdate.setEnd(value)
timeFrame = timeFrameToUpdate
} }
SessionRow.GAME -> { SessionRow.GAME -> {
if (value is ArrayList<*>) { if (value is ArrayList<*>) {
@ -652,16 +753,8 @@ open class Session : RealmObject(), SessionInterface, Savable,
localResult.tournamentFinalPosition = if (value == null) null else (value as String).toInt() localResult.tournamentFinalPosition = if (value == null) null else (value as String).toInt()
result = localResult result = localResult
} }
SessionRow.START_DATE -> if (value is Date?) { SessionRow.START_DATE -> if (value is Date) {
if (value == null) { this.startDate = value
timeFrame = null
} else {
val timeFrameToUpdate =
if (timeFrame != null) timeFrame as TimeFrame else realm.createObject(TimeFrame::class.java)
timeFrameToUpdate.setStart(value)
// timeFrameToUpdate.setDate(value, null)
timeFrame = timeFrameToUpdate
}
} }
SessionRow.TABLE_SIZE -> tableSize = value as Int? SessionRow.TABLE_SIZE -> tableSize = value as Int?
SessionRow.TIPS -> { SessionRow.TIPS -> {

@ -5,15 +5,53 @@ import io.realm.RealmObject
import io.realm.RealmResults 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.model.interfaces.Timed
import java.util.*
open class SessionSet() : RealmObject() { open class SessionSet : RealmObject(), Timed {
var startDate: Date = Date()
set(value) {
field = value
this.computeNetDuration()
}
var endDate: Date = Date()
set(value) {
field = value
this.computeNetDuration()
}
override fun startDate(): Date? {
return this.startDate
}
override fun endDate(): Date {
return this.endDate
}
override var breakDuration: Long = 0L
set(value) {
field = value
this.computeNetDuration()
}
/**
* The start date of the break
*/
override var pauseDate: Date? = null
/**
* the net duration of the set
*/
override var netDuration: Long = 0L
companion object { companion object {
fun newInstance(realm: Realm) : SessionSet { fun newInstance(realm: Realm) : SessionSet {
val sessionSet: SessionSet = realm.createObject(SessionSet::class.java) val sessionSet: SessionSet = realm.createObject(SessionSet::class.java)
sessionSet.timeFrame = realm.createObject(TimeFrame::class.java) // sessionSet.timeFrame = realm.createObject(TimeFrame::class.java)
return realm.copyToRealm(sessionSet) return realm.copyToRealm(sessionSet)
} }
@ -22,46 +60,25 @@ 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 sessions associated with this set * The list of endedSessions associated with this set
*/ */
@LinkingObjects("sessionSet") @LinkingObjects("sessionSet")
val sessions: RealmResults<Session>? = null val sessions: RealmResults<Session>? = null
@Ignore // a netDuration shortcut
var duration: Long = 0L
get() {
return this.timeFrame?.netDuration ?: 0L
}
@Ignore // a netDuration in hour
var hourlyDuration: Double = 0.0
get() {
return this.timeFrame?.hourlyDuration ?: 0.0
}
@Ignore // a netResult shortcut
var netResult: Double = 0.0
get () {
return this.sessions?.sumByDouble { it.value } ?: 0.0
}
@Ignore // a netDuration shortcut
var hourlyRate: Double = 0.0
get () {
return this.netResult / this.hourlyDuration
}
@Ignore @Ignore
var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0) val ratedNet: Double = this.sessions?.sumByDouble { it.ratedNet } ?: 0.0
@Ignore @Ignore
var bbNetResult: Double = 0.0 val hourlyRate: Double = this.ratedNet / this.hourlyDuration
@Ignore
val estimatedHands: Double = 25.0 * this.hourlyDuration
@Ignore
var bbNetResult: Double = this.sessions?.sumByDouble { it.bbNetResult } ?: 0.0
} }

@ -1,280 +1,288 @@
package net.pokeranalytics.android.model.realm //package net.pokeranalytics.android.model.realm
//
import io.realm.RealmObject //import io.realm.RealmObject
import io.realm.RealmQuery //import io.realm.RealmQuery
import io.realm.RealmResults //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 timber.log.Timber
import java.util.* //import java.util.*
//
open class TimeFrame : RealmObject() { //open class TimeFrame : RealmObject() {
//
// A start date // // A start date
var startDate: Date = Date() // var startDate: Date = Date()
private set(value) { // private set(value) {
field = value // field = value
this.computeDuration() // this.computeNetDuration()
} // }
//
// An end date // // An end date
var endDate: Date? = null // var endDate: Date? = null
private set(value) { // private set(value) {
field = value // field = value
this.computeDuration() // this.computeNetDuration()
} // }
//
// The latest pause date // // The latest pause date
var pauseDate: Date? = null // var pauseDate: Date? = null
set(value) { // set(value) {
field?.let { // field?.let {
if (value == null && field != null) { // if (value == null && field != null) {
breakDuration += Date().time - it.time // breakDuration += Date().time - it.time
} // }
} // }
field = value // field = value
this.computeDuration() // this.computeNetDuration()
} // }
//
// The break netDuration // // The break netDuration
var breakDuration: Long = 0L // var breakDuration: Long = 0L
set(value) { // set(value) {
field = value // field = value
this.computeDuration() // this.computeNetDuration()
} // }
//
// the total netDuration // // the total netDuration
var netDuration: Long = 0L // var netDuration: Long = 0L
private set // private set
//
var hourlyDuration: Double = 0.0 // var hourlyDuration: Double = 0.0
get() { // get() {
return this.netDuration / 3600000.0 // 3.6 millions of milliseconds // return this.netDuration / 3600000.0 // 3.6 millions of milliseconds
} // }
//
// indicates a state of pause // // Session
var paused: Boolean = false // @LinkingObjects("timeFrame")
// private val endedSessions: RealmResults<Session>? = null // we should have only one session
// Session //
@LinkingObjects("timeFrame") // @Ignore
private val sessions: RealmResults<Session>? = null // we should have only one session // var session: Session? = null
// get() = if (this.endedSessions != null && this.endedSessions.isEmpty()) null else this.endedSessions?.first()
@Ignore //
var session: Session? = null // // Group
get() = if (this.sessions != null && this.sessions.isEmpty()) null else this.sessions?.first() // @LinkingObjects("timeFrame")
// private val sets: RealmResults<SessionSet>? = null // we should have only one sessionGroup
// Group //
@LinkingObjects("timeFrame") // @Ignore
private val sets: RealmResults<SessionSet>? = null // we should have only one sessionGroup // var set: SessionSet? = null
// get() = this.sets?.first()
@Ignore //
var set: SessionSet? = null // fun setStart(startDate: Date) {
get() = this.sets?.first() // this.startDate = startDate
// this.session?.let {
fun setStart(startDate: Date) { // this.notifySessionDateChange(it)
this.startDate = startDate // }
this.session?.let { // }
this.notifySessionDateChange(it) //
} // fun setEnd(endDate: Date?) {
} // this.endDate = endDate
// this.session?.let {
fun setEnd(endDate: Date?) { // this.notifySessionDateChange(it)
this.endDate = endDate // }
this.session?.let { // }
this.notifySessionDateChange(it) //
} // fun setDate(startDate: Date, endDate: Date?) {
} // this.startDate = startDate
// this.endDate = endDate
fun setDate(startDate: Date, endDate: Date?) { //
this.startDate = startDate // this.session?.let {
this.endDate = endDate // this.notifySessionDateChange(it)
// }
this.session?.let { // }
this.notifySessionDateChange(it) //
} // /**
} // * Computes the net netDuration of the session
// */
/** // private fun computeNetDuration() {
* Computes the net netDuration of the session // var endDate: Date = this.endDate ?: Date()
*/ // this.netDuration = endDate.time - this.startDate.time - this.breakDuration
private fun computeDuration() { // }
var endDate: Date = this.endDate ?: Date() //
this.netDuration = endDate.time - this.startDate.time - this.breakDuration // /**
} // * Queries all time frames that might be impacted by the date change
// * Makes all necessary changes to keep sequential time frames
/** // */
* Queries all time frames that might be impacted by the date change // fun notifySessionDateChange(owner: Session) {
* Makes all necessary changes to keep sequential time frames //
*/ // var query: RealmQuery<SessionSet> = this.realm.where(SessionSet::class.java)
fun notifySessionDateChange(owner: Session) { // query.isNotNull("timeFrame")
//
var query: RealmQuery<SessionSet> = this.realm.where(SessionSet::class.java) //// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}")
query.isNotNull("timeFrame") //
// val sets = realm.where(SessionSet::class.java).findAll()
// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}") //// Timber.d("set count = ${sets.size}")
//
val sets = realm.where(SessionSet::class.java).findAll() // if (this.endDate == null) {
// Timber.d("set count = ${sets.size}") // query.greaterThanOrEqualTo("timeFrame.startDate", this.startDate)
// .or()
if (this.endDate == null) { // .greaterThanOrEqualTo("timeFrame.endDate", this.startDate)
query.greaterThanOrEqualTo("timeFrame.startDate", this.startDate) // .or()
.or() // .isNull("timeFrame.endDate")
.greaterThanOrEqualTo("timeFrame.endDate", this.startDate) // } else {
.or() // val endDate = this.endDate!!
.isNull("timeFrame.endDate") // query
} else { // .lessThanOrEqualTo("timeFrame.startDate", this.startDate)
val endDate = this.endDate!! // .greaterThanOrEqualTo("timeFrame.endDate", this.startDate)
query // .or()
.lessThanOrEqualTo("timeFrame.startDate", this.startDate) // .lessThanOrEqualTo("timeFrame.startDate", endDate)
.greaterThanOrEqualTo("timeFrame.endDate", this.startDate) // .greaterThanOrEqualTo("timeFrame.endDate", endDate)
.or() // .or()
.lessThanOrEqualTo("timeFrame.startDate", endDate) // .greaterThanOrEqualTo("timeFrame.startDate", this.startDate)
.greaterThanOrEqualTo("timeFrame.endDate", endDate) // .lessThanOrEqualTo("timeFrame.endDate", endDate)
.or() // .or()
.greaterThanOrEqualTo("timeFrame.startDate", this.startDate) // .isNull("timeFrame.endDate")
.lessThanOrEqualTo("timeFrame.endDate", endDate) // .lessThanOrEqualTo("timeFrame.startDate", endDate)
.or() // }
.isNull("timeFrame.endDate") //
.lessThanOrEqualTo("timeFrame.startDate", endDate) // val sessionGroups = query.findAll()
} //
// this.updateTimeFrames(sessionGroups, owner)
val sessionGroups = query.findAll() //
// }
this.updateTimeFrames(sessionGroups, owner) //
// /**
} // * Update Time frames from sets
// */
/** // private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, owner: Session) {
* Update Time frames from sets //
*/ // when (sessionSets.size) {
private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, owner: Session) { // 0 -> this.createOrUpdateSessionSet(owner)
// 1 -> this.updateSessionGroup(owner, sessionSets.first()!!)
when (sessionSets.size) { // else -> this.mergeSessionGroups(owner, sessionSets)
0 -> this.createSessionGroup(owner) // }
1 -> this.updateSessionGroup(owner, sessionSets.first()!!) //
else -> this.mergeSessionGroups(owner, sessionSets) // }
} //
// /**
} // * Creates the session sessionGroup when the session has none
// */
/** // private fun createOrUpdateSessionSet(owner: Session) {
* Creates the session sessionGroup when the session has none //
*/ // val set = owner.sessionSet
private fun createSessionGroup(owner: Session) { // if (set != null) {
// set.timeFrame?.startDate = this.startDate
val set: SessionSet = SessionSet.newInstance(this.realm) // set.timeFrame?.endDate = this.endDate
set.timeFrame?.let { // } else {
it.startDate = this.startDate // this.createSessionSet(owner)
it.endDate = this.endDate // }
} ?: run { //
throw ModelException("TimeFrame should never be null here") //// Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}")
} // Timber.d("netDuration 1 = : ${set?.timeFrame?.netDuration}")
//
owner.sessionSet = set // }
//
// Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}") // fun createSessionSet(owner: Session) {
Timber.d("netDuration 1 = : ${set.timeFrame?.netDuration}") // val set: SessionSet = SessionSet.newInstance(this.realm)
// set.timeFrame?.let {
} // it.startDate = this.startDate
// it.endDate = this.endDate
/** // } ?: run {
* Single SessionSet update, the session might be the owner // throw ModelException("TimeFrame should never be null here")
* Changes the sessionGroup timeframe using the current timeframe dates // }
*/ //
private fun updateSessionGroup(owner: Session, sessionSet: SessionSet) { // owner.sessionSet = set
// }
var timeFrame: TimeFrame = sessionSet.timeFrame!! // tested in the query //
// timeFrame.setDate(this.startDate, this.endDate) //
// /**
val sisterSessions = sessionSet.sessions!! // shouldn't crash ever // * Single SessionSet update, the session might be the owner
// * Changes the sessionGroup timeframe using the current timeframe dates
// if we have only one session in the set and that it corresponds to the set // */
if (sessionSet.sessions?.size == 1 && sessionSet.sessions?.first() == owner) { // private fun updateSessionGroup(owner: Session, sessionSet: SessionSet) {
timeFrame.setDate(this.startDate, this.endDate) //
} else { // there are 2+ sessions to manage and possible splits // var timeFrame: TimeFrame = sessionSet.timeFrame!! // tested in the query
//// timeFrame.setDate(this.startDate, this.endDate)
val endDate = this.endDate //
// val sisterSessions = sessionSet.endedSessions!! // shouldn't crash ever
// case where all sessions are over but the set is not, we might have a split, so we delete the set and save everything again //
if (endDate != null && sisterSessions.all { it.timeFrame?.endDate != null } && timeFrame.endDate == null) { // // if we have only one session in the set and that it corresponds to the set
var sessions = mutableListOf<Session>(owner) // if (sessionSet.endedSessions?.size == 1 && sessionSet.endedSessions?.first() == owner) {
sessionSet.sessions?.forEach { sessions.add(it) } // timeFrame.setDate(this.startDate, this.endDate)
sessionSet.deleteFromRealm() // } else { // there are 2+ endedSessions to manage and possible splits
sessions.forEach { it.timeFrame?.notifySessionDateChange(it) } //
} else { // val endDate = this.endDate
//
if (this.startDate.before(timeFrame.startDate)) { // // case where all endedSessions are over but the set is not, we might have a split, so we delete the set and save everything again
timeFrame.startDate = this.startDate // if (endDate != null && sisterSessions.all { it.timeFrame?.endDate != null } && timeFrame.endDate == null) {
} // var endedSessions = mutableListOf<Session>(owner)
if (endDate != null && timeFrame.endDate != null && endDate.after(timeFrame.endDate)) { // sessionSet.endedSessions?.forEach { endedSessions.add(it) }
timeFrame.endDate = endDate // sessionSet.deleteFromRealm()
} else if (endDate == null) { // endedSessions.forEach { it.timeFrame?.notifySessionDateChange(it) }
timeFrame.endDate = null // } else {
} //
// if (this.startDate.before(timeFrame.startDate)) {
owner.sessionSet = sessionSet // timeFrame.startDate = this.startDate
// }
// Timber.d("sd = : ${sessionSet.timeFrame?.startDate}, ed = ${sessionSet.timeFrame?.endDate}") // if (endDate != null && timeFrame.endDate != null && endDate.after(timeFrame.endDate)) {
Timber.d("netDuration 2 = : ${sessionSet.timeFrame?.netDuration}") // timeFrame.endDate = endDate
} // } else if (endDate == null) {
// timeFrame.endDate = null
} // }
//
} // owner.sessionSet = sessionSet
//
/** //// Timber.d("sd = : ${sessionSet.timeFrame?.startDate}, ed = ${sessionSet.timeFrame?.endDate}")
* Multiple session sets update: // Timber.d("netDuration 2 = : ${sessionSet.timeFrame?.netDuration}")
* Merges all sets into one (delete all then create a new one) // }
*/ //
private fun mergeSessionGroups(owner: Session, sessionSets: RealmResults<SessionSet>) { // }
//
var startDate: Date = this.startDate // }
var endDate: Date? = this.endDate //
// /**
// find earlier and later dates from all sets // * Multiple session sets update:
val timeFrames = sessionSets.mapNotNull { it.timeFrame } // * Merges all sets into one (delete all then create a new one)
timeFrames.forEach { tf -> // */
if (tf.startDate.before(startDate)) { // private fun mergeSessionGroups(owner: Session, sessionSets: RealmResults<SessionSet>) {
startDate = tf.startDate //
} // var startDate: Date = this.startDate
// var endDate: Date? = this.endDate
endDate?.let { ed -> //
tf.endDate?.let { tfed -> // // find earlier and later dates from all sets
if (tfed.after(ed)) { // val timeFrames = sessionSets.mapNotNull { it.timeFrame }
endDate = tfed // timeFrames.forEach { tf ->
} // if (tf.startDate.before(startDate)) {
} // startDate = tf.startDate
} ?: run { // }
endDate = tf.endDate //
} // endDate?.let { ed ->
// tf.endDate?.let { tfed ->
} // if (tfed.after(ed)) {
// endDate = tfed
// get all sessions from sets // }
var sessions = mutableSetOf<Session>() // }
sessionSets.forEach { set -> // } ?: run {
set.sessions?.asIterable()?.let { sessions.addAll(it) } // endDate = tf.endDate
} // }
//
// delete all sets // }
sessionSets.deleteAllFromRealm() //
// // get all endedSessions from sets
// Create a new sets // var endedSessions = mutableSetOf<Session>()
val set: SessionSet = SessionSet.newInstance(this.realm) // sessionSets.forEach { set ->
set.timeFrame?.let { // set.endedSessions?.asIterable()?.let { endedSessions.addAll(it) }
it.setDate(startDate, endDate) // }
} ?: run { //
throw ModelException("TimeFrame should never be null here") // // delete all sets
} // sessionSets.deleteAllFromRealm()
//
// Add the session linked to this timeframe to the new sessionGroup // // Create a new sets
owner.sessionSet = set // val set: SessionSet = SessionSet.newInstance(this.realm)
// set.timeFrame?.let {
// Add all orphan sessions // it.setDate(startDate, endDate)
sessions.forEach { it.sessionSet = set } // } ?: run {
Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}") // throw ModelException("TimeFrame should never be null here")
// }
} //
// // Add the session linked to this timeframe to the new sessionGroup
} // owner.sessionSet = set
//
// // Add all orphan endedSessions
// endedSessions.forEach { it.sessionSet = set }
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}")
//
// }
//
//}

@ -99,14 +99,14 @@ class FavoriteSessionFinder {
/** /**
* Determines the favorite session given a [sessionType] and an optional [location] * Determines the favorite session given a [sessionType] and an optional [location]
*/ */
private fun favoriteSession(sessionType: Int, location: Location?, realm: Realm, context: Context) : Session? { fun favoriteSession(sessionType: Int, location: Location?, realm: Realm, context: Context) : Session? {
val lastSessionsQuery = realm.where(Session::class.java).equalTo("type", sessionType) val lastSessionsQuery = realm.where(Session::class.java).equalTo("type", sessionType)
if (location != null) { if (location != null) {
lastSessionsQuery.equalTo("location.id", location.id) lastSessionsQuery.equalTo("location.id", location.id)
} }
val lastSessions = lastSessionsQuery val lastSessions = lastSessionsQuery
.sort("timeFrame.startDate", Sort.DESCENDING) .sort("startDate", Sort.DESCENDING)
.limit(FAVORITE_SIGNIFICANT_SESSIONS) .limit(FAVORITE_SIGNIFICANT_SESSIONS)
.findAll() .findAll()

@ -0,0 +1,161 @@
package net.pokeranalytics.android.model.utils
import io.realm.RealmQuery
import io.realm.RealmResults
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import kotlin.math.max
/**
* The manager is in charge of updating the abstract concept of timeline,
* representing the sequenced time frames where the user plays.
*/
class SessionSetManager {
companion object {
/**
* Updates the global timeline using the updated [session]
*/
fun updateTimeline(session: Session) {
if (!session.realm.isInTransaction) {
throw IllegalStateException("realm should be in transaction at this point")
}
if (session.startDate == null || session.endDate == null) {
throw IllegalStateException("Start or end date should never be null here")
}
val endDate = session.endDate!! // tested above
val startDate = session.startDate!!
val realm = session.realm
val query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java)
query
.lessThanOrEqualTo("startDate", startDate)
.greaterThanOrEqualTo("endDate", startDate)
.or()
.lessThanOrEqualTo("startDate", endDate)
.greaterThanOrEqualTo("endDate", endDate)
.or()
.greaterThanOrEqualTo("startDate", startDate)
.lessThanOrEqualTo("endDate", endDate)
val sessionGroups = query.findAll()
this.updateTimeFrames(sessionGroups, session)
}
/**
* Update the global timeline using the impacted [sessionSets] and the updated [session]
*/
private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, session: Session) {
when (sessionSets.size) {
0 -> this.createOrUpdateSessionSet(session)
else -> this.mergeSessionGroups(session, sessionSets)
}
}
/**
* Creates or update the session set for the [session]
*/
private fun createOrUpdateSessionSet(session: Session) {
val set = session.sessionSet
if (set != null) {
set.startDate = session.startDate!! // tested above
set.endDate = session.endDate!!
} else {
this.createSessionSet(session)
}
}
/**
* Create a set and affect it to the [session]
*/
private fun createSessionSet(session: Session) {
val set: SessionSet = SessionSet.newInstance(session.realm)
set.startDate = session.startDate!!
set.endDate = session.endDate!!
set.breakDuration = session.breakDuration
session.sessionSet = set
}
/**
* Multiple session sets update:
* Merges all sets into one (delete all then create a new one)
*/
private fun mergeSessionGroups(session: Session, sessionSets: RealmResults<SessionSet>) {
var startDate = session.startDate!!
var endDate = session.endDate!!
// find earlier and later dates from all sets
sessionSets.forEach { set ->
if (set.startDate.before(startDate)) {
startDate = set.startDate
}
if (set.endDate.after(endDate)) {
endDate = set.endDate
}
}
// get all endedSessions from sets
val sessions = mutableSetOf<Session>()
sessionSets.forEach { set ->
set.sessions?.asIterable()?.let { sessions.addAll(it) }
}
// delete all sets
sessionSets.deleteAllFromRealm()
// Create a new set
val set: SessionSet = SessionSet.newInstance(session.realm)
set.startDate = startDate
set.endDate = endDate
// Add the session linked to this timeframe to the new sessionGroup
session.sessionSet = set
// Add all orphan endedSessions
sessions.forEach { session ->
session.sessionSet = set
set.breakDuration = max(set.breakDuration, session.breakDuration)
}
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}")
}
/**
* Removes the [session] from the timeline
*/
fun removeFromTimeline(session: Session) {
if (!session.realm.isInTransaction) {
throw IllegalStateException("realm should be in transaction at this point")
}
val sessionSet = session.sessionSet
if (sessionSet != null) {
val sessions = mutableSetOf<Session>()
sessionSet.sessions?.asIterable()?.let { sessions.addAll(it) }
sessions.remove(session)
sessionSet.deleteFromRealm()
sessions.forEach {
SessionSetManager.updateTimeline(it)
}
}
}
}
}

@ -68,6 +68,8 @@ interface StaticRowRepresentableDataSource: RowRepresentableDataSource {
} }
} }
/** /**
* Interface extending [RowRepresentableDataSource] to guide the implementation of a live list of [RowRepresentable] * Interface extending [RowRepresentableDataSource] to guide the implementation of a live list of [RowRepresentable]
* To do that, this interface overrides and provides a default implementation to specific methods of [RowRepresentableDataSource] * To do that, this interface overrides and provides a default implementation to specific methods of [RowRepresentableDataSource]
@ -89,6 +91,10 @@ class DisplayDescriptor(
var context: Context? = null) { var context: Context? = null) {
} }
class UnmanagedRowRepresentableException(message: String) : Exception(message) {
}
/** /**
* An interface used to provide RowRepresentableAdapter content and value in the form of rows * An interface used to provide RowRepresentableAdapter content and value in the form of rows
* *
@ -100,7 +106,6 @@ class DisplayDescriptor(
*/ */
interface DisplayableDataSource { interface DisplayableDataSource {
fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? { fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? {
return null return null
} }

@ -11,7 +11,9 @@ 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.exceptions.TypeException
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.TournamentKind
import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.Savable import net.pokeranalytics.android.model.interfaces.Savable
import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Bankroll
@ -47,7 +49,6 @@ class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate,
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initData()
initUI() initUI()
} }
@ -69,18 +70,17 @@ class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate,
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) { override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) { when (row) {
LocationRow.LOCATE_ME -> PlacePickerManager.create(parentActivity, row, this) LocationRow.LOCATE_ME -> {
else -> { if (item is Location) {
BottomSheetFragment.create( (item as Location).isLookingForPlaces = true
fragmentManager, PlacePickerManager.create(parentActivity, row, this)
row, rowRepresentableAdapter.refreshRow(row)
this, } else {
(this.item as RowRepresentableDataSource).editDescriptors(row) throw TypeException("Need to manage LocationRow.LOCATE_ME for ${item::class.java}")
) }
} }
else -> BottomSheetFragment.create(fragmentManager, row, this, (this.item as RowRepresentableDataSource).editDescriptors(row))
} }
} }
override fun clickOnAdd(row: RowRepresentable) { override fun clickOnAdd(row: RowRepresentable) {
@ -91,14 +91,12 @@ class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate,
this.getRealm().executeTransaction { this.getRealm().executeTransaction {
(this.item as Savable).updateValue(value, row) (this.item as Savable).updateValue(value, row)
} }
when(row) { when (row) {
LocationRow.LOCATE_ME -> rowRepresentableAdapter.notifyDataSetChanged() LocationRow.LOCATE_ME -> {
rowRepresentableAdapter.notifyDataSetChanged()
}
else -> rowRepresentableAdapter.refreshRow(row) else -> rowRepresentableAdapter.refreshRow(row)
} }
}
private fun initData() {
} }
/** /**
@ -142,12 +140,12 @@ class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate,
} }
} else { } else {
val message = when(item) { val message = when (item) {
is Bankroll -> R.string.empty_name_for_br_error is Bankroll -> R.string.empty_name_for_br_error
is Location -> R.string.location_empty_field_error is Location -> R.string.location_empty_field_error
is Game -> R.string.location_empty_field_error is Game -> R.string.location_empty_field_error
is TournamentName -> R.string.tt_empty_field_error is TournamentName -> R.string.tt_empty_field_error
//is TransactionType -> R.string.operation_type_empty_field_error is TournamentKind -> R.string.tt_empty_field_error
else -> throw IllegalStateException("Need to manage ${item::class.java} error") else -> throw IllegalStateException("Need to manage ${item::class.java} error")
} }
@ -157,41 +155,24 @@ class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate,
builder.show() builder.show()
} }
} }
/** /**
* Delete data * Delete data
*/ */
private fun deleteData() { private fun deleteData() {
val builder = AlertDialog.Builder(requireContext()) val builder = AlertDialog.Builder(requireContext())
builder.setTitle(R.string.warning) builder.setTitle(R.string.warning)
.setMessage(R.string.are_you_sure_you_want_to_do_that_) .setMessage(R.string.are_you_sure_you_want_to_do_that_)
.setNegativeButton(R.string.no, null) .setNegativeButton(R.string.no, null)
.setPositiveButton(R.string.yes) { _, _ -> .setPositiveButton(R.string.yes) { _, _ ->
//TODO: Maybe update this code, does the object need to be managed? //TODO: Maybe update this code, does the object need to be managed?
this.getRealm().executeTransaction { this.getRealm().executeTransaction {
this.liveDataType.deleteData(it, (this.item as Savable)) this.liveDataType.deleteData(it, (this.item as Savable))
} }
/*
if (this.item.isManaged) {
Toast.makeText(requireContext(), "isManaged", Toast.LENGTH_SHORT).show()
Timber.d("is managed")
this.getRealm().executeTransaction {
this.liveDataType.deleteData(it, (this.item as Savable))
}
} else {
Toast.makeText(requireContext(), "isNotManaged", Toast.LENGTH_SHORT).show()
Timber.d("is not managed")
}
*/
this.activity?.finish() this.activity?.finish()
} }
builder.show() builder.show()
} }
/** /**
@ -224,5 +205,13 @@ class EditableDataFragment : PokerAnalyticsFragment(), RowRepresentableDelegate,
this this
) )
this.recyclerView.adapter = rowRepresentableAdapter this.recyclerView.adapter = rowRepresentableAdapter
// When creating an object, open automatically the keyboard for the first row
if (!isUpdating && this.item is RowRepresentableDataSource) {
val row = (this.item as RowRepresentableDataSource).adapterRows()?.firstOrNull()
row?.let {
onRowSelected(0, it)
}
}
} }
} }

@ -67,7 +67,7 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
super.onResume() super.onResume()
//rows.clear() //rows.clear()
//sessions.addAll(getRealm().copyFromRealm(realmSessions)) //endedSessions.addAll(getRealm().copyFromRealm(realmSessions))
createSessionsHeaders() createSessionsHeaders()
} }
@ -105,7 +105,7 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
} }
/** /**
* Create the sessions headers * Create the endedSessions headers
*/ */
private fun createSessionsHeaders() { private fun createSessionsHeaders() {

@ -90,13 +90,14 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Bott
requireContext(), requireContext(),
row, row,
this, this,
currentSession.timeFrame?.startDate ?: Date() currentSession.startDate
) )
SessionRow.END_DATE -> DateTimePickerManager.create( SessionRow.END_DATE -> DateTimePickerManager.create(
requireContext(), requireContext(),
row, row,
this, this,
currentSession.timeFrame?.endDate ?: currentSession.timeFrame?.startDate ?: Date() currentSession.endDate ?: currentSession.startDate ?: Date(),
currentSession.startDate
) )
SessionRow.BANKROLL -> { SessionRow.BANKROLL -> {
BottomSheetFragment.create(fragmentManager, row, this, data, false) BottomSheetFragment.create(fragmentManager, row, this, data, false)
@ -111,7 +112,8 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Bott
sessionAdapter.refreshRow(row) sessionAdapter.refreshRow(row)
when (row) { when (row) {
SessionRow.CASHED_OUT, SessionRow.PRIZE, SessionRow.NET_RESULT, SessionRow.BUY_IN, SessionRow.TIPS, SessionRow.CASHED_OUT, SessionRow.PRIZE, SessionRow.NET_RESULT, SessionRow.BUY_IN, SessionRow.TIPS,
SessionRow.START_DATE, SessionRow.END_DATE -> updateSessionUI() SessionRow.START_DATE, SessionRow.END_DATE, SessionRow.BANKROLL -> updateSessionUI()
SessionRow.BREAK_TIME -> this.sessionAdapter.notifyDataSetChanged()
} }
} }
@ -182,8 +184,6 @@ class SessionFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Bott
floatingActionButton.animate().scaleX(0f).scaleY(0f).alpha(0f) floatingActionButton.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setInterpolator(FastOutSlowInInterpolator()).start() .setInterpolator(FastOutSlowInInterpolator()).start()
} }
else -> {
}
} }
updateMenuUI() updateMenuUI()

@ -21,7 +21,7 @@ import net.pokeranalytics.android.util.NULL_TEXT
class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource { class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource {
private var rowRepresentables: ArrayList<RowRepresentable> = ArrayList<RowRepresentable>() private var rowRepresentables: ArrayList<RowRepresentable> = ArrayList()
private lateinit var statsAdapter: RowRepresentableAdapter private lateinit var statsAdapter: RowRepresentableAdapter
@ -56,7 +56,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
} }
override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? { override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? {
var dc = DisplayDescriptor() val dc = DisplayDescriptor()
dc.textFormat = TextFormat(NULL_TEXT) dc.textFormat = TextFormat(NULL_TEXT)
if (row is StatRepresentable) { if (row is StatRepresentable) {
context?.let { context -> context?.let { context ->
@ -107,7 +107,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
val cgSessions = mutableListOf<Session>() val cgSessions = mutableListOf<Session>()
val tSessions = mutableListOf<Session>() val tSessions = mutableListOf<Session>()
super.sessions.forEach { session -> super.endedSessions.forEach { session ->
if (session.isCashGame()) { if (session.isCashGame()) {
cgSessions.add(session) cgSessions.add(session)
} else { } else {
@ -115,8 +115,8 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
} }
} }
val allStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_DURATION, Stat.DURATION) val allStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE, Stat.NUMBER_OF_SETS, Stat.AVERAGE_DURATION, Stat.DURATION)
val allSessionGroup = SessionGroup(getString(R.string.all), super.sessions, allStats) val allSessionGroup = SessionGroup(getString(R.string.all), super.endedSessions, allStats)
val cgStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.NET_BB_PER_100_HANDS, Stat.HOURLY_RATE_BB, Stat.AVERAGE, Stat.STANDARD_DEVIATION_HOURLY, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) val cgStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.NET_BB_PER_100_HANDS, Stat.HOURLY_RATE_BB, Stat.AVERAGE, Stat.STANDARD_DEVIATION_HOURLY, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)
val cgSessionGroup = SessionGroup(getString(R.string.cash_game), cgSessions, cgStats) val cgSessionGroup = SessionGroup(getString(R.string.cash_game), cgSessions, cgStats)
val tStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) val tStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)

@ -6,12 +6,12 @@ import net.pokeranalytics.android.model.realm.Session
open class SessionObserverFragment : PokerAnalyticsFragment() { open class SessionObserverFragment : PokerAnalyticsFragment() {
val sessions: RealmResults<Session> val endedSessions: RealmResults<Session>
init { init {
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
this.sessions = realm.where(Session::class.java).isNotNull("timeFrame.endDate").findAll() this.endedSessions = realm.where(Session::class.java).isNotNull("endDate").findAll()
this.sessions.addChangeListener { _, _ -> this.endedSessions.addChangeListener { _, _ ->
this.sessionsChanged() this.sessionsChanged()
} }
} }

@ -7,6 +7,7 @@ import android.content.DialogInterface
import android.text.format.DateFormat import android.text.format.DateFormat
import android.widget.DatePicker import android.widget.DatePicker
import android.widget.TimePicker import android.widget.TimePicker
import android.widget.Toast
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
@ -17,16 +18,18 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener,
private var context: Context? = null private var context: Context? = null
lateinit var row: RowRepresentable private lateinit var row: RowRepresentable
lateinit var delegate: RowRepresentableDelegate private lateinit var delegate: RowRepresentableDelegate
lateinit var calendar: Calendar private lateinit var calendar: Calendar
private var minimumDate: Date? = null
companion object { companion object {
fun create( fun create(
context: Context, context: Context,
row: RowRepresentable, row: RowRepresentable,
delegate: RowRepresentableDelegate, delegate: RowRepresentableDelegate,
date: Date? date: Date?,
minimumDate: Date? = null
) : DateTimePickerManager { ) : DateTimePickerManager {
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
@ -37,6 +40,7 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener,
dateTimePickerManager.row = row dateTimePickerManager.row = row
dateTimePickerManager.delegate = delegate dateTimePickerManager.delegate = delegate
dateTimePickerManager.calendar = calendar dateTimePickerManager.calendar = calendar
dateTimePickerManager.minimumDate = minimumDate
dateTimePickerManager.showDatePicker() dateTimePickerManager.showDatePicker()
@ -54,7 +58,19 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener,
override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) {
calendar.set(Calendar.HOUR_OF_DAY, hourOfDay) calendar.set(Calendar.HOUR_OF_DAY, hourOfDay)
calendar.set(Calendar.MINUTE, minute) calendar.set(Calendar.MINUTE, minute)
delegate.onRowValueChanged(calendar.time, row)
if (minimumDate != null) {
minimumDate?.let {
if (calendar.time < it) {
Toast.makeText(context, R.string.end_date_not_possible, Toast.LENGTH_LONG).show()
} else {
delegate.onRowValueChanged(calendar.time, row)
}
}
} else {
delegate.onRowValueChanged(calendar.time, row)
}
} }
/** /**
@ -66,6 +82,10 @@ class DateTimePickerManager : DatePickerDialog.OnDateSetListener,
val day = calendar.get(Calendar.DAY_OF_MONTH) val day = calendar.get(Calendar.DAY_OF_MONTH)
context?.let { context?.let {
val datePickerDialog = DatePickerDialog(it, this, year, month, day) val datePickerDialog = DatePickerDialog(it, this, year, month, day)
minimumDate?.let {
datePickerDialog.datePicker.minDate = it.time
}
datePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, it.getString(R.string.clear)) { dialog, _ -> datePickerDialog.setButton(DialogInterface.BUTTON_NEGATIVE, it.getString(R.string.clear)) { dialog, _ ->
delegate.onRowValueChanged(null, row) delegate.onRowValueChanged(null, row)
dialog.dismiss() dialog.dismiss()

@ -16,22 +16,26 @@ class PlacePickerManager {
activity: PokerAnalyticsActivity, activity: PokerAnalyticsActivity,
row: RowRepresentable, row: RowRepresentable,
delegate: RowRepresentableDelegate, delegate: RowRepresentableDelegate,
maxResults: Int? = 2 maxResults: Int? = 3
) { ) {
activity.askForPlacesRequest { success, places -> activity.askForPlacesRequest { success, places ->
if (success && places.size > 0) { if (success && places.size > 0) {
val placesArray = ArrayList<CharSequence>() val placesArray = ArrayList<CharSequence>()
for ((index, place) in places.withIndex()) { for ((index, place) in places.withIndex()) {
placesArray.add(place.place.name.toString()) placesArray.add(place.place.name.toString())
if (index == maxResults) { if (index == (maxResults ?: 3) - 1) {
break break
} }
} }
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
builder.setItems(placesArray.toTypedArray()) { _, which -> builder.setItems(placesArray.toTypedArray()) { _, which ->
delegate.onRowValueChanged(places[which].place, row) delegate.onRowValueChanged(places[which].place, row)
}.setOnCancelListener {
delegate.onRowValueChanged(null, row)
} }
builder.show() builder.show()
} else {
delegate.onRowValueChanged(null, row)
} }
} }
} }

@ -7,6 +7,8 @@ import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.appcompat.widget.SwitchCompat import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.row_history_session.view.* import kotlinx.android.synthetic.main.row_history_session.view.*
import kotlinx.android.synthetic.main.row_stats_title_value.view.* import kotlinx.android.synthetic.main.row_stats_title_value.view.*
@ -52,7 +54,7 @@ enum class RowViewType(private var layoutRes: Int) {
STAT(R.layout.row_stats_title_value), STAT(R.layout.row_stats_title_value),
// Separator // Separator
SEPARATOR(R.layout.row_separator), ; SEPARATOR(R.layout.row_separator);
/** /**
* View holder * View holder
@ -205,6 +207,8 @@ enum class RowViewType(private var layoutRes: Int) {
inner class RowButtonViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder { inner class RowButtonViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) { override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) {
itemView.findViewById<AppCompatTextView>(R.id.title).text = row.localizedTitle(itemView.context) itemView.findViewById<AppCompatTextView>(R.id.title).text = row.localizedTitle(itemView.context)
itemView.findViewById<AppCompatTextView>(R.id.title).isVisible = !adapter.dataSource.boolForRow(row)
itemView.findViewById<ContentLoadingProgressBar>(R.id.progressBar).isVisible = adapter.dataSource.boolForRow(row)
val listener = View.OnClickListener { val listener = View.OnClickListener {
adapter.delegate?.onRowSelected(position, row) adapter.delegate?.onRowSelected(position, row)
} }

@ -95,11 +95,12 @@ class SessionRowView : FrameLayout {
rowHistorySession.sessionTitle.text = title rowHistorySession.sessionTitle.text = title
// Duration // Duration
rowHistorySession.sessionInfoDurationIcon.isVisible = session.timeFrame != null // rowHistorySession.sessionInfoDurationIcon.isVisible = session.timeFrame != null
rowHistorySession.sessionInfoDurationValue.isVisible = session.timeFrame != null // rowHistorySession.sessionInfoDurationValue.isVisible = session.timeFrame != null
session.timeFrame?.let { // session.timeFrame?.let {
rowHistorySession.sessionInfoDurationValue.text = session.getDuration() // rowHistorySession.sessionInfoDurationValue.text = session.getFormattedDuration()
} // }
rowHistorySession.sessionInfoDurationValue.text = session.getFormattedDuration()
// Location // Location
rowHistorySession.sessionInfoLocationIcon.isVisible = session.location != null rowHistorySession.sessionInfoLocationIcon.isVisible = session.location != null
@ -115,19 +116,28 @@ class SessionRowView : FrameLayout {
rowHistorySession.sessionInfoTableValue.text = TableSize(it).localizedTitle(context) rowHistorySession.sessionInfoTableValue.text = TableSize(it).localizedTitle(context)
} }
val state = session.getState()
rowHistorySession.sessionInfoDurationIcon.isVisible = state.hasStarted
rowHistorySession.sessionInfoDurationValue.isVisible = state.hasStarted
// State // State
if (session.getState() == SessionState.STARTED) { if (state == SessionState.STARTED) {
rowHistorySession.gameResult.isVisible = false rowHistorySession.gameResult.isVisible = false
rowHistorySession.infoIcon.isVisible = true rowHistorySession.infoIcon.isVisible = true
rowHistorySession.infoIcon.setImageResource(R.drawable.chip) rowHistorySession.infoIcon.setImageResource(R.drawable.chip)
rowHistorySession.infoTitle.isVisible = true rowHistorySession.infoTitle.isVisible = true
rowHistorySession.infoTitle.text = context.getString(R.string.running_session_state) rowHistorySession.infoTitle.text = context.getString(R.string.running_session_state)
} else if (session.getState() == SessionState.PLANNED) { } else if (state == SessionState.PLANNED) {
rowHistorySession.gameResult.isVisible = false rowHistorySession.gameResult.isVisible = false
rowHistorySession.infoIcon.isVisible = true rowHistorySession.infoIcon.isVisible = true
rowHistorySession.infoIcon.setImageResource(R.drawable.ic_planned) rowHistorySession.infoIcon.setImageResource(R.drawable.ic_planned)
rowHistorySession.infoTitle.isVisible = true rowHistorySession.infoTitle.isVisible = true
rowHistorySession.infoTitle.text = session.timeFrame?.startDate?.shortTime() rowHistorySession.infoTitle.text = session.startDate!!.shortTime()
} else if (state == SessionState.PENDING) {
rowHistorySession.gameResult.isVisible = false
rowHistorySession.infoIcon.isVisible = false
rowHistorySession.infoTitle.isVisible = false
} else { } else {
rowHistorySession.gameResult.isVisible = true rowHistorySession.gameResult.isVisible = true
rowHistorySession.infoIcon.isVisible = false rowHistorySession.infoIcon.isVisible = false

@ -12,7 +12,7 @@ enum class BankrollRow : RowRepresentable {
override val resId: Int? override val resId: Int?
get() { get() {
return when (this) { return when (this) {
LIVE -> R.string.live LIVE -> R.string.online
} }
} }

@ -75,7 +75,7 @@ enum class SessionRow : RowRepresentable {
) )
} else { } else {
arrayListOf( arrayListOf(
NET_RESULT, BUY_IN, TIPS, NET_RESULT,
SeparatorRowRepresentable(), SeparatorRowRepresentable(),
GAME, BLINDS, LOCATION, BANKROLL, TABLE_SIZE, START_DATE, END_DATE, BREAK_TIME, COMMENT GAME, BLINDS, LOCATION, BANKROLL, TABLE_SIZE, START_DATE, END_DATE, BREAK_TIME, COMMENT
) )

@ -85,7 +85,7 @@ fun Date.getMonthAndYear(): String {
} }
// Return the netDuration between two dates // Return the netDuration between two dates
fun Date.getDuration(toDate: Date) : String { fun Date.getFormattedDuration(toDate: Date) : String {
val difference = (toDate.time - this.time).toInt() val difference = (toDate.time - this.time).toInt()
val numOfDays = (difference / (1000 * 60 * 60 * 24)) val numOfDays = (difference / (1000 * 60 * 60 * 24))
val hours = (difference / (1000 * 60 * 60)) val hours = (difference / (1000 * 60 * 60))

@ -4,23 +4,36 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="56dp"
android:background="?selectableItemBackground"> android:background="?selectableItemBackground">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
style="@style/PokerAnalyticsTheme.TextView.ButtonRow" style="@style/PokerAnalyticsTheme.TextView.ButtonRow"
android:gravity="center" android:gravity="center"
android:paddingTop="16dp"
android:paddingBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/guidelineEnd" app:layout_constraintEnd_toEndOf="@+id/guidelineEnd"
app:layout_constraintStart_toStartOf="@+id/guidelineStart" app:layout_constraintStart_toStartOf="@+id/guidelineStart"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Data Type Title" /> tools:text="Data Type Title" />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline <androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineStart" android:id="@+id/guidelineStart"
android:layout_width="wrap_content" android:layout_width="wrap_content"

@ -8,7 +8,7 @@
<!-- Stats --> <!-- Stats -->
<string name="hourly_rate">Hourly Rate</string> <string name="hourly_rate">Hourly Rate</string>
<string name="number_of_groups">Number of groups</string> <string name="number_of_groups">Number of sessions</string>
<string name="number_of_games">Number of games</string> <string name="number_of_games">Number of games</string>
<string name="average_duration">Average duration</string> <string name="average_duration">Average duration</string>
<string name="net_bb_per_100_hands">Net(BB) per 100 hands</string> <string name="net_bb_per_100_hands">Net(BB) per 100 hands</string>
@ -20,6 +20,7 @@
<string name="address">Address</string> <string name="address">Address</string>
<string name="data_deleted" formatted="false">%s deleted</string> <string name="data_deleted" formatted="false">%s deleted</string>
<string name="end_date_not_possible">The end date should be after the start date</string>
<!-- <!--

Loading…
Cancel
Save