Updated iOS PA import

csv
Laurent 6 years ago
parent 9dca2eb647
commit 97ed99750f
  1. 2
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  2. 17
      app/src/main/java/net/pokeranalytics/android/model/TableSize.kt
  3. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/Import.kt
  4. 41
      app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt
  5. 21
      app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
  6. 24
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt
  7. 10
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt
  8. 5
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt
  9. 51
      app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt
  10. 10
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt
  11. 2
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt
  12. 8
      app/src/test/java/net/pokeranalytics/android/BasicUnitTest.kt

@ -60,7 +60,7 @@ class PokerAnalyticsApplication : Application() {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}") Timber.d("UserPreferences.defaultCurrency: ${UserDefaults.currency.symbol}")
this.createFakeSessions() // this.createFakeSessions()
} }
Patcher.patchAll(this.applicationContext) Patcher.patchAll(this.applicationContext)

@ -4,6 +4,8 @@ import android.content.Context
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import java.text.NumberFormat
import java.text.ParseException
class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITLE_GRID.ordinal) : RowRepresentable { class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITLE_GRID.ordinal) : RowRepresentable {
@ -15,8 +17,18 @@ class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITL
{ index -> TableSize(index + 2) }).toList() { index -> TableSize(index + 2) }).toList()
} }
fun valueForLabel(label: String) : Int? { /**
return when (label) { * Tries to parse a label into a Table Size Int value,
* using number parsing or label recognition
* A numberFormat can be passed when dealing with lots of volume
*/
fun valueForLabel(label: String, numberFormat: NumberFormat? = null) : Int? {
return try {
val nf = numberFormat ?: NumberFormat.getInstance()
nf.parse(label).toInt()
} catch (e: ParseException) {
when (label) {
"Full Ring", "Full-Ring" -> 10 "Full Ring", "Full-Ring" -> 10
"Short-Handed", "Short Handed" -> 6 "Short-Handed", "Short Handed" -> 6
"Heads-Up", "Heads Up" -> 2 "Heads-Up", "Heads Up" -> 2
@ -24,6 +36,7 @@ class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITL
} }
} }
} }
}
override fun getDisplayName(context: Context): String { override fun getDisplayName(context: Context): String {
return if (this.numberOfPlayer == 2) { return if (this.numberOfPlayer == 2) {

@ -4,7 +4,7 @@ import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import java.util.* import java.util.*
class Import : RealmObject() { open class Import : RealmObject() {
var date: Date = Date() var date: Date = Date()

@ -9,6 +9,7 @@ import io.realm.annotations.RealmClass
import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import timber.log.Timber
@RealmClass @RealmClass
open class Result : RealmObject(), Filterable { open class Result : RealmObject(), Filterable {
@ -51,18 +52,28 @@ open class Result : RealmObject(), Filterable {
var netResult: Double? = null var netResult: Double? = null
set(value) { set(value) {
var errorMessage: String? = null
this.session?.bankroll?.let { bankroll -> this.session?.bankroll?.let { bankroll ->
if (bankroll.live) { if (bankroll.live) {
throw PAIllegalStateException("Can't set net result on a live bankroll") errorMessage = "Can't set net result on a live bankroll"
} }
} ?: run { } ?: run {
throw PAIllegalStateException("Session doesn't have any bankroll") errorMessage = "Session doesn't have any bankroll"
}
errorMessage?.let {
if (isManaged) {
throw PAIllegalStateException(it)
} else {
Timber.w(it)
}
} }
field = value field = value
this.computeNet() this.computeNet()
if (value != null) { if (value != null) {
this.session.end() this.session?.end()
} }
} }
@ -107,15 +118,23 @@ open class Result : RealmObject(), Filterable {
val transactionsSum = transactions.sumByDouble { it.amount } val transactionsSum = transactions.sumByDouble { it.amount }
val isLive = this.session?.isLive ?: true this.netResult?.let {
if (isLive) { this.net = it + transactionsSum
} ?: run {
val buyin = this.buyin ?: 0.0 val buyin = this.buyin ?: 0.0
val cashOut = this.cashout ?: 0.0 val cashedOut = this.cashout ?: 0.0
this.net = cashOut - buyin + transactionsSum this.net = cashedOut - buyin + transactionsSum
} else { }
val netResult = this.netResult ?: 0.0
this.net = netResult + transactionsSum // val isLive = this.session?.isLive ?: true
} // if (isLive) {
// val buyin = this.buyin ?: 0.0
// val cashOut = this.cashout ?: 0.0
// this.net = cashOut - buyin + transactionsSum
// } else {
// val netResult = this.netResult ?: 0.0
// this.net = netResult + transactionsSum
// }
// Precompute results // Precompute results
this.session?.computeStats() this.session?.computeStats()

@ -16,6 +16,7 @@ import androidx.core.content.FileProvider
import androidx.core.view.isVisible import androidx.core.view.isVisible
import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.util.DeviceUtils import net.pokeranalytics.android.util.DeviceUtils
@ -104,9 +105,13 @@ fun PokerAnalyticsActivity.showAlertDialog(title: Int? = null, message: Int? = n
showAlertDialog(this, title, message) showAlertDialog(this, title, message)
} }
fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = null) { fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = null, messageString: String? = null,
cancelButtonTitle: Int? = null, showCancelButton: Boolean = false,
positiveAction: (() -> Unit)? = null, negativeAction: (() -> Unit)? = null) {
context?.let { context?.let {
showAlertDialog(it, title, message) showAlertDialog(it, title, message, messageString, cancelButtonTitle, showCancelButton, positiveAction, negativeAction)
} ?: run {
throw PAIllegalStateException("Fragment has no context")
} }
} }
@ -114,9 +119,10 @@ fun PokerAnalyticsFragment.showAlertDialog(title: Int? = null, message: Int? = n
* Create and show an alert dialog * Create and show an alert dialog
*/ */
fun showAlertDialog( fun showAlertDialog(
context: Context, title: Int? = null, message: Int? = null, cancelButtonTitle: Int? = null, showCancelButton: Boolean = false, context: Context, title: Int? = null, message: Int? = null, messageString: String? = null,
positiveAction: (() -> Unit)? = null, negativeAction: (() -> Unit)? = null cancelButtonTitle: Int? = null, showCancelButton: Boolean = false,
) { positiveAction: (() -> Unit)? = null, negativeAction: (() -> Unit)? = null) {
val builder = AlertDialog.Builder(context) val builder = AlertDialog.Builder(context)
title?.let { title?.let {
builder.setTitle(title) builder.setTitle(title)
@ -124,7 +130,10 @@ fun showAlertDialog(
message?.let { message?.let {
builder.setMessage(message) builder.setMessage(message)
} }
builder.setPositiveButton(net.pokeranalytics.android.R.string.ok) { _, _ -> messageString?.let {
builder.setMessage(it)
}
builder.setPositiveButton(R.string.ok) { _, _ ->
positiveAction?.invoke() positiveAction?.invoke()
} }

@ -10,6 +10,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.extensions.showAlertDialog
import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.util.csv.CSVImporter import net.pokeranalytics.android.util.csv.CSVImporter
import net.pokeranalytics.android.util.csv.ImportDelegate import net.pokeranalytics.android.util.csv.ImportDelegate
@ -69,7 +70,7 @@ class ImportFragment : RealmFragment(), ImportDelegate {
private fun startImport() { private fun startImport() {
// var shouldDismissActivity = false var errorMessage: String? = null
this.importer = CSVImporter(inputStream) this.importer = CSVImporter(inputStream)
this.importer.delegate = this this.importer.delegate = this
@ -83,7 +84,7 @@ class ImportFragment : RealmFragment(), ImportDelegate {
try { try {
importer.start() importer.start()
} catch (e: ImportException) { } catch (e: ImportException) {
// shouldDismissActivity = true errorMessage = e.message
} }
val e = Date() val e = Date()
val duration = (e.time - s.time) / 1000.0 val duration = (e.time - s.time) / 1000.0
@ -92,16 +93,17 @@ class ImportFragment : RealmFragment(), ImportDelegate {
} }
test.await() test.await()
// if (shouldDismissActivity) { if (errorMessage != null) {
//
// activity?.let { showAlertDialog(
// it.setResult(ResultCode.IMPORT_UNRECOGNIZED_FORMAT.value) messageString = errorMessage,
// it.finish() positiveAction = {
// } activity?.finish()
// })
// } else {
// } } else {
importDidFinish() importDidFinish()
}
} }

@ -9,6 +9,7 @@ import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.findById
import org.apache.commons.csv.CSVRecord import org.apache.commons.csv.CSVRecord
import timber.log.Timber import timber.log.Timber
import java.text.NumberFormat
/** /**
* The various sources of CSV * The various sources of CSV
@ -127,9 +128,9 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField)
count++ count++
} }
} }
val mandatoryfields = this.fields.filter { it.optional == false } val mandatoryFields = this.fields.filter { it.optional == false }
Timber.d("source= ${this.source.name} > total fields = ${this.fields.size}, identified = $count") Timber.d("source= ${this.source.name} > total fields = ${this.fields.size}, identified = $count")
return count >= mandatoryfields.size return count >= mandatoryFields.size
} }
fun mapCustomField(record: CSVRecord, realm: Realm) { fun mapCustomField(record: CSVRecord, realm: Realm) {
@ -142,8 +143,9 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField)
val splitter = "|" val splitter = "|"
if (header.contains(splitter)) { if (header.contains(splitter)) {
val info = header.split(splitter) val info = header.split(splitter)
val typeIdentifier = header.last().toInt() val typeIdentifier = NumberFormat.getInstance().parse(header.last().toString())
val type = CustomField.Type.valueByIdentifier(typeIdentifier) Timber.d("header = $header, info = $info, id = $typeIdentifier")
val type = CustomField.Type.valueByIdentifier(typeIdentifier.toInt())
CustomField.getOrCreate(realm, info.first(), type) CustomField.getOrCreate(realm, info.first(), type)
} }
} }

@ -178,7 +178,10 @@ open class CSVImporter(istream: InputStream) {
*/ */
private fun findDescriptor(record: CSVRecord, realm: Realm): CSVDescriptor? { private fun findDescriptor(record: CSVRecord, realm: Realm): CSVDescriptor? {
ProductCSVDescriptors.all.forEach { descriptor -> val allCSVDescriptors = ProductCSVDescriptors.all
Timber.d(allCSVDescriptors.toString())
allCSVDescriptors.forEach { descriptor ->
Timber.d("descriptor = $descriptor, record = $record")
if (descriptor.matches(record)) { if (descriptor.matches(record)) {
descriptor.mapCustomField(record, realm) descriptor.mapCustomField(record, realm)
this.currentDescriptor = descriptor this.currentDescriptor = descriptor

@ -9,15 +9,16 @@ class ProductCSVDescriptors {
*/ */
val all: List<CSVDescriptor> = val all: List<CSVDescriptor> =
listOf( listOf(
ProductCSVDescriptors.pokerIncomeCash, pokerIncomeCash,
ProductCSVDescriptors.pokerBankrollTracker, pokerBankrollTracker,
ProductCSVDescriptors.runGoodCashGames, runGoodCashGames,
ProductCSVDescriptors.runGoodTournaments, runGoodTournaments,
ProductCSVDescriptors.iOSPokerAnalytics iOSPokerAnalytics
) )
val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor( private val pokerIncomeCash: CSVDescriptor
get() {
return SessionCSVDescriptor(
DataSource.POKER_INCOME, DataSource.POKER_INCOME,
false, false,
SessionField.Start("Start Time"), SessionField.Start("Start Time"),
@ -34,8 +35,11 @@ class ProductCSVDescriptors {
SessionField.Tips("Tips"), SessionField.Tips("Tips"),
SessionField.Blind("Stake") SessionField.Blind("Stake")
) )
}
val pokerBankrollTracker: CSVDescriptor = SessionCSVDescriptor( private val pokerBankrollTracker: CSVDescriptor
get() {
return SessionCSVDescriptor(
DataSource.POKER_BANKROLL_TRACKER, DataSource.POKER_BANKROLL_TRACKER,
true, true,
SessionField.Start("starttime", dateFormat = "MM/dd/yy HH:mm"), SessionField.Start("starttime", dateFormat = "MM/dd/yy HH:mm"),
@ -63,29 +67,34 @@ class ProductCSVDescriptors {
SessionField.CurrencyRate("exchangerate"), SessionField.CurrencyRate("exchangerate"),
SessionField.TableSize("tablesize") SessionField.TableSize("tablesize")
) )
}
val iOSPokerAnalytics: CSVDescriptor = SessionCSVDescriptor(
private val iOSPokerAnalytics: CSVDescriptor
get() {
return SessionCSVDescriptor(
DataSource.POKER_ANALYTICS, DataSource.POKER_ANALYTICS,
true, true,
SessionField.Start("Start date", dateFormat = "MM/dd/yy HH:mm:ss"), SessionField.Start("Start Date", dateFormat = "MM/dd/yy HH:mm:ss"),
SessionField.End("End date", dateFormat = "MM/dd/yy HH:mm:ss"), SessionField.End("End Date", dateFormat = "MM/dd/yy HH:mm:ss"),
SessionField.Break("Break", callback = { string -> SessionField.Break("Break", callback = { string ->
val number = NumberCSVField.defaultParse(string) val number = NumberCSVField.defaultParse(string)
return@Break number?.times(1000.0) return@Break number?.times(1000.0)
}), }),
SessionField.SessionType("Type"), SessionField.SessionType("Type"),
SessionField.Live("Live"), SessionField.Live("Live"),
SessionField.Buyin("Buy-in"), SessionField.NumberOfTables("Tables"),
SessionField.Buyin("Buyin"),
SessionField.CashedOut("Cashed Out"), SessionField.CashedOut("Cashed Out"),
SessionField.NetResult("Net Result"), SessionField.NetResult("Online Net"),
SessionField.Tips("Tips"), SessionField.Tips("Tips"),
SessionField.LimitType("Limit"), SessionField.LimitType("Limit"),
SessionField.Game("Game"), SessionField.Game("Game"),
SessionField.TableSize("Table size"), SessionField.TableSize("Table Size"),
SessionField.Location("Location"), SessionField.Location("Location"),
SessionField.NumberOfTables("Tables"),
SessionField.Bankroll("Bankroll"), SessionField.Bankroll("Bankroll"),
SessionField.CurrencyCode("Currency Code"), SessionField.CurrencyCode("Currency Code"),
SessionField.CurrencyRate("Currency Rate"),
SessionField.SmallBlind("Small Blind"), SessionField.SmallBlind("Small Blind"),
SessionField.BigBlind("Big Blind"), SessionField.BigBlind("Big Blind"),
SessionField.TournamentType("Tournament Type"), SessionField.TournamentType("Tournament Type"),
@ -95,8 +104,11 @@ class ProductCSVDescriptors {
SessionField.TournamentPosition("Position"), SessionField.TournamentPosition("Position"),
SessionField.Comment("Comment") SessionField.Comment("Comment")
) )
}
val runGoodTournaments: CSVDescriptor = SessionCSVDescriptor( private val runGoodTournaments: CSVDescriptor
get() {
return SessionCSVDescriptor(
DataSource.RUNGOOD, DataSource.RUNGOOD,
true, true,
SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"),
@ -119,10 +131,12 @@ class ProductCSVDescriptors {
SessionField.TournamentNumberOfPlayers("Total Players"), SessionField.TournamentNumberOfPlayers("Total Players"),
SessionField.TournamentPosition("Finished Place"), SessionField.TournamentPosition("Finished Place"),
SessionField.TournamentType("Single-Table/Multi-Table") SessionField.TournamentType("Single-Table/Multi-Table")
) )
}
val runGoodCashGames: CSVDescriptor = SessionCSVDescriptor( private val runGoodCashGames: CSVDescriptor
get() {
return SessionCSVDescriptor(
DataSource.RUNGOOD, DataSource.RUNGOOD,
false, false,
SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"), SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"),
@ -152,6 +166,7 @@ class ProductCSVDescriptors {
} }
}) })
) )
}
} }

@ -14,6 +14,7 @@ import net.pokeranalytics.android.util.extensions.getOrCreate
import net.pokeranalytics.android.util.extensions.setHourMinutes import net.pokeranalytics.android.util.extensions.setHourMinutes
import org.apache.commons.csv.CSVRecord import org.apache.commons.csv.CSVRecord
import timber.log.Timber import timber.log.Timber
import java.text.NumberFormat
import java.util.* import java.util.*
/** /**
@ -124,12 +125,14 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean
private fun parseSession(realm: Realm, record: CSVRecord): Session? { private fun parseSession(realm: Realm, record: CSVRecord): Session? {
val session = Session.newInstance(realm, this.isTournament, managed = false) val session = Session.newInstance(realm, this.isTournament, managed = false)
val intFormatter = NumberFormat.getInstance()
var startDate: Date? = null var startDate: Date? = null
var endDate: Date? = null var endDate: Date? = null
var isLive = true var isLive = true
var bankrollName = "" var bankrollName = ""
var netResult: Double? = null
var currencyCode: String? = null var currencyCode: String? = null
var currencyRate: Double? = null var currencyRate: Double? = null
var additionalBuyins = 0.0 // rebuy + addon var additionalBuyins = 0.0 // rebuy + addon
@ -167,6 +170,7 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean
} }
} }
is SessionField.CashedOut -> session.result?.cashout = field.parse(value) is SessionField.CashedOut -> session.result?.cashout = field.parse(value)
is SessionField.NetResult -> netResult = field.parse(value)
is SessionField.SessionType -> { is SessionField.SessionType -> {
Session.Type.getValueFromString(value)?.let { type -> Session.Type.getValueFromString(value)?.let { type ->
session.type = type.ordinal session.type = type.ordinal
@ -202,7 +206,7 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean
} }
is SessionField.SmallBlind -> session.cgSmallBlind = field.parse(value) is SessionField.SmallBlind -> session.cgSmallBlind = field.parse(value)
is SessionField.BigBlind -> session.cgBigBlind = field.parse(value) is SessionField.BigBlind -> session.cgBigBlind = field.parse(value)
is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value) is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value, intFormatter)
is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition = is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition =
field.parse(value)?.toInt() field.parse(value)?.toInt()
is SessionField.TournamentName -> { is SessionField.TournamentName -> {
@ -248,6 +252,10 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean
val bankroll = Bankroll.getOrCreate(realm, bankrollName, isLive, currencyCode, currencyRate) val bankroll = Bankroll.getOrCreate(realm, bankrollName, isLive, currencyCode, currencyRate)
session.bankroll = bankroll session.bankroll = bankroll
netResult?.let {
session.result?.netResult = it // need to be set after BR
}
session.result?.buyin?.let { session.result?.buyin?.let {
session.result?.buyin = it + additionalBuyins session.result?.buyin = it + additionalBuyins
} }

@ -132,7 +132,7 @@ sealed class SessionField {
data class Blind(override var header: String, override var callback: ((String) -> Pair<Double, Double>?)? = null) : data class Blind(override var header: String, override var callback: ((String) -> Pair<Double, Double>?)? = null) :
BlindCSVField BlindCSVField
data class Stakes(override var header: String) : CSVField // data class Stakes(override var header: String) : CSVField
data class Game(override var header: String) : CSVField data class Game(override var header: String) : CSVField
data class Location(override var header: String) : CSVField data class Location(override var header: String) : CSVField
data class LocationType(override var header: String) : CSVField data class LocationType(override var header: String) : CSVField

@ -1,5 +1,6 @@
package net.pokeranalytics.android package net.pokeranalytics.android
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.util.extensions.kmbFormatted import net.pokeranalytics.android.util.extensions.kmbFormatted
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
@ -33,4 +34,11 @@ class BasicUnitTest : RealmUnitTest() {
} }
@Test
fun testTableSizeParsing() {
Assert.assertEquals(TableSize.valueForLabel("1"), 1)
Assert.assertEquals(TableSize.valueForLabel("Full Ring"), 10)
Assert.assertNotEquals(TableSize.valueForLabel("pouet1"), 1)
}
} }

Loading…
Cancel
Save