diff --git a/app/build.gradle b/app/build.gradle index 53631d80..17e9d74a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' -apply plugin: 'io.fabric' +//apply plugin: 'io.fabric' repositories { maven { url 'https://maven.fabric.io/public' } @@ -51,7 +51,7 @@ dependencies { implementation 'com.google.firebase:firebase-core:16.0.7' // Crashlytics - implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9' + //implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9' // Kotlin implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dc02fbf9..8b57b40e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,7 +20,7 @@ - + diff --git a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt index b1400069..33fcc8c2 100644 --- a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt +++ b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt @@ -1,11 +1,9 @@ package net.pokeranalytics.android import android.app.Application -import com.crashlytics.android.Crashlytics -import io.fabric.sdk.android.Fabric import io.realm.Realm -import io.realm.Realm.setDefaultConfiguration import io.realm.RealmConfiguration +import net.pokeranalytics.android.util.PokerAnalyticsLogs import timber.log.Timber @@ -24,9 +22,9 @@ class PokerAnalyticsApplication: Application() { if (BuildConfig.DEBUG) { // Logs - Timber.plant(Timber.DebugTree()) + Timber.plant(PokerAnalyticsLogs()) } else { - Fabric.with(this, Crashlytics()) + //Fabric.with(this, Crashlytics()) } } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt index 3cb8d1ff..4dbdd4b0 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt @@ -1,5 +1,6 @@ package net.pokeranalytics.android.calculus +import net.pokeranalytics.android.calculus.Stat.* import net.pokeranalytics.android.model.realm.TimeFrameGroup @@ -25,6 +26,15 @@ class Calculator { var evolutionValues: EvolutionValues = EvolutionValues.NONE var displayedStats: List = listOf() + fun shouldComputeStandardDeviation() : Boolean { + this.displayedStats.forEach { stat -> + when (stat) { + STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> return true + } + } + return false + } + // var aggregation: Aggregation? = null } @@ -62,43 +72,113 @@ class Calculator { fun compute(group: SessionGroup, options: Options) : ComputedResults { val sessions: List = group.sessionGroup - val series: Set = setOf() // @todo get unique list of serie + val sessionGroups: Set = setOf() // @todo get unique list of serie var results: ComputedResults = ComputedResults() var sum: Double = 0.0 - var duration: Double = 0.0 + var totalHands: Double = 0.0 + var bbSum: Double = 0.0 + var bbSessionCount: Int = 0 + var winningSessionCount: Int = 0 + var totalBuyin: Double = 0.0 + // @todo add all stats // Compute for each session - sessions.forEach { item -> - sum += item.value - - when (options.evolutionValues) { - Options.EvolutionValues.STANDARD -> { - results.addEvolutionValue(sum, Stat.NETRESULT) - } - Options.EvolutionValues.DATED -> { - results.addEvolutionValue(sum, duration, Stat.NETRESULT) - } + var index: Int = 0 + sessions.forEach { s -> + index++; + + bbSessionCount += s.bigBlindSessionCount + if (s.value >= 0) { + winningSessionCount++ + } + totalBuyin += s.buyin + + if (options.evolutionValues != Options.EvolutionValues.NONE) { + + sum += s.value + totalHands += s.estimatedHands + bbSum += s.bbNetResult + + results.addEvolutionValue(sum / index, AVERAGE) + results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES) + results.addEvolutionValue(bbSum / bbSessionCount, AVERAGE_NET_BB) + results.addEvolutionValue((winningSessionCount / index).toDouble(), WIN_RATIO) + results.addEvolutionValue(totalBuyin / index, AVERAGE_BUYIN) + results.addEvolutionValue(Stat.returnOnInvestment(sum, totalBuyin), ROI) } } // Compute for each serie - series.forEach { serie -> - duration += serie.duration + var duration: Double = 0.0 + var hourlyRate: Double = 0.0; var hourlyRateBB: Double = 0.0 + + index = 0; sum = 0.0; totalHands = 0.0; bbSum = 0.0; + sessionGroups.forEach { group -> + index++ + duration += group.duration + sum += group.netResult + totalHands += group.estimatedHands + bbSum += group.bbNetResult + + hourlyRate = sum / duration * 3600.0 + hourlyRateBB = bbSum / duration * 3600.0 if (options.evolutionValues != Options.EvolutionValues.NONE) { - results.addEvolutionValue(duration, Stat.DURATION) + results.addEvolutionValue(sum, duration, NETRESULT) + results.addEvolutionValue(sum / duration * 3600.0, duration, HOURLY_RATE) + results.addEvolutionValue(Stat.netBBPer100Hands(bbSum, totalHands), duration, NET_BB_PER_100_HANDS) + results.addEvolutionValue(hourlyRate, duration, HOURLY_RATE) + results.addEvolutionValue(index.toDouble(), duration, NUMBER_OF_GROUPS) + results.addEvolutionValue(group.duration, duration, DURATION) + results.addEvolutionValue(duration / index, duration, AVERAGE_DURATION) + results.addEvolutionValue(hourlyRateBB, duration, HOURLY_RATE_BB) } } + val average: Double = sum / sessions.size + // Create stats results.addStats(setOf( - ComputedStat(Stat.NETRESULT, sum), - ComputedStat(Stat.AVERAGE,sum / sessions.size), - ComputedStat(Stat.DURATION, duration) - )) + ComputedStat(NETRESULT, sum), + ComputedStat(HOURLY_RATE, hourlyRate), + ComputedStat(AVERAGE, average), + ComputedStat(DURATION, duration), + ComputedStat(NUMBER_OF_GROUPS, sessionGroups.size.toDouble()), + ComputedStat(NUMBER_OF_GAMES, sessions.size.toDouble()), + ComputedStat(AVERAGE_DURATION, (duration / 3600.0) / sessions.size), + ComputedStat(NET_BB_PER_100_HANDS, Stat.netBBPer100Hands(bbSum, totalHands)), + ComputedStat(HOURLY_RATE_BB, bbSum / duration * 3600.0), + ComputedStat(AVERAGE_NET_BB, bbSum / bbSessionCount), + ComputedStat(WIN_RATIO, (winningSessionCount / sessions.size).toDouble()), + ComputedStat(AVERAGE_BUYIN, totalBuyin / sessions.size), + ComputedStat(ROI, Stat.returnOnInvestment(sum, totalBuyin)) + + )) + + + // Standard Deviation + if (options.shouldComputeStandardDeviation()) { + + var stdSum: Double = 0.0 + sessions.forEach { s -> + stdSum += Math.pow(s.value - average, 2.0) + } + val standardDeviation: Double = Math.sqrt(stdSum / sessions.size) + + var hourlyStdSum: Double = 0.0 + sessionGroups.forEach { sg -> + hourlyStdSum += Math.pow(sg.hourlyRate - hourlyRate, 2.0) + } + val hourlyStandardDeviation: Double = Math.sqrt(hourlyStdSum / sessionGroups.size) + + results.addStats(setOf( + ComputedStat(STANDARD_DEVIATION, standardDeviation), + ComputedStat(Stat.STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) + )) + } return results } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt index fda109f8..2d341fa9 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Computable.kt @@ -10,6 +10,10 @@ interface Summable { // An interface describing some class that can be computed interface SessionInterface : Summable { var serie: TimeFrameGroup + var estimatedHands: Double + var bbNetResult: Double + var bigBlindSessionCount: Int // 0 or 1 + var buyin: Double } /** diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt index d3a9afda..a77de252 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt @@ -7,25 +7,48 @@ interface AnyStat { enum class Stat : AnyStat { NETRESULT, - HOURLYRATE, - DURATION, + HOURLY_RATE, AVERAGE, - STANDARDDEVIATION, - HOURLYSTANDARDDEVIATION; + NUMBER_OF_GROUPS, + NUMBER_OF_GAMES, + DURATION, + AVERAGE_DURATION, + NET_BB_PER_100_HANDS, + HOURLY_RATE_BB, + AVERAGE_NET_BB, + WIN_RATIO, + AVERAGE_BUYIN, + ROI, + STANDARD_DEVIATION, + STANDARD_DEVIATION_HOURLY, + STANDARD_DEVIATION_BB_PER_100_HANDS; fun label() : String = when (this) { NETRESULT -> "" - HOURLYRATE -> "" + HOURLY_RATE -> "" else -> throw IllegalArgumentException("Label not defined") } fun hasDistributionSorting() : Boolean { when (this) { - STANDARDDEVIATION, HOURLYSTANDARDDEVIATION -> return true + STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> return true else -> return false } } + companion object { + + fun returnOnInvestment(netResult: Double, buyin: Double) : Double { + return netResult / buyin + } + + fun netBBPer100Hands(netBB: Double, numberOfHands: Double) : Double { + return netBB / numberOfHands * 100 + } + + } + + } enum class CashSessionStat : AnyStat { diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt index 86ccd723..46ac57e8 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt @@ -1,12 +1,17 @@ package net.pokeranalytics.android.model.realm +import android.content.Context import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey +import net.pokeranalytics.android.ui.adapter.components.DisplayableData +import net.pokeranalytics.android.ui.adapter.components.DynamicRowInterface +import net.pokeranalytics.android.ui.adapter.components.RowViewType +import net.pokeranalytics.android.ui.fragment.components.BottomSheetType import java.util.* -open class Bankroll(name: String = "") : RealmObject() { +open class Bankroll(name: String = "") : RealmObject(), DisplayableData { @PrimaryKey var id = UUID.randomUUID().toString() @@ -24,4 +29,6 @@ open class Bankroll(name: String = "") : RealmObject() { var currency: Currency? = null // @todo rate management + + override var title: String = this.name } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt index f0fa0f89..548f27b0 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt @@ -36,7 +36,7 @@ open class Result : RealmObject() { } // The net (readonly) - var net: Double? = null + var net: Double = 0.0 // The transactions associated with the Result, impacting the result var transactions: RealmList = RealmList() 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 0536b0ad..01a66795 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 @@ -1,92 +1,150 @@ package net.pokeranalytics.android.model.realm -import io.realm.RealmList -import io.realm.Realm -import io.realm.RealmObject -import io.realm.RealmResults -import io.realm.Sort +import io.realm.* +import io.realm.annotations.Ignore import io.realm.annotations.PrimaryKey -import net.pokeranalytics.android.* +import net.pokeranalytics.android.ui.adapter.components.DynamicRowDelegate +import net.pokeranalytics.android.ui.adapter.components.DynamicRowInterface +import net.pokeranalytics.android.ui.adapter.components.SessionRow import net.pokeranalytics.android.util.data.sessionDao import java.util.* -import java.util.UUID.randomUUID +import kotlin.collections.ArrayList +open class Session(comment: String = "") : RealmObject(), DynamicRowDelegate { -open class Session(comment: String = "") : RealmObject() { + @PrimaryKey + var id = UUID.randomUUID().toString() - @PrimaryKey - var id = UUID.randomUUID().toString() + // The result of the main user + var result: Result? = null - // The time frame of the Session, i.e. the start & end date - var timeFrame: TimeFrame? = null + // The time frame of the Session, i.e. the start & end date + var timeFrame: TimeFrame? = null - // The time frame group, which can contain multiple sessions - var timeFrameGroup: TimeFrameGroup? = null + // The time frame group, which can contain multiple sessions + var timeFrameGroup: TimeFrameGroup? = null - // the date of creation of the app + // the date of creation of the app var creationDate: Date = Date() - // The limit type: NL, PL... + // The bankroll hosting the results + var bankroll: Bankroll? = null + + // The limit type: NL, PL... var limit: Int? = null - // The number of tables played at the same time - var numberOfTables: Int = 1 + // The game played during the Session + var game: Game? = null - // The number of players at the table + // The number of players at the table var tableSize: Int? = null - // The game played during the Session - var game: Game? = null + // the location where the session is played + var location: Location? = null - // The bankroll hosting the results - var bankroll: Bankroll? = null + // The number of tables played at the same time + var numberOfTables: Int = 1 - // The hands list associated with the Session + // The hands list associated with the Session var hands: RealmList = RealmList() - // the location where the session is played - var location: Location? = null - - // The result of the main user - var result: Result? = null - - // The list of opponents who participated to the session + // The list of opponents who participated to the session var opponents: RealmList = RealmList() - // A comment written by the user - var comment: String = "" + // A comment written by the user + var comment: String = "" - // Cash Game + // Cash Game - // The small blind value - var cgSmallBlind: Double? = null + // The small blind value + var cgSmallBlind: Double? = null - // The big blind value - var cgBigBlind: Double? = null + // The big blind value + var cgBigBlind: Double? = null // Tournament - // The entry fee of the tournament + // The entry fee of the tournament var tournamentEntryFee: Double? = null - // The total number of players who participated in the tournament + // The total number of players who participated in the tournament var tournamentNumberOfPlayers: Int? = null - // The name of the tournament - var tournamentType: TournamentName? = null - - // The kind of the tournament, MTT or SnG - var tournamentKind: Int? = null - - // The features of the tournament, like Knockout, Shootout, Turbo... - var tournamentFeatures: RealmList = RealmList() - + // The name of the tournament + var tournamentType: TournamentName? = null + + // The kind of the tournament, MTT or SnG + var tournamentKind: Int? = null + + // The features of the tournament, like Knockout, Shootout, Turbo... + var tournamentFeatures: RealmList = RealmList() + + companion object { + + fun newInstance() : Session { + var session: Session = Session() + session.result = Result() + session.timeFrame = TimeFrame() + return session + } + + } + + @Ignore + var estimatedHands: Double = 0.0 + + @Ignore + var bbNetResult: Double = 0.0 + get() { + this.cgBigBlind?.let { bb -> + this.result?.let { result -> + return result.net / bb + } + } + return 0.0 + } + + @Ignore + var bigBlindSessionCount: Int = if (this.cgBigBlind != null) 1 else 0 +// get() = if (this.cgBigBlind != null) 1 else 0 + + @Ignore + var buyin: Double = 0.0 + get() { + this.result?.let { + it.buyin?.let { + return it + } + } + return 0.0 + } + + + override fun adapterRows(): ArrayList { + val rows = ArrayList() + rows.addAll(SessionRow.values()) + return rows + } + + override fun boolForRow(row: DynamicRowInterface): Boolean { + + return false + } + + override fun stringForRow(row: DynamicRowInterface): String { + return when (row) { + SessionRow.BLINDS -> "Blinds" + SessionRow.GAME -> "Game" + SessionRow.DATE -> "Date" + else -> "--" + } + } } enum class TournamentKind { - MTT, - SNG + MTT, + SNG } @@ -95,63 +153,63 @@ enum class TournamentKind { */ class SessionDao(realmDb: Realm) { - var realm: Realm = realmDb - - /** - * Create or update session - */ - fun createOrUpdateSession(session: Session): Session { - realm.beginTransaction() - val sessionToSave = realm.copyToRealmOrUpdate(session) - realm.commitTransaction() - return realm.copyFromRealm(sessionToSave) - } - - /** - * Create or update sessions - */ - fun createOrUpdateSessions(sessions: List): List { - - realm.beginTransaction() - - // Update - val sessionsToSave = realm.copyToRealmOrUpdate(sessions) - realm.commitTransaction() - - return realm.copyFromRealm(sessionsToSave) - } - - /** - * Find all sessions - */ - fun findAllSessions(): RealmResults { - return realm.where(Session::class.java).findAll().sort("creationDate", Sort.DESCENDING) - } - - /** - * Find session by id - */ - fun findSessionById(sessionId: Int): Session? { - return realm.copyFromRealm(realm.where(Session::class.java).equalTo("id", sessionId).findFirst()) - } - - /** - * Delete session - */ - fun deleteSession(sessionId: Int) { - realm.beginTransaction() - realm.sessionDao().findSessionById(sessionId)?.deleteFromRealm() - realm.commitTransaction() - } - - /** - * Delete all sessions - */ - fun deleteAllSessions() { - realm.beginTransaction() - realm.sessionDao().findAllSessions().deleteAllFromRealm() - realm.commitTransaction() - } + var realm: Realm = realmDb + + /** + * Create or update session + */ + fun createOrUpdateSession(session: Session): Session { + realm.beginTransaction() + val sessionToSave = realm.copyToRealmOrUpdate(session) + realm.commitTransaction() + return realm.copyFromRealm(sessionToSave) + } + + /** + * Create or update sessions + */ + fun createOrUpdateSessions(sessions: List): List { + + realm.beginTransaction() + + // Update + val sessionsToSave = realm.copyToRealmOrUpdate(sessions) + realm.commitTransaction() + + return realm.copyFromRealm(sessionsToSave) + } + + /** + * Find all sessions + */ + fun findAllSessions(): RealmResults { + return realm.where(Session::class.java).findAll().sort("creationDate", Sort.DESCENDING) + } + + /** + * Find session by id + */ + fun findSessionById(sessionId: Int): Session? { + return realm.copyFromRealm(realm.where(Session::class.java).equalTo("id", sessionId).findFirst()) + } + + /** + * Delete session + */ + fun deleteSession(sessionId: Int) { + realm.beginTransaction() + realm.sessionDao().findSessionById(sessionId)?.deleteFromRealm() + realm.commitTransaction() + } + + /** + * Delete all sessions + */ + fun deleteAllSessions() { + realm.beginTransaction() + realm.sessionDao().findAllSessions().deleteAllFromRealm() + realm.commitTransaction() + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt index 886028a7..32042c33 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt @@ -1,24 +1,79 @@ package net.pokeranalytics.android.model.realm import io.realm.RealmObject -import io.realm.annotations.PrimaryKey +import io.realm.RealmResults +import io.realm.annotations.LinkingObjects import java.util.* + + open class TimeFrame : RealmObject() { // A start date var startDate: Date = Date() + set(value) { + field = value + this.computeDuration() + if (this.session != null) { + this.notifyDateChange() + } + } // An end date var endDate: Date? = null + set(value) { + field = value + this.computeDuration() + if (this.session != null) { + this.notifyDateChange() + } + } // The break duration - var breakDuration: Double = 0.0 + var breakDuration: Long = 0L + set(value) { + field = value + this.computeDuration() + } // the total duration - var duration: Double = 0.0 + var duration: Long = 0L + private set // indicates a state of pause var paused: Boolean = false + @LinkingObjects("timeFrame") + private val session: RealmResults? = null + + @LinkingObjects("timeFrame") + private val group: RealmResults? = null + + private fun computeDuration() { + + var endDate: Date + if (this.endDate != null) { + endDate = this.endDate!! + } else { + endDate = Date() + } + this.duration = endDate.time - startDate.time - this.breakDuration + } + + private fun notifyDateChange() { +// val realm = Realm.getDefaultInstance() +// +// var query: RealmQuery = realm.where(TimeFrame::class.java) +// query.greaterThan("startDate", this.startDate.time) +// +// this.endDate?.let { endDate -> +// query.or() +// .greaterThan("startDate", endDate.time) +// .lessThan("endDate", endDate.time) +// } +// +// +// realm.close() + } + } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrameGroup.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrameGroup.kt index 4cd6726e..02777b1e 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrameGroup.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrameGroup.kt @@ -3,8 +3,6 @@ package net.pokeranalytics.android.model.realm import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Ignore -import io.realm.annotations.PrimaryKey -import java.util.* open class TimeFrameGroup() : RealmObject() { @@ -13,10 +11,21 @@ open class TimeFrameGroup() : RealmObject() { var timeFrame: TimeFrame? = null // The list of Session played within the group, i.e. played within the same time frame - var timeFrames: RealmList = RealmList() + var sessions: RealmList = RealmList() @Ignore // a duration shortcut var duration: Double = 0.0 + @Ignore // a netResult shortcut + var netResult: Double = 0.0 + + @Ignore // a duration shortcut + var hourlyRate: Double = 0.0 + + @Ignore + var estimatedHands: Double = 0.0 + + @Ignore + var bbNetResult: Double = 0.0 } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt new file mode 100644 index 00000000..e41805f7 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/DataListActivity.kt @@ -0,0 +1,52 @@ +package net.pokeranalytics.android.ui.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import io.realm.Realm +import io.realm.kotlin.where +import kotlinx.android.synthetic.main.activity_data_list.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.Session +import net.pokeranalytics.android.ui.fragment.DataListFragment +import net.pokeranalytics.android.util.PokerAnalyticsActivity +import net.pokeranalytics.android.util.data.sessionDao +import java.util.* + +class DataListActivity : PokerAnalyticsActivity() { + + companion object { + fun newInstance(context: Context, dataType: Int) { + val intent = Intent(context, DataListActivity::class.java) + intent.putExtra("dataType", dataType) + context.startActivity(intent) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_data_list) + + initUI() + } + + /** + * Init UI + */ + private fun initUI() { + + val dataType = intent.getIntExtra("dataType", 0) + val fragment = dataListFragment as DataListFragment + fragment.setData(dataType) + } + + /** + * Init data + */ + private fun initData() { + + + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt index a4fdad7b..968d04ca 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt @@ -66,16 +66,16 @@ class HomeActivity : PokerAnalyticsActivity() { private fun createDefaultSessions() { val realm = Realm.getDefaultInstance() - realm.sessionDao().deleteAllSessions() - - val data = ArrayList() - - for (i in 0..100) { - val session = Session() - session.creationDate = Date() - data.add(session) + if (realm.where(Session::class.java).findAll().isEmpty()) { + realm.sessionDao().deleteAllSessions() + val data = ArrayList() + for (i in 0..100) { + val session = Session() + session.creationDate = Date() + data.add(session) + } + realm.sessionDao().createOrUpdateSessions(data) } - realm.sessionDao().createOrUpdateSessions(data) realm.close() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/HistoryAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/HistoryAdapter.kt index 62f40465..f782256c 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/HistoryAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/HistoryAdapter.kt @@ -8,7 +8,6 @@ import io.realm.RealmResults import kotlinx.android.synthetic.main.row_history_session.view.* import net.pokeranalytics.android.R import net.pokeranalytics.android.model.realm.Session -import net.pokeranalytics.android.ui.view.SessionRowView import timber.log.Timber class HistoryAdapter(private var sessions: RealmResults) : RecyclerView.Adapter() { @@ -25,8 +24,6 @@ class HistoryAdapter(private var sessions: RealmResults) : RecyclerView inner class RowSessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(session: Session?) { - Timber.d("Bind session") - session?.let { itemView.sessionRow.setData(session) itemView.sessionRow.setOnClickListener { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/SettingsAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/SettingsAdapter.kt deleted file mode 100644 index 1ecf3062..00000000 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/SettingsAdapter.kt +++ /dev/null @@ -1,79 +0,0 @@ -package net.pokeranalytics.android.ui.adapter - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.row_data_cell.view.* -import kotlinx.android.synthetic.main.row_history_session.view.* -import net.pokeranalytics.android.R - -class SettingsAdapter() : RecyclerView.Adapter() { - - companion object { - const val ROW_DATA: Int = 100 - enum class DataType { - BANKROLL { - override fun localizedName(): String { - return "Bankroll" - } - }, - GAME { - override fun localizedName(): String { - return "Game" - } - }, - LOCATION { - override fun localizedName(): String { - return "Location" - } - }, - TOURNAMENT_TYPE { - override fun localizedName(): String { - return "Tournament Type" - } - }, - TRANSACTION_TYPE { - override fun localizedName(): String { - return "Transaction" - } - }; - - abstract fun localizedName(): String - } - } - - var onClickOnData: ((position: Int, dataType: Int) -> Unit)? = null - - inner class RowDataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - fun bind(dataType: DataType) { - itemView.dataRow.setData(dataType.localizedName()) - itemView.dataRow.setOnClickListener { - onClickOnData?.invoke(adapterPosition, dataType.ordinal) - } - } - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - when (viewType) { - ROW_DATA -> return RowDataViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_data_cell, parent, false)) - else -> throw IllegalStateException("Need to implement type $viewType in Settings Adapter") - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (getItemViewType(position)) { - ROW_DATA -> (holder as SettingsAdapter.RowDataViewHolder).bind(enumValues()[position]) - } - } - - override fun getItemCount(): Int { - return enumValues().count() - } - - override fun getItemViewType(position: Int): Int { - return ROW_DATA - } - - -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DataListAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DataListAdapter.kt new file mode 100644 index 00000000..37b35ca5 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DataListAdapter.kt @@ -0,0 +1,51 @@ +package net.pokeranalytics.android.ui.adapter.components + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.row_session.view.* +import net.pokeranalytics.android.R + +interface DataRowDelegate { + fun data(position: Int) : DisplayableData + fun size() : Int +} + +interface DataRowCallback { + fun onRowSelected(position: Int) +} + +interface DisplayableData { + var title: String +} + +class DataListAdapter(var delegate: DataRowDelegate, var callBackDelegate: DataRowCallback? = null) : RecyclerView.Adapter() { + + inner class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(row: DisplayableData, listener: View.OnClickListener) { + itemView.title.text = row.title + itemView.container.setOnClickListener(listener) + } + } + + override fun getItemViewType(position: Int): Int { + return RowViewType.TITLE.ordinal + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return DataViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_title, parent, false)) + } + + override fun getItemCount(): Int { + return delegate.size() + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val listener = View.OnClickListener { + callBackDelegate?.onRowSelected(position) + } + (holder as DataViewHolder).bind(this.delegate.data(position), listener) + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DynamicListAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DynamicListAdapter.kt index ef13d20e..83778819 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DynamicListAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DynamicListAdapter.kt @@ -1,39 +1,45 @@ package net.pokeranalytics.android.ui.adapter.components +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -enum class RowType { +interface EditableDataDelegate { + fun setValue(value: Any, row: DynamicRowInterface) +} + +interface DynamicRowDelegate { + + fun adapterRows() : ArrayList + fun boolForRow(row: DynamicRowInterface) : Boolean { return false } + fun stringForRow(row: DynamicRowInterface) : String { return "" } + /** + * Manages: + * - label (string) + * - segmented control (live/online) + * - textfield (string) + * - switch (bool) + * - static content + * */ } -//interface RowDelegate { -// -// fun groupedRow() : ArrayList() -// fun (row: DynamicRowInterface) : String -//} +interface DynamicRowCallback { + fun onRowSelected(row: DynamicRowInterface) +} +class DynamicListAdapter(var delegate: DynamicRowDelegate, var callBackDelegate: DynamicRowCallback? = null) : RecyclerView.Adapter() { -class DynamicListAdapter : RecyclerView.Adapter() { + private var rows: ArrayList = ArrayList() - var groupedRows = ArrayList() + init { + this.rows = delegate.adapterRows() + } override fun getItemViewType(position: Int): Int { - - var sectionIndex: Int = 0 - var rowIndex: Int = position - while (rowIndex >= this.groupedRows[sectionIndex].size + 1) { - rowIndex -= (groupedRows[sectionIndex].size + 1) - sectionIndex++ - } - - if (rowIndex == 0) { - return this.groupedRows[sectionIndex].viewType - } else { - return this.groupedRows[sectionIndex].rows[rowIndex - 1].viewType - } + return this.rows[position].viewType } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { @@ -42,17 +48,15 @@ class DynamicListAdapter : RecyclerView.Adapter() { } override fun getItemCount(): Int { - - return this.groupedRows.size + this.groupedRows.fold(0) { acc: Int, group: RowGroup -> - return acc + group.size - } - + return this.rows.size } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + val dynamicRow = this.rows[position] + val listener = View.OnClickListener { + callBackDelegate?.onRowSelected(dynamicRow) + } + (holder as DynamicHolder).bind(dynamicRow, this.delegate, listener) } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DynamicRowInterface.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DynamicRowInterface.kt index da7fc074..4d114ce5 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DynamicRowInterface.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/DynamicRowInterface.kt @@ -2,66 +2,77 @@ package net.pokeranalytics.android.ui.adapter.components import android.content.Context import net.pokeranalytics.android.R +import net.pokeranalytics.android.ui.fragment.components.BottomSheetType -class RowGroup(stringRes: Int?, rows: ArrayList) : DynamicRowInterface { - - var stringRes: Int? = stringRes - var rows: ArrayList = rows - - var size: Int = 0 - get() { - return this.rows.size - } - - - override fun localizedTitle(context: Context): String? { - stringRes?.let { - return context.getString(it) - } - return null - } - - override var viewType: Int = 0 - +interface DynamicRowInterface { + fun localizedTitle(context: Context): String + var viewType: Int + var bottomSheetType: BottomSheetType } -interface DynamicRowInterface { +class SectionRow(stringRes: Int) : DynamicRowInterface { + + var stringRes: Int = stringRes - fun localizedTitle(context: Context): String? - var viewType: Int + override fun localizedTitle(context: Context): String { + return context.getString(this.stringRes) + } + override var viewType: Int = 0 + override var bottomSheetType: BottomSheetType = BottomSheetType.NONE } enum class SessionRow(val resId: Int) : DynamicRowInterface { - BLINDS(R.string.app_name), - GAME(R.string.app_name), - DATE(R.string.app_name); - - override fun localizedTitle(context: Context): String? { - return context.getString(this.resId) - } - - override var viewType: Int = RowViewType.HEADER.ordinal - get() { - return when (this) { - BLINDS -> 1 - GAME -> 2 - DATE -> 1 - } - } - + BLINDS(R.string.app_name), + GAME(R.string.title_history), + DATE(R.string.title_settings); + + override fun localizedTitle(context: Context): String { + return context.getString(this.resId) + } + + override var viewType: Int = RowViewType.HEADER.ordinal + get() { + return when (this) { + BLINDS, GAME, DATE -> RowViewType.TITLE_VALUE.ordinal + } + } + + override var bottomSheetType: BottomSheetType = BottomSheetType.NONE + get() { + return when (this) { + BLINDS -> BottomSheetType.BLINDS + GAME -> BottomSheetType.GAME + DATE -> BottomSheetType.DATE + } + } } enum class BankrollRow(val resId: Int) : DynamicRowInterface { - NAME(R.string.app_name), - LIVE(R.string.app_name), - CURRENCY(R.string.app_name); + NAME(R.string.app_name), + LIVE(R.string.app_name), + CURRENCY(R.string.app_name); - override fun localizedTitle(context: Context): String? { - return context.getString(this.resId) - } + override fun localizedTitle(context: Context): String { + return context.getString(this.resId) + } - override var viewType: Int = 1 + override var viewType: Int = 1 + override var bottomSheetType: BottomSheetType = BottomSheetType.NONE +} -} \ No newline at end of file +enum class DataObjectRowType(val resId: Int) : DynamicRowInterface { + BANKROLL(R.string.bankroll), + GAME(R.string.game), + LOCATION(R.string.location), + TOURNAMENT_TYPE(R.string.tournament_type), + TRANSACTION_TYPE(R.string.transaction_type); + + override fun localizedTitle(context: Context): String { + return context.getString(this.resId) + } + + override var viewType: Int = RowViewType.TITLE.ordinal + override var bottomSheetType: BottomSheetType = BottomSheetType.NONE +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/RowViewType.kt b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/RowViewType.kt index d9a5d0c4..f5ac0bd2 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/RowViewType.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/adapter/components/RowViewType.kt @@ -1,19 +1,67 @@ package net.pokeranalytics.android.ui.adapter.components +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.row_session.view.* +import net.pokeranalytics.android.R + +interface DynamicHolder { + + fun bind(row: DynamicRowInterface, delegate: DynamicRowDelegate? = null, listener: View.OnClickListener) + +} enum class RowViewType { HEADER, - TEXTFIELD; + EDIT_TEXT, + TITLE, + TITLE_VALUE; + + inner class FakeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), DynamicHolder { + override fun bind(row: DynamicRowInterface, delegate: DynamicRowDelegate?, listener: View.OnClickListener) { + } + } - inner class PlaceholderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + inner class TitleValueViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), DynamicHolder { + override fun bind(row: DynamicRowInterface, delegate: DynamicRowDelegate?, listener: View.OnClickListener) { + itemView.title.text = row.localizedTitle(itemView.context) + delegate?.let { + itemView.value.text = it.stringForRow(row) + } + itemView.container.setOnClickListener(listener) + } + } + inner class TitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), DynamicHolder { + override fun bind(row: DynamicRowInterface, delegate: DynamicRowDelegate?, listener: View.OnClickListener) { + itemView.title.text = row.localizedTitle(itemView.context) + itemView.container.setOnClickListener(listener) + } } - fun viewHolder(parent: ViewGroup) : RecyclerView.ViewHolder { - return PlaceholderViewHolder(parent) + + fun viewHolder(parent: ViewGroup): RecyclerView.ViewHolder { + return when (this) { + TITLE_VALUE -> TitleValueViewHolder( + LayoutInflater.from(parent.context).inflate( + R.layout.row_session, + parent, + false + ) + ) + TITLE -> TitleViewHolder( + LayoutInflater.from(parent.context).inflate( + R.layout.row_title, + parent, + false + ) + ) + + else -> FakeViewHolder(parent) + } } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt new file mode 100644 index 00000000..b771d810 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt @@ -0,0 +1,83 @@ +package net.pokeranalytics.android.ui.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import io.realm.Realm +import io.realm.RealmObject +import io.realm.RealmResults +import io.realm.Sort +import kotlinx.android.synthetic.main.fragment_new_session.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.* +import net.pokeranalytics.android.ui.adapter.components.* +import net.pokeranalytics.android.util.PokerAnalyticsFragment +import net.pokeranalytics.android.util.data.sessionDao +import java.util.* +import kotlin.collections.ArrayList + +class DataListFragment : PokerAnalyticsFragment(), DataRowDelegate, DataRowCallback { + + private lateinit var dataType: DataObjectRowType + + private lateinit var realmObjects: ArrayList + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_data_list, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + } + + override fun data(position: Int): DisplayableData { + return (realmObjects[position] as DisplayableData) + } + + override fun onRowSelected(position: Int) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun size(): Int { + return realmObjects.size + } + + private fun initData() { + } + + /** + * Init UI + */ + private fun initUI() { + + val viewManager = LinearLayoutManager(requireContext()) + val dataListAdapter = DataListAdapter(this, this) + + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + adapter = dataListAdapter + } + } + + + /** + * Set fragment data + */ + fun setData(dataType: Int) { + this.dataType = DataObjectRowType.values()[dataType] + val realm = Realm.getDefaultInstance() + + realmObjects = ArrayList( when (DataObjectRowType.values()[dataType]) { + DataObjectRowType.BANKROLL -> realm.where(Bankroll::class.java).findAll().sort("name", Sort.DESCENDING) + DataObjectRowType.GAME-> realm.where(Game::class.java).findAll().sort("name", Sort.DESCENDING) + DataObjectRowType.LOCATION -> realm.where(Location::class.java).findAll().sort("name", Sort.DESCENDING) + DataObjectRowType.TOURNAMENT_TYPE -> realm.where(TournamentFeature::class.java).findAll().sort("name", Sort.DESCENDING) + DataObjectRowType.TRANSACTION_TYPE -> realm.where(TransactionType::class.java).findAll().sort("name", Sort.DESCENDING) + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt index 93dcf34c..6164094f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt @@ -91,8 +91,6 @@ class HistoryFragment : PokerAnalyticsFragment() { } it.addChangeListener { newSessions -> - Toast.makeText(requireContext(), "Change listener: ${newSessions.size}", Toast.LENGTH_SHORT).show() - historyAdapter.notifyDataSetChanged() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/NewSessionFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/NewSessionFragment.kt index 8b9f0f15..845b1835 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/NewSessionFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/NewSessionFragment.kt @@ -4,14 +4,20 @@ 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 kotlinx.android.synthetic.main.fragment_new_session.* import net.pokeranalytics.android.R import net.pokeranalytics.android.model.realm.Session -import net.pokeranalytics.android.ui.adapter.NewSessionAdapter +import net.pokeranalytics.android.ui.adapter.components.DynamicListAdapter +import net.pokeranalytics.android.ui.adapter.components.DynamicRowCallback +import net.pokeranalytics.android.ui.adapter.components.DynamicRowInterface +import net.pokeranalytics.android.ui.adapter.components.EditableDataDelegate +import net.pokeranalytics.android.ui.fragment.components.BottomSheetFragment +import net.pokeranalytics.android.ui.fragment.components.BottomSheetType import net.pokeranalytics.android.util.PokerAnalyticsFragment -class NewSessionFragment: PokerAnalyticsFragment() { +class NewSessionFragment : PokerAnalyticsFragment(), DynamicRowCallback, EditableDataDelegate { private lateinit var newSession: Session @@ -21,11 +27,26 @@ class NewSessionFragment: PokerAnalyticsFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initData() initUI() } + override fun onRowSelected(row: DynamicRowInterface) { + + val data = when (row.bottomSheetType) { + BottomSheetType.BLINDS -> newSession + BottomSheetType.DATE -> newSession.timeFrame + BottomSheetType.GAME -> newSession.game + else -> Any() + } + + BottomSheetFragment.create(fragmentManager, row, this, data) + } + + override fun setValue(value: Any, row: DynamicRowInterface) { + Toast.makeText(requireContext(), "Callback for ${row.localizedTitle(requireContext())} ($value)", Toast.LENGTH_SHORT).show() + } + private fun initData() { newSession = Session() } @@ -36,7 +57,7 @@ class NewSessionFragment: PokerAnalyticsFragment() { private fun initUI() { val viewManager = LinearLayoutManager(requireContext()) - val newSessionAdapter = NewSessionAdapter(newSession) + val newSessionAdapter = DynamicListAdapter(newSession, this) recyclerView.apply { setHasFixedSize(true) 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 7259a810..5b78f727 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 @@ -4,55 +4,66 @@ 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 kotlinx.android.synthetic.main.fragment_settings.* import net.pokeranalytics.android.R -import net.pokeranalytics.android.ui.activity.DataManagementActivity -import net.pokeranalytics.android.ui.adapter.SettingsAdapter +import net.pokeranalytics.android.ui.activity.DataListActivity +import net.pokeranalytics.android.ui.activity.NewSessionActivity +import net.pokeranalytics.android.ui.adapter.components.* import net.pokeranalytics.android.util.PokerAnalyticsFragment -class SettingsFragment : PokerAnalyticsFragment() { - - companion object { - - /** - * Create new instance - */ - fun newInstance(): SettingsFragment { - val fragment = SettingsFragment() - val bundle = Bundle() - fragment.arguments = bundle - return fragment - } - } - - private lateinit var settingsAdapter: SettingsAdapter - - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_settings, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initData() - } - - /** - * Init data - */ - private fun initData() { - - val viewManager = LinearLayoutManager(requireContext()) - settingsAdapter = SettingsAdapter() - settingsAdapter.onClickOnData = { position, dataType -> - DataManagementActivity.newInstance(requireContext(), 0) - } - - recyclerView.apply { - setHasFixedSize(true) - layoutManager = viewManager - adapter = settingsAdapter - } - } +class SettingsFragment : PokerAnalyticsFragment(), DynamicRowDelegate, DynamicRowCallback { + + companion object { + + /** + * Create new instance + */ + fun newInstance(): SettingsFragment { + val fragment = SettingsFragment() + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + } + + private lateinit var settingsAdapter: DynamicListAdapter + + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_settings, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + } + + override fun adapterRows(): ArrayList { + val rows = ArrayList() + rows.addAll(DataObjectRowType.values()) + return rows + } + + override fun onRowSelected(row: DynamicRowInterface) { + DataListActivity.newInstance(requireContext(), (row as DataObjectRowType).ordinal) + } + + /** + * Init data + */ + private fun initData() { + + val viewManager = LinearLayoutManager(requireContext()) + settingsAdapter = DynamicListAdapter( + this, this + ) + + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + adapter = settingsAdapter + } + } } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetBlindsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetBlindsFragment.kt new file mode 100644 index 00000000..6eaf122d --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetBlindsFragment.kt @@ -0,0 +1,57 @@ +package net.pokeranalytics.android.ui.fragment.components + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.inputmethod.EditorInfo +import kotlinx.android.synthetic.main.bottom_sheet_blinds.* +import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.Session + +class BottomSheetBlindsFragment : BottomSheetFragment() { + + private var session: Session = Session() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + } + + override fun clickOnCheck() { + super.clickOnCheck() + valueDelegate.setValue(session, row) + dismiss() + } + + override fun onStart() { + super.onStart() + smallBlind.requestFocus() + } + + /** + * Init data + */ + private fun initData() { + val data = getData() + session = if (data is Session) data else Session() + } + + /** + * Init UI + */ + private fun initUI() { + + LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_blinds, view?.bottomSheetContainer, true) + + bigBlind.setOnEditorActionListener { v, actionId, event -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + clickOnCheck() + } + true + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetDateFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetDateFragment.kt new file mode 100644 index 00000000..377da2c8 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetDateFragment.kt @@ -0,0 +1,61 @@ +package net.pokeranalytics.android.ui.fragment.components + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import kotlinx.android.synthetic.main.bottom_sheet_date.* +import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.TimeFrame +import net.pokeranalytics.android.ui.fragment.components.BottomSheetFragment +import net.pokeranalytics.android.util.DatePickerFragment +import net.pokeranalytics.android.util.TimePickerFragment + +class BottomSheetDateFragment : BottomSheetFragment() { + + private var timeFrame: TimeFrame = TimeFrame() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + } + + override fun clickOnCheck() { + super.clickOnCheck() + valueDelegate.setValue(timeFrame, row) + dismiss() + } + + /** + * Init data + */ + private fun initData() { + val data = getData() + timeFrame = if (data is TimeFrame) data else TimeFrame() + } + + /** + * Init UI + */ + private fun initUI() { + + LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_date, view?.bottomSheetContainer, true) + + setAddButtonVisible(false) + + startDate.setOnClickListener { + val dateFragment = DatePickerFragment() + dateFragment.show(fragmentManager, "datePicker") + } + + endDate.setOnClickListener { + val timeFragment = TimePickerFragment() + timeFragment.show(fragmentManager, "timePicker") + } + + //data.startDate = Date() + //data.endDate = Date() + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetFragment.kt new file mode 100644 index 00000000..2604736d --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetFragment.kt @@ -0,0 +1,143 @@ +package net.pokeranalytics.android.ui.fragment.components + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.fragment.app.FragmentManager +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.android.synthetic.main.fragment_bottom_sheet.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.ui.adapter.components.DynamicRowInterface +import net.pokeranalytics.android.ui.adapter.components.EditableDataDelegate + +enum class BottomSheetType { + NONE, + GAME, + BLINDS, + LOCATION, + BANKROLL, + TABLE_SIZE, + DATE +} + +interface BottomSheetInterface { + fun clickOnClear() + fun clickOnCheck() + fun clickOnAdd() +} + +open class BottomSheetFragment : BottomSheetDialogFragment(), BottomSheetInterface { + + lateinit var row: DynamicRowInterface + lateinit var valueDelegate: EditableDataDelegate + + private var data: Any? = null + + companion object { + fun create( fragmentManager: FragmentManager?, row: DynamicRowInterface, valueDelegate: EditableDataDelegate, data: Any?): BottomSheetFragment { + + val bottomSheetFragment = when (row.bottomSheetType) { + BottomSheetType.BLINDS -> BottomSheetBlindsFragment() + BottomSheetType.DATE -> BottomSheetDateFragment() + BottomSheetType.GAME -> BottomSheetGameFragment() + else -> BottomSheetFragment() + } + + bottomSheetFragment.show(fragmentManager, "bottomSheet") + bottomSheetFragment.row = row + bottomSheetFragment.valueDelegate = valueDelegate + bottomSheetFragment.data = data + return bottomSheetFragment + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate( + net.pokeranalytics.android.R.layout.fragment_bottom_sheet, + container, + false + ) as ConstraintLayout + + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initUI() + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + // To display correctly the keyboard + dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) + } + + override fun clickOnClear() { + } + + override fun clickOnCheck() { + } + + override fun clickOnAdd() { + } + + /** + * Init UI + */ + private fun initUI() { + row.let { + bottomSheetToolbar.title = row.localizedTitle(requireContext()) + bottomSheetToolbar.inflateMenu(net.pokeranalytics.android.R.menu.bottom_sheet_menu) + bottomSheetToolbar.setOnMenuItemClickListener { + false + } + } + + // Menu + bottomSheetToolbar.menu.findItem(R.id.actionClear).setOnMenuItemClickListener { + clickOnClear() + true + } + bottomSheetToolbar.menu.findItem(R.id.actionAdd).setOnMenuItemClickListener { + clickOnAdd() + true + } + bottomSheetToolbar.menu.findItem(R.id.actionCheck).setOnMenuItemClickListener { + clickOnCheck() + true + } + } + + /** + * Return the data object + */ + fun getData() : Any? { + return data + } + + /** + * Set clear button visibility + */ + fun setClearButtonVisibile(visible: Boolean) { + bottomSheetToolbar.menu.findItem(R.id.actionClear).isVisible = visible + } + + /** + * Set check button visibility + */ + fun setCheckButtonVisibile(visible: Boolean) { + bottomSheetToolbar.menu.findItem(R.id.actionCheck).isVisible = visible + } + + /** + * Set add button visibility + */ + fun setAddButtonVisible(visible: Boolean) { + bottomSheetToolbar.menu.findItem(R.id.actionAdd).isVisible = visible + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetGameFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetGameFragment.kt new file mode 100644 index 00000000..f6cdec3c --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/BottomSheetGameFragment.kt @@ -0,0 +1,77 @@ +package net.pokeranalytics.android.ui.fragment.components + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.android.synthetic.main.bottom_sheet_game.* +import kotlinx.android.synthetic.main.fragment_bottom_sheet.view.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.model.realm.Game +import net.pokeranalytics.android.ui.adapter.components.DynamicListAdapter +import net.pokeranalytics.android.ui.adapter.components.DynamicRowDelegate +import net.pokeranalytics.android.ui.adapter.components.DynamicRowInterface + + + +class BottomSheetGameFragment : BottomSheetFragment(), DynamicRowDelegate { + + private var game: Game = Game() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + } + + override fun clickOnCheck() { + super.clickOnCheck() + valueDelegate.setValue(game, row) + dismiss() + } + + override fun adapterRows(): ArrayList { + val array = ArrayList() + array.add(TitleObject("Game 1")) + array.add(TitleObject("Game 2")) + array.add(TitleObject("Game 3")) + return array + } + + /** + * Init data + */ + private fun initData() { + val data = getData() + game = if (data is Game) data else Game() + } + + /** + * Init UI + */ + private fun initUI() { + LayoutInflater.from(requireContext()).inflate(R.layout.bottom_sheet_game, view?.bottomSheetContainer, true) + + val viewManager = LinearLayoutManager(requireContext()) + val dataAdapter = DynamicListAdapter(this) + + gameNameRecyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + //adapter = dataAdapter + } + + } + + + inner class TitleObject(var title: String) : DynamicRowInterface { + override fun localizedTitle(context: Context): String { + return title + } + + override var viewType: Int = 0 + override var bottomSheetType: BottomSheetType = BottomSheetType.NONE + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/DataRowView.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/DataRowView.kt deleted file mode 100644 index def52bde..00000000 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/DataRowView.kt +++ /dev/null @@ -1,55 +0,0 @@ -package net.pokeranalytics.android.ui.view - -import android.widget.FrameLayout -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import androidx.constraintlayout.widget.ConstraintLayout -import kotlinx.android.synthetic.main.row_data_cell.view.* -import kotlinx.android.synthetic.main.row_data_content_view.view.* -import net.pokeranalytics.android.R - - -class DataRowView : FrameLayout { - - private lateinit var rowDataCell: ConstraintLayout - - /** - * Constructors - */ - constructor(context: Context) : super(context) { - init() - } - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { - init() - } - - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { - init() - } - - /** - * Init - * - * @param attrs - */ - private fun init() { - val layoutInflater = LayoutInflater.from(context) - rowDataCell = layoutInflater.inflate(R.layout.row_data_content_view, this, false) as ConstraintLayout - val layoutParams = FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT - ) - - addView(rowDataCell, layoutParams) - } - - /** - * Set the session data to the view - */ - fun setData(title: String) { - rowDataCell.rowTitle.text = title - } - -} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt index 6800081a..b3df53be 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt @@ -6,7 +6,7 @@ import android.graphics.Color import android.util.AttributeSet import android.view.LayoutInflater import androidx.constraintlayout.widget.ConstraintLayout -import kotlinx.android.synthetic.main.row_session.view.* +import kotlinx.android.synthetic.main.row_session_view.view.* import net.pokeranalytics.android.R import net.pokeranalytics.android.model.realm.Session import timber.log.Timber @@ -38,7 +38,7 @@ class SessionRowView : FrameLayout { */ private fun init() { val layoutInflater = LayoutInflater.from(context) - rowHistorySession = layoutInflater.inflate(R.layout.row_session, this, false) as ConstraintLayout + rowHistorySession = layoutInflater.inflate(R.layout.row_session_view, this, false) as ConstraintLayout val layoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT @@ -51,7 +51,6 @@ class SessionRowView : FrameLayout { * Set the session data to the view */ fun setData(session: Session) { - Timber.d("Set data: ${session.creationDate}") rowHistorySession.date.text = session.creationDate.toString() } diff --git a/app/src/main/java/net/pokeranalytics/android/util/DatePickerFragment.kt b/app/src/main/java/net/pokeranalytics/android/util/DatePickerFragment.kt new file mode 100644 index 00000000..dd81a825 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/DatePickerFragment.kt @@ -0,0 +1,26 @@ +package net.pokeranalytics.android.util + +import android.app.DatePickerDialog +import android.app.Dialog +import android.os.Bundle +import android.widget.DatePicker +import androidx.fragment.app.DialogFragment +import java.util.* + +class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Use the current date as the default date in the picker + val c = Calendar.getInstance() + val year = c.get(Calendar.YEAR) + val month = c.get(Calendar.MONTH) + val day = c.get(Calendar.DAY_OF_MONTH) + + // Create a new instance of DatePickerDialog and return it + return DatePickerDialog(activity, this, year, month, day) + } + + override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) { + // Do something with the date chosen by the user + } +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/PokerAnalyticsFragment.kt b/app/src/main/java/net/pokeranalytics/android/util/PokerAnalyticsFragment.kt index 721dd024..ac67fe13 100644 --- a/app/src/main/java/net/pokeranalytics/android/util/PokerAnalyticsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/util/PokerAnalyticsFragment.kt @@ -1,7 +1,6 @@ package net.pokeranalytics.android.util import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import io.realm.Realm diff --git a/app/src/main/java/net/pokeranalytics/android/util/PokerAnalyticsLogs.kt b/app/src/main/java/net/pokeranalytics/android/util/PokerAnalyticsLogs.kt new file mode 100644 index 00000000..d1a6ba66 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/PokerAnalyticsLogs.kt @@ -0,0 +1,9 @@ +package net.pokeranalytics.android.util + +import timber.log.Timber + +class PokerAnalyticsLogs : Timber.DebugTree() { + override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) { + super.log(priority, "PokerAnalytics:$tag", message, throwable) + } +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/util/TimePickerFragment.kt b/app/src/main/java/net/pokeranalytics/android/util/TimePickerFragment.kt new file mode 100644 index 00000000..aeae4652 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/util/TimePickerFragment.kt @@ -0,0 +1,26 @@ +package net.pokeranalytics.android.util + +import android.app.Dialog +import android.app.TimePickerDialog +import android.os.Bundle +import android.text.format.DateFormat +import android.widget.TimePicker +import androidx.fragment.app.DialogFragment +import java.util.* + +class TimePickerFragment : DialogFragment(), TimePickerDialog.OnTimeSetListener { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Use the current time as the default values for the picker + val c = Calendar.getInstance() + val hour = c.get(Calendar.HOUR_OF_DAY) + val minute = c.get(Calendar.MINUTE) + + // Create a new instance of TimePickerDialog and return it + return TimePickerDialog(activity, this, hour, minute, DateFormat.is24HourFormat(activity)) + } + + override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) { + // Do something with the time chosen by the user + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-xxhdpi/add_button_background.png b/app/src/main/res/drawable-xxhdpi/add_button_background.png new file mode 100644 index 00000000..78b48c86 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/add_button_background.png differ diff --git a/app/src/main/res/drawable-xxhdpi/add_cash_game.png b/app/src/main/res/drawable-xxhdpi/add_cash_game.png new file mode 100644 index 00000000..12fbaaf6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/add_cash_game.png differ diff --git a/app/src/main/res/drawable-xxhdpi/add_hand.png b/app/src/main/res/drawable-xxhdpi/add_hand.png new file mode 100644 index 00000000..68664089 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/add_hand.png differ diff --git a/app/src/main/res/drawable-xxhdpi/add_tournoi.png b/app/src/main/res/drawable-xxhdpi/add_tournoi.png new file mode 100644 index 00000000..f78065a3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/add_tournoi.png differ diff --git a/app/src/main/res/drawable-xxhdpi/add_transaction.png b/app/src/main/res/drawable-xxhdpi/add_transaction.png new file mode 100644 index 00000000..692a51e0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/add_transaction.png differ diff --git a/app/src/main/res/drawable-xxhdpi/back_disclosure.png b/app/src/main/res/drawable-xxhdpi/back_disclosure.png new file mode 100644 index 00000000..368c3346 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/back_disclosure.png differ diff --git a/app/src/main/res/drawable-xxhdpi/big_export.png b/app/src/main/res/drawable-xxhdpi/big_export.png new file mode 100644 index 00000000..f37c80b4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/big_export.png differ diff --git a/app/src/main/res/drawable-xxhdpi/blog.png b/app/src/main/res/drawable-xxhdpi/blog.png new file mode 100644 index 00000000..a15daeb7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/blog.png differ diff --git a/app/src/main/res/drawable-xxhdpi/blog_black.png b/app/src/main/res/drawable-xxhdpi/blog_black.png new file mode 100644 index 00000000..89c7f776 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/blog_black.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bottom_disclosure.png b/app/src/main/res/drawable-xxhdpi/bottom_disclosure.png new file mode 100644 index 00000000..a73a421c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bottom_disclosure.png differ diff --git a/app/src/main/res/drawable-xxhdpi/chip.png b/app/src/main/res/drawable-xxhdpi/chip.png new file mode 100644 index 00000000..abd953b0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/chip.png differ diff --git a/app/src/main/res/drawable-xxhdpi/clock.png b/app/src/main/res/drawable-xxhdpi/clock.png new file mode 100644 index 00000000..e87829a6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/clock.png differ diff --git a/app/src/main/res/drawable-xxhdpi/cross.png b/app/src/main/res/drawable-xxhdpi/cross.png new file mode 100644 index 00000000..1a13a7bd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/cross.png differ diff --git a/app/src/main/res/drawable-xxhdpi/disclosure.png b/app/src/main/res/drawable-xxhdpi/disclosure.png new file mode 100644 index 00000000..4daf21b6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/disclosure.png differ diff --git a/app/src/main/res/drawable-xxhdpi/facebook.png b/app/src/main/res/drawable-xxhdpi/facebook.png new file mode 100644 index 00000000..83e47ed6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/facebook.png differ diff --git a/app/src/main/res/drawable-xxhdpi/facebook_black.png b/app/src/main/res/drawable-xxhdpi/facebook_black.png new file mode 100644 index 00000000..2a1238c9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/facebook_black.png differ diff --git a/app/src/main/res/drawable-xxhdpi/fullscreen.png b/app/src/main/res/drawable-xxhdpi/fullscreen.png new file mode 100644 index 00000000..d4e74d16 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/fullscreen.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_poker_chip.png b/app/src/main/res/drawable-xxhdpi/icon_poker_chip.png new file mode 100644 index 00000000..61739ae2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_poker_chip.png differ diff --git a/app/src/main/res/drawable-xxhdpi/info.png b/app/src/main/res/drawable-xxhdpi/info.png new file mode 100644 index 00000000..3bf4453d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/info.png differ diff --git a/app/src/main/res/drawable-xxhdpi/insta.png b/app/src/main/res/drawable-xxhdpi/insta.png new file mode 100644 index 00000000..636462bf Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/insta.png differ diff --git a/app/src/main/res/drawable-xxhdpi/insta_black.png b/app/src/main/res/drawable-xxhdpi/insta_black.png new file mode 100644 index 00000000..0599fbce Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/insta_black.png differ diff --git a/app/src/main/res/drawable-xxhdpi/logo_pa.png b/app/src/main/res/drawable-xxhdpi/logo_pa.png new file mode 100644 index 00000000..68f45519 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/logo_pa.png differ diff --git a/app/src/main/res/drawable-xxhdpi/pause.png b/app/src/main/res/drawable-xxhdpi/pause.png new file mode 100644 index 00000000..d32085d6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/pause.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_allin.png b/app/src/main/res/drawable-xxhdpi/picto_allin.png new file mode 100644 index 00000000..8564b1c3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_allin.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_arrow_down.png b/app/src/main/res/drawable-xxhdpi/picto_arrow_down.png new file mode 100644 index 00000000..6f62df10 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_arrow_down.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_arrow_raise.png b/app/src/main/res/drawable-xxhdpi/picto_arrow_raise.png new file mode 100644 index 00000000..aa39fac6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_arrow_raise.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_arrow_right.png b/app/src/main/res/drawable-xxhdpi/picto_arrow_right.png new file mode 100644 index 00000000..bfba8786 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_arrow_right.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_arrow_up.png b/app/src/main/res/drawable-xxhdpi/picto_arrow_up.png new file mode 100644 index 00000000..7ce8e484 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_arrow_up.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_bankroll.png b/app/src/main/res/drawable-xxhdpi/picto_bankroll.png new file mode 100644 index 00000000..2c3dae8c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_bankroll.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_call_allin.png b/app/src/main/res/drawable-xxhdpi/picto_call_allin.png new file mode 100644 index 00000000..bbe4bcd8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_call_allin.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_cards.png b/app/src/main/res/drawable-xxhdpi/picto_cards.png new file mode 100644 index 00000000..496a79ee Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_cards.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_check.png b/app/src/main/res/drawable-xxhdpi/picto_check.png new file mode 100644 index 00000000..8a3b0162 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_check.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_comment.png b/app/src/main/res/drawable-xxhdpi/picto_comment.png new file mode 100644 index 00000000..e7d3b734 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_comment.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_engrenage.png b/app/src/main/res/drawable-xxhdpi/picto_engrenage.png new file mode 100644 index 00000000..7f7ac774 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_engrenage.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_gear.png b/app/src/main/res/drawable-xxhdpi/picto_gear.png new file mode 100644 index 00000000..0476a1c0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_gear.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_gear_selected.png b/app/src/main/res/drawable-xxhdpi/picto_gear_selected.png new file mode 100644 index 00000000..f985b0e3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_gear_selected.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_m.png b/app/src/main/res/drawable-xxhdpi/picto_m.png new file mode 100644 index 00000000..a23f001e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_m.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_reports.png b/app/src/main/res/drawable-xxhdpi/picto_reports.png new file mode 100644 index 00000000..1a023a37 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_reports.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_reports_selected.png b/app/src/main/res/drawable-xxhdpi/picto_reports_selected.png new file mode 100644 index 00000000..f537bd00 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_reports_selected.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_stars.png b/app/src/main/res/drawable-xxhdpi/picto_stars.png new file mode 100644 index 00000000..32c5c66e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_stars.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_stars_selected.png b/app/src/main/res/drawable-xxhdpi/picto_stars_selected.png new file mode 100644 index 00000000..545463ee Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_stars_selected.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_stats.png b/app/src/main/res/drawable-xxhdpi/picto_stats.png new file mode 100644 index 00000000..45d7cfc0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_stats.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_table.png b/app/src/main/res/drawable-xxhdpi/picto_table.png new file mode 100644 index 00000000..7d3f6cda Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_table.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_user.png b/app/src/main/res/drawable-xxhdpi/picto_user.png new file mode 100644 index 00000000..09fba699 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_user.png differ diff --git a/app/src/main/res/drawable-xxhdpi/picto_user_empty.png b/app/src/main/res/drawable-xxhdpi/picto_user_empty.png new file mode 100644 index 00000000..efb87cff Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/picto_user_empty.png differ diff --git a/app/src/main/res/drawable-xxhdpi/pin.png b/app/src/main/res/drawable-xxhdpi/pin.png new file mode 100644 index 00000000..340e5a99 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/pin.png differ diff --git a/app/src/main/res/drawable-xxhdpi/pro_subscriber.png b/app/src/main/res/drawable-xxhdpi/pro_subscriber.png new file mode 100644 index 00000000..b4dcb76f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/pro_subscriber.png differ diff --git a/app/src/main/res/drawable-xxhdpi/release_note_icon.png b/app/src/main/res/drawable-xxhdpi/release_note_icon.png new file mode 100644 index 00000000..2acd8034 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/release_note_icon.png differ diff --git a/app/src/main/res/drawable-xxhdpi/round_free.png b/app/src/main/res/drawable-xxhdpi/round_free.png new file mode 100644 index 00000000..70933468 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/round_free.png differ diff --git a/app/src/main/res/drawable-xxhdpi/star_pro.png b/app/src/main/res/drawable-xxhdpi/star_pro.png new file mode 100644 index 00000000..5e86c173 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/star_pro.png differ diff --git a/app/src/main/res/drawable-xxhdpi/star_semipro.png b/app/src/main/res/drawable-xxhdpi/star_semipro.png new file mode 100644 index 00000000..c44d11ba Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/star_semipro.png differ diff --git a/app/src/main/res/drawable-xxhdpi/tab_calendar.png b/app/src/main/res/drawable-xxhdpi/tab_calendar.png new file mode 100644 index 00000000..36c2d425 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/tab_calendar.png differ diff --git a/app/src/main/res/drawable-xxhdpi/three_lines.png b/app/src/main/res/drawable-xxhdpi/three_lines.png new file mode 100644 index 00000000..2627e33b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/three_lines.png differ diff --git a/app/src/main/res/drawable-xxhdpi/top_disclosure.png b/app/src/main/res/drawable-xxhdpi/top_disclosure.png new file mode 100644 index 00000000..2465d028 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/top_disclosure.png differ diff --git a/app/src/main/res/drawable-xxhdpi/twitter.png b/app/src/main/res/drawable-xxhdpi/twitter.png new file mode 100644 index 00000000..f1220cdb Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/twitter.png differ diff --git a/app/src/main/res/drawable-xxhdpi/twitter_black.png b/app/src/main/res/drawable-xxhdpi/twitter_black.png new file mode 100644 index 00000000..b261c9f0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/twitter_black.png differ diff --git a/app/src/main/res/drawable/ic_add_white_24dp.xml b/app/src/main/res/drawable/ic_add_white_24dp.xml new file mode 100644 index 00000000..e3979cd7 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_white_24dp.xml b/app/src/main/res/drawable/ic_check_white_24dp.xml new file mode 100644 index 00000000..17aca2af --- /dev/null +++ b/app/src/main/res/drawable/ic_check_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_close_24dp.xml b/app/src/main/res/drawable/ic_close_24dp.xml new file mode 100644 index 00000000..ede4b710 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close_white_24dp.xml b/app/src/main/res/drawable/ic_close_white_24dp.xml new file mode 100644 index 00000000..0c8775c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete_white_24dp.xml b/app/src/main/res/drawable/ic_delete_white_24dp.xml new file mode 100644 index 00000000..8bed121a --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/rectangle_rounded_green.xml b/app/src/main/res/drawable/rectangle_rounded_green.xml new file mode 100644 index 00000000..2a2b7a64 --- /dev/null +++ b/app/src/main/res/drawable/rectangle_rounded_green.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangle_rounded_green_ripple.xml b/app/src/main/res/drawable/rectangle_rounded_green_ripple.xml new file mode 100644 index 00000000..162d2204 --- /dev/null +++ b/app/src/main/res/drawable/rectangle_rounded_green_ripple.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_data_list.xml b/app/src/main/res/layout/activity_data_list.xml new file mode 100644 index 00000000..2911b5ae --- /dev/null +++ b/app/src/main/res/layout/activity_data_list.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 6668dbb0..9c60a84a 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -1,31 +1,30 @@ - + + android:id="@+id/container" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@+id/navigation" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:id="@+id/navigation" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="0dp" + android:layout_marginEnd="0dp" + android:background="?android:attr/windowBackground" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:menu="@menu/navigation" /> \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_bankroll.xml b/app/src/main/res/layout/bottom_sheet_bankroll.xml new file mode 100644 index 00000000..56b38a58 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_bankroll.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_blinds.xml b/app/src/main/res/layout/bottom_sheet_blinds.xml new file mode 100644 index 00000000..c0d86a55 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_blinds.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_date.xml b/app/src/main/res/layout/bottom_sheet_date.xml new file mode 100644 index 00000000..d04e9d17 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_date.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_game.xml b/app/src/main/res/layout/bottom_sheet_game.xml new file mode 100644 index 00000000..5a052178 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_game.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_location.xml b/app/src/main/res/layout/bottom_sheet_location.xml new file mode 100644 index 00000000..7546616e --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_location.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_table_size.xml b/app/src/main/res/layout/bottom_sheet_table_size.xml new file mode 100644 index 00000000..7546616e --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_table_size.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_bottom_sheet.xml b/app/src/main/res/layout/fragment_bottom_sheet.xml new file mode 100644 index 00000000..76eabc63 --- /dev/null +++ b/app/src/main/res/layout/fragment_bottom_sheet.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_data_list.xml b/app/src/main/res/layout/fragment_data_list.xml new file mode 100644 index 00000000..6fea404a --- /dev/null +++ b/app/src/main/res/layout/fragment_data_list.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index e7d86807..79607455 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -1,73 +1,82 @@ - + - + - + + - - - - + + android:id="@+id/recyclerView" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginTop="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/newHand" + tools:listitem="@layout/row_history_session" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index ecd6264b..6202c2e5 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -3,19 +3,29 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/container" android:layout_width="match_parent" - android:layout_height="match_parent" - tools:context=".ui.activity.HomeActivity"> + android:layout_height="match_parent"> + + + android:layout_marginTop="8dp"/> \ No newline at end of file diff --git a/app/src/main/res/layout/row_data_cell.xml b/app/src/main/res/layout/row_data_cell.xml deleted file mode 100644 index bfe6f84a..00000000 --- a/app/src/main/res/layout/row_data_cell.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/row_session.xml b/app/src/main/res/layout/row_session.xml index 6e56cf1e..6198d040 100644 --- a/app/src/main/res/layout/row_session.xml +++ b/app/src/main/res/layout/row_session.xml @@ -1,25 +1,41 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?selectableItemBackground" + android:id="@+id/container" + android:padding="16dp"> - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_session_view.xml b/app/src/main/res/layout/row_session_view.xml new file mode 100644 index 00000000..18852686 --- /dev/null +++ b/app/src/main/res/layout/row_session_view.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/row_data_content_view.xml b/app/src/main/res/layout/row_title.xml similarity index 89% rename from app/src/main/res/layout/row_data_content_view.xml rename to app/src/main/res/layout/row_title.xml index 6cc65075..44662103 100644 --- a/app/src/main/res/layout/row_data_content_view.xml +++ b/app/src/main/res/layout/row_title.xml @@ -5,10 +5,12 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?selectableItemBackground" + android:id="@+id/container" android:padding="16dp"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 69b22338..438fd332 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,6 +1,13 @@ - #008577 - #00574B - #D81B60 + #008577 + #00574B + #D81B60 + + #000000 + #FFFFFF + #58C473 + #141414 + #1B1F1B + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 29ecc5a1..dfc3bef0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,4 +5,10 @@ Stats Settings + Bankroll + Game + Location + Tournament Type + Transaction Type + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5885930d..9d7c9fb5 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,11 +1,17 @@ - + + + diff --git a/app/src/test/java/net/pokeranalytics/android/ExampleUnitTest.kt b/app/src/test/java/net/pokeranalytics/android/ExampleUnitTest.kt index 4dc74ef7..b6fbf93a 100644 --- a/app/src/test/java/net/pokeranalytics/android/ExampleUnitTest.kt +++ b/app/src/test/java/net/pokeranalytics/android/ExampleUnitTest.kt @@ -1,17 +1,52 @@ package net.pokeranalytics.android +import net.pokeranalytics.android.calculus.* +import net.pokeranalytics.android.model.realm.TimeFrameGroup +import org.junit.Assert.fail import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * * See [testing documentation](http://d.android.com/tools/testing). */ + class ExampleUnitTest { + + class Grade(someValue: Double) : SessionInterface { +// override var serie: Serie = Serie(TimeFrame()) + override var value: Double = someValue + + override var serie: TimeFrameGroup = TimeFrameGroup() + override var estimatedHands: Double = 0.0 + override var bbNetResult: Double = 0.0 + override var bigBlindSessionCount: Int = 0 // 0 or 1 + override var buyin: Double = 0.0 + + } + @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) + fun testStats() { + + val grades: List = listOf(Grade(someValue = 10.0), Grade(someValue = 20.0)) + val group = SessionGroup(name = "test", sessions = grades) + + val results: ComputedResults = Calculator.compute(group, Calculator.Options()) + + val sum = results.computedStat(Stat.NETRESULT) + if (sum != null) { + assert(sum.value == 30.0) + } else { + fail("No Net result stat") + } + + val average = results.computedStat(Stat.AVERAGE) + if (average != null) { + assert(average.value == 15.0) + } else { + fail("No AVERAGE stat") + } + } + }