Merges master into hh, fix conflicts

hh
Laurent 6 years ago
commit fca74a3d5e
  1. 2
      app/build.gradle
  2. 10
      app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt
  3. 32
      app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt
  4. 6
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  5. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt
  6. 4
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  7. 12
      app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt
  8. 26
      app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt
  9. 61
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  10. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt
  11. 25
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt
  12. 19
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt
  13. 11
      app/src/main/java/net/pokeranalytics/android/ui/modules/session/SessionFragment.kt
  14. 9
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt
  15. 1
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionRow.kt
  16. 32
      app/src/main/java/net/pokeranalytics/android/util/FileUtils.kt
  17. 37
      app/src/main/java/net/pokeranalytics/android/util/Preferences.kt
  18. 13
      app/src/main/java/net/pokeranalytics/android/util/TextFormat.kt
  19. 2
      app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt
  20. 60
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt
  21. 94
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt
  22. 3
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt
  23. 272
      app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt
  24. 64
      app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt
  25. 376
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt
  26. 135
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt
  27. 118
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt
  28. 78
      app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt
  29. 5
      app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt
  30. 28
      app/src/main/res/layout/fragment_subscription.xml
  31. 25
      app/src/main/res/values-de/strings.xml
  32. 24
      app/src/main/res/values-es/strings.xml
  33. 7
      app/src/main/res/values-fr/strings.xml
  34. 24
      app/src/main/res/values-hi/strings.xml
  35. 24
      app/src/main/res/values-it/strings.xml
  36. 24
      app/src/main/res/values-ja/strings.xml
  37. 24
      app/src/main/res/values-pt/strings.xml
  38. 24
      app/src/main/res/values-ru/strings.xml
  39. 24
      app/src/main/res/values-zh/strings.xml
  40. 17
      app/src/main/res/values/strings.xml
  41. 11
      app/src/main/res/xml/provider_paths.xml
  42. 10
      app/src/test/java/net/pokeranalytics/android/BasicUnitTest.kt
  43. 4
      app/standard/release/output.json
  44. 2
      gradle/wrapper/gradle-wrapper.properties

