diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt index 0727dd0d..c0d0775c 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt @@ -12,13 +12,14 @@ import net.pokeranalytics.android.R import net.pokeranalytics.android.ui.activity.components.ResultCode import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.util.csv.CSVImporter +import net.pokeranalytics.android.util.csv.ImportDelegate import net.pokeranalytics.android.util.csv.ImportException import timber.log.Timber import java.io.InputStream import java.util.* import kotlin.coroutines.CoroutineContext -class ImportFragment : RealmFragment() { +class ImportFragment : RealmFragment(), ImportDelegate { val coroutineContext: CoroutineContext get() = Dispatchers.Main @@ -81,4 +82,10 @@ class ImportFragment : RealmFragment() { } + // ImportDelegate + + override fun parsingCountUpdate(count: Int) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt index f4ef3174..bf517d5e 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -3,6 +3,7 @@ package net.pokeranalytics.android.util.csv import io.realm.Realm import io.realm.RealmModel import org.apache.commons.csv.CSVRecord +import timber.log.Timber /** * The various sources of CSV @@ -77,9 +78,8 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) if (index >= 0) { count++ } - } - + Timber.d("source= ${this.source.name} > total fields = ${this.fields.size}, identified = $count") return count == this.fields.size } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt index 7a1bfb90..a2772236 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt @@ -11,6 +11,10 @@ import java.io.Reader class ImportException(message: String) : Exception(message) +interface ImportDelegate { + fun parsingCountUpdate(count: Int) +} + /** * A CSVImporter is a class in charge of parsing a CSV file and processing it * When starting the parsing of a file, the instance will search for a CSVDescriptor, which describes @@ -20,6 +24,11 @@ class ImportException(message: String) : Exception(message) */ open class CSVImporter(istream: InputStream) { + /** + * The object being notified of the import progress + */ + var delegate: ImportDelegate? = null + /** * Number of commits required to commit a Realm transaction */ @@ -100,6 +109,7 @@ open class CSVImporter(istream: InputStream) { val parsingIndex = index + 1 if (parsingIndex % COMMIT_FREQUENCY == 0) { Timber.d("****** committing at $parsingIndex sessions...") + this.delegate?.parsingCountUpdate(parsingIndex) realm.commitTransaction() realm.beginTransaction() } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt index d33e719f..c70d15bb 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -1,11 +1,14 @@ package net.pokeranalytics.android.util.csv import io.realm.Realm +import io.realm.RealmModel import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.TableSize import net.pokeranalytics.android.model.TournamentType import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.model.realm.Transaction +import net.pokeranalytics.android.model.realm.TransactionType import net.pokeranalytics.android.model.utils.SessionUtils import net.pokeranalytics.android.util.extensions.getOrCreate import net.pokeranalytics.android.util.extensions.setHourMinutes @@ -83,7 +86,22 @@ sealed class SessionField { override val numberFormat: String? = null ) : NumberCSVField - data class Blind(override var header: String, override var callback: ((String) -> Pair?)? = null) : BlindCSVField + data class Rebuy( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + data class Addon( + override var header: String, + override var callback: ((String) -> Double?)? = null, + override val numberFormat: String? = null + ) : NumberCSVField + + + data class Blind(override var header: String, override var callback: ((String) -> Pair?)? = null) : + BlindCSVField + data class Game(override var header: String) : CSVField data class Location(override var header: String) : CSVField data class LocationType(override var header: String) : CSVField @@ -118,7 +136,8 @@ sealed class SessionField { /** * A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects */ -class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean, vararg elements: CSVField) : DataCSVDescriptor(source, *elements) { +class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean, vararg elements: CSVField) : + DataCSVDescriptor(source, *elements) { companion object { val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor( @@ -142,17 +161,19 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean val pokerBankrollTracker: CSVDescriptor = SessionCSVDescriptor( DataSource.POKER_BANKROLL_TRACKER, true, - SessionField.Start("starttime", dateFormat = "MM/dd/yyyy HH:mm"), - SessionField.End("endtime", dateFormat = "MM/dd/yyyy HH:mm"), + SessionField.Start("starttime", dateFormat = "MM/dd/yy HH:mm"), + SessionField.End("endtime", dateFormat = "MM/dd/yy HH:mm"), SessionField.SessionType("variant"), SessionField.Buyin("buyin"), SessionField.CashedOut("cashout"), + SessionField.Rebuy("rebuycosts"), + SessionField.Addon("addoncosts"), SessionField.Break("breakminutes"), SessionField.LimitType("limit"), SessionField.Game("game"), SessionField.Bankroll("currency"), // same as currency code - SessionField.Location("location"), - SessionField.Comment("sessionnote"), + SessionField.Location("type"), +// SessionField.Comment("sessionnote"), SessionField.Tips("expensesfromstack"), SessionField.SmallBlind("smallblind"), SessionField.BigBlind("bigblind"), @@ -209,7 +230,8 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean SessionField.LocationType("Location Type"), SessionField.Comment("Notes"), SessionField.CurrencyCode("Currency"), - SessionField.Blind("Stakes", callback = { value -> // $10/20 + SessionField.Blind("Stakes", callback = { value -> + // $10/20 value.drop(1) val blinds = value.split("/") if (blinds.size == 2) { @@ -222,10 +244,90 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean } + private enum class DataType { + TRANSACTION, + SESSION; + + companion object { + + fun valueForString(type: String): DataType? { + return when (type) { + "Deposit/Payout" -> TRANSACTION + "Cash Game", "Tournament" -> SESSION + else -> null + } + } + } + + } + /** * Parses a [record] and return an optional Session */ - override fun parseData(realm: Realm, record: CSVRecord): Session? { + override fun parseData(realm: Realm, record: CSVRecord): RealmModel? { + + var dataType: DataType? = null + val typeField = fields.firstOrNull { it is SessionField.SessionType } + typeField?.let { field -> + this.fieldMapping[field]?.let { index -> + val typeValue = record.get(index) + dataType = DataType.valueForString(typeValue) + } + } + + return when (dataType) { + DataType.TRANSACTION -> parseTransaction(realm, record) + else -> parseSession(realm, record) + } + + } + + fun parseTransaction(realm: Realm, record: CSVRecord): Transaction? { + + var date: Date? = null + var amount: Double? = null + var type: TransactionType? = null + var currencyCode: String? = null + var currencyRate: Double? = null + + fields.forEach { field -> + + val index = this.fieldMapping[field] + if (index != null) { + val value = record.get(index) + when (field) { + is SessionField.Start -> { + date = field.parse(value) + } + is SessionField.Buyin -> amount = field.parse(value) + is SessionField.SessionType -> { + type = realm.getOrCreate(value) + } + is SessionField.CurrencyCode -> currencyCode = value + is SessionField.CurrencyRate -> currencyRate = field.parse(value) + else -> { + } + } + } + } + + if (date != null && amount != null && type != null && currencyCode != null) { + + val transaction = realm.copyToRealm(Transaction()) + transaction.date = date!! + transaction.amount = amount!! + transaction.type = type + + val bankroll = Bankroll.getOrCreate(realm, currencyCode!!, currencyRate = currencyRate) + transaction.bankroll = bankroll + + return transaction + } + + return null + } + + fun parseSession(realm: Realm, record: CSVRecord): Session? { val session = Session.newInstance(realm, this.isTournament) @@ -233,6 +335,7 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean var bankrollName: String? = null var currencyCode: String? = null var currencyRate: Double? = null + var additionalBuyins = 0.0 // rebuy + addon fields.forEach { field -> @@ -246,18 +349,34 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean is SessionField.End -> { session.endDate = field.parse(value) } - is SessionField.StartTime -> { session.startDate?.setHourMinutes(value) } - is SessionField.EndTime -> { session.endDate?.setHourMinutes(value) } + is SessionField.StartTime -> { + session.startDate?.setHourMinutes(value) + } + is SessionField.EndTime -> { + session.endDate?.setHourMinutes(value) + } is SessionField.Buyin -> session.result?.buyin = field.parse(value) is SessionField.CashedOut -> session.result?.cashout = field.parse(value) + is SessionField.Addon -> additionalBuyins += field.parse(value) ?: 0.0 + is SessionField.Rebuy -> additionalBuyins += field.parse(value) ?: 0.0 is SessionField.Tips -> session.result?.tips = field.parse(value) is SessionField.Break -> { field.parse(value)?.let { session.breakDuration = it.toLong() * 60 * 1000 } } - is SessionField.Game -> session.game = realm.getOrCreate(value) - is SessionField.Location -> session.location = realm.getOrCreate(value) + is SessionField.Game -> { + if (value.isNotEmpty()) { + session.game = realm.getOrCreate(value) + } else { + } + } + is SessionField.Location -> { + if (value.isNotEmpty()) { + session.location = realm.getOrCreate(value) + } else { + } + } is SessionField.Bankroll -> bankrollName = value is SessionField.LimitType -> session.limit = Limit.getInstance(value)?.ordinal is SessionField.Comment -> session.comment = value @@ -274,31 +393,44 @@ class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean session.type = type.ordinal } } - is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition = field.parse(value)?.toInt() + is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition = + field.parse(value)?.toInt() is SessionField.TournamentName -> session.tournamentName = realm.getOrCreate(value) - is SessionField.TournamentType -> session.tournamentType = TournamentType.getValueForLabel(value)?.ordinal - is SessionField.TournamentNumberOfPlayers -> session.tournamentNumberOfPlayers = field.parse(value)?.toInt() + is SessionField.TournamentType -> session.tournamentType = + TournamentType.getValueForLabel(value)?.ordinal + is SessionField.TournamentNumberOfPlayers -> session.tournamentNumberOfPlayers = + field.parse(value)?.toInt() is SessionField.CurrencyCode -> currencyCode = value is SessionField.CurrencyRate -> currencyRate = field.parse(value) - else -> { } + else -> { + } } } } + if (bankrollName.isNullOrEmpty()) { + bankrollName = "Import" + } + session.bankroll = Bankroll.getOrCreate(realm, bankrollName ?: "Import", isLive, currencyCode, currencyRate) val startDate = session.startDate val endDate = session.endDate val net = session.result?.net + session.result?.buyin?.let { + session.result?.buyin = it + additionalBuyins + } - return if (startDate != null && endDate != null && net != null) { // valid session - val unique = SessionUtils.unicityCheck(realm, startDate, endDate, net) - if (unique) session else null - } else { // invalid session - null + if (startDate != null && endDate != null && net != null) { // valid session + if (SessionUtils.unicityCheck(realm, startDate, endDate, net)) { + return session + } } + session.deleteFromRealm() + return null + } } \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_sum.xml b/app/src/main/res/layout/bottom_sheet_sum.xml index b5451b6a..b657682d 100644 --- a/app/src/main/res/layout/bottom_sheet_sum.xml +++ b/app/src/main/res/layout/bottom_sheet_sum.xml @@ -15,7 +15,7 @@ android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" - android:text="+ 1000 $" + tools:text="+ 1000 $" app:layout_constraintEnd_toStartOf="@+id/button2" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_chainStyle="packed" @@ -30,7 +30,7 @@ android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" - android:text="+ 2000 $" + tools:text="+ 2000 $" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/button1" diff --git a/app/src/main/res/layout/fragment_import.xml b/app/src/main/res/layout/fragment_import.xml index efbb84d7..3d2e9ce5 100644 --- a/app/src/main/res/layout/fragment_import.xml +++ b/app/src/main/res/layout/fragment_import.xml @@ -5,4 +5,44 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index eecdebee..94f1afb6 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -351,4 +351,15 @@ 28sp + + + +