From 4a8047bfad9676621fc667c79fbb67a07859a439 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 22 Apr 2020 14:06:18 +0200 Subject: [PATCH 01/26] Show subscriptions terms of use --- .../android/util/billing/AppGuard.kt | 2 +- .../main/res/layout/fragment_subscription.xml | 28 +++++++++++++++---- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values/strings.xml | 9 ++---- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt b/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt index 9cecf6d8..cf4afdc4 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/billing/AppGuard.kt @@ -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 } diff --git a/app/src/main/res/layout/fragment_subscription.xml b/app/src/main/res/layout/fragment_subscription.xml index e281aa06..8549087f 100644 --- a/app/src/main/res/layout/fragment_subscription.xml +++ b/app/src/main/res/layout/fragment_subscription.xml @@ -66,16 +66,15 @@ + app:layout_constraintVertical_bias="0.5" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5eb8300e..209b2319 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -665,7 +665,7 @@ Semi-Pro Pro Conditions de l\'abonnement - 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 + 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\" Politique de confidentialité Nous sommes désolé, mais il y a problème…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! Appuyez et maintenez sur une session pour la dupliquer diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 147236df..6537558e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,7 +6,6 @@ Hour Minute More - Variant Lines Initial Value Can\'t show because there is less than two values to display! @@ -43,8 +42,9 @@ Stacking incoming Stacking outgoing There has been an issue with the import. Please check out your file or contact the support! + 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" + Show full screen - Address Naming suggestions Data deleted The end date should be after the start date @@ -62,7 +62,6 @@ This name already exists. One or more transactions are associated with this bankroll, please delete the linked transaction(s) first. Transaction type - From To st @@ -74,8 +73,6 @@ New tournament feature New filter - - %s ago @@ -661,7 +658,6 @@ Semi-Pro Pro Subscription terms - 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 Privacy policy We\'re truly sorry, but something is wrong here…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? Tap and hold on a session to duplicate it! @@ -757,7 +753,6 @@ Do you really want to delete this game? Comments Big Blind Ante - Show full screen From f5bac646b8e57c1c68079a7c836b6c0011a3c91a Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 22 Apr 2020 17:42:17 +0200 Subject: [PATCH 02/26] Fixes missing translation + typo --- app/src/main/res/values-de/strings.xml | 79 +++++++++++++++++++++++++- app/src/main/res/values-es/strings.xml | 12 ++++ app/src/main/res/values-fr/strings.xml | 12 ++++ app/src/main/res/values-hi/strings.xml | 12 ++++ app/src/main/res/values-it/strings.xml | 12 ++++ app/src/main/res/values-ja/strings.xml | 12 ++++ app/src/main/res/values-pt/strings.xml | 12 ++++ app/src/main/res/values-ru/strings.xml | 12 ++++ app/src/main/res/values-zh/strings.xml | 11 ++++ app/src/main/res/values/strings.xml | 4 +- 10 files changed, 173 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8f40f484..6997376d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,6 +1,82 @@ Poker Analytics + Stunde + Minute + Mehr + Linien + Unbegrenzt + Unterstützung + Importiert + Sparen + Steht aus + Von + Zu + + + Please set a start date for the session + Initial Value + Can\'t show because there is less than two values to display! + The object you\'re trying to access is invalid + It appears that you are not connected at the moment. Please check your internet connection and retry later. + The billing services are unavailable at the moment. Please retry later or contact us. + Upgrade to Pro + Go Pro + free trial + Track all your poker life by adding as many data as you want + Offline first + Poker Analytics is available at all times and the data is yours. Note: We will soon add exporting capabilities and you’re currently in charge of backups. Thanks for your patience! + Private + We do not own servers. We do not know anything about your wins and losses. + We try to answer as quickly as we can, in english or french ! + Loading, please wait… + Select your type of report + Select one or more statistics + Select one or more comparison criteria + Select a filter or launch report + Launch Report + Progress + Save Report + Do you want to proceed with the file import? + Update %s + Comparison chart + The filter cannot be deleted because it is currently selected. + Custom field + The item is used in one or more transactions…Please delete the linked transactions first + 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! + Staking incoming + Staking outgoing + There has been an issue with the import. Please check out your file or contact the support! + 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" + Show full screen + + Naming suggestions + Data deleted + The end date should be after the start date + Tournament name + Tournament names + Tournament feature + Tournament features + Poker Analytics is a poker tracking app.\nThe app works with a yearly subscription for an unlimited usage, but you get 10 sessions + a free trial to test the app. + I understand + You need to give a name to this tournament feature + A tournament feature with the same name already exists. + A tournament name can not be empty. + This name already exists. + One or more transactions are associated with this bankroll, please delete the linked transaction(s) first. + Transaction type + st + nd + rd + th + + New tournament name + New tournament feature + New filter + + + + vor %s %s hat keinen Zugriff auf Ihre Kontakte und kann den Namen Ihrer existierenden Freunde nicht abrufen. Sie können über die iPhone-Einstellungen Zugriff gewähren, um \'unbekannter Benutzer\' als Namen zu vermeiden. Ein Stern kennzeichnet eine Datei, die in Ihrem iCloud-Konto gespeichert ist. @@ -212,7 +288,6 @@ Nettostundensatz Stundenzeitfenster Exportieren Sie Ihre Daten zu iCloud, holen Sie sich die Vollversion, öffnen Sie es, importieren Sie Ihre Daten von iCloud. - Sie haben die maximal zulässige Anzahl an Sitzungen erreicht. Holen Sie sich ein neues Abonnement, um mehr Sitzungen zu nutzen! iCloud Die iCloud-Einstellungen wurden modifiziert, bitte warten Sie auf die Synchronisierung. Es wurde eine Sicherung Ihrer Daten in iCloud gefunden. Möchten Sie es auf diesem Gerät verwenden? Wenn ja, werden die lokalen Daten von diesem Gerät durch die Sicherung ersetzt. Wenn nein, behalten Sie die lokalen Daten von diesem Gerät und Ihr iCloud-Konto wird nicht modifiziert. @@ -589,7 +664,6 @@ Halbprofi Profi Abonnementbedingungen - Abonnementbedingungen:\n- Mit Ihrer Bestellbestätigung ziehen wir den fälligen Rechnungsbetrag von Ihrem iTunes-Konto ab.\n- Das Abonnement wird automatisch verlängert, sofern die automatische Verlängerung nicht mind. 24 Stunden vor Ablauf des aktuellen Abonnementzeitraums deaktiviert wird.\n- Abonnements können vom Nutzer verwaltet werden und die automatische Verlängerung kann nach dem Kauf im Benutzerkonto deaktiviert werden.\n- Bei Abonnementverlängerung wird der fällige Betrag für die Verlängerung in Höhe von 29,99€ innerhalb von 24 Stunden vor Ablauf des aktuellen Abonnementzeitraums abgezogen.\n- Ein aktuelles Abonnement kann nicht während eines aktiven Abonnementzeitraums gekündigt werden. Datenschutz-Bestimmungen We\'re truly sorry, but something is wrong here…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? Tippen und halten Sie eine Sitzung, um sie zu kopieren. @@ -677,7 +751,6 @@ Neue Transaktion Neuer Transaktionstyp Big Blind Ante - Show full screen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 6d2113b0..b4d8dbba 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1,6 +1,18 @@ Poker Analytics + Hora + Minuto + Más + Líneas + Ilimitado + Apoyo + Importado + Salvar + Pendiente + Desde + A + hace %s %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\'. Una estrella significa un archivo guardado en tu cuenta iCloud. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 209b2319..4036f039 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,6 +1,18 @@ Poker Analytics + Hour + Minute + More + Lines + Unlimited + Support + Imported + Save + Pending + From + To + Veuillez définir une date de démarrage pour la session Heure Minute diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 61a16083..65ffdd86 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,6 +1,18 @@ Poker Analytics + घंटा + मिनट + अधिक + पंक्तियां + असीमित + सहयोग + आयातित + सहेजें + विचाराधीन + से + सेवा + %s पहले %s की आपके संपर्कों तक पहुँच नहीं है और आपके मौजुदा दोस्तों के नामों को पुन: प्राप्त नहीं कर सकता है. किसी \'अज्ञात उपयोगकर्ता\' से बचाव के लिए आपको iPhone प्राथमिकताएं द्वारा पहुँच प्रदान करनी होगी. एक सि‍तारा आपके iCloud अकाउंट में स्टोर की गई फाइल दर्शाता है. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6f825284..4a7bcabd 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,6 +1,18 @@ Poker Analytics + Ora + Minuto + Di Più + Linee + Illimitato + Supporto + Importato + Salva + In attesa di + A partire dal + Per + %s fa %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 \"utente sconosciuto\". Una stella indica un file archiviato sul tuo account iCloud. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 29693161..4a5de6ec 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1,6 +1,18 @@ Poker Analytics + + + もっと + + 無制限 + サポート + 輸入 + セーブ + 保留中 + から + + %s 前 %s では、あなたの連絡先にアクセスできないため、既存のお友達の名前を取得できません。「不明なユーザー」名を回避するには、iPhone のユーザー設定からアクセス権限を付与してください。 星マークは、iCloud アカウントにファイルが保管されていることを表します。 diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 07ffef51..75c8f211 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,6 +1,18 @@ Poker Analytics + Hora + Minuto + Mais + Linhas + Ilimitado + Apoio, suporte + Importado + Salve + Pendente + De + Para + %s atrás 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. Um asterisco indica um arquivo armazenado na sua conta do iCloud. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d328257c..52562e59 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,6 +1,18 @@ Poker Analytics + Час + минут + Больше + линии + неограниченный + Поддержка + импортный + Сохранить + В ожидании + От + к + %s назад %s не имеет доступа к адресной книге и не может определить имена выбранных вами друзей. Предоставьте доступ к контактам в настройках телефона чтобы исключить имена \'неизвестный пользователь\' Звёздочкой отмечены файлы сохранённые на вашем аккаунте iCloud. diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 54d7f266..c3edfccd 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1,6 +1,17 @@ Poker Analytics + 小时 + 分钟 + 更多 + 线数 + 无限 + 支持 + 进口的 + 保存 + 待定 + + %s前 %s不能访问你的联系人且不能检索你的现有好友姓名。通过iPhone首选项可授权防止任何\'陌生用户\'名访问。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6537558e..c0c1ccb0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,8 +39,8 @@ The item is used in one or more transactions…Please delete the linked transactions first Imported 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! - Stacking incoming - Stacking outgoing + Staking incoming + Staking outgoing There has been an issue with the import. Please check out your file or contact the support! 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" Show full screen From bc78db7f0d8b7e1423ef27c4355a9cd0c361c205 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 23 Apr 2020 16:23:39 +0200 Subject: [PATCH 03/26] cleanup --- .../android/util/Preferences.kt | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt b/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt index d6d6d580..a24dfaaf 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/Preferences.kt @@ -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 From 594f7d5522435b76fa78d9c6910988b944f46672 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 13 May 2020 10:32:41 +0200 Subject: [PATCH 04/26] Fixes build issue --- app/src/main/res/values-fr/strings.xml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4036f039..34703d39 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,23 +1,23 @@ Poker Analytics - Hour - Minute - More - Lines - Unlimited - Support - Imported - Save - Pending - From - To + + + + + + + + + + + Veuillez définir une date de démarrage pour la session Heure Minute Autre - Jeu + Lignes Valeur initiale Il faut au moins deux valeurs pour afficher ce rapport! @@ -63,7 +63,7 @@ Mise-à-jour %s - Adresse + Suggestions de noms Élément effacé La date de fin doit être après la date de début From 294b30e86d8f8f3bc3a82e25d670b1f33cbe2fe2 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 13 May 2020 11:15:29 +0200 Subject: [PATCH 05/26] Adds CSV export first bricks --- .../android/ui/fragment/SettingsFragment.kt | 3 +++ .../ui/view/rowrepresentable/SettingRow.kt | 7 ++++++ .../android/util/csv/CSVDescriptor.kt | 22 +++++++++++++++++-- .../android/util/csv/CSVField.kt | 8 ++++--- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt index 55c42bf9..b4244dfc 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt @@ -128,6 +128,9 @@ 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 -> { + + } SettingRow.FOLLOW_US -> { when (position) { 0 -> parentActivity?.openUrl(URL.BLOG.value) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt index 00420032..f839db9f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt @@ -29,6 +29,9 @@ enum class SettingRow : RowRepresentable { LANGUAGE, CURRENCY, + // Export + EXPORT_CSV, + // Data management CUSTOM_FIELD, BANKROLL, @@ -64,6 +67,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)) + rows.add( CustomizableRowRepresentable( customViewType = RowViewType.HEADER_TITLE, @@ -99,6 +105,7 @@ enum class SettingRow : RowRepresentable { FOLLOW_US -> R.string.follow_us LANGUAGE -> R.string.language CURRENCY -> R.string.currency + EXPORT_CSV -> R.string.csv GDPR -> R.string.gdpr POKER_RUMBLE -> R.string.poker_rumble DISCORD -> R.string.join_discord diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt index a4e915f9..ded03d9d 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -28,7 +28,7 @@ abstract class DataCSVDescriptor(source: DataSource, vararg el /** * List of Realm object identificators */ - val realmModelIds = mutableListOf() + private val realmModelIds = mutableListOf() abstract fun parseData(realm: Realm, record: CSVRecord): T? @@ -75,6 +75,24 @@ abstract class DataCSVDescriptor(source: DataSource, vararg el this.realmModelIds.clear() } + val csvHeaders: String + get() { + val headers = mutableListOf() + this.fields.forEach { + headers.add(it.header) + } + return headers.joinToString(",") + } + + fun toCSV(data: T): String { + + val fields = mutableListOf() + this.fields.forEach { +// fields.add(it.toCSV(data)) + } + return fields.joinToString(",") + } + } /** @@ -92,7 +110,7 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) protected var fieldMapping: MutableMap = mutableMapOf() init { - if (elements.size > 0) { + if (elements.isNotEmpty()) { this.fields = elements.toList() } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt index a28c7a16..76e7c723 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt @@ -48,7 +48,7 @@ interface NumberCSVField: TypedCSVField { return try { formatter.parse(value).toDouble() } catch (e: ParseException) { - Timber.d("Field ${header} > Unparseable number: $value") + Timber.d("Field $header > Unparseable number: $value") null } } @@ -65,7 +65,7 @@ interface IntCSVField: TypedCSVField { return try { NumberFormat.getInstance().parse(value).toInt() } catch (e: ParseException) { - Timber.d("Field ${header} > Unparseable number: $value") + Timber.d("Field $header > Unparseable number: $value") null } } @@ -135,9 +135,11 @@ interface TypedCSVField : CSVField { } interface CSVField { + val header: String val optional: Boolean get() { return false } -} \ No newline at end of file + +} From 51a524116eeaf2cd35c677cc7403e3243ae46ef1 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 13 May 2020 17:22:17 +0200 Subject: [PATCH 06/26] Sessions CSV export first commit --- .../android/model/realm/Session.kt | 6 +- .../android/model/realm/TournamentFeature.kt | 2 +- .../android/ui/fragment/SettingsFragment.kt | 50 ++- .../pokeranalytics/android/util/FileUtils.kt | 24 ++ .../net/pokeranalytics/android/util/Global.kt | 2 - .../android/util/csv/CSVDescriptor.kt | 40 +- .../android/util/csv/CSVField.kt | 62 ++- .../android/util/csv/PACSVDescriptor.kt | 254 ++++++++++++ .../android/util/csv/ProductCSVDescriptors.kt | 49 ++- .../android/util/csv/SessionCSVDescriptor.kt | 370 ++---------------- .../android/util/csv/SessionField.kt | 38 +- .../csv/SessionTransactionCSVDescriptor.kt | 118 ++++++ build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 14 files changed, 630 insertions(+), 391 deletions(-) create mode 100644 app/src/main/java/net/pokeranalytics/android/util/FileUtils.kt create mode 100644 app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt create mode 100644 app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index 79bfee91..d887521b 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -48,9 +48,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 { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt index d0f937e2..61bbe997 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TournamentFeature.kt @@ -53,7 +53,7 @@ open class TournamentFeature : RealmObject(), NameManageable, StaticRowRepresent } override fun adapterRows(): List? { - return TournamentFeature.rowRepresentation + return rowRepresentation } override fun stringForRow(row: RowRepresentable): String { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt index b4244dfc..88dbf1c4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt @@ -8,6 +8,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.recyclerview.widget.LinearLayoutManager import io.realm.Realm import kotlinx.android.synthetic.main.fragment_settings.* @@ -24,19 +25,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.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.fullDateTime +import net.pokeranalytics.android.util.extensions.shortDateTime 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 { @@ -129,7 +136,7 @@ class SettingsFragment : BaseFragment(), RowRepresentableDelegate, StaticRowRepr 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 -> { - + this.csvExport() } SettingRow.FOLLOW_US -> { when (position) { @@ -151,6 +158,7 @@ class SettingsFragment : BaseFragment(), RowRepresentableDelegate, StaticRowRepr } } + /** * Init UI */ @@ -202,4 +210,40 @@ class SettingsFragment : BaseFragment(), RowRepresentableDelegate, StaticRowRepr } } + private fun csvExport() { + + val sessions = getRealm().where(Session::class.java).findAll().sort("startDate") + val csv = ProductCSVDescriptors.pokerAnalyticsAndroid.toCSV(sessions) + + try { + val fileName = "sessions_${Date().shortDateTime()}" + FileUtils.writeToFile(csv, fileName, requireContext()) + this.shareFile(fileName) + } catch (e: IOException) { + Toast.makeText(requireContext(), "File write failed: ${e.message}", Toast.LENGTH_LONG).show() + } + + } + + private fun shareFile(filePath: String) { + + val intentShareFile = Intent(Intent.ACTION_SEND) + val fileWithinMyDir = File(filePath) + + if (fileWithinMyDir.exists()) { + intentShareFile.type = "application/pdf" + intentShareFile.putExtra( + Intent.EXTRA_STREAM, + Uri.parse("file://$filePath") + ) + intentShareFile.putExtra( + Intent.EXTRA_SUBJECT, + "Sharing File..." + ) + intentShareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File...") + startActivity(Intent.createChooser(intentShareFile, "Share File")) + } + + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/FileUtils.kt b/app/src/main/java/net/pokeranalytics/android/util/FileUtils.kt new file mode 100644 index 00000000..22c20553 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/FileUtils.kt @@ -0,0 +1,24 @@ +package net.pokeranalytics.android.util + +import android.content.Context +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 writeToFile(string: String, fileName: String, context: Context) { + + val outputStreamWriter = OutputStreamWriter(context.openFileOutput(fileName, Context.MODE_PRIVATE)) + outputStreamWriter.write(string) + outputStreamWriter.close() + + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/Global.kt b/app/src/main/java/net/pokeranalytics/android/util/Global.kt index 5c7a43f4..4fb20690 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/Global.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/Global.kt @@ -1,5 +1,3 @@ package net.pokeranalytics.android.util -import android.content.Context - const val NULL_TEXT: String = "--" diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt index ded03d9d..c5da6ba8 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -16,7 +16,7 @@ enum class DataSource { POKER_ANALYTICS, POKER_INCOME, POKER_BANKROLL_TRACKER, - RUNGOOD, + RUN_GOOD, POKER_AGENT } @@ -75,24 +75,27 @@ abstract class DataCSVDescriptor(source: DataSource, vararg el this.realmModelIds.clear() } - val csvHeaders: String - get() { - val headers = mutableListOf() - this.fields.forEach { - headers.add(it.header) - } - return headers.joinToString(",") - } + fun toCSV(dataSequence: List): String { - fun toCSV(data: T): String { + val lines = mutableListOf() + lines.add(this.csvHeaders) - val fields = mutableListOf() - this.fields.forEach { -// fields.add(it.toCSV(data)) + dataSequence.forEach { data -> + val line = mutableListOf() + this.fields.forEach { field -> + line.add(this.toCSV(data, field)) + } + lines.add(line.joinToString(",")) } - return fields.joinToString(",") + return lines.joinToString("\n") + } + + protected open fun toCSV(data: T, field: CSVField): String { + return "" } +// abstract fun + } /** @@ -149,4 +152,13 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) return count >= mandatoryfields.size } + protected val csvHeaders: String + get() { + val headers = mutableListOf() + this.fields.forEach { + headers.add(it.header) + } + return headers.joinToString(",") + } + } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt index 76e7c723..1fc52eb4 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt @@ -1,5 +1,6 @@ package net.pokeranalytics.android.util.csv +import net.pokeranalytics.android.model.realm.TournamentFeature import timber.log.Timber import java.text.DateFormat import java.text.NumberFormat @@ -52,6 +53,14 @@ interface NumberCSVField: TypedCSVField { null } } + + override fun format(data: Double?): String? { + return if (data != null) { + NumberFormat.getInstance().format(data) + } else { + null + } + } } interface IntCSVField: TypedCSVField { @@ -69,19 +78,14 @@ interface IntCSVField: TypedCSVField { null } } -} - - -interface DataCSVField : TypedCSVField { - - override fun parse(value: String): T? { - this.callback?.let { - return it(value) + override fun format(data: Int?): String? { + return if (data != null) { + NumberFormat.getInstance().format(data) + } else { + null } - return null } - } interface DateCSVField : TypedCSVField { @@ -99,6 +103,29 @@ interface DateCSVField : TypedCSVField { } } + 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> { + + override fun parse(value: String): List? { + this.callback?.let { + return it(value) + } + return null + } + + override fun format(data: List?): String? { + return data?.joinToString(",") { it.name } + } + } interface BlindCSVField : TypedCSVField> { @@ -121,16 +148,31 @@ interface BlindCSVField : TypedCSVField> { return null } + override fun format(data: Pair?): String? { + data?.let { + val sb = NumberFormat.getInstance().format(data.first) + val bb = NumberFormat.getInstance().format(data.second) + return "$sb/$bb" + } ?: run { + return null + } + + } + } interface BooleanCSVField : TypedCSVField { override fun parse(value: String): Boolean? { return value == "1" } + override fun format(data: Boolean?): String { + return if (data != null && data) "1" else "0" + } } interface TypedCSVField : CSVField { fun parse(value: String) : T? + fun format(data: T?): String? var callback: ((String) -> T?)? } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt new file mode 100644 index 00000000..cd33448d --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt @@ -0,0 +1,254 @@ +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(source: DataSource, private var isTournament: Boolean?, vararg elements: CSVField) : DataCSVDescriptor(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.TournamentFeatures -> { + value.split(",").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) + } + 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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt index ffc006d0..b4d91075 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt @@ -16,7 +16,8 @@ class ProductCSVDescriptors { pokerBankrollTracker, runGoodCashGames, runGoodTournaments, - pokerAnalyticsiOS + pokerAnalyticsiOS, + pokerAnalyticsAndroid ) private val pokerAgent: CSVDescriptor @@ -57,7 +58,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 +91,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 +112,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 +122,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 +156,7 @@ class ProductCSVDescriptors { ) } - private val pokerAnalyticsiOS: CSVDescriptor + val pokerAnalyticsiOS: SessionCSVDescriptor get() { return SessionCSVDescriptor( DataSource.POKER_ANALYTICS, @@ -179,7 +180,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 +189,40 @@ class ProductCSVDescriptors { ) } + 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", Calendar.SECOND), + 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.TournamentType("Tournament Type"), + SessionField.TournamentName("Tournament Name"), + SessionField.TournamentEntryFee("Entry fee"), + SessionField.TournamentNumberOfPlayers("Number of players"), + SessionField.TournamentFeatures("Number of players"), + SessionField.TournamentPrizePool("Prize Pool"), + SessionField.TournamentPosition("Position"), + SessionField.Comment("Comment") + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt index c3c1907e..863095b4 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -1,351 +1,55 @@ 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.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(source, *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) - } +class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg elements: CSVField) : + PACSVDescriptor(source, isTournament, *elements) { + override fun parseData(realm: Realm, record: CSVRecord): Session? { + return this.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) - } - 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 -> { - } + override fun toCSV(data: Session, field: CSVField): String { + val string = 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 } } - } - - 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 - } - - private var sameDaySessionCount: Int = 0 - private var currentday: String = "" - private var startInSeconds: Double = 20 * 3600.0 - - 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 - } - 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)?.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 -> { - } - } - } - - } - - 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 + 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.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 + else -> null + } + return string ?: "" } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt index db4d316a..dacd13ec 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt @@ -1,16 +1,17 @@ package net.pokeranalytics.android.util.csv import net.pokeranalytics.android.exceptions.PAIllegalStateException +import net.pokeranalytics.android.model.realm.TournamentFeature import java.util.* -sealed class TransactionField { - - data class TransactionType( - override var header: String, - override var callback: ((String) -> net.pokeranalytics.android.model.realm.TransactionType?)? = null - ) : DataCSVField - -} +//sealed class TransactionField { +// +// data class TransactionType( +// override var header: String, +// override var callback: ((String) -> net.pokeranalytics.android.model.realm.TransactionType?)? = null +// ) : DataCSVField +// +//} /** * The enumeration of Session fields @@ -156,7 +157,11 @@ 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?)? = null + ) : TournamentFeaturesCSVField data class CurrencyRate( override var header: String, @@ -166,15 +171,18 @@ sealed class SessionField { 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, diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt new file mode 100644 index 00000000..0125b615 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt @@ -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(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 + + 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 + } + +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1d0eadaa..2aa87e09 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'com.android.tools.build:gradle:3.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'io.realm:realm-gradle-plugin:5.8.0' classpath 'com.google.gms:google-services:4.2.0' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ba7b0db..3b80afcf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Aug 26 10:00:03 CEST 2019 +#Wed May 13 12:21:50 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip From eb64f231f2794505dfd4ed9d8347933fbff2d0bd Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 14 May 2020 11:10:31 +0200 Subject: [PATCH 07/26] Upgrades gradle plugin to fix crash --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2aa87e09..cb6f60e2 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'io.realm:realm-gradle-plugin:5.8.0' + classpath 'io.realm:realm-gradle-plugin:5.15.2' classpath 'com.google.gms:google-services:4.2.0' classpath 'io.fabric.tools:gradle:1.28.1' } From 0048dfb692b496c598933ae0bbe606228dbc4638 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 14 May 2020 11:23:48 +0200 Subject: [PATCH 08/26] Shows import error message --- .../android/ui/fragment/ImportFragment.kt | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt index ef7ef3e7..d3c233c4 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt @@ -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,12 +95,15 @@ 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) + val textView = snackBar.view.findViewById(com.google.android.material.R.id.snackbar_text) + textView.maxLines = 4 + snackBar.show() } - - // if (shouldDismissActivity) { // // activity?.let { From bf3439963be3df5bd4e2d67f690d88fe7ebd68c8 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 14 May 2020 12:36:25 +0200 Subject: [PATCH 09/26] Makes sessions export work --- .../android/ui/fragment/SettingsFragment.kt | 47 ++++++++++--------- .../pokeranalytics/android/util/FileUtils.kt | 16 +++++-- .../android/util/extensions/DateExtension.kt | 6 +++ app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-hi/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-zh/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/xml/provider_paths.xml | 11 ++--- 14 files changed, 58 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt index 88dbf1c4..ecb5170c 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt @@ -9,6 +9,7 @@ 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.* @@ -35,8 +36,7 @@ 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.fullDateTime -import net.pokeranalytics.android.util.extensions.shortDateTime +import net.pokeranalytics.android.util.extensions.dateTimeFileFormatted import timber.log.Timber import java.io.File import java.io.IOException @@ -69,6 +69,7 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep 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) } @@ -215,33 +216,35 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep val sessions = getRealm().where(Session::class.java).findAll().sort("startDate") val csv = ProductCSVDescriptors.pokerAnalyticsAndroid.toCSV(sessions) + Timber.d("CSV = $csv") + try { - val fileName = "sessions_${Date().shortDateTime()}" - FileUtils.writeToFile(csv, fileName, requireContext()) - this.shareFile(fileName) + val fileName = "sessions_${Date().dateTimeFileFormatted}.csv" + FileUtils.writeFileToFilesDir(csv, fileName, requireContext()) + this.shareFile(fileName, "Poker Analytics Export", "CSV Sessions") } catch (e: IOException) { Toast.makeText(requireContext(), "File write failed: ${e.message}", Toast.LENGTH_LONG).show() } } - private fun shareFile(filePath: String) { - - val intentShareFile = Intent(Intent.ACTION_SEND) - val fileWithinMyDir = File(filePath) - - if (fileWithinMyDir.exists()) { - intentShareFile.type = "application/pdf" - intentShareFile.putExtra( - Intent.EXTRA_STREAM, - Uri.parse("file://$filePath") - ) - intentShareFile.putExtra( - Intent.EXTRA_SUBJECT, - "Sharing File..." - ) - intentShareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File...") - startActivity(Intent.createChooser(intentShareFile, "Share File")) + 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") } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/FileUtils.kt b/app/src/main/java/net/pokeranalytics/android/util/FileUtils.kt index 22c20553..666bf06b 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/FileUtils.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/FileUtils.kt @@ -1,6 +1,10 @@ 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 { @@ -11,11 +15,15 @@ class FileUtils { * Writes a [string] into a file named [fileName], using a [context] * Should be surrounded by a try/catch IOException */ - fun writeToFile(string: String, fileName: String, context: Context) { + fun writeFileToFilesDir(string: String, fileName: String, context: Context) { - val outputStreamWriter = OutputStreamWriter(context.openFileOutput(fileName, Context.MODE_PRIVATE)) - outputStreamWriter.write(string) - outputStreamWriter.close() + Timber.d("Writing to: $fileName ...\n$string") + + val file = File(context.filesDir, fileName) + + val fileOutputStream = FileOutputStream(file) + fileOutputStream.write(string.toByteArray()) + fileOutputStream.close() } diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt index 2b7f5cf5..47b4716a 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt @@ -113,6 +113,12 @@ fun Date.getMonthAndYear(): String { return SimpleDateFormat("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 { val difference = (toDate.time - this.time).toInt() diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6997376d..b5fc0c48 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -176,7 +176,7 @@ Die eingegrenzte Suche ergab keine Ergebnisse Bestätigung Kontakt - CSV (Importvorgang fehlgeschlagen) + CSV One of the lines in your CSV file is not well formed. Please check your file or contact the support. Währung Aktueller Monat diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b4d8dbba..8566c321 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -112,7 +112,7 @@ Los filtros aún no dieron ningún resultado. Confirmación Contáctanos - CSV (no se puede importar) + CSV Una de las líneas en tu archivo CSV no está bien formada. Verifica el archivo o contacta al soporte. Divisa Mes en curso diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 34703d39..14f8d682 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -180,7 +180,7 @@ Le filtre ne retourne aucun résultat Confirmation Contactez-nous - CSV (ne peut être importé) + CSV Une ligne de votre fichier CSV est mal formée. Veuillez vérifier votre fichier ou contacter le support. Devise Ce mois-ci diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 65ffdd86..6d5e34c2 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -112,7 +112,7 @@ फिल्टर से कोई परिणाम नहीं मिलेे पुष्टीकरण हमें संपर्क करें - CSV (इम्पोर्ट नहीं किया जा सकता) + CSV आपकी CSV फाइल में लाइनों से एक अच्छी तरह फार्मेट नहीं की गई है. कृपया अपनी फाइल जाँचे या सहायता टीम से संपर्क करें. करेंसी वर्तमान महीना diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4a7bcabd..054efd71 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -112,7 +112,7 @@ Il filtraggio non ha dato alcun risultato Conferma Contattaci - CSV (non si può importare) + CSV Una delle linee nel tuo file CSV non è ben formata. Controlla il tuo file o contatta l\'assistenza. Valuta Mese corrente diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 4a5de6ec..2bb60ca9 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -112,7 +112,7 @@ フィルタリングでは結果が戻されませんでした 確認 ご連絡ください - CSV (インポートできません) + CSV CSV ファイルのいずれかの行が正しく形成されていません。ファイルをご確認いただくが、サポートまでお問い合わせください。 通貨 今月 diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 75c8f211..8f6762a8 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -112,7 +112,7 @@ O filtro não apresentou nenhum resultado Confirmação Contate-nos - CSV (Não pode ser importado) + CSV Uma das linhas do seu arquivo CSV não está no formato correto. Por favor, verifique o seu arquivo ou contate o suporte. Moeda Mês atual diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 52562e59..f5e7392e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -112,7 +112,7 @@ Не найдено результатов после фильтрации Подтверждение Напишите нам - CSV (Импорт невозможен) + CSV Одна из строк в вашем файле CSV отформатирована некорректно. Пожалуйста проверьте ваш файл или свяжитесь со службой поддержки. Валюта Текущий месяц diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c3edfccd..c3f9cc97 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -98,7 +98,7 @@ 比较 确认 联系我们 - CSV(无法导入) + CSV CSV文件中的线之一不合适。请核查你的文件或联系支持团队。 货币 本月 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c0c1ccb0..f8699124 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -174,7 +174,7 @@ Filtering did not yield any results Confirmation Contact us - CSV (Cannot be imported) + CSV One of the lines in your CSV file is not well formed. Please check your file or contact the support. Currency Current month diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml index 43873cba..bfe997bf 100644 --- a/app/src/main/res/xml/provider_paths.xml +++ b/app/src/main/res/xml/provider_paths.xml @@ -1,11 +1,10 @@ - - + + + + + \ No newline at end of file From 96a4dc9c542b3c5dd3f01d745012da7761c3d4f2 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 14 May 2020 14:53:22 +0200 Subject: [PATCH 10/26] Fixes import issue --- .../net/pokeranalytics/android/util/csv/CSVDescriptor.kt | 4 ++-- .../java/net/pokeranalytics/android/util/csv/CSVField.kt | 7 ++++++- .../net/pokeranalytics/android/util/csv/PACSVDescriptor.kt | 2 +- .../android/util/csv/ProductCSVDescriptors.kt | 3 +-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt index c5da6ba8..3fd9e409 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -147,9 +147,9 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) count++ } } - val mandatoryfields = this.fields.filter { it.optional == false } + val mandatoryFields = this.fields.filter { !it.optional } Timber.d("source= ${this.source.name} > total fields = ${this.fields.size}, identified = $count") - return count >= mandatoryfields.size + return count >= mandatoryFields.size } protected val csvHeaders: String diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt index 1fc52eb4..3cedd81a 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt @@ -123,7 +123,7 @@ interface TournamentFeaturesCSVField : TypedCSVField> { } override fun format(data: List?): String? { - return data?.joinToString(",") { it.name } + return data?.joinToString(CSVField.separator) { it.name } } } @@ -178,6 +178,11 @@ interface TypedCSVField : CSVField { interface CSVField { + companion object { + const val delimiter = "\"" + const val separator = "|" + } + val header: String val optional: Boolean get() { diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt index cd33448d..5626d26f 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt @@ -187,7 +187,7 @@ abstract class PACSVDescriptor(source: DataSource, private var field.parse(value) is SessionField.TournamentEntryFee -> session.tournamentEntryFee = field.parse(value) is SessionField.TournamentFeatures -> { - value.split(",").forEach { featureName -> + value.split(CSVField.separator).forEach { featureName -> val tournamentFeature: TournamentFeature = realm.getOrCreate(featureName) session.tournamentFeatures.add(tournamentFeature) } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt index b4d91075..26dfd148 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt @@ -217,8 +217,7 @@ class ProductCSVDescriptors { SessionField.TournamentName("Tournament Name"), SessionField.TournamentEntryFee("Entry fee"), SessionField.TournamentNumberOfPlayers("Number of players"), - SessionField.TournamentFeatures("Number of players"), - SessionField.TournamentPrizePool("Prize Pool"), + SessionField.TournamentFeatures("Tournament Features"), SessionField.TournamentPosition("Position"), SessionField.Comment("Comment") ) From 9c2e183b5ebf8223d254ac91c9fe16497a668985 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 14 May 2020 14:56:14 +0200 Subject: [PATCH 11/26] Tournament Type was not imported --- .../java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt index 5626d26f..2878d173 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt @@ -186,6 +186,7 @@ abstract class PACSVDescriptor(source: DataSource, private var 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) From 18eb54309e835d4967bdb7a0f5b9c82c6af6bb61 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 14 May 2020 14:57:11 +0200 Subject: [PATCH 12/26] Remove warnings --- .../android/util/csv/PACSVDescriptor.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt index 2878d173..0d0e5623 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt @@ -55,7 +55,7 @@ abstract class PACSVDescriptor(source: DataSource, private var sameDaySessionCount = 0 } currentDay = value - } else {} + } } is SessionField.End -> { endDate = field.parse(value) @@ -104,7 +104,6 @@ abstract class PACSVDescriptor(source: DataSource, private var session.result?.buyin = buyin if (session.type == Session.Type.TOURNAMENT.ordinal) { session.tournamentEntryFee = buyin - } else { } } is SessionField.CashedOut -> session.result?.cashout = field.parse(value) @@ -136,20 +135,17 @@ abstract class PACSVDescriptor(source: DataSource, private var } 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 @@ -164,13 +160,13 @@ abstract class PACSVDescriptor(source: DataSource, private var 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 = @@ -178,7 +174,6 @@ abstract class PACSVDescriptor(source: DataSource, private var is SessionField.TournamentName -> { if (value.isNotEmpty()) { session.tournamentName = realm.getOrCreate(value) - } else { } } is SessionField.TournamentTypeName -> session.tournamentType = From 83dbad0e0faab4bf3f9cea5297939e23798ac8f7 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 14 May 2020 16:11:06 +0200 Subject: [PATCH 13/26] Added Transactions export / import --- .../android/model/realm/Transaction.kt | 3 +- .../android/model/realm/TransactionType.kt | 12 +++ .../android/ui/fragment/SettingsFragment.kt | 23 ++++-- .../ui/view/rowrepresentable/SettingRow.kt | 8 +- .../android/util/csv/CSVDescriptor.kt | 6 +- .../android/util/csv/PACSVDescriptor.kt | 16 ++-- .../android/util/csv/ProductCSVDescriptors.kt | 18 ++++- .../android/util/csv/SessionCSVDescriptor.kt | 7 +- .../android/util/csv/SessionField.kt | 41 ++++++++-- .../csv/SessionTransactionCSVDescriptor.kt | 2 +- .../util/csv/TransactionCSVDescriptor.kt | 78 +++++++++++++++++++ app/src/main/res/values/strings.xml | 2 + 12 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt index 03caa4b6..e024b9a2 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt @@ -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 } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt index e8bdf674..8a1c8f7c 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt @@ -69,6 +69,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 diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt index ecb5170c..69efbde1 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt @@ -18,6 +18,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 @@ -136,9 +137,8 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep 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 -> { - this.csvExport() - } + SettingRow.EXPORT_CSV_SESSIONS -> this.sessionsCSVExport() + SettingRow.EXPORT_CSV_TRANSACTIONS -> this.transactionsCSVExport() SettingRow.FOLLOW_US -> { when (position) { 0 -> parentActivity?.openUrl(URL.BLOG.value) @@ -211,17 +211,24 @@ class SettingsFragment : RealmFragment(), RowRepresentableDelegate, StaticRowRep } } - private fun csvExport() { + 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") + } - Timber.d("CSV = $csv") + private fun shareCSV(content: String, dataType: String) { try { - val fileName = "sessions_${Date().dateTimeFileFormatted}.csv" - FileUtils.writeFileToFilesDir(csv, fileName, requireContext()) - this.shareFile(fileName, "Poker Analytics Export", "CSV Sessions") + 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() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt index f839db9f..7b99d734 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt @@ -30,7 +30,8 @@ enum class SettingRow : RowRepresentable { CURRENCY, // Export - EXPORT_CSV, + EXPORT_CSV_SESSIONS, + EXPORT_CSV_TRANSACTIONS, // Data management CUSTOM_FIELD, @@ -68,7 +69,7 @@ enum class SettingRow : RowRepresentable { rows.addAll(arrayListOf(CURRENCY)) rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.export)) - rows.addAll(arrayListOf(EXPORT_CSV)) + rows.addAll(arrayListOf(EXPORT_CSV_SESSIONS, EXPORT_CSV_TRANSACTIONS)) rows.add( CustomizableRowRepresentable( @@ -105,7 +106,8 @@ enum class SettingRow : RowRepresentable { FOLLOW_US -> R.string.follow_us LANGUAGE -> R.string.language CURRENCY -> R.string.currency - EXPORT_CSV -> R.string.csv + 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 diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt index 3fd9e409..6c23ec7d 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -83,15 +83,15 @@ abstract class DataCSVDescriptor(source: DataSource, vararg el dataSequence.forEach { data -> val line = mutableListOf() this.fields.forEach { field -> - line.add(this.toCSV(data, field)) + line.add(this.toCSV(data, field) ?: "") } lines.add(line.joinToString(",")) } return lines.joinToString("\n") } - protected open fun toCSV(data: T, field: CSVField): String { - return "" + protected open fun toCSV(data: T, field: CSVField): String? { + return null } // abstract fun diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt index 0d0e5623..d68f8da7 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt @@ -55,7 +55,7 @@ abstract class PACSVDescriptor(source: DataSource, private var sameDaySessionCount = 0 } currentDay = value - } + } else {} } is SessionField.End -> { endDate = field.parse(value) @@ -104,7 +104,7 @@ abstract class PACSVDescriptor(source: DataSource, private var 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) @@ -135,18 +135,18 @@ abstract class PACSVDescriptor(source: DataSource, private var } 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 @@ -160,13 +160,13 @@ abstract class PACSVDescriptor(source: DataSource, private var 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 = @@ -174,7 +174,7 @@ abstract class PACSVDescriptor(source: DataSource, private var is SessionField.TournamentName -> { if (value.isNotEmpty()) { session.tournamentName = realm.getOrCreate(value) - } + } else {} } is SessionField.TournamentTypeName -> session.tournamentType = TournamentType.getValueForLabel(value)?.ordinal diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt index 26dfd148..7b36c4a0 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt @@ -17,7 +17,8 @@ class ProductCSVDescriptors { runGoodCashGames, runGoodTournaments, pokerAnalyticsiOS, - pokerAnalyticsAndroid + pokerAnalyticsAndroid, + pokerAnalyticsAndroidTransactions ) private val pokerAgent: CSVDescriptor @@ -189,6 +190,21 @@ 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( diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt index 863095b4..b4307189 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -15,8 +15,9 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el return this.parseSession(realm, record) } - override fun toCSV(data: Session, field: CSVField): String { - val string = when (field) { + override fun toCSV(data: Session, field: CSVField): String? { + + 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()) @@ -49,7 +50,7 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el is SessionField.Comment -> data.comment else -> null } - return string ?: "" + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt index dacd13ec..a9aea120 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt @@ -4,14 +4,39 @@ import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.model.realm.TournamentFeature import java.util.* -//sealed class TransactionField { -// -// data class TransactionType( -// override var header: String, -// override var callback: ((String) -> net.pokeranalytics.android.model.realm.TransactionType?)? = null -// ) : DataCSVField -// -//} +sealed class TrField { + + data class TransactionDate( + override var header: String, + 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, + override val numberFormat: String? = 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, + override val numberFormat: String? = null + ) : NumberCSVField + + data class TransactionType(override var header: String) : CSVField + data class Comment(override var header: String) : CSVField + +} /** * The enumeration of Session fields diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt index 0125b615..e71a1585 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt @@ -65,7 +65,7 @@ class SessionTransactionCSVDescriptor(source: DataSource, private var isTourname var buyin: Double? = null var cashedOut: Double? = null - fields.forEach { field -> + this.fields.forEach { field -> val index = this.fieldMapping[field] if (index != null) { diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt new file mode 100644 index 00000000..3b2db42d --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt @@ -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(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") + } + + } + +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f8699124..b4dbe382 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -768,5 +768,7 @@ Join us on Discord! We\'ve opened our Discord channel! Come to hang out, talk about poker or about the app! Good for you! + Sessions (CSV) + Transactions (CSV) From d964be2d16ac26386ccb8bad0843928542be7333 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 14 May 2020 16:12:38 +0200 Subject: [PATCH 14/26] Bumps version to 2.4 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 98d03541..d36188bd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,7 +34,7 @@ android { minSdkVersion 23 targetSdkVersion 28 versionCode 78 - versionName "2.3.6" + versionName "2.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } From 72d2dbf766014cb979e1f32ca1ed76d2a438c0c0 Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 19 May 2020 17:47:50 +0200 Subject: [PATCH 15/26] Custom field management in CSV import/export --- .../android/model/TournamentType.kt | 12 ++-- .../android/model/realm/CustomField.kt | 28 +++++++- .../android/util/csv/CSVDescriptor.kt | 34 +++++++-- .../android/util/csv/CSVField.kt | 12 +++- .../android/util/csv/CSVImporter.kt | 1 + .../android/util/csv/PACSVDescriptor.kt | 18 ++++- .../android/util/csv/ProductCSVDescriptors.kt | 4 +- .../android/util/csv/SessionCSVDescriptor.kt | 70 ++++++++++++++++++- .../android/util/csv/SessionField.kt | 66 ++++++++--------- .../csv/SessionTransactionCSVDescriptor.kt | 4 +- .../util/csv/TransactionCSVDescriptor.kt | 2 +- 11 files changed, 191 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt b/app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt index c17b647a..ac7deb47 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt @@ -5,20 +5,20 @@ 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 get() { - return TournamentType.values() as List + return values() as List } 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 } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt index 98266561..aa187757 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt @@ -29,6 +29,22 @@ 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 + customField + } + } + + } + @Ignore override val realmObjectClass: Class = CustomField::class.java @@ -283,6 +299,16 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa this.entriesToDelete.clear() } + fun getOrCreateEntry(value: String): CustomFieldEntry { + this.entries.firstOrNull { it.value == value }?.let { + return it + } ?: run { + val entry = this.addEntry() + entry.value = value + return entry + } + } + /** * Clean the entries if the type is not a list & remove the deleted entries from realm */ @@ -309,7 +335,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) } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt index 6c23ec7d..5379d292 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -75,14 +75,20 @@ abstract class DataCSVDescriptor(source: DataSource, vararg el this.realmModelIds.clear() } + open fun prepareCSVExport() { + + } + fun toCSV(dataSequence: List): String { + prepareCSVExport() + val lines = mutableListOf() lines.add(this.csvHeaders) dataSequence.forEach { data -> val line = mutableListOf() - this.fields.forEach { field -> + this.staticFields.forEach { field -> line.add(this.toCSV(data, field) ?: "") } lines.add(line.joinToString(",")) @@ -106,7 +112,13 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) /** * The CSVField list describing the CSV header format */ - protected var fields: List = listOf() + protected var staticFields: MutableList = mutableListOf() + + /** + * A list of dynamic CSVField, described in the CSV header + */ +// protected var dynamicFields: MutableList = mutableListOf() + /** * The mapping of CSVField with their index in the CSV file */ @@ -114,7 +126,7 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) init { if (elements.isNotEmpty()) { - this.fields = elements.toList() + this.staticFields = elements.toMutableList() } } @@ -139,7 +151,7 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) var count = 0 val headers = record.toSet() - this.fields.forEach { field -> + this.staticFields.forEach { field -> val index = headers.indexOf(field.header) if (index >= 0) { @@ -147,15 +159,23 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) count++ } } - val mandatoryFields = this.fields.filter { !it.optional } - Timber.d("source= ${this.source.name} > total fields = ${this.fields.size}, identified = $count") + val mandatoryFields = this.staticFields.filter { !it.optional } + Timber.d("source= ${this.source.name} > total fields = ${this.staticFields.size}, identified = $count") 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() - this.fields.forEach { + this.staticFields.forEach { headers.add(it.header) } return headers.joinToString(",") diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt index 3cedd81a..a9dadace 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt @@ -1,5 +1,6 @@ package net.pokeranalytics.android.util.csv +import net.pokeranalytics.android.model.realm.CustomField import net.pokeranalytics.android.model.realm.TournamentFeature import timber.log.Timber import java.text.DateFormat @@ -13,8 +14,6 @@ import java.util.* */ interface NumberCSVField: TypedCSVField { - val numberFormat: String? - companion object { fun defaultParse(value: String) : Double? { @@ -170,6 +169,14 @@ interface BooleanCSVField : TypedCSVField { } } +interface CustomFieldCSVField : CSVField { + var customField: CustomField + + companion object { + val separator: String = "::CF" + } +} + interface TypedCSVField : CSVField { fun parse(value: String) : T? fun format(data: T?): String? @@ -179,7 +186,6 @@ interface TypedCSVField : CSVField { interface CSVField { companion object { - const val delimiter = "\"" const val separator = "|" } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt index 05b37957..4f631b0d 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt @@ -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) { diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt index d68f8da7..2986e96e 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt @@ -14,7 +14,10 @@ import org.apache.commons.csv.CSVRecord import timber.log.Timber import java.util.* -abstract class PACSVDescriptor(source: DataSource, private var isTournament: Boolean?, vararg elements: CSVField) : DataCSVDescriptor(source, *elements) { +abstract class PACSVDescriptor(source: DataSource, + private var isTournament: Boolean?, + vararg elements: CSVField) + : DataCSVDescriptor(source, *elements) { private var sameDaySessionCount: Int = 0 private var currentDay: String = "" @@ -40,7 +43,7 @@ abstract class PACSVDescriptor(source: DataSource, private var var stackingIn: Double? = null var stackingOut: Double? = null - this.fields.forEach { field -> + this.staticFields.forEach { field -> this.fieldMapping[field]?.let { index -> @@ -196,6 +199,17 @@ abstract class PACSVDescriptor(source: DataSource, private var is SessionField.StackingOut -> { stackingOut = field.parse(value) } + is SessionField.ListCustomField -> { + val entry = field.customField.getOrCreateEntry(value) + session.customFieldEntries.add(entry) + } + is SessionField.NumberCustomField -> { + val customField = field.customField + val entry = CustomFieldEntry() + entry.numericValue = field.parse(value) + customField.entries.add(entry) + session.customFieldEntries.add(entry) + } else -> { } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt index 7b36c4a0..3a137468 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/ProductCSVDescriptors.kt @@ -212,7 +212,7 @@ class ProductCSVDescriptors { 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", Calendar.SECOND), + SessionField.Break("Break"), SessionField.SessionType("Type"), SessionField.Live("Live"), SessionField.NumberOfTables("Tables"), @@ -229,7 +229,7 @@ class ProductCSVDescriptors { SessionField.CurrencyRate("Currency Rate"), SessionField.SmallBlind("Small Blind"), SessionField.BigBlind("Big Blind"), - SessionField.TournamentType("Tournament Type"), + SessionField.TournamentTypeName("Tournament Type"), SessionField.TournamentName("Tournament Name"), SessionField.TournamentEntryFee("Entry fee"), SessionField.TournamentNumberOfPlayers("Number of players"), diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt index b4307189..4d7fc9ff 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -1,7 +1,10 @@ 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.TournamentType +import net.pokeranalytics.android.model.realm.CustomField import net.pokeranalytics.android.model.realm.Session import org.apache.commons.csv.CSVRecord @@ -11,6 +14,25 @@ import org.apache.commons.csv.CSVRecord class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg elements: CSVField) : PACSVDescriptor(source, isTournament, *elements) { + override fun prepareCSVExport() { + + 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.ordinal -> { + SessionField.NumberCustomField(header, customField) + } + else -> { + SessionField.ListCustomField(header, customField) + } + } + this.staticFields.add(f) + } + realm.close() + + } + override fun parseData(realm: Realm, record: CSVRecord): Session? { return this.parseSession(realm, record) } @@ -41,16 +63,62 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el 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.TournamentType -> { + data.tournamentType?.let { tt -> + TournamentType.values()[tt].label + } + } 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.first { it.customField == field.customField } + field.format(entry.numericValue) + } + is SessionField.ListCustomField -> data.customFieldEntries.first { it.customField == field.customField }.value else -> null } } + override fun hasMatched(realm: Realm, record: CSVRecord) { + super.hasMatched(realm, record) + + // 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 -> + + 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) + + val field = when (customField.type) { + CustomField.Type.LIST.ordinal -> { + SessionField.NumberCustomField(header, customField) + } + else -> { + SessionField.ListCustomField(header, customField) + } + } + + val index = headers.indexOf(header) // get index in the record + if (index >= 0) { + this.fieldMapping[field] = index + } + this.staticFields.add(field) + + } + + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt index a9aea120..bfc97d64 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt @@ -1,6 +1,7 @@ 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.* @@ -14,8 +15,7 @@ sealed class TrField { data class Amount( override var header: String, - override var callback: ((String) -> Double?)? = null, - override val numberFormat: String? = null + override var callback: ((String) -> Double?)? = null ) : NumberCSVField data class BankrollName(override var header: String) : CSVField @@ -29,8 +29,7 @@ sealed class TrField { 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 TransactionType(override var header: String) : CSVField @@ -70,33 +69,28 @@ 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? { @@ -118,48 +112,42 @@ sealed class SessionField { 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?)? = null) : - BlindCSVField + data class Blind(override var header: String, + override var callback: ((String) -> Pair?)? = null + ) : BlindCSVField data class Live( override var header: String, @@ -190,8 +178,7 @@ sealed class SessionField { 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( @@ -211,14 +198,23 @@ sealed class SessionField { 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 + } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt index e71a1585..5d720445 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt @@ -39,7 +39,7 @@ class SessionTransactionCSVDescriptor(source: DataSource, private var isTourname override fun parseData(realm: Realm, record: CSVRecord): Identifiable? { var dataType: DataType? = null - val typeField = fields.firstOrNull { it is SessionField.SessionType } + val typeField = staticFields.firstOrNull { it is SessionField.SessionType } typeField?.let { field -> this.fieldMapping[field]?.let { index -> val typeValue = record.get(index) @@ -65,7 +65,7 @@ class SessionTransactionCSVDescriptor(source: DataSource, private var isTourname var buyin: Double? = null var cashedOut: Double? = null - this.fields.forEach { field -> + this.staticFields.forEach { field -> val index = this.fieldMapping[field] if (index != null) { diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt index 3b2db42d..978aa016 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt @@ -24,7 +24,7 @@ class TransactionCSVDescriptor(source: DataSource, vararg elements: CSVField) : var currencyCode: String? = null var currencyRate: Double? = null - for (field in this.fields) { + for (field in this.staticFields) { val index = this.fieldMapping[field] if (index != null) { From cdfbf1bf3ff95370777be9d993357b153a462efe Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 20 May 2020 12:29:14 +0200 Subject: [PATCH 16/26] Fixes in custom fields creation --- .../android/model/realm/CustomField.kt | 12 ++++++--- .../android/ui/fragment/ImportFragment.kt | 2 +- .../android/util/csv/CSVDescriptor.kt | 18 +++++++------ .../android/util/csv/PACSVDescriptor.kt | 20 +++++++++----- .../android/util/csv/SessionCSVDescriptor.kt | 26 +++++++++++-------- .../android/util/csv/SessionField.kt | 24 ++++++++++------- .../csv/SessionTransactionCSVDescriptor.kt | 4 +-- .../util/csv/TransactionCSVDescriptor.kt | 2 +- 8 files changed, 66 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt index aa187757..97aac8c7 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/CustomField.kt @@ -23,6 +23,7 @@ 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 @@ -39,7 +40,7 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa val customField = CustomField() customField.name = name customField.type = type - customField + realm.copyToRealm(customField) } } @@ -299,12 +300,15 @@ open class CustomField : RealmObject(), NameManageable, StaticRowRepresentableDa this.entriesToDelete.clear() } - fun getOrCreateEntry(value: String): CustomFieldEntry { - this.entries.firstOrNull { it.value == value }?.let { + fun getOrCreateEntry(realm: Realm, value: String): CustomFieldEntry { + this.entries.find { it.value == value }?.let { + Timber.d("L>> get") return it } ?: run { - val entry = this.addEntry() + Timber.d("L>> create") + val entry = realm.copyToRealm(CustomFieldEntry()) entry.value = value + this.entries.add(entry) return entry } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt index d3c233c4..10ad57b1 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt @@ -100,7 +100,7 @@ class ImportFragment : RealmFragment(), ImportDelegate { val message = exceptionMessage + ". " + requireContext().getString(R.string.import_error) val snackBar = Snackbar.make(view!!, message, Snackbar.LENGTH_INDEFINITE) val textView = snackBar.view.findViewById(com.google.android.material.R.id.snackbar_text) - textView.maxLines = 4 + textView.maxLines = 5 snackBar.show() } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt index 5379d292..5585ef44 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt @@ -87,9 +87,11 @@ abstract class DataCSVDescriptor(source: DataSource, vararg el lines.add(this.csvHeaders) dataSequence.forEach { data -> + val line = mutableListOf() - this.staticFields.forEach { field -> - line.add(this.toCSV(data, field) ?: "") + this.fields.forEach { field -> + val string = this.toCSV(data, field) + line.add(string ?: "") } lines.add(line.joinToString(",")) } @@ -112,7 +114,7 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) /** * The CSVField list describing the CSV header format */ - protected var staticFields: MutableList = mutableListOf() + protected var fields: MutableList = mutableListOf() /** * A list of dynamic CSVField, described in the CSV header @@ -126,7 +128,7 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) init { if (elements.isNotEmpty()) { - this.staticFields = elements.toMutableList() + this.fields = elements.toMutableList() } } @@ -151,7 +153,7 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) var count = 0 val headers = record.toSet() - this.staticFields.forEach { field -> + this.fields.forEach { field -> val index = headers.indexOf(field.header) if (index >= 0) { @@ -159,8 +161,8 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) count++ } } - val mandatoryFields = this.staticFields.filter { !it.optional } - Timber.d("source= ${this.source.name} > total fields = ${this.staticFields.size}, identified = $count") + val mandatoryFields = this.fields.filter { !it.optional } + Timber.d("source= ${this.source.name} > total fields = ${this.fields.size}, identified = $count") return count >= mandatoryFields.size } @@ -175,7 +177,7 @@ abstract class CSVDescriptor(var source: DataSource, vararg elements: CSVField) protected val csvHeaders: String get() { val headers = mutableListOf() - this.staticFields.forEach { + this.fields.forEach { headers.add(it.header) } return headers.joinToString(",") diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt index 2986e96e..e0befb16 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/PACSVDescriptor.kt @@ -43,7 +43,7 @@ abstract class PACSVDescriptor(source: DataSource, var stackingIn: Double? = null var stackingOut: Double? = null - this.staticFields.forEach { field -> + this.fields.forEach { field -> this.fieldMapping[field]?.let { index -> @@ -200,15 +200,23 @@ abstract class PACSVDescriptor(source: DataSource, stackingOut = field.parse(value) } is SessionField.ListCustomField -> { - val entry = field.customField.getOrCreateEntry(value) + val entry = field.customField.getOrCreateEntry(realm, value) session.customFieldEntries.add(entry) } is SessionField.NumberCustomField -> { val customField = field.customField - val entry = CustomFieldEntry() - entry.numericValue = field.parse(value) - customField.entries.add(entry) - session.customFieldEntries.add(entry) + + 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 -> { } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt index 4d7fc9ff..d6db5cbf 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -7,6 +7,7 @@ import net.pokeranalytics.android.model.TournamentType import net.pokeranalytics.android.model.realm.CustomField import net.pokeranalytics.android.model.realm.Session import org.apache.commons.csv.CSVRecord +import timber.log.Timber /** * A SessionCSVDescriptor is a CSVDescriptor specialized in parsing Session objects @@ -20,14 +21,14 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el 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.ordinal -> { - SessionField.NumberCustomField(header, customField) + CustomField.Type.LIST.uniqueIdentifier -> { + SessionField.ListCustomField(header, customField) } else -> { - SessionField.ListCustomField(header, customField) + SessionField.NumberCustomField(header, customField) } } - this.staticFields.add(f) + this.fields.add(f) } realm.close() @@ -75,10 +76,13 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el is SessionField.TournamentPosition -> field.format(data.result?.tournamentFinalPosition) is SessionField.Comment -> data.comment is SessionField.NumberCustomField -> { - val entry = data.customFieldEntries.first { it.customField == field.customField } - field.format(entry.numericValue) + val entry = data.customFieldEntries.find { it.customField?.id == field.customField.id } + field.format(entry?.numericValue) + } + is SessionField.ListCustomField -> { + val entry = data.customFieldEntries.find { it.customField?.id == field.customField.id } + entry?.value } - is SessionField.ListCustomField -> data.customFieldEntries.first { it.customField == field.customField }.value else -> null } @@ -103,11 +107,11 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el val customField = CustomField.getOrCreate(realm, name, type) val field = when (customField.type) { - CustomField.Type.LIST.ordinal -> { - SessionField.NumberCustomField(header, customField) + CustomField.Type.LIST.uniqueIdentifier -> { + SessionField.ListCustomField(header, customField) } else -> { - SessionField.ListCustomField(header, customField) + SessionField.NumberCustomField(header, customField) } } @@ -115,7 +119,7 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el if (index >= 0) { this.fieldMapping[field] = index } - this.staticFields.add(field) + this.fields.add(field) } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt index bfc97d64..842c92c4 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionField.kt @@ -93,20 +93,26 @@ sealed class SessionField { 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(multiplier) + return v?.times(this.multiplier) + } + + override fun format(data: Double?): String? { + return super.format(data?.div(multiplier)) } } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt index 5d720445..e71a1585 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionTransactionCSVDescriptor.kt @@ -39,7 +39,7 @@ class SessionTransactionCSVDescriptor(source: DataSource, private var isTourname override fun parseData(realm: Realm, record: CSVRecord): Identifiable? { var dataType: DataType? = null - val typeField = staticFields.firstOrNull { it is SessionField.SessionType } + val typeField = fields.firstOrNull { it is SessionField.SessionType } typeField?.let { field -> this.fieldMapping[field]?.let { index -> val typeValue = record.get(index) @@ -65,7 +65,7 @@ class SessionTransactionCSVDescriptor(source: DataSource, private var isTourname var buyin: Double? = null var cashedOut: Double? = null - this.staticFields.forEach { field -> + this.fields.forEach { field -> val index = this.fieldMapping[field] if (index != null) { diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt index 978aa016..3b2db42d 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/TransactionCSVDescriptor.kt @@ -24,7 +24,7 @@ class TransactionCSVDescriptor(source: DataSource, vararg elements: CSVField) : var currencyCode: String? = null var currencyRate: Double? = null - for (field in this.staticFields) { + for (field in this.fields) { val index = this.fieldMapping[field] if (index != null) { From 806376db0a42438c3bf3c8f51bcff53ea918c3bf Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 20 May 2020 12:41:47 +0200 Subject: [PATCH 17/26] Fixing tournament type values --- .../net/pokeranalytics/android/ui/fragment/ImportFragment.kt | 3 +++ .../java/net/pokeranalytics/android/util/csv/CSVImporter.kt | 2 ++ .../pokeranalytics/android/util/csv/SessionCSVDescriptor.kt | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt index 10ad57b1..491b399f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/ImportFragment.kt @@ -99,6 +99,9 @@ class ImportFragment : RealmFragment(), ImportDelegate { 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(com.google.android.material.R.id.snackbar_text) textView.maxLines = 5 snackBar.show() diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt index 4f631b0d..f924f944 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt @@ -121,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") } } @@ -151,6 +152,7 @@ open class CSVImporter(istream: InputStream) { } } ?: run { realm.cancelTransaction() + realm.close() throw ImportException("CSVDescriptor should never be null here") } diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt index d6db5cbf..3e4b49fd 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt @@ -64,7 +64,8 @@ class SessionCSVDescriptor(source: DataSource, isTournament: Boolean?, vararg el 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 -> { + is SessionField.TournamentType -> field.format(data.tournamentType) + is SessionField.TournamentTypeName -> { data.tournamentType?.let { tt -> TournamentType.values()[tt].label } From 26867cb23c57f3f6f8d9f2eee165b60075f3bd82 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 21 May 2020 10:39:07 +0200 Subject: [PATCH 18/26] Bumps version code to 79 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d36188bd..0ab05d84 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,7 +33,7 @@ android { applicationId "net.pokeranalytics.android" minSdkVersion 23 targetSdkVersion 28 - versionCode 78 + versionCode 79 versionName "2.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } From ffd684774cf475c9a44b71ae40e56211f88e6339 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 22 May 2020 12:29:52 +0200 Subject: [PATCH 19/26] Fixing exporting issue with comma decimal separator --- app/build.gradle | 2 +- .../pokeranalytics/android/util/TextFormat.kt | 13 +++++++++++++ .../android/util/csv/CSVField.kt | 19 ++++++++++--------- .../pokeranalytics/android/BasicUnitTest.kt | 11 ++++++++++- app/standard/release/output.json | 2 +- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0ab05d84..70f4f297 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,7 +33,7 @@ android { applicationId "net.pokeranalytics.android" minSdkVersion 23 targetSdkVersion 28 - versionCode 79 + versionCode 81 versionName "2.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/net/pokeranalytics/android/util/TextFormat.kt b/app/src/main/java/net/pokeranalytics/android/util/TextFormat.kt index 5bea21f5..1522f9ee 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/TextFormat.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/TextFormat.kt @@ -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) { diff --git a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt index a9dadace..e56c2eda 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/csv/CSVField.kt @@ -2,6 +2,7 @@ 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 @@ -22,10 +23,10 @@ interface NumberCSVField: TypedCSVField { 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,10 +44,10 @@ interface NumberCSVField: TypedCSVField { 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") null @@ -55,7 +56,7 @@ interface NumberCSVField: TypedCSVField { override fun format(data: Double?): String? { return if (data != null) { - NumberFormat.getInstance().format(data) + CSVNumberFormat.format(data) } else { null } @@ -71,7 +72,7 @@ interface IntCSVField: TypedCSVField { } return try { - NumberFormat.getInstance().parse(value).toInt() + CSVNumberFormat.parse(value).toInt() } catch (e: ParseException) { Timber.d("Field $header > Unparseable number: $value") null @@ -80,7 +81,7 @@ interface IntCSVField: TypedCSVField { override fun format(data: Int?): String? { return if (data != null) { - NumberFormat.getInstance().format(data) + CSVNumberFormat.format(data) } else { null } @@ -149,8 +150,8 @@ interface BlindCSVField : TypedCSVField> { override fun format(data: Pair?): String? { data?.let { - val sb = NumberFormat.getInstance().format(data.first) - val bb = NumberFormat.getInstance().format(data.second) + val sb = CSVNumberFormat.format(data.first) + val bb = CSVNumberFormat.format(data.second) return "$sb/$bb" } ?: run { return null diff --git a/app/src/test/java/net/pokeranalytics/android/BasicUnitTest.kt b/app/src/test/java/net/pokeranalytics/android/BasicUnitTest.kt index ffc85579..70a56bec 100644 --- a/app/src/test/java/net/pokeranalytics/android/BasicUnitTest.kt +++ b/app/src/test/java/net/pokeranalytics/android/BasicUnitTest.kt @@ -1,10 +1,10 @@ package net.pokeranalytics.android +import net.pokeranalytics.android.util.CSVNumberFormat import net.pokeranalytics.android.util.Parser import net.pokeranalytics.android.util.extensions.kmbFormatted import org.junit.Assert import org.junit.Test -import java.text.NumberFormat class BasicUnitTest : RealmUnitTest() { @@ -47,4 +47,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) + } + } diff --git a/app/standard/release/output.json b/app/standard/release/output.json index e0ea7d03..698ad8d1 100644 --- a/app/standard/release/output.json +++ b/app/standard/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":74,"versionName":"2.3.2","enabled":true,"outputFile":"PokerAnalytics_2.3.2(74)_191108_1621_release.apk","fullName":"standardRelease","baseName":"standard-release"},"path":"PokerAnalytics_2.3.2(74)_191108_1621_release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":81,"versionName":"2.4","enabled":true,"outputFile":"PokerAnalytics_2.4(81)_200521_1134_release.apk","fullName":"standardRelease","baseName":"standard-release","dirName":""},"path":"PokerAnalytics_2.4(81)_200521_1134_release.apk","properties":{}}] \ No newline at end of file From 6d4b17aff3ee5fc4c363aba6253444e8f928f39c Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 25 May 2020 10:45:35 +0200 Subject: [PATCH 20/26] Bumps version to 82 - 2.4.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 70f4f297..d3604a9a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,8 +33,8 @@ android { applicationId "net.pokeranalytics.android" minSdkVersion 23 targetSdkVersion 28 - versionCode 81 - versionName "2.4" + versionCode 82 + versionName "2.4.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } From f32d8eee3cb6ca1e18b916952040b982ef136f5d Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 25 May 2020 10:46:03 +0200 Subject: [PATCH 21/26] Small refactoring --- .../android/model/realm/Transaction.kt | 1 - .../ui/fragment/data/TransactionDataFragment.kt | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt index e024b9a2..9e514fed 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt @@ -175,5 +175,4 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo return DefaultLegendValues(this.entryTitle(context), entryValue, totalStatValue, leftName = leftName) } - } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt index 78518743..579ef0ad 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt @@ -43,7 +43,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa } override fun adapterRows(): List? { - return transaction.adapterRows() + return this.transaction.adapterRows() } override fun stringForRow(row: RowRepresentable): String { @@ -93,12 +93,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 (viewModel.primaryKey == null) { // automatically change the row for new data + if (this.viewModel.primaryKey == null) { 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) @@ -106,6 +113,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa } } } + } override fun willSaveData() { From dc41ad36e61aac7722287dd1da954d624e1bbdcf Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 25 May 2020 11:31:57 +0200 Subject: [PATCH 22/26] Fixes small issues with transactions and data edition --- .../android/ui/fragment/SessionFragment.kt | 38 ++++++++++++++++--- .../fragment/data/CustomFieldDataFragment.kt | 2 +- .../ui/fragment/data/EditableDataFragment.kt | 4 +- .../fragment/data/TransactionDataFragment.kt | 7 ++-- .../view/rowrepresentable/TransactionRow.kt | 1 - 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt index c6f6b7b9..6757b428 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt @@ -11,6 +11,10 @@ import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.DiffUtil import com.crashlytics.android.Crashlytics import kotlinx.android.synthetic.main.fragment_session.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager import net.pokeranalytics.android.exceptions.PAIllegalStateException @@ -31,6 +35,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentableDiffCallback import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow +import net.pokeranalytics.android.ui.view.rowrepresentable.TransactionRow import net.pokeranalytics.android.ui.viewmodel.SessionViewModel import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.getNextMinuteInMilliseconds @@ -226,20 +231,43 @@ 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() } } + + /*** + * Selects the next row to ease the data capture + */ + private fun selectNextRow(currentRow: RowRepresentable) { + + if (this.viewModel.sessionId == null) { + GlobalScope.launch(Dispatchers.Main) { + delay(200) + when (currentRow) { + +// TransactionRow.BANKROLL -> onRowSelected(0, TransactionRow.TYPE) +// TransactionRow.TYPE -> onRowSelected(0, TransactionRow.AMOUNT) +// TransactionRow.AMOUNT -> onRowSelected(0, TransactionRow.DATE) +// TransactionRow.DATE -> onRowSelected(0, TransactionRow.COMMENT) + } + } + } + + } + + /** * Update the UI with the session data * Should be called after the initialization of the session diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt index 2a46cb58..df9be671 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/CustomFieldDataFragment.kt @@ -130,7 +130,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) } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt index 2a4bd647..adc60700 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/EditableDataFragment.kt @@ -50,8 +50,8 @@ open class EditableDataFragment : DataManagerFragment(), RowRepresentableDelegat // When creating an object, open automatically the keyboard for the first row if (!deleteButtonShouldAppear && shouldOpenKeyboard) { val row = dataSource.adapterRows()?.firstOrNull() - row?.let { - onRowSelected(0, it) + if (row != null && this.viewModel.primaryKey == null) { + onRowSelected(0, row) } } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt index 579ef0ad..92b27851 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/TransactionDataFragment.kt @@ -21,6 +21,7 @@ import net.pokeranalytics.android.util.extensions.round import net.pokeranalytics.android.util.extensions.shortDate import net.pokeranalytics.android.util.extensions.sorted import java.util.* +import kotlin.math.abs /** * Custom EditableDataFragment to manage the Transaction data @@ -50,7 +51,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.stringForRow(row) @@ -71,7 +72,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa "data" to getRealm().sorted() ) ) - 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) } @@ -120,7 +121,7 @@ class TransactionDataFragment : EditableDataFragment(), StaticRowRepresentableDa super.willSaveData() val additive = this.transaction.type?.additive ?: true if (!additive) { - this.transaction.amount *= -1 + this.transaction.amount = abs(this.transaction.amount) * -1 } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionRow.kt index c1cb566a..456cb5b8 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionRow.kt @@ -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 -> { From b5bfc875d84bf6cdd96c0c13cf6e967e8e425bd5 Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 25 May 2020 11:46:17 +0200 Subject: [PATCH 23/26] cleanup --- .../android/ui/fragment/SessionFragment.kt | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt index 6757b428..14b10a1e 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt @@ -246,28 +246,6 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate { } } - - /*** - * Selects the next row to ease the data capture - */ - private fun selectNextRow(currentRow: RowRepresentable) { - - if (this.viewModel.sessionId == null) { - GlobalScope.launch(Dispatchers.Main) { - delay(200) - when (currentRow) { - -// TransactionRow.BANKROLL -> onRowSelected(0, TransactionRow.TYPE) -// TransactionRow.TYPE -> onRowSelected(0, TransactionRow.AMOUNT) -// TransactionRow.AMOUNT -> onRowSelected(0, TransactionRow.DATE) -// TransactionRow.DATE -> onRowSelected(0, TransactionRow.COMMENT) - } - } - } - - } - - /** * Update the UI with the session data * Should be called after the initialization of the session From 875d37fc27824c7d6c5a65ff70f139ba23eeff5f Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 26 May 2020 11:05:29 +0200 Subject: [PATCH 24/26] Protects unsuccesful typeface loading from crashes --- .../android/ui/graph/GraphExtensions.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt index b93cc841..63193351 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt @@ -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 From 5d13069392391dcdece78217a7e137a18e1168fe Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 26 May 2020 11:05:37 +0200 Subject: [PATCH 25/26] Bumps version --- app/build.gradle | 4 ++-- app/standard/release/output.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d3604a9a..496cae0f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,8 +33,8 @@ android { applicationId "net.pokeranalytics.android" minSdkVersion 23 targetSdkVersion 28 - versionCode 82 - versionName "2.4.1" + versionCode 83 + versionName "2.4.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/standard/release/output.json b/app/standard/release/output.json index 698ad8d1..507ffc93 100644 --- a/app/standard/release/output.json +++ b/app/standard/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":81,"versionName":"2.4","enabled":true,"outputFile":"PokerAnalytics_2.4(81)_200521_1134_release.apk","fullName":"standardRelease","baseName":"standard-release","dirName":""},"path":"PokerAnalytics_2.4(81)_200521_1134_release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":83,"versionName":"2.4.2","enabled":true,"outputFile":"PokerAnalytics_2.4.2(83)_200525_1155_release.apk","fullName":"standardRelease","baseName":"standard-release","dirName":""},"path":"PokerAnalytics_2.4.2(83)_200525_1155_release.apk","properties":{}}] \ No newline at end of file From 6b18c8c5d0cda4fed53687b2ee06986aed135680 Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 26 May 2020 11:20:45 +0200 Subject: [PATCH 26/26] Bumps to 2.4.3 --- app/build.gradle | 4 ++-- app/standard/release/output.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 496cae0f..559a13e2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,8 +33,8 @@ android { applicationId "net.pokeranalytics.android" minSdkVersion 23 targetSdkVersion 28 - versionCode 83 - versionName "2.4.2" + versionCode 84 + versionName "2.4.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/standard/release/output.json b/app/standard/release/output.json index 507ffc93..42e537a6 100644 --- a/app/standard/release/output.json +++ b/app/standard/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":83,"versionName":"2.4.2","enabled":true,"outputFile":"PokerAnalytics_2.4.2(83)_200525_1155_release.apk","fullName":"standardRelease","baseName":"standard-release","dirName":""},"path":"PokerAnalytics_2.4.2(83)_200525_1155_release.apk","properties":{}}] \ No newline at end of file +[{"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":{}}] \ No newline at end of file