@ -33,7 +33,7 @@ android {
applicationId "net.pokeranalytics.android"
minSdkVersion 23
targetSdkVersion 28
versionCode 80
versionCode 85
versionName "3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

@ -5,9 +5,9 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
enum class TournamentType : RowRepresentable {
MTT,
SNG;
enum class TournamentType(val label: String) : RowRepresentable {
MTT("MTT"),
SNG("SNG");
companion object {
val all : List<TournamentType>
@ -17,8 +17,8 @@ enum class TournamentType : RowRepresentable {
fun getValueForLabel(label: String) : TournamentType? {
return when (label) {
"Single-Table" -> SNG
"Multi-Table" -> MTT
SNG.label, "Single-Table" -> SNG
MTT.label, "Multi-Table" -> MTT
else -> null
}
}

@ -23,12 +23,29 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.CustomFieldRow
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SimpleRow
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
import timber.log.Timber
import java.util.*
import kotlin.collections.ArrayList
open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDataSource, RowRepresentable {
companion object {
fun getOrCreate(realm: Realm, name: String, type: Int): CustomField {
val cf = realm.where(CustomField::class.java).equalTo("name", name).findFirst()
return if (cf != null) {
cf
} else {
val customField = CustomField()
customField.name = name
customField.type = type
realm.copyToRealm(customField)
}
}
}
@Ignore
override val realmObjectClass: Class<out Identifiable> = CustomField::class.java
@ -282,6 +299,19 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
this.entriesToDelete.clear()
}
fun getOrCreateEntry(realm: Realm, value: String): CustomFieldEntry {
this.entries.find { it.value == value }?.let {
Timber.d("L>> get")
return it
} ?: run {
Timber.d("L>> create")
val entry = realm.copyToRealm(CustomFieldEntry())
entry.value = value
this.entries.add(entry)
return entry
}
}
/**
* Clean the entries if the type is not a list & remove the deleted entries from realm
*/
@ -308,7 +338,7 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa
val criteria: Criteria
get() {
return when (this.type) {
CustomField.Type.LIST.uniqueIdentifier -> Criteria.ListCustomFields(this.id)
Type.LIST.uniqueIdentifier -> Criteria.ListCustomFields(this.id)
else -> Criteria.ValueCustomFields(this.id)
}
}

@ -50,9 +50,9 @@ typealias BB = Double
open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed,
TimeFilterable, Filterable, DatedBankrollGraphEntry {
enum class Type {
CASH_GAME,
TOURNAMENT;
enum class Type(val value: String) {
CASH_GAME("Cash Game"),
TOURNAMENT("Tournament");
companion object {

@ -53,7 +53,7 @@ open class TournamentFeature : RealmObject(), NameManageable, StaticRowRepresent
}
override fun adapterRows(): List<RowRepresentable>? {
return TournamentFeature.rowRepresentation
return rowRepresentation
}
override fun charSequenceForRow(

@ -30,13 +30,14 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
companion object {
fun newInstance(realm: Realm, bankroll: Bankroll, date: Date? = null, type: TransactionType, amount: Double): Transaction {
fun newInstance(realm: Realm, bankroll: Bankroll, date: Date? = null, type: TransactionType, amount: Double, comment: String? = null): Transaction {
val transaction = realm.copyToRealm(Transaction())
transaction.date = date ?: Date()
transaction.amount = amount
transaction.type = type
transaction.bankroll = bankroll
transaction.comment = comment ?: ""
return transaction
}
@ -174,5 +175,4 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
return DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName)
}
}

@ -68,6 +68,18 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
throw PAIllegalStateException("Transaction type ${value.name} should exist in database!")
}
fun getOrCreate(realm: Realm, name: String, additive: Boolean): TransactionType {
val type = realm.where(TransactionType::class.java).equalTo("name", name).findFirst()
return if (type != null) {
type
} else {
val transactionType = TransactionType()
transactionType.name = name
transactionType.additive = additive
realm.copyToRealm(transactionType)
}
}
}
@Ignore

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_import.*
import kotlinx.coroutines.Dispatchers
@ -14,9 +15,7 @@ import net.pokeranalytics.android.R
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.IOException
import java.io.InputStream
import java.text.NumberFormat
import java.util.*
@ -76,7 +75,7 @@ class ImportFragment : RealmFragment(), ImportDelegate {
this.importer = CSVImporter(inputStream)
this.importer.delegate = this
var error = false
var exception: Exception? = null
GlobalScope.launch(coroutineContext) {
@ -86,11 +85,8 @@ class ImportFragment : RealmFragment(), ImportDelegate {
try {
importer.start()
} catch (e: ImportException) {
// shouldDismissActivity = true
error = true
} catch (e: IOException) {
error = true
} catch (e: Exception) {
exception = e
}
val e = Date()
val duration = (e.time - s.time) / 1000.0
@ -99,11 +95,17 @@ class ImportFragment : RealmFragment(), ImportDelegate {
}
test.await()
if (error && view != null) {
Snackbar.make(view!!, R.string.import_error, Snackbar.LENGTH_INDEFINITE).show()
val exceptionMessage = exception?.message
if (exceptionMessage != null && view != null) {
val message = exceptionMessage + ". " + requireContext().getString(R.string.import_error)
val snackBar = Snackbar.make(view!!, message, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction(R.string.ok) {
snackBar.dismiss()
}
val textView = snackBar.view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
textView.maxLines = 5
snackBar.show()
}
// if (shouldDismissActivity) {
//

@ -9,6 +9,8 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.FileProvider
import androidx.recyclerview.widget.LinearLayoutManager
import io.realm.Realm
import kotlinx.android.synthetic.main.fragment_settings.*
@ -17,6 +19,7 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.activity.*
import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -25,20 +28,25 @@ import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.extensions.openContactMail
import net.pokeranalytics.android.ui.extensions.openPlayStorePage
import net.pokeranalytics.android.ui.extensions.openUrl
import net.pokeranalytics.android.ui.fragment.components.BaseFragment
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.modules.datalist.DataListActivity
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.SettingRow
import net.pokeranalytics.android.util.FileUtils
import net.pokeranalytics.android.util.Preferences
import net.pokeranalytics.android.util.URL
import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.billing.AppGuard
import net.pokeranalytics.android.util.billing.IAPProducts
import net.pokeranalytics.android.util.csv.ProductCSVDescriptors
import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.util.*
class SettingsFragment : BaseFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource {
class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRepresentableDataSource {
companion object {
@ -64,6 +72,7 @@ class SettingsFragment : BaseFragment(), RowRepresentableDelegate, StaticRowRepr
private lateinit var settingsAdapterRow: RowRepresentableAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_settings, container, false)
}
@ -134,6 +143,8 @@ class SettingsFragment : BaseFragment(), RowRepresentableDelegate, StaticRowRepr
SettingRow.CONTACT_US -> parentActivity?.openContactMail(R.string.contact)
SettingRow.BUG_REPORT -> parentActivity?.openContactMail(R.string.bug_report_subject, Realm.getDefaultInstance().path)
SettingRow.CURRENCY -> CurrenciesActivity.newInstanceForResult(this@SettingsFragment, RequestCode.CURRENCY.value)
SettingRow.EXPORT_CSV_SESSIONS -> this.sessionsCSVExport()
SettingRow.EXPORT_CSV_TRANSACTIONS -> this.transactionsCSVExport()
SettingRow.FOLLOW_US -> {
when (position) {
0 -> parentActivity?.openUrl(URL.BLOG.value)
@ -154,6 +165,7 @@ class SettingsFragment : BaseFragment(), RowRepresentableDelegate, StaticRowRepr
}
}
/**
* Init UI
*/
@ -205,4 +217,49 @@ class SettingsFragment : BaseFragment(), RowRepresentableDelegate, StaticRowRepr
}
}
private fun transactionsCSVExport() {
val transactions = getRealm().where(Transaction::class.java).findAll().sort("date")
val csv = ProductCSVDescriptors.pokerAnalyticsAndroidTransactions.toCSV(transactions)
this.shareCSV(csv, "Transactions")
}
private fun sessionsCSVExport() {
val sessions = getRealm().where(Session::class.java).findAll().sort("startDate")
val csv = ProductCSVDescriptors.pokerAnalyticsAndroid.toCSV(sessions)
this.shareCSV(csv, "Sessions")
}
private fun shareCSV(content: String, dataType: String) {
try {
val fileName = "${dataType.toLowerCase()}_${Date().dateTimeFileFormatted}.csv"
FileUtils.writeFileToFilesDir(content, fileName, requireContext())
this.shareFile(fileName, "Poker Analytics Export", "CSV $dataType")
} catch (e: IOException) {
Toast.makeText(requireContext(), "File write failed: ${e.message}", Toast.LENGTH_LONG).show()
}
}
private fun shareFile(filePath: String, subject: String, body: String) {
val intent = Intent(Intent.ACTION_SEND)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val sharedFile = File(requireContext().filesDir, filePath)
val uri = FileProvider.getUriForFile(requireContext(), "net.pokeranalytics.android.fileprovider", sharedFile)
if (sharedFile.exists()) {
intent.type = "application/csv"
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
intent.putExtra(Intent.EXTRA_TEXT, body)
startActivity(Intent.createChooser(intent, "Share File"))
} else {
Timber.d("File located at $filePath does not exists")
}
}
}

@ -135,7 +135,7 @@ class CustomFieldDataFragment : EditableDataFragment(), StaticRowRepresentableDa
override fun boolForRow(row: RowRepresentable): Boolean {
return when (row) {
CustomFieldRow.COPY_ON_DUPLICATE -> customField.duplicateValue
CustomFieldRow.TYPE -> isUpdating
// CustomFieldRow.TYPE -> isUpdating // very weird
else -> super.boolForRow(row)
}
}

@ -1,8 +1,6 @@
package net.pokeranalytics.android.ui.fragment.data
import android.content.Context
import android.os.Bundle
import android.view.View
import io.realm.kotlin.where
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -22,6 +20,7 @@ import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.round
import net.pokeranalytics.android.util.extensions.shortDate
import net.pokeranalytics.android.util.extensions.sorted
import kotlin.math.abs
/**
* Custom EditableDataFragment to manage the Transaction data
@ -55,7 +54,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
}
override fun adapterRows(): List<RowRepresentable>? {
return transaction.adapterRows()
return this.transaction.adapterRows()
}
override fun charSequenceForRow(
@ -66,7 +65,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
return when (row) {
TransactionRow.BANKROLL -> this.transaction.bankroll?.name ?: NULL_TEXT
TransactionRow.TYPE -> this.transaction.type?.name ?: NULL_TEXT
TransactionRow.AMOUNT -> if (this.transaction.amount != 0.0) this.transaction.amount.round() else NULL_TEXT
TransactionRow.AMOUNT -> if (this.transaction.amount != 0.0) abs(this.transaction.amount).round() else NULL_TEXT
TransactionRow.COMMENT -> if (this.transaction.comment.isNotEmpty()) this.transaction.comment else NULL_TEXT
TransactionRow.DATE -> this.transaction.date.shortDate()
else -> super.charSequenceForRow(row, context, 0)
@ -87,7 +86,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
"data" to getRealm().sorted<TransactionType>()
)
)
TransactionRow.AMOUNT -> row.editingDescriptors(mapOf("defaultValue" to (if (this.transaction.amount != 0.0) this.transaction.amount.round() else "")))
TransactionRow.AMOUNT -> row.editingDescriptors(mapOf("defaultValue" to (if (this.transaction.amount != 0.0) abs(this.transaction.amount).round() else "")))
TransactionRow.COMMENT -> row.editingDescriptors(mapOf("defaultValue" to this.transaction.comment))
else -> super.editDescriptors(row)
}
@ -109,12 +108,19 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
super.onRowValueChanged(value, row)
rowRepresentableAdapter.refreshRow(row)
this.rowRepresentableAdapter.refreshRow(row)
this.selectNextRow(row)
}
/***
* Selects the next row to ease the data capture
*/
private fun selectNextRow(currentRow: RowRepresentable) {
if (model.primaryKey == null) { // automatically change the row for new data
if (this.model.primaryKey == null) { // automatically change the row for new data
GlobalScope.launch(Dispatchers.Main) {
delay(200)
when (row) {
when (currentRow) {
TransactionRow.BANKROLL -> onRowSelected(0, TransactionRow.TYPE)
TransactionRow.TYPE -> onRowSelected(0, TransactionRow.AMOUNT)
// TransactionRow.AMOUNT -> onRowSelected(0, TransactionRow.DATE)
@ -122,13 +128,14 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa
}
}
}
}
override fun willSaveData() {
super.willSaveData()
val additive = this.transaction.type?.additive ?: true
if (!additive) {
this.transaction.amount *= -1
this.transaction.amount = abs(this.transaction.amount) * -1
}
}

@ -35,7 +35,7 @@ fun BarLineChartBase<*>.setStyle(
this.xAxis.granularity = 1.0f
this.xAxis.textColor = ContextCompat.getColor(context, R.color.chart_default)
try {
try { // can crash for unknown reasons, same below for Y axis
val font = ResourcesCompat.getFont(context, R.font.roboto_medium)
this.xAxis.typeface = font
} catch (e: Resources.NotFoundException) {
@ -47,12 +47,8 @@ fun BarLineChartBase<*>.setStyle(
this.xAxis.isEnabled = true
when (this) {
is BarChart -> {
this.xAxis.setDrawLabels(false)
}
else -> {
this.xAxis.setDrawLabels(true)
}
is BarChart -> this.xAxis.setDrawLabels(false)
else -> this.xAxis.setDrawLabels(true)
}
// Y Axis
@ -67,7 +63,14 @@ fun BarLineChartBase<*>.setStyle(
this.axisLeft.granularity = 1.0f
this.axisLeft.textColor = ContextCompat.getColor(context, R.color.chart_default)
this.axisLeft.typeface = ResourcesCompat.getFont(context, R.font.roboto_medium)
try {
val font = ResourcesCompat.getFont(context, R.font.roboto_medium)
this.axisLeft.typeface = font
} catch (e: Resources.NotFoundException) {
Crashlytics.log(e.message)
}
this.axisLeft.labelCount =
if (small) 1 else 7 // @todo not great if interval is [0..2] for number of records as we get decimals
this.axisLeft.textSize = 12f

@ -236,17 +236,18 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
sessionHasBeenUserCustomized = true
this.sessionHasBeenUserCustomized = true
try {
currentSession.updateValue(value, row)
this.currentSession.updateValue(value, row)
} catch (e: PAIllegalStateException) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
return
}
sessionAdapter.refreshRow(row)
this.sessionAdapter.refreshRow(row)
when (row) {
SessionRow.CASHED_OUT, SessionRow.PRIZE, SessionRow.NET_RESULT, SessionRow.BUY_IN, SessionRow.TIPS,
SessionRow.START_DATE, SessionRow.END_DATE, SessionRow.BANKROLL, SessionRow.BREAK_TIME -> updateSessionUI()
SessionRow.CASHED_OUT, SessionRow.PRIZE, SessionRow.NET_RESULT,
SessionRow.BUY_IN, SessionRow.TIPS, SessionRow.START_DATE,
SessionRow.END_DATE, SessionRow.BANKROLL, SessionRow.BREAK_TIME -> updateSessionUI()
}
}

@ -29,6 +29,10 @@ enum class SettingRow : RowRepresentable {
LANGUAGE,
CURRENCY,
// Export
EXPORT_CSV_SESSIONS,
EXPORT_CSV_TRANSACTIONS,
// Data management
CUSTOM_FIELD,
BANKROLL,
@ -64,6 +68,9 @@ enum class SettingRow : RowRepresentable {
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.preferences))
rows.addAll(arrayListOf(CURRENCY))
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.export))
rows.addAll(arrayListOf(EXPORT_CSV_SESSIONS, EXPORT_CSV_TRANSACTIONS))
rows.add(
CustomizableRowRepresentable(
customViewType = RowViewType.HEADER_TITLE,
@ -99,6 +106,8 @@ enum class SettingRow : RowRepresentable {
FOLLOW_US -> R.string.follow_us
LANGUAGE -> R.string.language
CURRENCY -> R.string.currency
EXPORT_CSV_SESSIONS -> R.string.sessions_csv
EXPORT_CSV_TRANSACTIONS -> R.string.transactions_csv
GDPR -> R.string.gdpr
POKER_RUMBLE -> R.string.poker_rumble
DISCORD -> R.string.join_discord

@ -73,7 +73,6 @@ enum class TransactionRow : RowRepresentable, DefaultEditDataSource {
defaultValue,
inputType = InputType.TYPE_CLASS_NUMBER
or InputType.TYPE_NUMBER_FLAG_DECIMAL
or InputType.TYPE_NUMBER_FLAG_SIGNED
))
}
COMMENT -> {

@ -0,0 +1,32 @@
package net.pokeranalytics.android.util
import android.content.Context
import timber.log.Timber
import java.io.BufferedWriter
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter
class FileUtils {
companion object{
/***
* Writes a [string] into a file named [fileName], using a [context]
* Should be surrounded by a try/catch IOException
*/
fun writeFileToFilesDir(string: String, fileName: String, context: Context) {
Timber.d("Writing to: $fileName ...\n$string")
val file = File(context.filesDir, fileName)
val fileOutputStream = FileOutputStream(file)
fileOutputStream.write(string.toByteArray())
fileOutputStream.close()
}
}
}

@ -14,22 +14,13 @@ import java.util.*
class Preferences {
interface PreferenceKey {
var identifier: String
}
enum class DBPatch(var key: String) : PreferenceKey {
LONE_COMPUTABLE_RESULTS("loneComputableResult");
override var identifier: String = ""
get() {
return "dbpatch." + this.key
}
val identifier: String
}
enum class Keys(override var identifier: String) : PreferenceKey {
CURRENCY_CODE("CurrencyCode"),
LOCALE_CODE("LocaleCode"),
FIRST_LAUNCH("firstLaunch"),
// FIRST_LAUNCH("firstLaunch"),
STOP_SHOWING_DISCLAIMER("stopShowingDisclaimer"),
STOP_SHOWING_DUPLICATE("stopShowingDuplicate"),
STOP_SHOWING_DISCORD("stopShowingDiscord"),
@ -99,26 +90,26 @@ class Preferences {
editor.apply()
}
private fun removeKey(key: Keys, context: Context) {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val editor = preferences.edit()
editor.remove(key.identifier)
editor.apply()
}
// private fun removeKey(key: Keys, context: Context) {
// val preferences = PreferenceManager.getDefaultSharedPreferences(context)
// val editor = preferences.edit()
// editor.remove(key.identifier)
// editor.apply()
// }
fun getString(key: Keys, context: Context): String? {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
return preferences.getString(key.identifier, null)
}
fun setBoolean(key: PreferenceKey, value: Boolean, context: Context) {
private fun setBoolean(key: PreferenceKey, value: Boolean, context: Context) {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val editor = preferences.edit()
editor.putBoolean(key.identifier, value)
editor.apply()
}
fun getBoolean(
private fun getBoolean(
key: PreferenceKey,
context: Context,
defaultValue: Boolean? = false
@ -140,15 +131,15 @@ class Preferences {
return getString(Keys.ACTIVE_FILTER_ID, context)
}
fun removeActiveFilterId(context: Context) {
removeKey(Keys.ACTIVE_FILTER_ID, context)
}
// fun removeActiveFilterId(context: Context) {
// removeKey(Keys.ACTIVE_FILTER_ID, context)
// }
private fun getCurrencyCode(context: Context): String? {
return getString(Keys.CURRENCY_CODE, context)
}
fun getCurrencyLocale(context: Context): Locale? {
private fun getCurrencyLocale(context: Context): Locale? {
getCurrencyCode(context)?.let { currencyCode ->
UserDefaults.availableCurrencyLocales.filter {
Currency.getInstance(it).currencyCode == currencyCode

@ -3,6 +3,19 @@ package net.pokeranalytics.android.util
import android.content.Context
import android.graphics.Color
import androidx.core.content.ContextCompat
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
object DotFormatSymbols : DecimalFormatSymbols() {
init {
this.decimalSeparator = '.'
}
}
object CSVNumberFormat : DecimalFormat("#.######", DotFormatSymbols) {
init {
this.isGroupingUsed = false
}
}
class TextFormat(var text: String, var color: Int? = null) {

@ -64,7 +64,7 @@ object AppGuard : PurchasesUpdatedListener {
if (this.endOfUse != null) return true
return if (BuildConfig.DEBUG) {
true //false //true
false //true
} else {
this._isProUser
}

@ -16,7 +16,7 @@ enum class DataSource {
POKER_ANALYTICS,
POKER_INCOME,
POKER_BANKROLL_TRACKER,
RUNGOOD,
RUN_GOOD,
POKER_AGENT
}
@ -28,7 +28,7 @@ abstract class DataCSVDescriptor<T : Identifiable>(source: DataSource, vararg el
/**
* List of Realm object identificators
*/
val realmModelIds = mutableListOf<ObjectIdentifier>()
private val realmModelIds = mutableListOf<ObjectIdentifier>()
abstract fun parseData(realm: Realm, record: CSVRecord): T?
@ -75,6 +75,35 @@ abstract class DataCSVDescriptor<T : Identifiable>(source: DataSource, vararg el
this.realmModelIds.clear()
}
open fun prepareCSVExport() {
}
fun toCSV(dataSequence: List<T>): String {
prepareCSVExport()
val lines = mutableListOf<String>()
lines.add(this.csvHeaders)
dataSequence.forEach { data ->
val line = mutableListOf<String>()
this.fields.forEach { field ->
val string = this.toCSV(data, field)
line.add(string ?: "")
}
lines.add(line.joinToString(","))
}
return lines.joinToString("\n")
}
protected open fun toCSV(data: T, field: CSVField): String? {
return null
}
// abstract fun
}
/**
@ -85,7 +114,13 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField)
/**
* The CSVField list describing the CSV header format
*/
protected var fields: List<CSVField> = listOf()
protected var fields: MutableList<CSVField> = mutableListOf()
/**
* A list of dynamic CSVField, described in the CSV header
*/
// protected var dynamicFields: MutableList<CSVField> = mutableListOf()
/**
* The mapping of CSVField with their index in the CSV file
*/
@ -93,7 +128,7 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField)
init {
if (elements.isNotEmpty()) {
this.fields = elements.toList()
this.fields = elements.toMutableList()
}
}
@ -131,4 +166,21 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField)
return count >= mandatoryFields.size
}
/***
* Method called when the descriptor has matched a record and has been identified
* as able to parse the file
*/
open fun hasMatched(realm: Realm, record: CSVRecord) {
}
protected val csvHeaders: String
get() {
val headers = mutableListOf<String>()
this.fields.forEach {
headers.add(it.header)
}
return headers.joinToString(",")
}
}

@ -1,5 +1,8 @@
package net.pokeranalytics.android.util.csv
import net.pokeranalytics.android.model.realm.CustomField
import net.pokeranalytics.android.model.realm.TournamentFeature
import net.pokeranalytics.android.util.CSVNumberFormat
import timber.log.Timber
import java.text.DateFormat
import java.text.NumberFormat
@ -12,8 +15,6 @@ import java.util.*
*/
interface NumberCSVField: TypedCSVField<Double> {
val numberFormat: String?
companion object {
fun defaultParse(value: String) : Double? {
@ -22,10 +23,10 @@ interface NumberCSVField: TypedCSVField<Double> {
return null
}
val formatter = NumberFormat.getInstance(Locale.US)
// val formatter = NumberFormat.getInstance(Locale.US)
return try {
formatter.parse(value).toDouble()
CSVNumberFormat.parse(value).toDouble()
} catch (e: ParseException) {
Timber.d("Field > Unparseable number: $value")
null
@ -43,12 +44,20 @@ interface NumberCSVField: TypedCSVField<Double> {
return it(value)
}
val formatter = NumberFormat.getInstance(Locale.US)
// val formatter = NumberFormat.getInstance(Locale.US)
return try {
formatter.parse(value).toDouble()
CSVNumberFormat.parse(value).toDouble()
} catch (e: ParseException) {
Timber.d("Field ${header} > Unparseable number: $value")
Timber.d("Field $header > Unparseable number: $value")
null
}
}
override fun format(data: Double?): String? {
return if (data != null) {
CSVNumberFormat.format(data)
} else {
null
}
}
@ -63,25 +72,20 @@ interface IntCSVField: TypedCSVField<Int> {
}
return try {
NumberFormat.getInstance().parse(value).toInt()
CSVNumberFormat.parse(value).toInt()
} catch (e: ParseException) {
Timber.d("Field ${header} > Unparseable number: $value")
Timber.d("Field $header > Unparseable number: $value")
null
}
}
}
interface DataCSVField<T> : TypedCSVField<T> {
override fun parse(value: String): T? {
this.callback?.let {
return it(value)
override fun format(data: Int?): String? {
return if (data != null) {
CSVNumberFormat.format(data)
} else {
null
}
return null
}
}
interface DateCSVField : TypedCSVField<Date> {
@ -99,6 +103,29 @@ interface DateCSVField : TypedCSVField<Date> {
}
}
override fun format(data: Date?): String? {
return if (data != null) {
val formatter = if (dateFormat != null) SimpleDateFormat(dateFormat) else SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
formatter.format(data)
} else {
null
}
}
}
interface TournamentFeaturesCSVField : TypedCSVField<List<TournamentFeature>> {
override fun parse(value: String): List<TournamentFeature>? {
this.callback?.let {
return it(value)
}
return null
}
override fun format(data: List<TournamentFeature>?): String? {
return data?.joinToString(CSVField.separator) { it.name }
}
}
interface BlindCSVField : TypedCSVField<Pair<Double, Double>> {
@ -121,23 +148,52 @@ interface BlindCSVField : TypedCSVField<Pair<Double, Double>> {
return null
}
override fun format(data: Pair<Double, Double>?): String? {
data?.let {
val sb = CSVNumberFormat.format(data.first)
val bb = CSVNumberFormat.format(data.second)
return "$sb/$bb"
} ?: run {
return null
}
}
}
interface BooleanCSVField : TypedCSVField<Boolean> {
override fun parse(value: String): Boolean? {
return value == "1"
}
override fun format(data: Boolean?): String {
return if (data != null && data) "1" else "0"
}
}
interface CustomFieldCSVField : CSVField {
var customField: CustomField
companion object {
val separator: String = "::CF"
}
}
interface TypedCSVField<T> : CSVField {
fun parse(value: String) : T?
fun format(data: T?): String?
var callback: ((String) -> T?)?
}
interface CSVField {
companion object {
const val separator = "|"
}
val header: String
val optional: Boolean
get() {
return false
}
}

@ -112,6 +112,7 @@ open class CSVImporter(istream: InputStream) {
if (this.currentDescriptor == null) { // find descriptor
this.currentDescriptor = this.findDescriptor(record)
this.currentDescriptor?.hasMatched(realm, record)
if (this.currentDescriptor == null) {
@ -120,6 +121,7 @@ open class CSVImporter(istream: InputStream) {
}
if (this.descriptorFindingAttempts >= VALID_RECORD_ATTEMPTS_BEFORE_THROWING_EXCEPTION) {
realm.cancelTransaction()
realm.close()
throw ImportException("This type of file is not supported")
}
}
@ -150,6 +152,7 @@ open class CSVImporter(istream: InputStream) {
}
} ?: run {
realm.cancelTransaction()
realm.close()
throw ImportException("CSVDescriptor should never be null here")
}

@ -0,0 +1,272 @@
package net.pokeranalytics.android.util.csv
import net.pokeranalytics.android.model.interfaces.Identifiable
import io.realm.Realm
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.utils.DataUtils
import net.pokeranalytics.android.util.extensions.getOrCreate
import net.pokeranalytics.android.util.extensions.setHourMinutes
import org.apache.commons.csv.CSVRecord
import timber.log.Timber
import java.util.*
abstract class PACSVDescriptor<T : Identifiable>(source: DataSource,
private var isTournament: Boolean?,
vararg elements: CSVField)
: DataCSVDescriptor<T>(source, *elements) {
private var sameDaySessionCount: Int = 0
private var currentDay: String = ""
private var startInSeconds: Double = 20 * 3600.0
/**
* Parses a [record] and return an optional Session
*/
protected fun parseSession(realm: Realm, record: CSVRecord): Session? {
val isTournament = isTournament ?: false
val session = Session.newInstance(realm, isTournament, managed = false)
var startDate: Date? = null
var endDate: Date? = null
var isLive = true
var bankrollName = ""
var currencyCode: String? = null
var currencyRate: Double? = null
var additionalBuyins = 0.0 // rebuy + addon
var stackingIn: Double? = null
var stackingOut: Double? = null
this.fields.forEach { field ->
this.fieldMapping[field]?.let { index ->
val value = record.get(index)
when (field) {
is SessionField.Start -> {
startDate = field.parse(value)
if (source == DataSource.POKER_AGENT) {
if (currentDay == value) {
sameDaySessionCount++
} else {
sameDaySessionCount = 0
}
currentDay = value
} else {}
}
is SessionField.End -> {
endDate = field.parse(value)
}
is SessionField.StartTime -> {
startDate?.setHourMinutes(value)
}
is SessionField.EndTime -> {
endDate?.setHourMinutes(value)
}
is SessionField.Duration -> {
val hoursDuration = field.parse(value) ?: throw PAIllegalStateException("null duration")
if (startDate != null) {
if (field.randomTime) {
if (sameDaySessionCount == 0) {
startInSeconds = 20 * 3600.0
} else {
startInSeconds -= hoursDuration * 3600.0
}
if (startInSeconds < 0) {
startInSeconds = 20 * 3600.0
// throw PAIllegalStateException("negative start: $startDate, start = $startInSeconds, net = ${session.result?.netResult}")
}
val hour = (startInSeconds / 3600.0).toInt()
val minutes = ((startInSeconds - hour * 3600.0) / 60.0).toInt()
val formattedTime = "$hour:$minutes"
startDate?.setHourMinutes(formattedTime)
}
val seconds = (hoursDuration * 3600.0).toInt()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.add(Calendar.SECOND, seconds)
endDate = calendar.time
} else {
throw PAIllegalStateException("start date ($startDate) + hoursDuration ($hoursDuration) required")
}
}
is SessionField.Buyin -> {
val buyin = field.parse(value)
session.result?.buyin = buyin
if (session.type == Session.Type.TOURNAMENT.ordinal) {
session.tournamentEntryFee = buyin
} else {}
}
is SessionField.CashedOut -> session.result?.cashout = field.parse(value)
is SessionField.NetResult -> session.result?.netResult = field.parse(value)
is SessionField.SessionType -> {
Session.Type.getValueFromString(value)?.let { type ->
session.type = type.ordinal
}
}
is SessionField.Live -> isLive = field.parse(value) ?: false
is SessionField.NumberOfTables -> session.numberOfTables = field.parse(value) ?: 1
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()
}
}
is SessionField.LimitAndGame -> {
if (value.isNotEmpty()) {
var limitAndGame = value
for (someLimit in Limit.values()) {
if (value.startsWith(someLimit.longName)) {
session.limit = someLimit.ordinal
limitAndGame = limitAndGame.removePrefix(someLimit.longName)
break
}
}
session.game = realm.getOrCreate(limitAndGame.trim())
} else {}
}
is SessionField.Game -> {
if (value.isNotEmpty()) {
session.game = realm.getOrCreate(value)
} else {}
}
is SessionField.Location -> {
val trimmedValue = value.trim()
if (trimmedValue.isNotEmpty()) {
session.location = realm.getOrCreate(trimmedValue)
} else {}
}
is SessionField.Bankroll -> bankrollName = value
is SessionField.LimitType -> session.limit = Limit.getInstance(value)?.ordinal
is SessionField.Comment -> session.comment = value
is SessionField.Blind -> { // 1/2
val blinds = field.parse(value)
session.cgSmallBlind = blinds?.first
session.cgBigBlind = blinds?.second
}
is SessionField.SmallBlind -> {
val sb = field.parse(value)
if (sb != null && sb > 0.0) {
session.cgSmallBlind = sb
} else {}
}
is SessionField.BigBlind -> {
val bb = field.parse(value)
if (bb != null && bb > 0.0) {
session.cgBigBlind = bb
} else {}
}
is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value)
is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition =
field.parse(value)
is SessionField.TournamentName -> {
if (value.isNotEmpty()) {
session.tournamentName = realm.getOrCreate(value)
} else {}
}
is SessionField.TournamentTypeName -> session.tournamentType =
TournamentType.getValueForLabel(value)?.ordinal
is SessionField.TournamentNumberOfPlayers -> session.tournamentNumberOfPlayers =
field.parse(value)
is SessionField.TournamentEntryFee -> session.tournamentEntryFee = field.parse(value)
is SessionField.TournamentType -> session.tournamentType = field.parse(value)
is SessionField.TournamentFeatures -> {
value.split(CSVField.separator).forEach { featureName ->
val tournamentFeature: TournamentFeature = realm.getOrCreate(featureName)
session.tournamentFeatures.add(tournamentFeature)
}
}
is SessionField.CurrencyCode -> currencyCode = value
is SessionField.CurrencyRate -> currencyRate = field.parse(value)
is SessionField.StackingIn -> {
stackingIn = field.parse(value)
}
is SessionField.StackingOut -> {
stackingOut = field.parse(value)
}
is SessionField.ListCustomField -> {
val entry = field.customField.getOrCreateEntry(realm, value)
session.customFieldEntries.add(entry)
}
is SessionField.NumberCustomField -> {
val customField = field.customField
field.parse(value)?.let { number ->
Timber.d("N>> create: $number")
val entry = realm.copyToRealm(CustomFieldEntry())
entry.numericValue = number
customField.entries.add(entry)
session.customFieldEntries.add(entry)
} ?: run {
Timber.w("failed parse of numeric value: $value")
}
}
else -> {
}
}
}
}
if (bankrollName.isEmpty()) {
bankrollName = "Import"
}
val bankroll = Bankroll.getOrCreate(realm, bankrollName, isLive, currencyCode, currencyRate)
session.bankroll = bankroll
session.result?.buyin?.let {
session.result?.buyin = it + additionalBuyins
}
val net = session.result?.net
if (startDate != null && endDate != null && net != null) { // valid session
// session already in realm, we'd love not put it in Realm before doing the check
val count = DataUtils.sessionCount(realm, startDate!!, endDate!!, net)
if (count == 0) {
val managedSession = realm.copyToRealm(session)
managedSession.startDate = startDate
managedSession.endDate = endDate
if (stackingIn != null && stackingIn != 0.0) {
val type = TransactionType.getByValue(TransactionType.Value.STACKING_INCOMING, realm)
val transaction = Transaction.newInstance(realm, bankroll, startDate, type, stackingIn!!)
this.addAdditionallyCreatedIdentifiable(transaction)
}
if (stackingOut != null && stackingOut != 0.0) {
val type = TransactionType.getByValue(TransactionType.Value.STACKING_OUTGOING, realm)
val transaction = Transaction.newInstance(realm, bankroll, startDate, type, stackingOut!!)
this.addAdditionallyCreatedIdentifiable(transaction)
}
return managedSession
} else {
Timber.d("Session already exists(count=$count): sd=$startDate, ed=$endDate, net=$net")
}
} else {
Timber.d("Can't import session: sd=$startDate, ed=$endDate, net=$net")
}
return null
}
}

@ -16,7 +16,9 @@ class ProductCSVDescriptors {
pokerBankrollTracker,
runGoodCashGames,
runGoodTournaments,
pokerAnalyticsiOS
pokerAnalyticsiOS,
pokerAnalyticsAndroid,
pokerAnalyticsAndroidTransactions
)
private val pokerAgent: CSVDescriptor
@ -57,7 +59,7 @@ class ProductCSVDescriptors {
private val pokerBankrollTracker: CSVDescriptor
get() {
return SessionCSVDescriptor(
return SessionTransactionCSVDescriptor(
DataSource.POKER_BANKROLL_TRACKER,
true,
SessionField.Start("starttime", dateFormat = "MM/dd/yy HH:mm"),
@ -90,7 +92,7 @@ class ProductCSVDescriptors {
private val runGoodTournaments: CSVDescriptor
get() {
return SessionCSVDescriptor(
DataSource.RUNGOOD,
DataSource.RUN_GOOD,
true,
SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"),
SessionField.StartTime("Start Time"),
@ -111,7 +113,7 @@ class ProductCSVDescriptors {
SessionField.TournamentName("Event Name"),
SessionField.TournamentNumberOfPlayers("Total Players"),
SessionField.TournamentPosition("Finished Place"),
SessionField.TournamentType("Single-Table/Multi-Table")
SessionField.TournamentTypeName("Single-Table/Multi-Table")
)
}
@ -121,7 +123,7 @@ class ProductCSVDescriptors {
get() {
return SessionCSVDescriptor(
DataSource.RUNGOOD,
DataSource.RUN_GOOD,
false,
SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"),
SessionField.StartTime("Start Time", dateFormat = "HH:mm"),
@ -155,7 +157,7 @@ class ProductCSVDescriptors {
)
}
private val pokerAnalyticsiOS: CSVDescriptor
val pokerAnalyticsiOS: SessionCSVDescriptor
get() {
return SessionCSVDescriptor(
DataSource.POKER_ANALYTICS,
@ -179,7 +181,7 @@ class ProductCSVDescriptors {
SessionField.CurrencyRate("Currency Rate"),
SessionField.SmallBlind("Small Blind"),
SessionField.BigBlind("Big Blind"),
SessionField.TournamentType("Tournament Type"),
SessionField.TournamentTypeName("Tournament Type"),
SessionField.TournamentEntryFee("Entry fee"),
SessionField.TournamentNumberOfPlayers("Number of players"),
SessionField.TournamentPrizePool("Prize Pool"),
@ -188,6 +190,54 @@ class ProductCSVDescriptors {
)
}
val pokerAnalyticsAndroidTransactions: TransactionCSVDescriptor
get() {
return TransactionCSVDescriptor(
DataSource.POKER_ANALYTICS,
TrField.TransactionDate("Date", dateFormat = "MM/dd/yy HH:mm:ss"),
TrField.Amount("Amount"),
TrField.TransactionType("Type"),
TrField.BankrollName("Bankroll"),
TrField.Live("Live"),
TrField.CurrencyCode("Currency Code"),
TrField.CurrencyRate("Currency Rate"),
TrField.Comment("Comment")
)
}
val pokerAnalyticsAndroid: SessionCSVDescriptor
get() {
return SessionCSVDescriptor(
DataSource.POKER_ANALYTICS,
true,
SessionField.Start("Start Date", dateFormat = "MM/dd/yy HH:mm:ss"),
SessionField.End("End Date", dateFormat = "MM/dd/yy HH:mm:ss"),
SessionField.Break("Break"),
SessionField.SessionType("Type"),
SessionField.Live("Live"),
SessionField.NumberOfTables("Tables"),
SessionField.Buyin("Buyin"),
SessionField.CashedOut("Cashed Out"),
SessionField.NetResult("Online Net"),
SessionField.Tips("Tips"),
SessionField.LimitType("Limit"),
SessionField.Game("Game"),
SessionField.TableSize("Table Size"),
SessionField.Location("Location"),
SessionField.Bankroll("Bankroll"),
SessionField.CurrencyCode("Currency Code"),
SessionField.CurrencyRate("Currency Rate"),
SessionField.SmallBlind("Small Blind"),
SessionField.BigBlind("Big Blind"),
SessionField.TournamentTypeName("Tournament Type"),
SessionField.TournamentName("Tournament Name"),
SessionField.TournamentEntryFee("Entry fee"),
SessionField.TournamentNumberOfPlayers("Number of players"),
SessionField.TournamentFeatures("Tournament Features"),
SessionField.TournamentPosition("Position"),
SessionField.Comment("Comment")
)
}
}
}

@ -3,349 +3,127 @@ package net.pokeranalytics.android.util.csv
import io.realm.Realm
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.CustomField
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.DataUtils
import net.pokeranalytics.android.util.extensions.getOrCreate
import net.pokeranalytics.android.util.extensions.setHourMinutes
import org.apache.commons.csv.CSVRecord
import timber.log.Timber
import java.util.*
/**
* A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects
*/
class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean?, vararg elements: CSVField) :
DataCSVDescriptor<Identifiable>(source, *elements) {
class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg elements: CSVField) :
PACSVDescriptor<Session>(source, isTournament, *elements) {
private enum class DataType {
TRANSACTION,
SESSION;
override fun prepareCSVExport() {
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): Identifiable? {
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)
}
}
private fun parseTransaction(realm: Realm, record: CSVRecord): Transaction? {
var date: Date? = null
var type: TransactionType? = null
var currencyCode: String? = null
var currencyRate: Double? = null
// Poker Bankroll Tracker specifics
var buyin: Double? = null
var cashedOut: 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)
val realm = Realm.getDefaultInstance()
realm.where(CustomField::class.java).findAll().sort("name").forEach { customField ->
val header = customField.name + CustomFieldCSVField.separator + customField.type
val f = when (customField.type) {
CustomField.Type.LIST.uniqueIdentifier -> {
SessionField.ListCustomField(header, customField)
}
is SessionField.Buyin -> buyin = field.parse(value)
is SessionField.CashedOut -> cashedOut = field.parse(value)
is SessionField.CurrencyCode -> currencyCode = value
is SessionField.CurrencyRate -> currencyRate = field.parse(value)
else -> {
SessionField.NumberCustomField(header, customField)
}
}
this.fields.add(f)
}
}
realm.close()
val amount = if (buyin != null && buyin!! > 0) {
type = TransactionType.getByValue(TransactionType.Value.WITHDRAWAL, realm)
buyin!! * -1
} else if (cashedOut != null && cashedOut!! > 0) {
type = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm)
cashedOut
} else {
null
}
if (date != null && amount != null && type != null && currencyCode != null) {
if (DataUtils.transactionUnicityCheck(realm, date!!, amount, type)) {
val bankroll = Bankroll.getOrCreate(
realm,
currencyCode!!,
currencyCode = currencyCode!!,
currencyRate = currencyRate
)
return Transaction.newInstance(realm, bankroll, date!!, type, amount)
} else {
Timber.d("Transaction already exists")
}
} else {
Timber.d("Can't import transaction: date=$date, amount=$amount, type=${type?.name}")
}
return null
override fun parseData(realm: Realm, record: CSVRecord): Session? {
return this.parseSession(realm, record)
}
private var sameDaySessionCount: Int = 0
private var currentday: String = ""
private var startInSeconds: Double = 20 * 3600.0
override fun toCSV(data: Session, field: CSVField): String? {
private fun parseSession(realm: Realm, record: CSVRecord): Session? {
val isTournament = isTournament ?: false
val session = Session.newInstance(realm, isTournament, managed = false)
var startDate: Date? = null
var endDate: Date? = null
var isLive = true
var bankrollName = ""
var currencyCode: String? = null
var currencyRate: Double? = null
var additionalBuyins = 0.0 // rebuy + addon
var stackingIn: Double? = null
var stackingOut: Double? = null
fields.forEach { field ->
this.fieldMapping[field]?.let { index ->
val value = record.get(index)
when (field) {
is SessionField.Start -> {
startDate = field.parse(value)
if (source == DataSource.POKER_AGENT) {
if (currentday == value) {
sameDaySessionCount++
} else {
sameDaySessionCount = 0
return when (field) {
is SessionField.Start -> field.format(data.startDate)
is SessionField.End -> field.format(data.endDate)
is SessionField.Break -> field.format(data.breakDuration.toDouble())
is SessionField.SessionType -> Session.Type.values()[data.type].value
is SessionField.Live -> field.format(data.isLive)
is SessionField.NumberOfTables -> field.format(data.numberOfTables)
is SessionField.Buyin -> field.format(data.result?.buyin)
is SessionField.CashedOut -> field.format(data.result?.cashout)
is SessionField.NetResult -> field.format(data.result?.netResult)
is SessionField.Tips -> field.format(data.result?.tips)
is SessionField.LimitType -> {
data.limit?.let { limit ->
Limit.values()[limit].longName
}
currentday = value
} else {}
}
is SessionField.End -> {
endDate = field.parse(value)
is SessionField.Game -> data.game?.name
is SessionField.TableSize -> data.tableSize?.toString()
is SessionField.Location -> data.location?.name
is SessionField.Bankroll -> data.bankroll?.name
is SessionField.CurrencyCode -> data.bankroll?.currency?.code
is SessionField.CurrencyRate -> field.format(data.bankroll?.currency?.rate)
is SessionField.SmallBlind -> field.format(data.cgSmallBlind)
is SessionField.BigBlind -> field.format(data.cgBigBlind)
is SessionField.TournamentType -> field.format(data.tournamentType)
is SessionField.TournamentTypeName -> {
data.tournamentType?.let { tt ->
TournamentType.values()[tt].label
}
is SessionField.StartTime -> {
startDate?.setHourMinutes(value)
}
is SessionField.EndTime -> {
endDate?.setHourMinutes(value)
is SessionField.TournamentName -> data.tournamentName?.name
is SessionField.TournamentFeatures -> field.format(data.tournamentFeatures)
is SessionField.TournamentEntryFee -> field.format(data.tournamentEntryFee)
is SessionField.TournamentNumberOfPlayers -> field.format(data.tournamentNumberOfPlayers)
is SessionField.TournamentPosition -> field.format(data.result?.tournamentFinalPosition)
is SessionField.Comment -> data.comment
is SessionField.NumberCustomField -> {
val entry = data.customFieldEntries.find { it.customField?.id == field.customField.id }
field.format(entry?.numericValue)
}
is SessionField.Duration -> {
val hoursDuration = field.parse(value) ?: throw PAIllegalStateException("null duration")
if (startDate != null) {
if (field.randomTime) {
if (sameDaySessionCount == 0) {
startInSeconds = 20 * 3600.0
} else {
startInSeconds -= hoursDuration * 3600.0
is SessionField.ListCustomField -> {
val entry = data.customFieldEntries.find { it.customField?.id == field.customField.id }
entry?.value
}
if (startInSeconds < 0) {
startInSeconds = 20 * 3600.0
// throw PAIllegalStateException("negative start: $startDate, start = $startInSeconds, net = ${session.result?.netResult}")
else -> null
}
val hour = (startInSeconds / 3600.0).toInt()
val minutes = ((startInSeconds - hour * 3600.0) / 60.0).toInt()
val formattedTime = "$hour:$minutes"
startDate?.setHourMinutes(formattedTime)
}
val seconds = (hoursDuration * 3600.0).toInt()
val calendar = Calendar.getInstance()
calendar.time = startDate
calendar.add(Calendar.SECOND, seconds)
endDate = calendar.time
} else {
throw PAIllegalStateException("start date ($startDate) + hoursDuration ($hoursDuration) required")
}
override fun hasMatched(realm: Realm, record: CSVRecord) {
super.hasMatched(realm, record)
}
is SessionField.Buyin -> {
val buyin = field.parse(value)
session.result?.buyin = buyin
if (session.type == Session.Type.TOURNAMENT.ordinal) {
session.tournamentEntryFee = buyin
} else {
}
}
is SessionField.CashedOut -> session.result?.cashout = field.parse(value)
is SessionField.NetResult -> session.result?.netResult = field.parse(value)
is SessionField.SessionType -> {
Session.Type.getValueFromString(value)?.let { type ->
session.type = type.ordinal
}
}
is SessionField.Live -> isLive = field.parse(value) ?: false
is SessionField.NumberOfTables -> session.numberOfTables = field.parse(value) ?: 1
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()
}
}
is SessionField.LimitAndGame -> {
if (value.isNotEmpty()) {
var limitAndGame = value
for (someLimit in Limit.values()) {
if (value.startsWith(someLimit.longName)) {
session.limit = someLimit.ordinal
limitAndGame = limitAndGame.removePrefix(someLimit.longName)
break
}
}
session.game = realm.getOrCreate(limitAndGame.trim())
// identify if the headers has custom fields headers:
// create the custom fields if not existing
// adds the field to the dynamic list
val headers = record.toSet()
headers.filter { it.contains(CustomFieldCSVField.separator) }.forEach { header ->
} else {
}
}
is SessionField.Game -> {
if (value.isNotEmpty()) {
session.game = realm.getOrCreate(value)
} else {
}
}
is SessionField.Location -> {
val trimmedValue = value.trim()
if (trimmedValue.isNotEmpty()) {
session.location = realm.getOrCreate(trimmedValue)
} else {
}
}
is SessionField.Bankroll -> bankrollName = value
is SessionField.LimitType -> session.limit = Limit.getInstance(value)?.ordinal
is SessionField.Comment -> session.comment = value
is SessionField.Blind -> { // 1/2
val blinds = field.parse(value)
session.cgSmallBlind = blinds?.first
session.cgBigBlind = blinds?.second
}
is SessionField.SmallBlind -> {
val sb = field.parse(value)
if (sb != null && sb > 0.0) {
session.cgSmallBlind = sb
} else {}
}
is SessionField.BigBlind -> {
val bb = field.parse(value)
if (bb != null && bb > 0.0) {
session.cgBigBlind = bb
} else {}
}
is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value)
is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition =
field.parse(value)?.toInt()
is SessionField.TournamentName -> {
if (value.isNotEmpty()) {
session.tournamentName = realm.getOrCreate(value)
} else {
}
}
is SessionField.TournamentType -> session.tournamentType =
TournamentType.getValueForLabel(value)?.ordinal
is SessionField.TournamentNumberOfPlayers -> session.tournamentNumberOfPlayers =
field.parse(value)?.toInt()
is SessionField.TournamentEntryFee -> session.tournamentEntryFee = field.parse(value)
is SessionField.CurrencyCode -> currencyCode = value
is SessionField.CurrencyRate -> currencyRate = field.parse(value)
is SessionField.StackingIn -> {
stackingIn = field.parse(value)
}
is SessionField.StackingOut -> {
stackingOut = field.parse(value)
}
else -> {
}
}
val cfProperties = header.split(CustomFieldCSVField.separator)
if (cfProperties.size != 2) {
throw PAIllegalStateException("A custom field header is wrongly formed: $header")
}
}
val name = cfProperties.first()
val type = cfProperties.last().toInt()
val customField = CustomField.getOrCreate(realm, name, type)
if (bankrollName.isEmpty()) {
bankrollName = "Import"
val field = when (customField.type) {
CustomField.Type.LIST.uniqueIdentifier -> {
SessionField.ListCustomField(header, customField)
}
val bankroll = Bankroll.getOrCreate(realm, bankrollName, isLive, currencyCode, currencyRate)
session.bankroll = bankroll
session.result?.buyin?.let {
session.result?.buyin = it + additionalBuyins
else -> {
SessionField.NumberCustomField(header, customField)
}
val net = session.result?.net
if (startDate != null && endDate != null && net != null) { // valid session
// session already in realm, we'd love not put it in Realm before doing the check
val count = DataUtils.sessionCount(realm, startDate!!, endDate!!, net)
if (count == 0) {
val managedSession = realm.copyToRealm(session)
managedSession.startDate = startDate
managedSession.endDate = endDate
if (stackingIn != null && stackingIn != 0.0) {
val type = TransactionType.getByValue(TransactionType.Value.STACKING_INCOMING, realm)
val transaction = Transaction.newInstance(realm, bankroll, startDate, type, stackingIn!!)
this.addAdditionallyCreatedIdentifiable(transaction)
}
if (stackingOut != null && stackingOut != 0.0) {
val type = TransactionType.getByValue(TransactionType.Value.STACKING_OUTGOING, realm)
val transaction = Transaction.newInstance(realm, bankroll, startDate, type, stackingOut!!)
this.addAdditionallyCreatedIdentifiable(transaction)
val index = headers.indexOf(header) // get index in the record
if (index >= 0) {
this.fieldMapping[field] = index
}
this.fields.add(field)
return managedSession
} else {
Timber.d("Session already exists(count=$count): sd=$startDate, ed=$endDate, net=$net")
}
} else {
Timber.d("Can't import session: sd=$startDate, ed=$endDate, net=$net")
}
return null
}
}

@ -1,14 +1,39 @@
package net.pokeranalytics.android.util.csv
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.CustomField
import net.pokeranalytics.android.model.realm.TournamentFeature
import java.util.*
sealed class TransactionField {
sealed class TrField {
data class TransactionType(
data class TransactionDate(
override var header: String,
override var callback: ((String) -> net.pokeranalytics.android.model.realm.TransactionType?)? = null
) : DataCSVField<net.pokeranalytics.android.model.realm.TransactionType>
override var callback: ((String) -> Date?)? = null,
override val dateFormat: String? = null
) : DateCSVField
data class Amount(
override var header: String,
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class BankrollName(override var header: String) : CSVField
data class Live(
override var header: String,
override var callback: ((String) -> Boolean?)? = null
) : BooleanCSVField
data class CurrencyCode(override var header: String) : CSVField
data class CurrencyRate(
override var header: String,
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class TransactionType(override var header: String) : CSVField
data class Comment(override var header: String) : CSVField
}
@ -44,96 +69,91 @@ sealed class SessionField {
data class Duration(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null,
val randomTime: Boolean = false
) : NumberCSVField
data class Buyin(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class NetResult(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class CashedOut(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class Break(
override var header: String,
var unit: Int = Calendar.MINUTE,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField {
override fun parse(value: String): Double? {
private val multiplier: Int
get() {
return when (unit) {
Calendar.HOUR -> 3600 * 1000
Calendar.MINUTE -> 60 * 1000
Calendar.SECOND -> 1000
else -> throw PAIllegalStateException("Unmanaged time unit: $unit")
}
}
override fun parse(value: String): Double? {
this.callback?.let {
return it(value)
}
val v = NumberCSVField.defaultParse(value)
val multiplier = when (unit) {
Calendar.HOUR -> 3600 * 1000
Calendar.MINUTE -> 60 * 1000
Calendar.SECOND -> 1000
else -> throw PAIllegalStateException("Unmanaged time unit: $unit")
return v?.times(this.multiplier)
}
return v?.times(multiplier)
override fun format(data: Double?): String? {
return super.format(data?.div(multiplier))
}
}
data class Tips(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class SmallBlind(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class BigBlind(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class Rebuy(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class Addon(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class StackingIn(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class StackingOut(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class Blind(override var header: String, override var callback: ((String) -> Pair<Double, Double>?)? = null) :
BlindCSVField
data class Blind(override var header: String,
override var callback: ((String) -> Pair<Double, Double>?)? = null
) : BlindCSVField
data class Live(
override var header: String,
@ -156,36 +176,51 @@ sealed class SessionField {
data class TableSize(override var header: String) : CSVField
data class CurrencyCode(override var header: String) : CSVField
data class TournamentName(override var header: String) : CSVField
data class TournamentType(override var header: String) : CSVField
data class TournamentTypeName(override var header: String) : CSVField
data class TournamentFeatures(override var header: String,
override var callback: ((String) -> List<TournamentFeature>?)? = null
) : TournamentFeaturesCSVField
data class CurrencyRate(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class TournamentPosition(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberCSVField
override var callback: ((String) -> Int?)? = null
) : IntCSVField
data class TournamentNumberOfPlayers(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberCSVField
override var callback: ((String) -> Int?)? = null
) : IntCSVField
data class TournamentType(
override var header: String,
override var callback: ((String) -> Int?)? = null
) : IntCSVField
data class TournamentEntryFee(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class TournamentPrizePool(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
override var callback: ((String) -> Double?)? = null
) : NumberCSVField
data class NumberCustomField(
override val header: String,
override var customField: CustomField,
override var callback: ((String) -> Double?)? = null
) : CustomFieldCSVField, NumberCSVField
data class ListCustomField(
override val header: String,
override var customField: CustomField
) : CustomFieldCSVField
}

@ -0,0 +1,118 @@
package net.pokeranalytics.android.util.csv
import io.realm.Realm
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.model.realm.TransactionType
import net.pokeranalytics.android.model.utils.DataUtils
import org.apache.commons.csv.CSVRecord
import timber.log.Timber
import java.util.*
/**
* A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects
*/
class SessionTransactionCSVDescriptor(source: DataSource, private var isTournament: Boolean?, vararg elements: CSVField) :
PACSVDescriptor<Identifiable>(source, isTournament, *elements) {
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): Identifiable? {
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)
}
}
private fun parseTransaction(realm: Realm, record: CSVRecord): Transaction? {
var date: Date? = null
var type: TransactionType? = null
var currencyCode: String? = null
var currencyRate: Double? = null
// Poker Bankroll Tracker specifics
var buyin: Double? = null
var cashedOut: Double? = null
this.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 -> buyin = field.parse(value)
is SessionField.CashedOut -> cashedOut = field.parse(value)
is SessionField.CurrencyCode -> currencyCode = value
is SessionField.CurrencyRate -> currencyRate = field.parse(value)
else -> {
}
}
}
}
val amount = if (buyin != null && buyin!! > 0) {
type = TransactionType.getByValue(TransactionType.Value.WITHDRAWAL, realm)
buyin!! * -1
} else if (cashedOut != null && cashedOut!! > 0) {
type = TransactionType.getByValue(TransactionType.Value.DEPOSIT, realm)
cashedOut
} else {
null
}
if (date != null && amount != null && type != null && currencyCode != null) {
if (DataUtils.transactionUnicityCheck(realm, date!!, amount, type)) {
val bankroll = Bankroll.getOrCreate(
realm,
currencyCode!!,
currencyCode = currencyCode!!,
currencyRate = currencyRate
)
return Transaction.newInstance(realm, bankroll, date!!, type, amount)
} else {
Timber.d("Transaction already exists")
}
} else {
Timber.d("Can't import transaction: date=$date, amount=$amount, type=${type?.name}")
}
return null
}
}

@ -0,0 +1,78 @@
package net.pokeranalytics.android.util.csv
import io.realm.Realm
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.model.realm.TransactionType
import net.pokeranalytics.android.model.utils.DataUtils
import org.apache.commons.csv.CSVRecord
import timber.log.Timber
import java.util.*
class TransactionCSVDescriptor(source: DataSource, vararg elements: CSVField) :
DataCSVDescriptor<Transaction>(source, *elements) {
override fun parseData(realm: Realm, record: CSVRecord): Transaction? {
var date: Date? = null
var typeName: String? = null
var bankrollName: String? = null
var amount: Double? = null
var comment: String? = null
var live = false
var currencyCode: String? = null
var currencyRate: Double? = null
for (field in this.fields) {
val index = this.fieldMapping[field]
if (index != null) {
val value = record.get(index)
when (field) {
is TrField.TransactionDate -> date = field.parse(value)
is TrField.TransactionType -> typeName = value
is TrField.Amount -> amount = field.parse(value)
is TrField.BankrollName -> bankrollName = value
is TrField.Live -> live = field.parse(value) ?: true
is TrField.CurrencyCode -> currencyCode = value
is TrField.CurrencyRate -> currencyRate = field.parse(value)
is TrField.Comment -> comment = value
}
}
}
if (date != null && amount != null && typeName != null && bankrollName != null) {
val type = TransactionType.getOrCreate(realm, typeName, amount > 0)
if (DataUtils.transactionUnicityCheck(realm, date, amount, type)) {
val bankroll = Bankroll.getOrCreate(realm, bankrollName, live, currencyCode, currencyRate)
return Transaction.newInstance(realm, bankroll, date, type, amount, comment)
} else {
Timber.d("Transaction already exists")
}
} else {
Timber.d("Can't import transaction: date=$date, amount=$amount, type=$typeName")
}
return null
}
override fun toCSV(data: Transaction, field: CSVField): String? {
return when(field) {
is TrField.TransactionDate -> field.format(data.date)
is TrField.TransactionType -> data.type?.name
is TrField.Amount -> field.format(data.amount)
is TrField.BankrollName -> data.bankroll?.name
is TrField.Live -> field.format(data.bankroll?.live)
is TrField.CurrencyCode -> data.bankroll?.currency?.code
is TrField.CurrencyRate -> field.format(data.bankroll?.currency?.rate)
is TrField.Comment -> data.comment
else -> throw PAIllegalStateException("unmanaged field: $field")
}
}
}

@ -117,6 +117,11 @@ fun Date.getMonthAndYear(): String {
fun Date.getDayMonthYear(): String {
return SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()).format(this).capitalize()
}
// Returns a file friendly date time string
val Date.dateTimeFileFormatted: String
get() {
return SimpleDateFormat("yy_MM_dd_hh_mm_ss", Locale.getDefault()).format(this)
}
// Return the netDuration between two dates
fun Date.getFormattedDuration(toDate: Date): String {

@ -66,16 +66,15 @@
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@+id/message"
app:layout_constraintBottom_toTopOf="@+id/pageIndicator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.6"
app:layout_constraintHeight_percent="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="0.4" />
app:layout_constraintVertical_bias="0.5" />
<LinearLayout
android:id="@+id/pageIndicator"
@ -107,12 +106,31 @@
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="32dp"
android:layout_marginBottom="16dp"
android:text="@string/pro_purchase"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@id/conditions_scrollview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ScrollView
android:id="@+id/conditions_scrollview"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.appcompat.widget.AppCompatTextView
android:text="@string/subscription_text"
android:textColor="@color/gray"
android:paddingBottom="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

@ -1,11 +1,19 @@
<resources>
<string name="app_name">Poker Analytics</string>
<string name="session_missing_start_date">Bitte legen Sie ein Startdatum für die Sitzung fest</string>
<string name="hour">Hour</string>
<string name="hour">Stunde</string>
<string name="minute">Minute</string>
<string name="more">More</string>
<string name="lines">Lines</string>
<string name="more">Mehr</string>
<string name="lines">Linien</string>
<string name="f_unlimited">Unbegrenzt</string>
<string name="f_support">Unterstützung</string>
<string name="imported">Importiert</string>
<string name="save">Sparen</string>
<string name="pending">Steht aus</string>
<string name="from_time">Von</string>
<string name="to_time">Zu</string>
<string name="session_missing_start_date">Bitte legen Sie ein Startdatum für die Sitzung fest</string>
<string name="initial_value">Anfangswert</string>
<string name="less_then_2_values_for_display">Kann nicht gezeigt werden, weil weniger als zwei Werte anzuzeigen sind!</string>
<string name="invalid_object">Das Objekt, auf das Sie zugreifen wollen, ist ungültig.</string>
@ -14,13 +22,11 @@
<string name="pro_upgrade">Upgrade auf Pro</string>
<string name="pro_purchase">Pro werden</string>
<string name="free_trial">Gratisversion</string>
<string name="f_unlimited">Unlimited</string>
<string name="f_unlimited_desc">Verfolgen Sie Ihr gesamtes Poker-Leben und ergänzen Sie so viele Daten wie gewünscht</string>
<string name="f_offline">Zuerst offline</string>
<string name="f_offline_desc">Poker Analytics ist jederzeit verfügbar und die Daten gehören Ihnen. Hinweis: Wir werden bald Exportfunktionen hinzufügen und derzeit sind Sie verantwortlich für Sicherungen. Vielen Dank für Ihre Geduld!</string>
<string name="f_privacy">Privat</string>
<string name="f_privacy_desc">Wir sind nicht Eigentümer von Servern. Wir wissen nichts über Ihre Gewinne und Verluste.</string>
<string name="f_support">Support</string>
<string name="f_support_desc">Wir versuchen so schnell wie möglich zu antworten, auf Englisch oder Französisch!</string>
<string name="loading_please_wait">Wird geladen, bitte warten…</string>
<string name="new_report_step_type">Wählen Sie Ihren Berichtstyp</string>
@ -36,7 +42,6 @@
<string name="filter_currently_selected">Der Filter kann nicht gelöscht werden, weil er derzeit ausgewählt ist.</string>
<string name="custom_field">Benutzerdefiniertes Feld</string>
<string name="transaction_relationship_error">Dieser Posten wird derzeit in einer oder mehreren Transaktionen genutzt…Bitte löschen Sie zuerst die verknüpften Transaktionen.</string>
<string name="imported">Imported</string>
<string name="iap_session_message">Sie haben die maximale Anzahl von Gratis-Sitzungen erreicht. Bitte abonnieren Sie für eine unbegrenzte Nutzung und zögern Sie nicht, uns mehr über Ihre derzeitige Nutzererfahrung zu sagen!</string>
<string name="stacking_incoming">Eingehender Einsatz</string>
<string name="stacking_outgoing">Ausgehender Einsatz</string>
@ -47,12 +52,10 @@
<string name="suggestions">Namensvorschläge</string>
<string name="data_deleted">Daten gelöscht</string>
<string name="end_date_not_possible">Das Enddatum sollte nach dem Startdatum liegen</string>
<string name="save">Save</string>
<string name="tournament_name">Turnierbezeichnung</string>
<string name="tournament_names">Turnierbezeichnungen</string>
<string name="tournament_feature">Turniermerkmal</string>
<string name="tournament_features">Turniermerkmale</string>
<string name="pending">Pending</string>
<string name="disclaimer">Poker Analytics ist eine Poker-Tracking-App.\nSie können die App über ein Jahresabonnement für unbegrenzte Nutzung erwerben, aber Sie erhalten auch 10 Sitzungen + eine Gratisversion, um die App auszuprobieren.</string>
<string name="iunderstand">Ich verstehe</string>
<string name="tournament_feature_empty_field_error">Sie müssen diesem Turniermerkmal einen Namen geben</string>
@ -61,8 +64,6 @@
<string name="duplicate_tournament_name_error">Diese Bezeichnung existiert bereits.</string>
<string name="bankroll_relationship_error_transactions">Eine oder mehrere Transaktionen sind mit dieser Finanzierung verknüpft. Bitte löschen Sie zuerst die verknüpften Transaktion(en).</string>
<string name="operation_type">Transaktionstyp</string>
<string name="from_time">From</string>
<string name="to_time">To</string>
<string name="ordinal_suffix_first">.</string>
<string name="ordinal_suffix_second">.</string>
<string name="ordinal_suffix_third">.</string>
@ -184,7 +185,7 @@
<string name="conditioning_report_filtering_returns_no_data">Die eingegrenzte Suche ergab keine Ergebnisse</string>
<string name="confirmation">Bestätigung</string>
<string name="contact">Kontakt</string>
<string name="csv">CSV (Importvorgang fehlgeschlagen)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">One of the lines in your CSV file is not well formed. Please check your file or contact the support.</string>
<string name="currency">Währung</string>
<string name="current_month">Aktueller Monat</string>

@ -2,10 +2,6 @@
<string name="app_name">Poker Analytics</string>
<string name="session_missing_start_date">Establece una fecha de inicio para la sesión</string>
<string name="hour">Hour</string>
<string name="minute">Minute</string>
<string name="more">More</string>
<string name="lines">Lines</string>
<string name="initial_value">Valor inicial</string>
<string name="less_then_2_values_for_display">No se puede mostrar porque hay menos de dos valores para mostrar</string>
<string name="invalid_object">El objeto al que intentas acceder no es válido</string>
@ -14,13 +10,11 @@
<string name="pro_upgrade">Actualizar a la versión Pro</string>
<string name="pro_purchase">Avanzar a Pro</string>
<string name="free_trial">Prueba gratuita</string>
<string name="f_unlimited">Unlimited</string>
<string name="f_unlimited_desc">Rastrea toda tu vida de póker agregando tantos datos como quieras</string>
<string name="f_offline">Desconectado primero</string>
<string name="f_offline_desc">Poker Analytics está disponible en todo momento y los datos son tuyos. Nota: pronto agregaremos capacidad de exportación y por ahora tú estás a cargo de las copias de seguridad. ¡Gracias por tu paciencia!</string>
<string name="f_privacy">Privado</string>
<string name="f_privacy_desc">No poseemos servidores. No sabemos nada sobre tus victorias y derrotas.</string>
<string name="f_support">Support</string>
<string name="f_support_desc">Intentamos responder lo más rápido posible en inglés o francés.</string>
<string name="loading_please_wait">Cargando, espera…</string>
<string name="new_report_step_type">Selecciona tu tipo de reporte</string>
@ -37,7 +31,6 @@
<string name="custom_field">Campo personalizado</string>
<string name="transaction_relationship_error">El artículo se utiliza en una o más transacciones.…
Primero elimina las transacciones vinculadas</string>
<string name="imported">Imported</string>
<string name="iap_session_message">Has alcanzado el número máximo de sesiones gratuitas. ¡Suscríbete para un uso ilimitado y no olvides contarnos tu propia experiencia!</string>
<string name="stacking_incoming">Apuesta entrante</string>
<string name="stacking_outgoing">Apuesta saliente</string>
@ -50,12 +43,10 @@ Una suscripción se renueva automáticamente a menos que se cancele\n• Puedes
<string name="suggestions">Sugerencias de nombres</string>
<string name="data_deleted">Datos borrados</string>
<string name="end_date_not_possible">La fecha de finalización debe ser posterior a la fecha de inicio</string>
<string name="save">Save</string>
<string name="tournament_name">Nombre del torneo</string>
<string name="tournament_names">Nombres de torneos</string>
<string name="tournament_feature">Función del torneo</string>
<string name="tournament_features">Funciones de torneos</string>
<string name="pending">Pending</string>
<string name="disclaimer">Poker Analytics es una aplicación de seguimiento de póker.\n
La aplicación funciona con una suscripción anual para uso ilimitado, pero obtienes 10 sesiones, además de una prueba gratuita para probar la aplicación.</string>
<string name="iunderstand">Comprendo</string>
@ -65,8 +56,6 @@ La aplicación funciona con una suscripción anual para uso ilimitado, pero obti
<string name="duplicate_tournament_name_error">Ya existe este nombre</string>
<string name="bankroll_relationship_error_transactions">Una o más transacciones están asociadas con este fondo, elimina primero la o las transacciones vinculadas.</string>
<string name="operation_type">Tipo de transacción</string>
<string name="from_time">From</string>
<string name="to_time">To</string>
<string name="ordinal_suffix_first"> </string>
<string name="ordinal_suffix_second"> </string>
<string name="ordinal_suffix_third"> </string>
@ -88,6 +77,17 @@ La aplicación funciona con una suscripción anual para uso ilimitado, pero obti
<string name="street_flop">Flop</string>
<string name="street_turn">Turn</string>
<string name="street_river">River</string>
<string name="hour">Hora</string>
<string name="minute">Minuto</string>
<string name="more">Más</string>
<string name="lines">Líneas</string>
<string name="f_unlimited">Ilimitado</string>
<string name="f_support">Apoyo</string>
<string name="imported">Importado</string>
<string name="save">Salvar</string>
<string name="pending">Pendiente</string>
<string name="from_time">Desde</string>
<string name="to_time">A</string>
<string name="_ago">hace %s </string>
<string name="_doesn_t_have_access_to_your_contacts_alert_message">%s no tiene acceso a tus contactos y no puede encontrar el nombre de tus amigos existentes. Puedes otorgar acceso a través de las preferencias del iPhone para evitar nombres de \'usuario desconocido\'.</string>
@ -188,7 +188,7 @@ La aplicación funciona con una suscripción anual para uso ilimitado, pero obti
<string name="conditioning_report_filtering_returns_no_data">Los filtros aún no dieron ningún resultado.</string>
<string name="confirmation">Confirmación</string>
<string name="contact">Contáctanos</string>
<string name="csv">CSV (no se puede importar)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">Una de las líneas en tu archivo CSV no está bien formada. Verifica el archivo o contacta al soporte.</string>
<string name="currency">Divisa</string>
<string name="current_month">Mes en curso</string>

@ -10,7 +10,6 @@
<string name="hour">Heure</string>
<string name="minute">Minute</string>
<string name="more">Autre</string>
<string name="variant">Jeu</string>
<string name="lines">Lignes</string>
<string name="initial_value">Valeur initiale</string>
<string name="less_then_2_values_for_display">Il faut au moins deux valeurs pour afficher ce rapport!</string>
@ -56,7 +55,7 @@
<string name="update_entity" formatted="false">Mise-à-jour %s</string>
<!-- Not translated -->
<string name="address">Adresse</string>
<!-- <string name="address">Adresse</string>-->
<string name="suggestions">Suggestions de noms</string>
<string name="data_deleted">Élément effacé</string>
<string name="end_date_not_possible">La date de fin doit être après la date de début</string>
@ -173,7 +172,7 @@
<string name="conditioning_report_filtering_returns_no_data">Le filtre ne retourne aucun résultat</string>
<string name="confirmation">Confirmation</string>
<string name="contact">Contactez-nous</string>
<string name="csv">CSV (ne peut être importé)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">Une ligne de votre fichier CSV est mal formée. Veuillez vérifier votre fichier ou contacter le support.</string>
<string name="currency">Devise</string>
<string name="current_month">Ce mois-ci</string>
@ -670,7 +669,7 @@
<string name="intermediate_sub_short_title">Semi-Pro</string>
<string name="pro_sub_short_title">Pro</string>
<string name="subscription_details">Conditions de l\'abonnement</string>
<string name="subscription_text">Conditions d’utilisations concernant l’abonnement:\n- Le paiement sera facturé sur votre compte iTunes.\n- L’abonnement est renouvelé automatiquement chaque année, à moins d’avoir été désactivé au moins 24 heures avant la fin de la période de l’abonnement.\n- L’abonnement peut être géré par l’utilisateur et désactivé en allant dans les réglages de son compte après s’être abonné.\n- Le compte sera facturé pour le renouvellement de l\'abonnement dans les 24 heures précédent la fin de la période d’abonnement.\n- Un abonnement en cours ne peut être annulé.\n- Toute partie inutilisée de l\'offre gratuite, si souscrite, sera abandonnée lorsque l\'utilisateur s\'abonnera, dans les cas applicables</string>
<string name="subscription_text">Conditions d’utilisations:\n• Votre compte ne sera pas prélevé pendant toute la durée de l\'essai gratuit\n• A la fin de l\'essai gratuit, vous serez automatiquement prélevé du montant total de l\'abonnement annuel\n• Un abonnement est automatiquement renouvelé sauf si annulé\n• Votre abonnement peut-être géré en allant dans le tab \"Autre\" de l\'app, puis \"Abonnement\"</string>
<string name="privacy_policy">Politique de confidentialité</string>
<string name="bug_report_message">Nous sommes désolé, mais il y a problème&#8230;Il est possible qu\'iCloud synchronise vos données. Veuillez réessayer plus tard. Pouvez-vous nous envoyer un rapport expliquant l\'état de l\'app pour nous aider à résoudre le problème? Merci!</string>
<string name="longtap_to_duplicate">Appuyez et maintenez sur une session pour la dupliquer</string>

@ -2,10 +2,6 @@
<string name="app_name">Poker Analytics</string>
<string name="session_missing_start_date">पय सतर किए एक आरभ तख सट कर</string>
<string name="hour">Hour</string>
<string name="minute">Minute</string>
<string name="more">More</string>
<string name="lines">Lines</string>
<string name="initial_value">आरिक म</string>
<string name="less_then_2_values_for_display">रदरित नह कर सकतििसपन द कम ह!</string>
<string name="invalid_object">उदय जिसक आप परयस कर रह वह अमय ह</string>
@ -14,13 +10,11 @@
<string name="pro_upgrade"> अपगड कर</string>
<string name="pro_purchase"></string>
<string name="free_trial">त टयल</string>
<string name="f_unlimited">Unlimited</string>
<string name="f_unlimited_desc">आप जितन उतनकर आपक समसत पकर जवन कक कर</string>
<string name="f_offline">ऑफलइन पहल</string>
<string name="f_offline_desc">Poker Analytics हर समय उपलबध ह और ड आपक. नट: हम जलद ह एकसपिग कषमत और वरतमन म आप बकअप कज ह. आपकय किए धनयवद!</string>
<string name="f_privacy">ि</string>
<string name="f_privacy_desc">हमस सरवर नह. हम आपकत और हछ भ नहनत.</string>
<string name="f_support">Support</string>
<string name="f_support_desc">हम जितन जलभव ह, इिश यच म जवब दरयस करत!</string>
<string name="loading_please_wait">ड ह रह, कपयरत कर</string>
<string name="new_report_step_type">आपकिट करकर च</string>
@ -36,7 +30,6 @@
<string name="filter_currently_selected">िटर हट नह सकति वरतमन म इसआ ह.</string>
<string name="custom_field">कसटम फ</string>
<string name="transaction_relationship_error">यह आयटम एक य अधिक लनदन म उपयग क गई ह…कपय पहलिक हए लनदन हट</string>
<string name="imported">Imported</string>
<string name="iap_session_message">आप मत सत अधिकतम स तक पहच गए ह. असित उपयग किए कपय सदसयत और हमिच बति आपक अपन वरतमन अनभव कथ क महसस ह!</string>
<string name="stacking_incoming">व इनकमि</string>
<string name="stacking_outgoing">व आउटग</string>
@ -47,12 +40,10 @@
<string name="suggestions">मकरण स</string>
<string name="data_deleted"> हट गय</string>
<string name="end_date_not_possible">समिख आरभ तख कद हि</string>
<string name="save">Save</string>
<string name="tournament_name">ट क</string>
<string name="tournament_names">ट क</string>
<string name="tournament_feature">ट किषत</string>
<string name="tournament_features">ट किषत</string>
<string name="pending">Pending</string>
<string name="disclaimer">Poker Analytics एक पकर टिग एपप ह.\nएपप विक सदसयतथ एक असित उपयग कप मय करत, लिन आपक एपप क परषण करनिए 10 सतर + एक मत टयल मिलत.</string>
<string name="iunderstand"> समझ गय/गई</string>
<string name="tournament_feature_empty_field_error">आपक इस टट विषतिए एक नम द जररत ह</string>
@ -61,8 +52,6 @@
<string name="duplicate_tournament_name_error">यह नम पहलद ह.</string>
<string name="bankroll_relationship_error_transactions">इस बकरल कथ एक य अधिक लनदन ज, कपय पहलिक हए लनदन (न) क हट.</string>
<string name="operation_type">नदन करक</string>
<string name="from_time">From</string>
<string name="to_time">To</string>
<string name="ordinal_suffix_first">st</string>
<string name="ordinal_suffix_second">nd</string>
<string name="ordinal_suffix_third">rd</string>
@ -84,6 +73,17 @@
<string name="street_flop">Flop</string>
<string name="street_turn">Turn</string>
<string name="street_river">River</string>
<string name="hour"></string>
<string name="minute">िनट</string>
<string name="more">अधि</string>
<string name="lines">ि</string>
<string name="f_unlimited">असि</string>
<string name="f_support">सहय</string>
<string name="imported">आयि</string>
<string name="save">सह</string>
<string name="pending">ि</string>
<string name="from_time"></string>
<string name="to_time"></string>
<string name="_ago">%s पहल</string>
<string name="_doesn_t_have_access_to_your_contacts_alert_message">%s क आपकपर तक पहच नह और आपकन: पत नह कर सकत. कि \'अजत उपयगकर\' स बचव किए आपक iPhone पथमिकत पहच परदन करन.</string>
@ -184,7 +184,7 @@
<string name="conditioning_report_filtering_returns_no_data">िटर सई परिम नहि</string>
<string name="confirmation">करण</string>
<string name="contact">हमपरक कर</string>
<string name="csv">CSV (इमट नहि सकत)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">आपक CSV फइल मइन एक अच तरह फट नह गई ह. कपय अपनइल ज सहयतम सपरक कर.</string>
<string name="currency">कर</string>
<string name="current_month">वरतमन मह</string>

@ -2,10 +2,6 @@
<string name="app_name">Poker Analytics</string>
<string name="session_missing_start_date">Imposta una data di inizio per la sessione</string>
<string name="hour">Hour</string>
<string name="minute">Minute</string>
<string name="more">More</string>
<string name="lines">Lines</string>
<string name="initial_value">Valore iniziale</string>
<string name="less_then_2_values_for_display">Non mostrabile, perché ci sono meno di due valori da visualizzare!</string>
<string name="invalid_object">L\'oggetto a cui stai cercando di accedere non è valido</string>
@ -14,13 +10,11 @@
<string name="pro_upgrade">Passa a Pro</string>
<string name="pro_purchase">Passa a Pro</string>
<string name="free_trial">prova gratuita</string>
<string name="f_unlimited">Unlimited</string>
<string name="f_unlimited_desc">Segui tutta la tua vita da pokerista aggiungendo tutti i dati desiderati</string>
<string name="f_offline">Prima offline</string>
<string name="f_offline_desc">Poker Analytics è disponibile sempre e i dati sono tuoi. Nota: presto aggiungeremo la funzione di esportazione e al momento spetta a te fare i backup. Grazie per la pazienza!</string>
<string name="f_privacy">Privato</string>
<string name="f_privacy_desc">Non siamo proprietari dei server. Non abbiamo alcuna informazione sulle tue vincite né sulle tue perdite.</string>
<string name="f_support">Support</string>
<string name="f_support_desc">Cerchiamo di rispondere il prima possibile, in inglese o francese!</string>
<string name="loading_please_wait">Caricamento in corso, attendere…</string>
<string name="new_report_step_type">Seleziona il tuo tipo di report</string>
@ -36,7 +30,6 @@
<string name="filter_currently_selected">Il filtro non può essere eliminato, perché è al momento selezionato.</string>
<string name="custom_field">Campo personalizzato</string>
<string name="transaction_relationship_error">La voce è utilizzata in una o più transazioni…Prima elimina le transazioni collegate</string>
<string name="imported">Imported</string>
<string name="iap_session_message">Hai raggiunto il limite massimo di sessioni gratuite. Abbonati per avere l\'uso illimitato, e non esitare a contattarci per farci sapere come ti trovi con la versione illimitata!</string>
<string name="stacking_incoming">Staking in entrata</string>
<string name="stacking_outgoing">Staking in uscita</string>
@ -47,12 +40,10 @@
<string name="suggestions">Suggerimenti di nomi</string>
<string name="data_deleted">Dati eliminati</string>
<string name="end_date_not_possible">La data di fine deve essere successiva alla data di inizio</string>
<string name="save">Save</string>
<string name="tournament_name">Nome torneo</string>
<string name="tournament_names">Nomi torneo</string>
<string name="tournament_feature">Caratteristica torneo</string>
<string name="tournament_features">Caratteristiche torneo</string>
<string name="pending">Pending</string>
<string name="disclaimer">Poker Analytics è un\'app per monitorare le proprie partite a poker.\nL\'app funziona con un abbonamento annuale per uso illimitato, ma ogni utente riceve 10 sessioni + una prova gratuita per testare l\'app.</string>
<string name="iunderstand">Ho compreso</string>
<string name="tournament_feature_empty_field_error">Assegna un nome a questa caratteristica di torneo</string>
@ -61,8 +52,6 @@
<string name="duplicate_tournament_name_error">Questo nome esiste già.</string>
<string name="bankroll_relationship_error_transactions">Una o più transazioni sono associate a questa posta di gioco. Prima elimina una o più transazioni associate.</string>
<string name="operation_type">Tipo di transazione</string>
<string name="from_time">From</string>
<string name="to_time">To</string>
<string name="ordinal_suffix_first">°</string>
<string name="ordinal_suffix_second">°</string>
<string name="ordinal_suffix_third">°</string>
@ -84,6 +73,17 @@
<string name="street_flop">Flop</string>
<string name="street_turn">Turn</string>
<string name="street_river">River</string>
<string name="hour">Ora</string>
<string name="minute">Minuto</string>
<string name="more">Di Più</string>
<string name="lines">Linee</string>
<string name="f_unlimited">Illimitato</string>
<string name="f_support">Supporto</string>
<string name="imported">Importato</string>
<string name="save">Salva</string>
<string name="pending">In attesa di</string>
<string name="from_time">A partire dal</string>
<string name="to_time">Per</string>
<string name="_ago">%s fa</string>
<string name="_doesn_t_have_access_to_your_contacts_alert_message">%s non ha accesso ai tuoi contatti e non può recuperare il nome dei tuoi amici esistenti. Potresti consentire l\'accesso attraverso le preferenze di iPhone per evitare qualsiasi nome di \&quot;utente sconosciuto\&quot;.</string>
@ -184,7 +184,7 @@
<string name="conditioning_report_filtering_returns_no_data">Il filtraggio non ha dato alcun risultato</string>
<string name="confirmation">Conferma</string>
<string name="contact">Contattaci</string>
<string name="csv">CSV (non si può importare)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">Una delle linee nel tuo file CSV non è ben formata. Controlla il tuo file o contatta l\'assistenza.</string>
<string name="currency">Valuta</string>
<string name="current_month">Mese corrente</string>

@ -2,10 +2,6 @@
<string name="app_name">Poker Analytics</string>
<string name="session_missing_start_date">セッションの開始日を設定してください</string>
<string name="hour">Hour</string>
<string name="minute">Minute</string>
<string name="more">More</string>
<string name="lines">Lines</string>
<string name="initial_value">初期値</string>
<string name="less_then_2_values_for_display">表示できません。表示するには最低2つの値が必要です。</string>
<string name="invalid_object">アクセスしようとしているオブジェクトは無効です</string>
@ -14,13 +10,11 @@
<string name="pro_upgrade">プロにアップグレードする</string>
<string name="pro_purchase">プロになる</string>
<string name="free_trial">無料お試し</string>
<string name="f_unlimited">Unlimited</string>
<string name="f_unlimited_desc">お好きなだけデータを追加し、ポーカーライフをトラッキングしましょう</string>
<string name="f_offline">オフラインファースト</string>
<string name="f_offline_desc">Poker Analyticsはいつでもご利用できます。留意点:近々エクスポート機能が追加されます。現在はバックアップ機能をご使用ください。ご理解のほど、お願い申し上げます。</string>
<string name="f_privacy">プライベート</string>
<string name="f_privacy_desc">私達はサーバーを所有しておらず、ユーザー様の勝敗については何も把握しておりません。</string>
<string name="f_support">Support</string>
<string name="f_support_desc">出来るかぎり早く英語またはフランス語でご返信するよう努めます。</string>
<string name="loading_please_wait">ロード中です。しばらくお待ちください…</string>
<string name="new_report_step_type">報告の種類を選んでください</string>
@ -36,7 +30,6 @@
<string name="filter_currently_selected">フィルターは現在選択中のため削除できません。</string>
<string name="custom_field">カスタムフィールド</string>
<string name="transaction_relationship_error">アイテムは一つまたは複数の取引で使用中です。…まずは該当する取引を削除してください。</string>
<string name="imported">Imported</string>
<string name="iap_session_message">無料セッションの利用限度に達しました。無制限利用のサブスクリプションを購
入し、現在の感想をお聞かせください。</string>
<string name="stacking_incoming">ステーキングを受け取っています</string>
@ -50,12 +43,10 @@
<string name="suggestions">名前の候補</string>
<string name="data_deleted">データは削除されました</string>
<string name="end_date_not_possible">終了日は開始日以降を選んでください</string>
<string name="save">Save</string>
<string name="tournament_name">トーナメント名</string>
<string name="tournament_names">トーナメント名</string>
<string name="tournament_feature">トーナメントの機能</string>
<string name="tournament_features">トーナメントの機能</string>
<string name="pending">Pending</string>
<string name="disclaimer">Poker Analyticsはポーカーのトラッキングアプリです。\nアプリは年間サブスクリプションで無制限でご利用できますが、アプリのお試し用に10のセッションと無料期間があります。</string>
<string name="iunderstand">分かりました</string>
<string name="tournament_feature_empty_field_error">このトーナメントの機能に名前をつけてください</string>
@ -65,8 +56,6 @@
<string name="bankroll_relationship_error_transactions">このバンクロールは一つまたは複数の取引に賭けられています。まずは該当す
る取引を削除してください。</string>
<string name="operation_type">取引の種類</string>
<string name="from_time">From</string>
<string name="to_time">To</string>
<string name="ordinal_suffix_first">st</string>
<string name="ordinal_suffix_second">nd</string>
<string name="ordinal_suffix_third">rd</string>
@ -88,6 +77,17 @@
<string name="street_flop">Flop</string>
<string name="street_turn">Turn</string>
<string name="street_river">River</string>
<string name="hour"></string>
<string name="minute"></string>
<string name="more">もっと</string>
<string name="lines"></string>
<string name="f_unlimited">無制限</string>
<string name="f_support">サポート</string>
<string name="imported">輸入</string>
<string name="save">セーブ</string>
<string name="pending">保留中</string>
<string name="from_time">から</string>
<string name="to_time"></string>
<string name="_ago">%s 前</string>
<string name="_doesn_t_have_access_to_your_contacts_alert_message">%s では、あなたの連絡先にアクセスできないため、既存のお友達の名前を取得できません。「不明なユーザー」名を回避するには、iPhone のユーザー設定からアクセス権限を付与してください。</string>
@ -188,7 +188,7 @@
<string name="conditioning_report_filtering_returns_no_data">フィルタリングでは結果が戻されませんでした</string>
<string name="confirmation">確認</string>
<string name="contact">ご連絡ください</string>
<string name="csv">CSV (インポートできません)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">CSV ファイルのいずれかの行が正しく形成されていません。ファイルをご確認いただくが、サポートまでお問い合わせください。</string>
<string name="currency">通貨</string>
<string name="current_month">今月</string>

@ -2,10 +2,6 @@
<string name="app_name">Poker Analytics</string>
<string name="session_missing_start_date">Defina uma data de início para a sessão</string>
<string name="hour">Hour</string>
<string name="minute">Minute</string>
<string name="more">More</string>
<string name="lines">Lines</string>
<string name="initial_value">Valor inicial</string>
<string name="less_then_2_values_for_display">Não é possível exibir porque há menos de dois valores para exibir!</string>
<string name="invalid_object">O objeto que você está tentando acessar é inválido</string>
@ -14,13 +10,11 @@
<string name="pro_upgrade">Fazer upgrade para Pro</string>
<string name="pro_purchase">Seja Pro</string>
<string name="free_trial">avaliação gratuita</string>
<string name="f_unlimited">Unlimited</string>
<string name="f_unlimited_desc">Monitore toda a sua vida no pôquer com quantos dados você quiser</string>
<string name="f_offline">Offline primeiro</string>
<string name="f_offline_desc">O Poker Analytics está disponível sempre e os dados são seus. Nota: Adicionaremos a capacidade de exportar em breve e você é responsável por seus backups no momento. Agradecemos a paciência!</string>
<string name="f_privacy">Privado</string>
<string name="f_privacy_desc">Não temos servidores. Não sabemos nada sobre suas vitórias e derrotas.</string>
<string name="f_support">Support</string>
<string name="f_support_desc">Tentamos responder o mais rápido possível, em inglês ou francês!</string>
<string name="loading_please_wait">Carregando, aguarde…</string>
<string name="new_report_step_type">Selecione seu tipo de relatório</string>
@ -36,7 +30,6 @@
<string name="filter_currently_selected">O filtro não pode ser excluído porque está selecionado no momento.</string>
<string name="custom_field">Campo personalizado</string>
<string name="transaction_relationship_error">O item é usado em uma ou mais transações…Exclua as transações vinculadas primeiro</string>
<string name="imported">Imported</string>
<string name="iap_session_message">Você atingiu o número máximo de sessões gratuitas. Assine para usar sem limites e não deixe de nos contar o que você está achando até agora da experiência!</string>
<string name="stacking_incoming">Staking chegando</string>
<string name="stacking_outgoing">Staking saindo</string>
@ -47,12 +40,10 @@
<string name="suggestions">Sugestões de nome</string>
<string name="data_deleted">Dados excluídos</string>
<string name="end_date_not_possible">A data de término deve ser depois da data de início</string>
<string name="save">Save</string>
<string name="tournament_name">Nome do torneio</string>
<string name="tournament_names">Nomes dos torneios</string>
<string name="tournament_feature">Função do torneio</string>
<string name="tournament_features">Funções do torneio</string>
<string name="pending">Pending</string>
<string name="disclaimer">O Poker Analytics é um app de monitoramento.\nO app funciona com uma assinatura anual com uso ilimitado, mas você tem 10 sessões + teste grátis para testar o app.</string>
<string name="iunderstand">Entendi</string>
<string name="tournament_feature_empty_field_error">Você precisa dar um nome para esta função do torneio</string>
@ -61,8 +52,6 @@
<string name="duplicate_tournament_name_error">Este nome já existe.</string>
<string name="bankroll_relationship_error_transactions">Uma ou mais transações associadas com esta banca. Exclua as transações vinculadas primeiro.</string>
<string name="operation_type">Tipo de transação</string>
<string name="from_time">From</string>
<string name="to_time">To</string>
<string name="ordinal_suffix_first">º</string>
<string name="ordinal_suffix_second">º</string>
<string name="ordinal_suffix_third">º</string>
@ -84,6 +73,17 @@
<string name="street_flop">Flop</string>
<string name="street_turn">Turn</string>
<string name="street_river">River</string>
<string name="hour">Hora</string>
<string name="minute">Minuto</string>
<string name="more">Mais</string>
<string name="lines">Linhas</string>
<string name="f_unlimited">Ilimitado</string>
<string name="f_support">Apoio, suporte</string>
<string name="imported">Importado</string>
<string name="save">Salve</string>
<string name="pending">Pendente</string>
<string name="from_time">De</string>
<string name="to_time">Para</string>
<string name="_ago">%s atrás</string>
<string name="_doesn_t_have_access_to_your_contacts_alert_message">O %s não possui acesso à lista de contatos e não pode obter os nomes dos seus amigos. Você pode permitir o acesso nos ajustes dos iPhone para evitar exibir \'usuário desconhecido\' no lugar do nome.</string>
@ -184,7 +184,7 @@
<string name="conditioning_report_filtering_returns_no_data">O filtro não apresentou nenhum resultado</string>
<string name="confirmation">Confirmação</string>
<string name="contact">Contate-nos</string>
<string name="csv">CSV (Não pode ser importado)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">Uma das linhas do seu arquivo CSV não está no formato correto. Por favor, verifique o seu arquivo ou contate o suporte.</string>
<string name="currency">Moeda</string>
<string name="current_month">Mês atual</string>

@ -2,10 +2,6 @@
<string name="app_name">Poker Analytics</string>
<string name="session_missing_start_date">Пожалуйста, установите дату начала сессии</string>
<string name="hour">Hour</string>
<string name="minute">Minute</string>
<string name="more">More</string>
<string name="lines">Lines</string>
<string name="initial_value">Начальное значение</string>
<string name="less_then_2_values_for_display">Невозможно показать, потому что для отображения меньше двух значений!</string>
<string name="invalid_object">Объект, к которому пытаются получить доступ, недействителен.</string>
@ -14,13 +10,11 @@
<string name="pro_upgrade">Повысить категорию до Pro</string>
<string name="pro_purchase">Перейти на Pro</string>
<string name="free_trial">бесплатное опробование</string>
<string name="f_unlimited">Unlimited</string>
<string name="f_unlimited_desc">Отслеживайте всю свою жизнь в покере, добавляя столько данных, сколько хотите</string>
<string name="f_offline">Сначала автономно</string>
<string name="f_offline_desc">Poker Analytics доступно в любое время, а данные - ваши. Примечание: вскоре мы добавим возможности экспорта, но в настоящее время вы отвечаете за резервное копирование. Спасибо за ваше терпение!</string>
<string name="f_privacy">Частное</string>
<string name="f_privacy_desc">Мы не владеем серверами. Мы ничего не знаем о ваших выигрышах и проигрышах.</string>
<string name="f_support">Support</string>
<string name="f_support_desc">Мы стараемся ответить как можно быстрее, по-английски или по-французски!</string>
<string name="loading_please_wait">Загрузка, пожалуйста, подождите…</string>
<string name="new_report_step_type">Выберите свой тип отчёта</string>
@ -36,7 +30,6 @@
<string name="filter_currently_selected">Фильтр не может быть удален, так как он выбран в данный момент.</string>
<string name="custom_field">Пользовательское поле</string>
<string name="transaction_relationship_error">Элемент используется в одной или нескольких транзакциях…Пожалуйста, сначала удалите связанные транзакции</string>
<string name="imported">Imported</string>
<string name="iap_session_message">Вы достигли максимального количества бесплатных сессий. Пожалуйста, подпишитесь на неограниченное использование и без колебаний сообщайте нам, что вы думаете о своих текущих впечатлениях!</string>
<string name="stacking_incoming">Ставка прибывает</string>
<string name="stacking_outgoing">Ставка убывает</string>
@ -47,12 +40,10 @@
<string name="suggestions">Предложения по наименованию</string>
<string name="data_deleted">Данные удалены</string>
<string name="end_date_not_possible">Дата окончания должна быть после даты начала</string>
<string name="save">Save</string>
<string name="tournament_name">Название турнира</string>
<string name="tournament_names">Названия турниров</string>
<string name="tournament_feature">Функция турнира</string>
<string name="tournament_features">Функции турниров</string>
<string name="pending">Pending</string>
<string name="disclaimer">Poker Analytics - это приложение для отслеживания покера.\nПриложение работает по годовой подписке на неограниченное использование, но вы получаете 10 сессий + бесплатную пробную версию для тестирования приложения.</string>
<string name="iunderstand">Вас понял(а)</string>
<string name="tournament_feature_empty_field_error">Нужно дать название этой турнирной функции</string>
@ -61,8 +52,6 @@
<string name="duplicate_tournament_name_error">Это название уже существует.</string>
<string name="bankroll_relationship_error_transactions">С этой финансовой операцией связана одна или несколько транзакций, пожалуйста, сначала удалите связанную(ые) транзакцию(ы).</string>
<string name="operation_type">Тип транзакции</string>
<string name="from_time">From</string>
<string name="to_time">To</string>
<string name="ordinal_suffix_first"></string>
<string name="ordinal_suffix_second"></string>
<string name="ordinal_suffix_third"></string>
@ -84,6 +73,17 @@
<string name="street_flop">Flop</string>
<string name="street_turn">Turn</string>
<string name="street_river">River</string>
<string name="hour">Час</string>
<string name="minute">минут</string>
<string name="more">Больше</string>
<string name="lines">линии</string>
<string name="f_unlimited">неограниченный</string>
<string name="f_support">Поддержка</string>
<string name="imported">импортный</string>
<string name="save">Сохранить</string>
<string name="pending">В ожидании</string>
<string name="from_time">От</string>
<string name="to_time">к</string>
<string name="_ago">%s назад</string>
<string name="_doesn_t_have_access_to_your_contacts_alert_message">%s не имеет доступа к адресной книге и не может определить имена выбранных вами друзей. Предоставьте доступ к контактам в настройках телефона чтобы исключить имена \'неизвестный пользователь\'</string>
@ -184,7 +184,7 @@
<string name="conditioning_report_filtering_returns_no_data">Не найдено результатов после фильтрации</string>
<string name="confirmation">Подтверждение</string>
<string name="contact">Напишите нам</string>
<string name="csv">CSV (Импорт невозможен)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">Одна из строк в вашем файле CSV отформатирована некорректно. Пожалуйста проверьте ваш файл или свяжитесь со службой поддержки.</string>
<string name="currency">Валюта</string>
<string name="current_month">Текущий месяц</string>

@ -2,10 +2,6 @@
<string name="app_name">Poker Analytics</string>
<string name="session_missing_start_date">请设置该进程的开始日期</string>
<string name="hour">Hour</string>
<string name="minute">Minute</string>
<string name="more">More</string>
<string name="lines">Lines</string>
<string name="initial_value">初始值</string>
<string name="less_then_2_values_for_display">不能显示,因为要显示的值少于两个!</string>
<string name="invalid_object">您试图访问的对象无效</string>
@ -14,13 +10,11 @@
<string name="pro_upgrade">升级到专业版</string>
<string name="pro_purchase">获取专业版</string>
<string name="free_trial">免费试用</string>
<string name="f_unlimited">Unlimited</string>
<string name="f_unlimited_desc">通过添加尽可能多的数据来追踪您的所有扑克游戏</string>
<string name="f_offline">先离线</string>
<string name="f_offline_desc">Poker Analytics随时可用且该数据属于你。注意:我们将很快添加导出功能,您目前负责备份。谢谢你的耐心!</string>
<string name="f_privacy">私人</string>
<string name="f_privacy_desc">我们没有服务器。我们对你的输赢一无所知。</string>
<string name="f_support">Support</string>
<string name="f_support_desc">我们尽快用英语或法语回答!</string>
<string name="loading_please_wait">正在加载,请等待…</string>
<string name="new_report_step_type">选择您的报告类型</string>
@ -36,7 +30,6 @@
<string name="filter_currently_selected">无法删除该筛选条件,因为它目前已被选取。</string>
<string name="custom_field">自定义字段</string>
<string name="transaction_relationship_error">该项目用于一个或多个交易…请先删除链接的交易</string>
<string name="imported">Imported</string>
<string name="iap_session_message">您已经达到了免费进程的最大数量。请订阅无限制使用,不要犹豫,告诉我们您对目前的体验感觉如何!</string>
<string name="stacking_incoming">接收的赌注</string>
<string name="stacking_outgoing">发出的赌注</string>
@ -47,12 +40,10 @@
<string name="suggestions">命名建议</string>
<string name="data_deleted">数据已删除</string>
<string name="end_date_not_possible">结束日期应在开始日期后</string>
<string name="save">Save</string>
<string name="tournament_name">锦标赛名称</string>
<string name="tournament_names">锦标赛名称</string>
<string name="tournament_feature">锦标赛功能</string>
<string name="tournament_features">锦标赛功能</string>
<string name="pending">Pending</string>
<string name="disclaimer">Poker Analytics是一款扑克追踪应用。该应用可包年订阅获取无限制使用,但你可以获得10次进程+1次免费试用来测试本应用。</string>
<string name="iunderstand">我理解</string>
<string name="tournament_feature_empty_field_error">您需要为此锦标赛功能指定一个名称</string>
@ -61,8 +52,6 @@
<string name="duplicate_tournament_name_error">此名称已存在。</string>
<string name="bankroll_relationship_error_transactions">一个或多个交易与此资金相关,请先删除相关交易。</string>
<string name="operation_type">交易类型</string>
<string name="from_time">From</string>
<string name="to_time">To</string>
<string name="ordinal_suffix_first">.</string>
<string name="ordinal_suffix_second">.</string>
<string name="ordinal_suffix_third">.</string>
@ -83,6 +72,17 @@
<string name="street_flop">Flop</string>
<string name="street_turn">Turn</string>
<string name="street_river">River</string>
<string name="hour">小时</string>
<string name="minute">分钟</string>
<string name="more">更多</string>
<string name="lines">线数</string>
<string name="f_unlimited">无限</string>
<string name="f_support">支持</string>
<string name="imported">进口的</string>
<string name="save">保存</string>
<string name="pending">待定</string>
<string name="from_time"></string>
<string name="to_time"></string>
<string name="_ago">%s前</string>
<string name="_doesn_t_have_access_to_your_contacts_alert_message">%s不能访问你的联系人且不能检索你的现有好友姓名。通过iPhone首选项可授权防止任何\'陌生用户\'名访问。</string>
@ -169,7 +169,7 @@
<string name="comparison">比较</string>
<string name="confirmation">确认</string>
<string name="contact">联系我们</string>
<string name="csv">CSV(无法导入)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">CSV文件中的线之一不合适。请核查你的文件或联系支持团队。</string>
<string name="currency">货币</string>
<string name="current_month">本月</string>

@ -11,7 +11,6 @@
<string name="hour">Hour</string>
<string name="minute">Minute</string>
<string name="more">More</string>
<string name="variant">Variant</string>
<string name="lines">Lines</string>
<string name="initial_value">Initial Value</string>
<string name="less_then_2_values_for_display">Can\'t show because there is less than two values to display!</string>
@ -45,11 +44,12 @@
<string name="transaction_relationship_error">The item is used in one or more transactions&#8230;Please delete the linked transactions first</string>
<string name="imported">Imported</string>
<string name="iap_session_message">You\'ve reached the maximum number of free sessions. Please subscribe for unlimited use and don\'t hesitate to tell us how you feel about your current experience!</string>
<string name="stacking_incoming">Stacking incoming</string>
<string name="stacking_outgoing">Stacking outgoing</string>
<string name="stacking_incoming">Staking incoming</string>
<string name="stacking_outgoing">Staking outgoing</string>
<string name="import_error">There has been an issue with the import. Please check out your file or contact the support!</string>
<string name="subscription_text">Subscription terms of use:\n• When subscribing, the free trial prevents you from being charged until the period ends\n• At the end of the free trial, you will be automatically charged the yearly subscription amount\n• A subscription automatically renews unless canceled\n• You can manage your subscription by going in the app "More" tab, then "Subscription"</string>
<string name="show_fullscreen">Show full screen</string>
<string name="address">Address</string>
<string name="suggestions">Naming suggestions</string>
<string name="data_deleted">Data deleted</string>
<string name="end_date_not_possible">The end date should be after the start date</string>
@ -67,7 +67,6 @@
<string name="duplicate_tournament_name_error">This name already exists.</string>
<string name="bankroll_relationship_error_transactions">One or more transactions are associated with this bankroll, please delete the linked transaction(s) first.</string>
<string name="operation_type">Transaction type</string>
<string name="from_time">From</string>
<string name="to_time">To</string>
<string name="ordinal_suffix_first">st</string>
@ -79,8 +78,6 @@
<string name="new_tournament_feature">New tournament feature</string>
<string name="new_filter">New filter</string>
<!-- Translated -->
<string name="_ago">%s ago</string>
@ -182,7 +179,7 @@
<string name="conditioning_report_filtering_returns_no_data">Filtering did not yield any results</string>
<string name="confirmation">Confirmation</string>
<string name="contact">Contact us</string>
<string name="csv">CSV (Cannot be imported)</string>
<string name="csv">CSV</string>
<string name="csv_bad_row_error_message">One of the lines in your CSV file is not well formed. Please check your file or contact the support.</string>
<string name="currency">Currency</string>
<string name="current_month">Current month</string>
@ -666,7 +663,6 @@
<string name="intermediate_sub_short_title">Semi-Pro</string>
<string name="pro_sub_short_title">Pro</string>
<string name="subscription_details">Subscription terms</string>
<string name="subscription_text">Subscription terms of use:\n• Payment will be charged to iTunes Account at confirmation of purchase\n• Subscription automatically renews unless auto-renew is turned off at least 24-hours before the end of the current period\n• Account will be charged for renewal within 24-hours prior to the end of the current period, and identify the cost of the renewal\n• Subscriptions may be managed by the user and auto-renewal may be turned off by going to the user\'s Account Settings after purchase\n• Any unused portion of a free trial period, if offered, will be forfeited when the user purchases a subscription to that publication, where applicable</string>
<string name="privacy_policy">Privacy policy</string>
<string name="bug_report_message">We\'re truly sorry, but something is wrong here&#8230;You may be waiting for iCloud sync. Please wait and retry later. Would you mind sending us a report explaining your current state to help us solve this issue?</string>
<string name="longtap_to_duplicate">Tap and hold on a session to duplicate it!</string>
@ -762,7 +758,6 @@
<string name="do_you_really_to_delete_this_game">Do you really want to delete this game?</string>
<string name="comments">Comments</string>
<string name="bb_ante_option">Big Blind Ante</string>
<string name="show_fullscreen">Show full screen</string>
<!-- Info.plist -->
@ -778,6 +773,8 @@
<string name="join_discord">Join us on Discord!</string>
<string name="discord_feed_message">We\'ve opened our Discord channel! Come to hang out, talk about poker or about the app!</string>
<string name="good_for_you">Good for you!</string>
<string name="sessions_csv">Sessions (CSV)</string>
<string name="transactions_csv">Transactions (CSV)</string>
<string name="posts_sb">posts</string>
<string name="post_bb">posts</string>

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path
name="files"
path="." />
<external-path
name="external_files"
path="." />
<files-path name="files" path="." />
<external-path name="external_files" path="." />
<!-- <cache-path name="name" path="path" />-->
</paths>

@ -1,5 +1,6 @@
package net.pokeranalytics.android
import net.pokeranalytics.android.util.CSVNumberFormat
import net.pokeranalytics.android.util.Parser
import net.pokeranalytics.android.util.extensions.formatted
import net.pokeranalytics.android.util.extensions.kmbFormatted
@ -56,4 +57,13 @@ class BasicUnitTest : RealmUnitTest() {
}
@Test
fun testCSVFormatter() {
val str1 = CSVNumberFormat.format(1111.2567)
Assert.assertEquals("1111.2567", str1)
val str2 = CSVNumberFormat.format(1000)
Assert.assertEquals("1000", str2)
}
}

@ -1 +1,5 @@
<<<<<<< HEAD
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":79,"versionName":"3.0","enabled":true,"outputFile":"PokerAnalytics_3.0(79)_200428_1456_release.apk","fullName":"standardRelease","baseName":"standard-release","dirName":""},"path":"PokerAnalytics_3.0(79)_200428_1456_release.apk","properties":{}}]
=======
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":84,"versionName":"2.4.3","enabled":true,"outputFile":"PokerAnalytics_2.4.3(84)_200526_1106_release.apk","fullName":"standardRelease","baseName":"standard-release","dirName":""},"path":"PokerAnalytics_2.4.3(84)_200526_1106_release.apk","properties":{}}]
>>>>>>> master

@ -1,4 +1,4 @@
#Wed Mar 04 11:24:53 CET 2020
#Wed May 13 12:21:50 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

Loading…
Cancel
Save