Adds Player feature

od
Laurent 6 years ago
parent b061038186
commit 6f539a277f
  1. 356
      app/src/main/AndroidManifest.xml
  2. 2
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  3. 1
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  4. 7
      app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
  5. 19
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  6. 78
      app/src/main/java/net/pokeranalytics/android/model/realm/Comment.kt
  7. 184
      app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt
  8. 77
      app/src/main/java/net/pokeranalytics/android/ui/activity/ColorPickerActivity.kt
  9. 38
      app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt
  10. 268
      app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt
  11. 10
      app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
  12. 54
      app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
  13. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
  14. 11
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt
  15. 218
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/PlayerDataFragment.kt
  16. 144
      app/src/main/java/net/pokeranalytics/android/ui/view/PlayerImageView.kt
  17. 3
      app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt
  18. 54
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  19. 69
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/PlayerRow.kt
  20. 7
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt
  21. 377
      app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt
  22. 28
      app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt
  23. 15
      app/src/main/res/drawable-xxhdpi/circle_player_color_1.xml
  24. 11
      app/src/main/res/drawable-xxhdpi/circle_player_color_2.xml
  25. 11
      app/src/main/res/drawable-xxhdpi/circle_player_color_3.xml
  26. 11
      app/src/main/res/drawable-xxhdpi/circle_player_color_4.xml
  27. 11
      app/src/main/res/drawable-xxhdpi/circle_player_color_5.xml
  28. 11
      app/src/main/res/drawable-xxhdpi/circle_player_color_6.xml
  29. 11
      app/src/main/res/drawable-xxhdpi/circle_player_color_7.xml
  30. 11
      app/src/main/res/drawable-xxhdpi/circle_player_color_8.xml
  31. 11
      app/src/main/res/drawable-xxhdpi/circle_player_color_9.xml
  32. 12
      app/src/main/res/drawable-xxhdpi/circle_stroke_kaki.xml
  33. 9
      app/src/main/res/drawable-xxhdpi/ic_outline_people.xml
  34. 9
      app/src/main/res/drawable-xxhdpi/ic_outline_search.xml
  35. 136
      app/src/main/res/layout/activity_color_picker.xml
  36. 80
      app/src/main/res/layout/fragment_player.xml
  37. 47
      app/src/main/res/layout/row_content.xml
  38. 18
      app/src/main/res/layout/row_header_subtitle.xml
  39. 91
      app/src/main/res/layout/row_player.xml
  40. 20
      app/src/main/res/layout/row_player_image.xml
  41. 47
      app/src/main/res/layout/row_title_subtitle.xml
  42. 55
      app/src/main/res/layout/view_player_image.xml
  43. 13
      app/src/main/res/menu/toolbar_data_list.xml
  44. 11
      app/src/main/res/values/colors.xml
  45. 4
      app/src/main/res/values/dimens.xml
  46. 29
      app/src/main/res/values/styles.xml
  47. 11
      app/src/main/res/xml/provider_paths.xml

@ -1,179 +1,185 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="net.pokeranalytics.android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:name=".PokerAnalyticsApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/PokerAnalyticsTheme">
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<activity
android:name="net.pokeranalytics.android.ui.activity.HomeActivity"
android:launchMode="singleTop"
android:label="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.ImportActivity"
android:screenOrientation="portrait"
android:launchMode="singleTop">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="text/comma-separated-values" />
<data android:mimeType="text/csv" />
</intent-filter>
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.SessionActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing" />
<!-- No screenOrientation="portrait" to fix Oreo crash -->
<activity
android:name="net.pokeranalytics.android.ui.activity.NewDataMenuActivity"
android:launchMode="singleTop"
android:theme="@style/PokerAnalyticsTheme.MenuDialog" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BankrollActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BankrollDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.Top10Activity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.SettingsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.GraphActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ProgressReportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ComparisonReportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.CalendarDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ComparisonChartActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.DataListActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FiltersListActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.EditableDataActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.CurrenciesActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FiltersActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FilterDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.GDPRActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BillingActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ReportCreationActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.TableReportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
xmlns:tools="http://schemas.android.com/tools"
package="net.pokeranalytics.android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name=".PokerAnalyticsApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/PokerAnalyticsTheme">
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<activity
android:name="net.pokeranalytics.android.ui.activity.HomeActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.ImportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="text/comma-separated-values" />
<data android:mimeType="text/csv" />
</intent-filter>
</activity>
<activity
android:name="net.pokeranalytics.android.ui.activity.SessionActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing" />
<!-- No screenOrientation="portrait" to fix Oreo crash -->
<activity
android:name="net.pokeranalytics.android.ui.activity.NewDataMenuActivity"
android:launchMode="singleTop"
android:theme="@style/PokerAnalyticsTheme.MenuDialog" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BankrollActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BankrollDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.Top10Activity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.SettingsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.GraphActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ProgressReportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ComparisonReportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.CalendarDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ComparisonChartActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.DataListActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FiltersListActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.EditableDataActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.CurrenciesActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FiltersActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.FilterDetailsActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.GDPRActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.BillingActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ReportCreationActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.TableReportActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<activity
android:name="net.pokeranalytics.android.ui.activity.ColorPickerActivity"
android:theme="@style/PokerAnalyticsTheme.AlertDialog"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>

@ -33,7 +33,7 @@ class PokerAnalyticsApplication : Application() {
Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(7)
.schemaVersion(8)
.migration(PokerAnalyticsMigration())
.initialData(Seed(this))
.build()

@ -25,4 +25,5 @@ sealed class PokerAnalyticsException(message: String) : Exception(message) {
data class QueryValueMapMissingKeys(val missingKeys: List<String>) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys")
data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow")
data class MissingFieldNameForQueryCondition(val name: String) : PokerAnalyticsException(message = "Missing fieldname for QueryCondition ${name}")
object InputFragmentException : PokerAnalyticsException(message = "RowEditableDelegate must be a Fragment")
}

@ -21,7 +21,8 @@ enum class LiveData : Localizable {
TRANSACTION_TYPE,
FILTER,
CUSTOM_FIELD,
REPORT_SETUP;
REPORT_SETUP,
PLAYER;
var subType:Int? = null
@ -38,6 +39,7 @@ enum class LiveData : Localizable {
FILTER -> Filter::class.java
CUSTOM_FIELD -> CustomField::class.java
REPORT_SETUP -> ReportSetup::class.java
PLAYER -> Player::class.java
}
}
@ -78,6 +80,7 @@ enum class LiveData : Localizable {
FILTER -> R.string.filter
CUSTOM_FIELD -> R.string.custom_field
REPORT_SETUP -> R.string.custom
PLAYER -> R.string.player
}
}
@ -94,6 +97,7 @@ enum class LiveData : Localizable {
FILTER -> R.string.filters
CUSTOM_FIELD -> R.string.custom_fields
REPORT_SETUP -> R.string.custom
PLAYER -> R.string.players
}
}
@ -110,6 +114,7 @@ enum class LiveData : Localizable {
FILTER -> R.string.new_filter
CUSTOM_FIELD -> R.string.new_custom_field
REPORT_SETUP -> R.string.new_report
PLAYER -> R.string.new_friend
}
}

@ -2,7 +2,9 @@ package net.pokeranalytics.android.model.migrations
import io.realm.DynamicRealm
import io.realm.RealmMigration
import net.pokeranalytics.android.model.realm.Comment
import timber.log.Timber
import java.util.*
class PokerAnalyticsMigration : RealmMigration {
@ -151,6 +153,23 @@ class PokerAnalyticsMigration : RealmMigration {
schema.get("TransactionType")?.addField("useCount", Int::class.java)
currentVersion++
}
// Migrate to version 8
if (currentVersion == 7) {
schema.create("Comment")?.let {
it.addField("id", String::class.java).setRequired("id", true)
it.addField("content", String::class.java)
it.addField("date", Date::class.java)
}
schema.get("Player")?.let {
it.addField("summary", String::class.java).setRequired("summary", true)
it.addField("color", Int::class.java).setNullable("color", true)
it.addField("picture", String::class.java)
it.addRealmListField("comments", Comment::class.java)
}
currentVersion++
}
}
override fun equals(other: Any?): Boolean {

@ -0,0 +1,78 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.Manageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import java.util.*
open class Comment : RealmObject(), Manageable, RowRepresentable {
@PrimaryKey
override var id = UUID.randomUUID().toString()
var content: String = ""
var date: Date = Date()
@Ignore
override val realmObjectClass: Class<out Identifiable> = Comment::class.java
@Ignore
override val viewType: Int = RowViewType.CONTENT.ordinal
@Ignore
override val bottomSheetType: BottomSheetType = BottomSheetType.EDIT_TEXT_MULTI_LINES
// @Ignore
// override val valueCanBeClearedWhenEditing: Boolean = false
override fun localizedTitle(context: Context): String {
return context.getString(R.string.comment)
}
override fun getDisplayName(context: Context): String {
return if (content.isNotEmpty()) content else NULL_TEXT
}
// override fun startEditing(dataSource: Any?, parent: Fragment?) {
// if (parent == null) return
// if (parent !is RowRepresentableDelegate) return
// val data = RowEditableDataSource()
// data.append(this.content, R.string.value)
// InputFragment.buildAndShow(this, parent, data, isDeletable = true)
// }
override fun updateValue(value: Any?, row: RowRepresentable) {
this.content = value as String? ?: ""
}
override fun isValidForSave(): Boolean {
return true
}
override fun alreadyExists(realm: Realm): Boolean {
return realm.where(this::class.java).notEqualTo("id", this.id).findAll().isNotEmpty()
}
override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled")
}
override fun isValidForDelete(realm: Realm): Boolean {
return true
}
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return R.string.cf_entry_delete_popup_message
}
}

@ -1,15 +1,191 @@
package net.pokeranalytics.android.model.realm
import android.content.Context
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rowrepresentable.PlayerRow
import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.isSameDay
import net.pokeranalytics.android.util.extensions.mediumDate
import java.util.*
import kotlin.collections.ArrayList
open class Player : RealmObject() {
open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresentableDataSource, RowRepresentable {
@PrimaryKey
var id = UUID.randomUUID().toString()
override var id = UUID.randomUUID().toString()
// The name of the player
var name: String = ""
override var name: String = ""
}
// New fields
var summary: String = ""
var color: Int? = null
var picture: String? = null
var comments: RealmList<Comment> = RealmList()
@Ignore
override val realmObjectClass: Class<out Identifiable> = Player::class.java
@Ignore
override val viewType: Int = RowViewType.ROW_PLAYER.ordinal
@Ignore
private var rowRepresentation: List<RowRepresentable> = mutableListOf()
@Ignore
private var commentsToDelete: ArrayList<Comment> = ArrayList()
override fun isValidForDelete(realm: Realm): Boolean {
//TODO
return true
}
override fun getFailedSaveMessage(status: SaveValidityStatus): Int {
return when(status) {
SaveValidityStatus.ALREADY_EXISTS -> R.string.duplicate_user_error
else -> super.getFailedSaveMessage(status)
}
}
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
//TODO
return R.string.relationship_error
}
override fun adapterRows(): List<RowRepresentable>? {
return rowRepresentation
}
override fun getDisplayName(context: Context): String {
return this.name
}
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
PlayerRow.NAME -> if (this.name.isNotEmpty()) this.name else NULL_TEXT
else -> return super.stringForRow(row)
}
}
override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) {
PlayerRow.NAME -> this.name = value as String? ?: ""
PlayerRow.SUMMARY -> this.summary = value as String? ?: ""
PlayerRow.IMAGE -> this.picture = value as String? ?: ""
}
}
/**
* Update the row representation
*/
private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> {
val rows = ArrayList<RowRepresentable>()
rows.add(PlayerRow.IMAGE)
rows.add(PlayerRow.NAME)
rows.add(PlayerRow.SUMMARY)
if (comments.size > 0) {
// Adds Comments section
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_TITLE, R.string.comments))
val currentCommentCalendar = Calendar.getInstance()
val currentDateCalendar = Calendar.getInstance()
val commentsToDisplay = ArrayList<Comment>()
commentsToDisplay.addAll(comments)
commentsToDisplay.sortByDescending { it.date }
commentsToDisplay.forEachIndexed { index, comment ->
currentCommentCalendar.time = comment.date
if (!currentCommentCalendar.isSameDay(currentDateCalendar) || index == 0) {
currentDateCalendar.time = currentCommentCalendar.time
// Adds day sub section
rows.add(CustomizableRowRepresentable(RowViewType.HEADER_SUBTITLE, title = currentDateCalendar.time.mediumDate()))
}
// Adds comment
rows.add(comment)
}
rows.add(SeparatorRow())
}
return rows
}
/**
* Return if the player has a picture
*/
fun hasPicture(): Boolean {
return picture != null && picture?.isNotEmpty() == true
}
/**
* Update row representation
*/
fun updateRowRepresentation() {
this.rowRepresentation = this.updatedRowRepresentationForCurrentState()
}
/**
* Add an entry
*/
fun addComment(): Comment {
val entry = Comment()
this.comments.add(entry)
updateRowRepresentation()
return entry
}
/**
* Delete an entry
*/
fun deleteComment(comment: Comment) {
commentsToDelete.add(comment)
this.comments.remove(comment)
updateRowRepresentation()
}
/**
* Clean up deleted entries
*/
fun cleanupComments() { // called when saving the custom field
val realm = Realm.getDefaultInstance()
realm.executeTransaction {
this.commentsToDelete.forEach { // entries are out of realm
realm.where<Comment>().equalTo("id", it.id).findFirst()?.deleteFromRealm()
}
}
realm.close()
this.commentsToDelete.clear()
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
when (row) {
PlayerRow.NAME -> return row.editingDescriptors(mapOf("defaultValue" to this.name))
PlayerRow.SUMMARY -> return row.editingDescriptors(mapOf("defaultValue" to this.summary))
}
return null
}
}

@ -0,0 +1,77 @@
package net.pokeranalytics.android.ui.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_color_picker.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
class ColorPickerActivity : PokerAnalyticsActivity() {
companion object {
const val INTENT_COLOR = "INTENT_COLOR"
fun newInstance(context: Context) {
val intent = Intent(context, ColorPickerActivity::class.java)
context.startActivity(intent)
}
/**
* Create a new instance for result
*/
fun newInstanceForResult(fragment: Fragment, requestCode: Int) {
val intent = Intent(fragment.requireContext(), ColorPickerActivity::class.java)
fragment.startActivityForResult(intent, requestCode)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_color_picker)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
color1.setOnClickListener { manageSelectedColor(it) }
color2.setOnClickListener { manageSelectedColor(it) }
color3.setOnClickListener { manageSelectedColor(it) }
color4.setOnClickListener { manageSelectedColor(it) }
color5.setOnClickListener { manageSelectedColor(it) }
color6.setOnClickListener { manageSelectedColor(it) }
color7.setOnClickListener { manageSelectedColor(it) }
color8.setOnClickListener { manageSelectedColor(it) }
color9.setOnClickListener { manageSelectedColor(it) }
}
private fun manageSelectedColor(view: View) {
val color = when(view) {
color1 -> getColor(R.color.player_color_1)
color2 -> getColor(R.color.player_color_2)
color3 -> getColor(R.color.player_color_3)
color4 -> getColor(R.color.player_color_4)
color5 -> getColor(R.color.player_color_5)
color6 -> getColor(R.color.player_color_6)
color7 -> getColor(R.color.player_color_7)
color8 -> getColor(R.color.player_color_8)
color9 -> getColor(R.color.player_color_9)
else -> getColor(R.color.player_color_1)
}
val intent = Intent()
intent.putExtra(INTENT_COLOR, color)
setResult(Activity.RESULT_OK, intent)
finish()
}
}

@ -4,13 +4,14 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.crashlytics.android.Crashlytics
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.activity.components.MediaActivity
import net.pokeranalytics.android.ui.fragment.data.*
import java.io.File
import java.util.*
class EditableDataActivity : PokerAnalyticsActivity() {
class EditableDataActivity : MediaActivity() {
enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"),
@ -44,6 +45,8 @@ class EditableDataActivity : PokerAnalyticsActivity() {
}
private var currentFragment: EditableDataFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_editable_data)
@ -58,20 +61,35 @@ class EditableDataActivity : PokerAnalyticsActivity() {
val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0)
val primaryKey = intent.getStringExtra(IntentKey.PRIMARY_KEY.keyName)
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment: EditableDataFragment = when (dataType) {
currentFragment = when (dataType) {
LiveData.BANKROLL.ordinal -> BankrollDataFragment()
LiveData.LOCATION.ordinal -> LocationDataFragment()
LiveData.TRANSACTION.ordinal -> TransactionDataFragment()
LiveData.CUSTOM_FIELD.ordinal -> CustomFieldDataFragment()
LiveData.TRANSACTION_TYPE.ordinal -> TransactionTypeDataFragment()
LiveData.PLAYER.ordinal -> PlayerDataFragment()
else -> EditableDataFragment()
}
Crashlytics.log("creating EditableDataFragment with dataType = $dataType")
fragment.setData(dataType, primaryKey)
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
currentFragment?.let { fragment ->
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit()
fragment.setData(dataType, primaryKey)
}
}
override fun isLoadingNewPictures() {
super.isLoadingNewPictures()
currentFragment?.isLoadingNewPhotos()
}
override fun getPictures(files: ArrayList<File>) {
super.getPictures(files)
currentFragment?.getPhotos(files)
}
}

@ -0,0 +1,268 @@
package net.pokeranalytics.android.ui.activity.components
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.provider.MediaStore
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.pokeranalytics.android.util.ImageUtils
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.util.*
open class MediaActivity : PokerAnalyticsActivity() {
companion object {
const val SELECTED_CHOICE_TAKE_PICTURE = 10
const val SELECTED_CHOICE_SELECT_PICTURE = 11
const val REQUEST_CODE_TAKE_PICTURE = 100
const val REQUEST_CODE_SELECT_PICTURE = 101
const val PERMISSION_REQUEST_EXTERNAL_STORAGE = 201
const val PERMISSION_REQUEST_CAMERA = 202
}
// Data
private var tempFile: File? = null
private var mCurrentPhotoPath: String? = null
private var selectedChoice = -1
private var multiplePictures = false
override fun onDestroy() {
super.onDestroy()
if (tempFile != null) {
tempFile!!.delete()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_SELECT_PICTURE || requestCode == REQUEST_CODE_TAKE_PICTURE) {
val filesList = ArrayList<File>()
GlobalScope.launch {
if (tempFile != null) {
tempFile?.let {
GlobalScope.launch(Dispatchers.Main) {
filesList.add(it)
getPictures(filesList)
}
}
} else if (data?.clipData != null) {
data?.clipData?.let { clipData ->
try {
GlobalScope.launch(Dispatchers.Main) {
isLoadingNewPictures()
}
for (i in 0 until clipData.itemCount) {
val item = clipData.getItemAt(i)
val uri = item.uri
val inputStream = contentResolver.openInputStream(uri)
val photoFile = ImageUtils.createTempImageFile(this@MediaActivity)
ImageUtils.copyInputStreamToFile(inputStream!!, photoFile)
filesList.add(photoFile)
}
GlobalScope.launch(Dispatchers.Main) {
getPictures(filesList)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
} else if (data?.data != null) {
data?.data?.let { uri ->
try {
GlobalScope.launch(Dispatchers.Main) {
isLoadingNewPictures()
}
val inputStream = contentResolver.openInputStream(uri)
val photoFile = ImageUtils.createTempImageFile(this@MediaActivity)
ImageUtils.copyInputStreamToFile(inputStream!!, photoFile)
filesList.add(photoFile)
GlobalScope.launch(Dispatchers.Main) {
getPictures(filesList)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (grantResults.isNotEmpty()) {
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
//Toast.makeText(this, getString(R.string.photo_library_add_usage_description), Toast.LENGTH_SHORT).show()
selectedChoice = -1
return
}
}
when (selectedChoice) {
SELECTED_CHOICE_TAKE_PICTURE -> {
openImageCaptureIntent(multiplePictures)
}
SELECTED_CHOICE_SELECT_PICTURE -> {
openImageGalleryIntent(multiplePictures)
}
}
}
selectedChoice = -1
}
/**
* Open the Camera Intent
*/
fun openImageCaptureIntent(multiplePictures: Boolean) {
tempFile = null
this.mCurrentPhotoPath = null
this.multiplePictures = multiplePictures
// Test if we have the permission
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
selectedChoice = SELECTED_CHOICE_TAKE_PICTURE
askForStoragePermission()
return
}
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(packageManager) != null) {
// Create the File where the photo should go
try {
tempFile = ImageUtils.createImageFile(this)
mCurrentPhotoPath = "file:" + tempFile?.absolutePath
} catch (ex: IOException) {
// Error occurred while creating the File
}
// Continue only if the File was successfully created
if (tempFile != null) {
Timber.d("tempFile: ${tempFile?.absolutePath}")
val photoURI = FileProvider.getUriForFile(
this,
applicationContext.packageName + ".fileprovider", tempFile!!
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PICTURE)
}
}
}
/**
* Open the gallery intent
*/
fun openImageGalleryIntent(multiplePictures: Boolean) {
tempFile = null
this.multiplePictures = multiplePictures
// Test if we have the permission
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
selectedChoice = SELECTED_CHOICE_SELECT_PICTURE
askForStoragePermission()
return
}
this.multiplePictures = multiplePictures
val galleryIntent = Intent()
galleryIntent.type = "image/*"
galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiplePictures)
galleryIntent.action = Intent.ACTION_GET_CONTENT
startActivityForResult(galleryIntent, REQUEST_CODE_SELECT_PICTURE)
}
/**
* Ask for the external storage permission
*/
private fun askForStoragePermission() {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
PERMISSION_REQUEST_EXTERNAL_STORAGE)
}
}
/**
* Ask for the acmera permission
*/
private fun askForCameraPermission() {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA),
PERMISSION_REQUEST_CAMERA)
}
}
/**
* Ask for camera and storage permission
*/
private fun askForCameraAndStoragePermissions() {
val permissions = ArrayList<String>()
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.CAMERA)
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (permissions.size > 0) {
ActivityCompat.requestPermissions(this, permissions.toArray(arrayOfNulls<String>(permissions.size)), PERMISSION_REQUEST_CAMERA)
}
}
/**
* Called when a bitmap is return
*
* @param bitmap the bitmap returned
*/
open fun getBitmapImage(file: File?, bitmap: Bitmap?) {}
/**
* Called when the user is adding new photos
*/
open fun isLoadingNewPictures() {}
/**
* Called when the user has selected photos
*/
open fun getPictures(files: ArrayList<File>) {}
}

@ -7,9 +7,11 @@ import android.content.res.Resources
import android.net.Uri
import android.util.TypedValue
import android.view.View
import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatTextView
import androidx.appcompat.widget.SearchView
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
@ -160,4 +162,12 @@ fun View.showWithAnimation() {
fun View.addCircleRipple() = with(TypedValue()) {
context.theme.resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, this, true)
setBackgroundResource(resourceId)
}
fun SearchView.removeMargins() {
val searchEditFrame = findViewById<LinearLayout?>(R.id.search_edit_frame)
val layoutParams = searchEditFrame?.layoutParams as LinearLayout.LayoutParams?
layoutParams?.leftMargin = 0
layoutParams?.rightMargin = 0
searchEditFrame?.layoutParams = layoutParams
}

@ -3,9 +3,8 @@ package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
@ -23,10 +22,12 @@ import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.extensions.removeMargins
import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.extensions.find
import net.pokeranalytics.android.util.extensions.sorted
@ -40,6 +41,15 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
private lateinit var dataType: LiveData
private lateinit var items: RealmResults<out Deletable>
private var dataListMenu: Menu? = null
private var searchView: SearchView? = null
var isSearchable: Boolean = false
set(value) {
field = value
val searchMenuItem = dataListMenu?.findItem(R.id.action_search)
searchMenuItem?.isVisible = value
}
/**
* Set fragment data
@ -51,6 +61,11 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
setToolbarTitle(this.dataType.pluralLocalizedTitle(requireContext()))
this.items = this.retrieveItems(getRealm())
this.isSearchable = when (this.dataType) {
LiveData.PLAYER, LiveData.LOCATION -> true
else -> false
}
}
open fun retrieveItems(realm: Realm): RealmResults<out Deletable> {
@ -114,6 +129,31 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
this.recyclerView?.adapter?.notifyDataSetChanged()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.clear()
inflater.inflate(R.menu.toolbar_data_list, menu)
this.dataListMenu = menu
val searchMenuItem = menu.findItem(R.id.action_search)
searchMenuItem.isVisible = isSearchable
searchView = searchMenuItem.actionView as SearchView?
searchView?.removeMargins()
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
filterItemsWithSearch(newText)
return false
}
})
super.onCreateOptionsMenu(menu, inflater)
}
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return this.items[position] as RowRepresentable
}
@ -150,4 +190,12 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
this.addButton.isVisible = showAddButton
}
/**
* Filter the items list with the given search content
*/
private fun filterItemsWithSearch(searchContent: String?) {
this.items = getRealm().find(this.identifiableClass, searchContent)
dataListAdapter.notifyDataSetChanged()
}
}

@ -13,6 +13,7 @@ import io.realm.Realm
import kotlinx.android.synthetic.main.fragment_settings.*
import net.pokeranalytics.android.BuildConfig
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.ui.activity.*
@ -115,6 +116,7 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
when (row) {
SettingRow.BANKROLL_REPORT -> BankrollActivity.newInstance(requireContext())
SettingRow.TOP_10 -> Top10Activity.newInstance(requireContext())
SettingRow.PLAYERS -> DataListActivity.newInstance(requireContext(), LiveData.PLAYER.ordinal)
SettingRow.SUBSCRIPTION -> {
if (!AppGuard.isProUser) {
BillingActivity.newInstanceForResult(this, false)

@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment
import com.crashlytics.android.Crashlytics
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import java.io.File
open class PokerAnalyticsFragment : Fragment() {
@ -99,4 +100,14 @@ open class PokerAnalyticsFragment : Fragment() {
parentActivity?.supportActionBar?.setDisplayHomeAsUpEnabled(enabled)
}
/**
* Called when the user is adding new photos
*/
open fun isLoadingNewPhotos() {}
/*
* Called when the user has selected photos
*/
open fun getPhotos(files: ArrayList<File>) {}
}

@ -0,0 +1,218 @@
package net.pokeranalytics.android.ui.fragment.data
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import kotlinx.android.synthetic.main.fragment_player.*
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.model.realm.Comment
import net.pokeranalytics.android.model.realm.Player
import net.pokeranalytics.android.ui.activity.ColorPickerActivity
import net.pokeranalytics.android.ui.activity.components.MediaActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.extensions.showAlertDialog
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.PlayerRow
import net.pokeranalytics.android.util.NULL_TEXT
import java.io.File
/**
* Player data fragment
*/
class PlayerDataFragment : EditableDataFragment(), StaticRowRepresentableDataSource {
companion object {
const val REQUEST_CODE_PICK_COLOR = 1000
}
private val player: Player
get() {
return this.item as Player
}
private var mediaActivity: MediaActivity? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
shouldOpenKeyboard = false
return inflater.inflate(R.layout.fragment_player, container, false)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_PICK_COLOR && resultCode == RESULT_OK && data?.hasExtra(ColorPickerActivity.INTENT_COLOR) == true) {
val color = data.getIntExtra(ColorPickerActivity.INTENT_COLOR, Color.TRANSPARENT)
player.color = if (color != Color.TRANSPARENT) color else null
rowRepresentableAdapter.refreshRow(PlayerRow.IMAGE)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUI()
}
/**
* Init UI
*/
private fun initUI() {
mediaActivity = parentActivity as MediaActivity?
player.updateRowRepresentation()
if (!deleteButtonShouldAppear) {
onRowSelected(0, PlayerRow.NAME)
}
addComment.setOnClickListener {
val comment = player.addComment()
rowRepresentableAdapter.notifyDataSetChanged()
onRowSelected(-1, comment)
}
}
override fun getPhotos(files: ArrayList<File>) {
super.getPhotos(files)
files.firstOrNull()?.let { picture ->
player.updateValue(picture.absolutePath, PlayerRow.IMAGE)
rowRepresentableAdapter.refreshRow(PlayerRow.IMAGE)
}
}
override fun getDataSource(): RowRepresentableDataSource {
return this
}
override fun adapterRows(): List<RowRepresentable>? {
return player.adapterRows()
}
override fun viewTypeForPosition(position: Int): Int {
return when (position) {
0 -> RowViewType.ROW_PLAYER_IMAGE.ordinal
else -> super.viewTypeForPosition(position)
}
}
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return when (position) {
0 -> player
else -> super.rowRepresentableForPosition(position)
}
}
override fun stringForRow(row: RowRepresentable): String {
return when (row) {
PlayerRow.NAME -> if (player.name.isNotEmpty()) player.name else NULL_TEXT
PlayerRow.SUMMARY -> if (player.summary.isNotEmpty()) player.summary else NULL_TEXT
else -> super.stringForRow(row)
}
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
when (row) {
PlayerRow.IMAGE -> openPictureDialog()
is Comment -> {
val data = arrayListOf(RowRepresentableEditDescriptor(row.content))
BottomSheetFragment.create(fragmentManager, row, this, data, isClearable = false, isDeletable = true)
}
else -> super.onRowSelected(position, row, fromAction)
}
}
override fun onRowValueChanged(value: Any?, row: RowRepresentable) {
when (row) {
is Comment -> {
row.updateValue(value, row)
player.updateRowRepresentation()
rowRepresentableAdapter.notifyDataSetChanged()
}
else -> {
super.onRowValueChanged(value, row)
if (row == PlayerRow.NAME) {
rowRepresentableAdapter.refreshRow(PlayerRow.IMAGE)
}
}
}
}
override fun onRowDeleted(row: RowRepresentable) {
super.onRowDeleted(row)
when (row) {
is Comment -> {
if (row.isValidForDelete(getRealm())) {
GlobalScope.launch(Dispatchers.Main) {
delay(300)
showAlertDialog(requireContext(), message = R.string.are_you_sure_you_want_to_delete, showCancelButton = true, positiveAction = {
player.deleteComment(row)
rowRepresentableAdapter.notifyDataSetChanged()
})
}
}
}
}
}
/**
* Open picture dialog
*/
private fun openPictureDialog() {
val builder = AlertDialog.Builder(requireContext())
val placesArray = ArrayList<CharSequence>()
placesArray.add(getString(R.string.take_a_picture))
placesArray.add(getString(R.string.library))
placesArray.add(getString(R.string.select_a_color))
if (player.hasPicture()) {
placesArray.add(getString(R.string.remove_picture))
}
builder.setItems(placesArray.toTypedArray()) { _, which ->
when (placesArray[which]) {
getString(R.string.take_a_picture) -> mediaActivity?.openImageCaptureIntent(false)
getString(R.string.library) -> mediaActivity?.openImageGalleryIntent(false)
getString(R.string.select_a_color) -> {
ColorPickerActivity.newInstanceForResult(this, REQUEST_CODE_PICK_COLOR)
}
getString(R.string.remove_picture) -> {
player.updateValue(null, PlayerRow.IMAGE)
rowRepresentableAdapter.refreshRow(PlayerRow.IMAGE)
}
}
}
builder.show()
}
override fun onDataSaved() {
super.onDataSaved()
player.cleanupComments()
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
when (row) {
PlayerRow.NAME -> return arrayListOf(RowRepresentableEditDescriptor(this.player.name, R.string.name))
PlayerRow.SUMMARY -> return arrayListOf(RowRepresentableEditDescriptor(this.player.summary, R.string.summary))
}
return null
}
}

@ -0,0 +1,144 @@
package net.pokeranalytics.android.ui.view
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.util.TypedValue
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat.getColor
import androidx.core.content.res.ResourcesCompat
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade
import com.bumptech.glide.request.RequestOptions
import kotlinx.android.synthetic.main.view_player_image.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Player
import net.pokeranalytics.android.ui.extensions.px
/**
* Display a row session
*/
class PlayerImageView : FrameLayout {
enum class Size {
NORMAL,
SMALL;
fun getFontSize(): Float {
return when (this) {
NORMAL -> 32f
SMALL -> 16f
}
}
fun getStrokeSize() : Int {
return when (this) {
NORMAL -> 4.px
SMALL -> 2.px
}
}
}
private lateinit var playerImageView: ConstraintLayout
private var onImageClickListener: OnClickListener? = null
/**
* 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
*/
private fun init() {
val layoutInflater = LayoutInflater.from(context)
playerImageView = layoutInflater.inflate(R.layout.view_player_image, this, false) as ConstraintLayout
val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(playerImageView, layoutParams)
}
/**
* Set the session data to the view
*/
fun setPlayer(player: Player, size: Size = Size.NORMAL, isClickable: Boolean = true) {
// Initial
val playerInitial = if (player.name.isNotEmpty()) {
val playerData = player.name.split(" ")
if (playerData.size > 1) {
playerData[0].first().toString() + playerData[1].first().toString()
} else if (player.name.length > 1) {
player.name.substring(0, 2)
} else {
player.name.substring(0, player.name.length)
}
} else {
"" //NULL_TEXT
}
// Picture
if (player.hasPicture()) {
playerImageView.playerInitial.text = ""
Glide.with(this)
.load(player.picture)
.apply(RequestOptions().circleCrop())
.transition(withCrossFade())
.into(playerImageView.playerImage)
} else {
playerImageView.playerStroke.background = ResourcesCompat.getDrawable(resources, R.drawable.circle_stroke_kaki, null)
playerImageView.playerImage.setImageDrawable(null)
playerImageView.playerInitial.text = playerInitial
playerImageView.playerInitial.setTextSize(TypedValue.COMPLEX_UNIT_SP, size.getFontSize())
}
// Player color
val color = if (player.color != null) {
player.color as Int
} else if (player.hasPicture()) {
Color.TRANSPARENT
} else {
getColor(context, R.color.kaki)
}
// Stroke & initial
val drawable = playerImageView.playerStroke.background as GradientDrawable?
drawable?.setStroke(size.getStrokeSize(), color)
playerImageView.playerInitial.setTextColor(color)
// Click listener
if (isClickable) {
playerImageView.playerImageSelection.setOnClickListener {
onImageClickListener?.onClick(it)
}
} else {
playerImageView.playerImageSelection.background = null
}
}
/**
* Set image click listener
*/
fun setOnImageClickListener(onImageClickListener: OnClickListener) {
this.onImageClickListener = onImageClickListener
}
}

@ -19,6 +19,9 @@ interface EditDataSource {
fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
return null
}
// val valueCanBeClearedWhenEditing: Boolean
// get() { return true }
}
interface DefaultEditDataSource : EditDataSource, Localizable {

@ -26,6 +26,7 @@ import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.extensions.getFormattedGameType
import net.pokeranalytics.android.model.realm.CustomField
import net.pokeranalytics.android.model.realm.Player
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@ -59,6 +60,7 @@ enum class RowViewType(private var layoutRes: Int) {
HEADER_TITLE_VALUE(R.layout.row_header_title_value),
HEADER_TITLE_AMOUNT(R.layout.row_header_title_amount),
HEADER_TITLE_AMOUNT_BIG(R.layout.row_header_title_amount_big),
HEADER_SUBTITLE(R.layout.row_header_subtitle),
LOCATION_TITLE(R.layout.row_title),
INFO(R.layout.row_info),
@ -76,6 +78,8 @@ enum class RowViewType(private var layoutRes: Int) {
TITLE_CHECK(R.layout.row_title_check),
TITLE_VALUE_CHECK(R.layout.row_title_value_check),
LOADER(R.layout.row_loader),
CONTENT(R.layout.row_content),
TITLE_SUBTITLE(R.layout.row_title_subtitle),
// Custom row
ROW_SESSION(R.layout.row_feed_session),
@ -88,6 +92,8 @@ enum class RowViewType(private var layoutRes: Int) {
GRAPH(R.layout.row_graph),
LEGEND_DEFAULT(R.layout.row_legend_default),
LIST(R.layout.row_list),
ROW_PLAYER(R.layout.row_player),
ROW_PLAYER_IMAGE(R.layout.row_player_image),
// Separator
SEPARATOR(R.layout.row_separator);
@ -104,7 +110,7 @@ enum class RowViewType(private var layoutRes: Int) {
// Row View Holder
HEADER_TITLE, HEADER_TITLE_VALUE, HEADER_TITLE_AMOUNT, HEADER_TITLE_AMOUNT_BIG, LOCATION_TITLE,
INFO, TITLE, TITLE_ARROW, TITLE_ICON_ARROW, TITLE_VALUE, TITLE_VALUE_ARROW, TITLE_VALUE_ACTION, TITLE_GRID,
TITLE_SWITCH, TITLE_CHECK, TITLE_VALUE_CHECK,
TITLE_SWITCH, TITLE_CHECK, TITLE_VALUE_CHECK, CONTENT, TITLE_SUBTITLE, HEADER_SUBTITLE,
DATA, BOTTOM_SHEET_DATA, LOADER -> RowViewHolder(layout)
// Row Session
@ -116,6 +122,11 @@ enum class RowViewType(private var layoutRes: Int) {
// Row Transaction
ROW_TOP_10 -> RowTop10ViewHolder(layout)
ROW_PLAYER -> RowPlayerViewHolder(layout)
ROW_PLAYER_IMAGE -> RowPlayerImageViewHolder(layout)
// Row Button
ROW_BUTTON -> RowButtonViewHolder(layout)
@ -623,6 +634,47 @@ enum class RowViewType(private var layoutRes: Int) {
}
/**
* Display a player image view
*/
inner class RowPlayerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) {
if (row is Player) {
itemView.findViewById<PlayerImageView?>(R.id.playerImage)?.let { playerImageView ->
playerImageView.setPlayer(row, PlayerImageView.Size.SMALL, false)
}
itemView.findViewById<AppCompatTextView?>(R.id.playerName)?.let { textView ->
textView.text = row.name
}
itemView.findViewById<AppCompatTextView?>(R.id.playerSummary)?.let { textView ->
textView.text = row.summary
textView.isVisible = row.summary.isNotEmpty()
}
val listener = View.OnClickListener {
adapter.delegate?.onRowSelected(position, row)
}
itemView.setOnClickListener(listener)
}
}
}
/**
* Display a player image view
*/
inner class RowPlayerImageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) {
itemView.findViewById<PlayerImageView?>(R.id.playerImageView)?.let { playerImageView ->
val listener = View.OnClickListener {
adapter.delegate?.onRowSelected(position, PlayerRow.IMAGE)
}
playerImageView.setPlayer(row as Player)
playerImageView.setOnImageClickListener(listener)
}
}
}
/**
* Display a separator
*/

@ -0,0 +1,69 @@
package net.pokeranalytics.android.ui.view.rowrepresentable
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
import net.pokeranalytics.android.ui.view.RowViewType
/**
* An enum managing the player rows
*/
enum class PlayerRow : RowRepresentable {
IMAGE,
NAME,
SUMMARY;
override val resId: Int?
get() {
return when (this) {
IMAGE -> null
NAME -> R.string.name
SUMMARY -> R.string.summary
}
}
override val viewType: Int
get() {
return when (this) {
IMAGE -> RowViewType.ROW_PLAYER_IMAGE.ordinal
NAME -> RowViewType.TITLE_SUBTITLE.ordinal
SUMMARY -> RowViewType.TITLE_SUBTITLE.ordinal
}
}
override val bottomSheetType: BottomSheetType
get() {
return when (this) {
IMAGE -> BottomSheetType.NONE
NAME -> BottomSheetType.EDIT_TEXT
SUMMARY -> BottomSheetType.EDIT_TEXT_MULTI_LINES
}
}
override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
return null
}
// override fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
//
//
// }
// override fun startEditing(dataSource: Any?, parent: Fragment?) {
// if (dataSource == null) return
// if (dataSource !is Player) return
// if (parent == null) return
// if (parent !is RowRepresentableDelegate) return
// val data = RowEditableDataSource()
// when (this) {
// NAME -> data.append(dataSource.name)
// SUMMARY -> data.append(dataSource.summary)
// else -> PokerAnalyticsException.InputFragmentException
// }
//
// InputFragment.buildAndShow(this, parent, data)
// }
}

@ -11,6 +11,7 @@ enum class SettingRow : RowRepresentable {
// More
BANKROLL_REPORT,
TOP_10,
PLAYERS,
// About
SUBSCRIPTION,
@ -50,7 +51,7 @@ enum class SettingRow : RowRepresentable {
val rows = ArrayList<RowRepresentable>()
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.reports))
rows.addAll(arrayListOf(BANKROLL_REPORT, TOP_10))
rows.addAll(arrayListOf(BANKROLL_REPORT, TOP_10, PLAYERS))
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.information))
// rows.addAll(arrayListOf(VERSION, RATE_APP, CONTACT_US, BUG_REPORT))
@ -86,6 +87,7 @@ enum class SettingRow : RowRepresentable {
return when (this) {
BANKROLL_REPORT -> R.string.bankroll
TOP_10 -> R.string.top_10
PLAYERS -> R.string.players
SUBSCRIPTION -> R.string.subscription
VERSION -> R.string.version
RATE_APP -> R.string.releasenote_rating
@ -106,7 +108,7 @@ enum class SettingRow : RowRepresentable {
override val viewType: Int
get() {
return when (this) {
BANKROLL_REPORT, TOP_10 -> RowViewType.TITLE_ICON_ARROW.ordinal
BANKROLL_REPORT, TOP_10, PLAYERS -> RowViewType.TITLE_ICON_ARROW.ordinal
VERSION, SUBSCRIPTION -> RowViewType.TITLE_VALUE.ordinal
LANGUAGE, CURRENCY -> RowViewType.TITLE_VALUE_ARROW.ordinal
FOLLOW_US -> RowViewType.ROW_FOLLOW_US.ordinal
@ -119,6 +121,7 @@ enum class SettingRow : RowRepresentable {
return when(this) {
BANKROLL_REPORT -> R.drawable.ic_outline_lock
TOP_10 -> R.drawable.ic_outline_star
PLAYERS -> R.drawable.ic_outline_people
else -> null
}
}

@ -0,0 +1,377 @@
package net.pokeranalytics.android.util
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.*
import android.graphics.Paint.FILTER_BITMAP_FLAG
import android.media.ExifInterface
import android.net.Uri
import android.os.Environment
import androidx.core.content.ContextCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.text.SimpleDateFormat
import java.util.*
object ImageUtils {
/**
* Rotate a bitmap if it's necessary (depending of the EXIF data)
* Some devices don't rotate the picture but instead add the orientation
* value in the EXIF data.
* That's why we need sometimes to rotate by ourselves the bitmap
*
* @param src The file to check (for getting the Exif data)
* @param bitmap The bitmap to modify (if necessary)
* @return The bitmap in the correct orientation
*/
fun rotateBitmap(src: String, bitmap: Bitmap, updateFile: Boolean): Bitmap {
try {
val orientation = getExifOrientation(src)
if (orientation == ExifInterface.ORIENTATION_NORMAL) {
return bitmap
}
val matrix = Matrix()
when (orientation) {
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.setScale(-1f, 1f)
ExifInterface.ORIENTATION_ROTATE_180 -> matrix.setRotate(180f)
ExifInterface.ORIENTATION_FLIP_VERTICAL -> {
matrix.setRotate(180f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_TRANSPOSE -> {
matrix.setRotate(90f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_ROTATE_90 -> matrix.setRotate(90f)
ExifInterface.ORIENTATION_TRANSVERSE -> {
matrix.setRotate(-90f)
matrix.postScale(-1f, 1f)
}
ExifInterface.ORIENTATION_ROTATE_270 -> matrix.setRotate(-90f)
else -> return bitmap
}
try {
val oriented = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
bitmap.recycle()
if (updateFile) {
updateFile(src, oriented)
}
return oriented
} catch (e: OutOfMemoryError) {
e.printStackTrace()
return bitmap
}
} catch (e: IOException) {
e.printStackTrace()
}
return bitmap
}
/**
* Get the Exif orientation value
*
* @param filePath The path of the file
* @return the orientation value
* @throws IOException
*/
@Throws(IOException::class)
private fun getExifOrientation(filePath: String): Int {
val exifInterface = ExifInterface(filePath)
return exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
}
/**
* Save a bitmap into a file (& apply 90% compression)
*
* @param filePath Path of the file
* @param bitmap Bitmap to save
*/
fun updateFile(filePath: String, bitmap: Bitmap) {
var out: FileOutputStream? = null
try {
out = FileOutputStream(filePath)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
out?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
/**
* Resize a file with the given maximum width or height (and keep the ratio!)
* @param filePath String: File path
* @param bitmap Bitmap: Image
* @param maxWidth int: Max width
* @param maxHeight int: Max height
*/
fun resizeFile(filePath: String, bitmap: Bitmap, maxWidth: Int, maxHeight: Int) {
var bitmap = bitmap
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(filePath, options)
val imageWidth = options.outWidth
val imageHeight = options.outHeight
var newWidth: Int
var newHeight: Int
if (imageWidth > imageHeight) {
newWidth = maxWidth
newHeight = imageHeight * maxWidth / imageWidth
if (newHeight > maxHeight) {
newHeight = maxHeight
newWidth = imageWidth * maxHeight / imageHeight
}
} else {
newHeight = maxHeight
newWidth = imageWidth * maxHeight / imageHeight
if (newWidth > maxWidth) {
newWidth = maxWidth
newHeight = imageHeight * maxWidth / imageWidth
}
}
bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
updateFile(filePath, bitmap)
}
/**
* Create a unique temp image file name
*
* @return
* @throws IOException
*/
@Throws(IOException::class)
fun createTempImageFile(context: Context): File {
// Create an image file name
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val imageFileName = "JPEG_" + timeStamp + "_"
val storageDir = context.cacheDir
return File.createTempFile(imageFileName, ".jpg", storageDir)
}
/**
* Create a unique image file name
*
* @return
* @throws IOException
*/
@Throws(IOException::class)
fun createImageFile(context: Context): File {
// Create an image file name
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val imageFileName = "JPEG_" + timeStamp + "_"
val storage = ContextCompat.getExternalFilesDirs(context, Environment.DIRECTORY_PICTURES)
val storageDir = if (storage.isNotEmpty()) storage.first() else Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val appStorageDir = File(storageDir.path + "/" + context.getString(R.string.app_name))
if (!appStorageDir.exists()) {
appStorageDir.mkdirs()
}
return File.createTempFile(imageFileName, ".jpg", appStorageDir)
}
/**
* Decode sample Bitmap
*
* @param filePath The bitmap file path
* @param reqWidth Max width required
* @param reqHeight Max height required
* @return The sampled bitmap
*/
fun decodeSampledBitmapFromFile(filePath: String, reqWidth: Int, reqHeight: Int): Bitmap {
// First decode with inJustDecodeBounds=true to check dimensions
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(filePath, options)
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false
return BitmapFactory.decodeFile(filePath, options)
}
/**
* Calculate the sample size
*/
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int,
reqHeight: Int): Int {
// Raw height and width of image
val height = options.outHeight
val width = options.outWidth
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while (halfHeight / inSampleSize > reqHeight && halfWidth / inSampleSize > reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
/**
* Copy an input stream inside a file
*
* @param in Input Stream
* @param file Destination file
*/
fun copyInputStreamToFile(inputStream: InputStream, file: File) {
try {
val out = FileOutputStream(file)
val buf = ByteArray(4096)
var len = 0
while ({ len = inputStream.read(buf); len }() > 0) {
out.write(buf, 0, len)
}
out.close()
inputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* Update the gallery with the current file
*
* @param context Context
* @param filePath The file to add
*/
fun updateGallery(context: Context, filePath: String) {
val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
val f = File(filePath)
val contentUri = Uri.fromFile(f)
mediaScanIntent.data = contentUri
context.sendBroadcast(mediaScanIntent)
}
/**
* Save the bitmap in a file
*/
fun saveBitmapInFile(context: Context, bitmap: Bitmap, filename: String, action: (filePath: String) -> Unit) {
GlobalScope.launch {
val outputFile = File(context.filesDir, filename)
var out: FileOutputStream? = null
try {
out = FileOutputStream(outputFile.absolutePath)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out) // bmp is your Bitmap instance
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
if (out != null) {
out.close()
}
} catch (e: IOException) {
e.printStackTrace()
}
}
GlobalScope.launch(Dispatchers.Main) {
Timber.d("Save file here: ${outputFile.absolutePath}")
action(outputFile.absolutePath)
}
}
}
/**
* Bitmap resizer
*/
fun bitmapResizer(bitmap: Bitmap, newWidth: Int, newHeight: Int): Bitmap {
val scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888)
val ratioX = newWidth / bitmap.width.toFloat()
val ratioY = newHeight / bitmap.height.toFloat()
val middleX = newWidth / 2.0f
val middleY = newHeight / 2.0f
val scaleMatrix = Matrix()
scaleMatrix.setScale(ratioX, ratioY, middleX, middleY)
val canvas = Canvas(scaledBitmap)
canvas.matrix = scaleMatrix
canvas.drawBitmap(bitmap, middleX - bitmap.width / 2, middleY - bitmap.height / 2, Paint(FILTER_BITMAP_FLAG))
return scaledBitmap
}
/**
* Export a bitmap
*/
private fun exportFile(context: Activity, bitmap: Bitmap) {
/*
val outputFile = File.createTempFile("test_export", ".jpg", context.cacheDir)
var out: FileOutputStream? = null
try {
out = FileOutputStream(outputFile.absolutePath)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
if (out != null) {
out.close()
}
} catch (e: IOException) {
e.printStackTrace()
}
}
val uri = FileProvider.getUriForFile(context,
context.packageName + ".provider", outputFile)
val shareIntent = ShareCompat.IntentBuilder.from(context)
.setType("image/jpg")
.setSubject(context.getString(R.string.share_file_name))
.setStream(uri)
.setChooserTitle(context.getString(R.string.share_title))
.createChooserIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(shareIntent)
*/
}
}

@ -1,16 +1,10 @@
package net.pokeranalytics.android.util.extensions
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmResults
import io.realm.Sort
import io.realm.*
import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.TournamentFeature
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.model.realm.TransactionType
import net.pokeranalytics.android.model.realm.*
fun <T : RealmModel>Realm.count(clazz: Class<T>) : Long {
return this.where(clazz).count()
@ -107,4 +101,22 @@ fun <T : RealmModel>Realm.updateUsageCount(clazz: Class<T>) {
}
}
}
/**
* Returns all entities of the [clazz] which contain the given search content
*/
fun < T : RealmModel> Realm.find(clazz: Class<T>, searchContent: String?) : RealmResults<T> {
val query = this.where(clazz)
when (clazz.kotlin) {
Player::class -> {
query.contains("name", searchContent ?: "", Case.INSENSITIVE).or()
query.contains("summary", searchContent ?: "", Case.INSENSITIVE)
}
}
val items = query.findAll()
val sortField = arrayOf("name")
val resultSort = arrayOf(Sort.ASCENDING)
return items.sort(sortField, resultSort)
}

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/player_color_1" />
<stroke
android:color="@color/gray"
android:width="1dp" />
<size
android:width="48dp"
android:height="48dp" />
</shape>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/player_color_2" />
<size
android:width="48dp"
android:height="48dp" />
</shape>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/player_color_3" />
<size
android:width="48dp"
android:height="48dp" />
</shape>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/player_color_4" />
<size
android:width="48dp"
android:height="48dp" />
</shape>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/player_color_5" />
<size
android:width="48dp"
android:height="48dp" />
</shape>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/player_color_6" />
<size
android:width="48dp"
android:height="48dp" />
</shape>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/player_color_7" />
<size
android:width="48dp"
android:height="48dp" />
</shape>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/player_color_8" />
<size
android:width="48dp"
android:height="48dp" />
</shape>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/player_color_9" />
<size
android:width="48dp"
android:height="48dp" />
</shape>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke android:color="@color/kaki"
android:width="1dp"/>
<size
android:width="96dp"
android:height="96dp" />
</shape>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M9,13.75c-2.34,0 -7,1.17 -7,3.5L2,19h14v-1.75c0,-2.33 -4.66,-3.5 -7,-3.5zM4.34,17c0.84,-0.58 2.87,-1.25 4.66,-1.25s3.82,0.67 4.66,1.25L4.34,17zM9,12c1.93,0 3.5,-1.57 3.5,-3.5S10.93,5 9,5 5.5,6.57 5.5,8.5 7.07,12 9,12zM9,7c0.83,0 1.5,0.67 1.5,1.5S9.83,10 9,10s-1.5,-0.67 -1.5,-1.5S8.17,7 9,7zM16.04,13.81c1.16,0.84 1.96,1.96 1.96,3.44L18,19h4v-1.75c0,-2.02 -3.5,-3.17 -5.96,-3.44zM15,12c1.93,0 3.5,-1.57 3.5,-3.5S16.93,5 15,5c-0.54,0 -1.04,0.13 -1.5,0.35 0.63,0.89 1,1.98 1,3.15s-0.37,2.26 -1,3.15c0.46,0.22 0.96,0.35 1.5,0.35z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:cardCornerRadius="8dp"
app:cardBackgroundColor="@color/white">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/color1"
android:layout_width="@dimen/color_picker_circle_size"
android:layout_height="@dimen/color_picker_circle_size"
android:layout_marginStart="@dimen/color_picker_circle_margin"
android:layout_marginTop="@dimen/color_picker_circle_margin"
android:background="@drawable/circle_player_color_1"
android:foreground="?selectableItemBackgroundBorderless"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/color2"
android:layout_width="@dimen/color_picker_circle_size"
android:layout_height="@dimen/color_picker_circle_size"
android:layout_marginStart="@dimen/color_picker_circle_margin"
android:layout_marginTop="@dimen/color_picker_circle_margin"
android:background="@drawable/circle_player_color_2"
android:foreground="?selectableItemBackgroundBorderless"
app:layout_constraintStart_toEndOf="@+id/color1"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/color3"
android:layout_width="@dimen/color_picker_circle_size"
android:layout_height="@dimen/color_picker_circle_size"
android:layout_marginStart="@dimen/color_picker_circle_margin"
android:layout_marginTop="@dimen/color_picker_circle_margin"
android:layout_marginEnd="@dimen/color_picker_circle_margin"
android:background="@drawable/circle_player_color_3"
android:foreground="?selectableItemBackgroundBorderless"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/color2"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/color4"
android:layout_width="@dimen/color_picker_circle_size"
android:layout_height="@dimen/color_picker_circle_size"
android:layout_marginStart="@dimen/color_picker_circle_margin"
android:layout_marginTop="@dimen/color_picker_circle_margin"
android:background="@drawable/circle_player_color_4"
android:foreground="?selectableItemBackgroundBorderless"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/color1" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/color5"
android:layout_width="@dimen/color_picker_circle_size"
android:layout_height="@dimen/color_picker_circle_size"
android:layout_marginStart="@dimen/color_picker_circle_margin"
android:layout_marginTop="@dimen/color_picker_circle_margin"
android:background="@drawable/circle_player_color_5"
android:foreground="?selectableItemBackgroundBorderless"
app:layout_constraintStart_toEndOf="@+id/color4"
app:layout_constraintTop_toBottomOf="@+id/color2" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/color6"
android:layout_width="@dimen/color_picker_circle_size"
android:layout_height="@dimen/color_picker_circle_size"
android:layout_marginStart="@dimen/color_picker_circle_margin"
android:layout_marginTop="@dimen/color_picker_circle_margin"
android:layout_marginEnd="@dimen/color_picker_circle_margin"
android:background="@drawable/circle_player_color_6"
android:foreground="?selectableItemBackgroundBorderless"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/color5"
app:layout_constraintTop_toBottomOf="@+id/color2" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/color7"
android:layout_width="@dimen/color_picker_circle_size"
android:layout_height="@dimen/color_picker_circle_size"
android:layout_marginStart="@dimen/color_picker_circle_margin"
android:layout_marginTop="@dimen/color_picker_circle_margin"
android:layout_marginBottom="@dimen/color_picker_circle_margin"
android:background="@drawable/circle_player_color_7"
android:foreground="?selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/color4" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/color8"
android:layout_width="@dimen/color_picker_circle_size"
android:layout_height="@dimen/color_picker_circle_size"
android:layout_marginStart="@dimen/color_picker_circle_margin"
android:layout_marginTop="@dimen/color_picker_circle_margin"
android:layout_marginBottom="@dimen/color_picker_circle_margin"
android:background="@drawable/circle_player_color_8"
android:foreground="?selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/color4"
app:layout_constraintTop_toBottomOf="@+id/color5" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/color9"
android:layout_width="@dimen/color_picker_circle_size"
android:layout_height="@dimen/color_picker_circle_size"
android:layout_marginStart="@dimen/color_picker_circle_margin"
android:layout_marginTop="@dimen/color_picker_circle_margin"
android:layout_marginEnd="@dimen/color_picker_circle_margin"
android:layout_marginBottom="@dimen/color_picker_circle_margin"
android:background="@drawable/circle_player_color_9"
android:foreground="?selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/color8"
app:layout_constraintTop_toBottomOf="@+id/color6" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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="match_parent"
android:animateLayoutChanges="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/PokerAnalyticsTheme.Toolbar.Session"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="@string/player" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:paddingBottom="56dp"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_constraintBottom_toBottomOf="@+id/recyclerView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/addComment"
style="@style/PokerAnalyticsTheme.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="@string/add_comment" />
</FrameLayout>
</com.google.android.material.bottomappbar.BottomAppBar>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="wrap_content">
<include layout="@layout/layout_swipe_to_delete" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/foreground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray_dark"
android:foreground="?selectableItemBackground">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
style="@style/PokerAnalyticsTheme.TextView.RowContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/guidelineEnd"
app:layout_constraintStart_toStartOf="@+id/guidelineStart"
app:layout_constraintTop_toTopOf="parent"
tools:text="Data Type Title\nThis is a multilines textView" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
style="@style/PokerAnalyticsTheme.TextView.Header.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp"
tools:text="Header" />
</LinearLayout>

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="wrap_content">
<include layout="@layout/layout_swipe_to_delete" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/foreground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/gray_dark"
android:foreground="?selectableItemBackground">
<net.pokeranalytics.android.ui.view.PlayerImageView
android:id="@+id/playerImage"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:background="@android:color/transparent"
app:layout_constraintStart_toStartOf="@id/guidelineStart"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:gravity="center_vertical"
android:minHeight="48dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/nextArrow"
app:layout_constraintStart_toEndOf="@+id/playerImage"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/playerName"
style="@style/PokerAnalyticsTheme.TextView.RowTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
tools:text="John Smith" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/playerSummary"
style="@style/PokerAnalyticsTheme.TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="3"
android:textColor="@color/kaki_lighter"
android:visibility="visible"
tools:text="Summary" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/nextArrow"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_arrow_right"
android:tint="@color/gray_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/guidelineEnd"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="8dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/gray_dark"
android:foreground="?selectableItemBackground">
<net.pokeranalytics.android.ui.view.PlayerImageView
android:id="@+id/playerImageView"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_margin="16dp"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,47 @@
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
android:background="?selectableItemBackground">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
style="@style/PokerAnalyticsTheme.TextView.RowTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="@+id/guidelineEnd"
app:layout_constraintStart_toStartOf="@+id/guidelineStart"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/value"
style="@style/PokerAnalyticsTheme.TextView.RowValueMultilines"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/guidelineEnd"
app:layout_constraintStart_toStartOf="@+id/guidelineStart"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Value" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="16dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="@android:color/transparent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/playerInitial"
style="@style/PokerAnalyticsTheme.TextView.Player"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="AH" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/playerImage"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/playerStroke"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1"
android:background="@drawable/circle_stroke_kaki"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/playerImageSelection"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintWidth_percent="0.7"
app:layout_constraintHeight_percent="0.7"
android:background="?selectableItemBackgroundBorderless"
android:elevation="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_outline_search"
app:showAsAction="always|collapseActionView"
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:title="@android:string/search_go"
android:visible="false"/>
</menu>

@ -11,6 +11,7 @@
<color name="gray_light">#8AFFFFFF</color>
<color name="gray_darker">#141414</color>
<color name="gray_dark">#1B1F1B</color>
<color name="gray">#808080</color>
<color name="white_dark">#e0e0e0</color>
<color name="dark_clear">#40000000</color>
@ -43,4 +44,14 @@
<color name="chart_bar">#3c8c50</color>
<color name="chart_selected_bar">#71fb94</color>
<color name="player_color_1">#00000000</color>
<color name="player_color_2">#ffdf3e</color>
<color name="player_color_3">#65FF82</color>
<color name="player_color_4">#8efde7</color>
<color name="player_color_5">#3aaeff</color>
<color name="player_color_6">#6150ff</color>
<color name="player_color_7">#ff56ff</color>
<color name="player_color_8">#ff573d</color>
<color name="player_color_9">#ff971e</color>
</resources>

@ -5,4 +5,8 @@
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">12sp</dimen>
<dimen name="design_bottom_navigation_text_size" tools:override="true">11sp</dimen>
<dimen name="color_picker_circle_size">48dp</dimen>
<dimen name="color_picker_circle_margin">8dp</dimen>
</resources>

@ -108,6 +108,11 @@
<item name="android:textSize">18sp</item>
</style>
<style name="PokerAnalyticsTheme.TextView.Header.Subtitle">
<item name="android:fontFamily">@font/roboto_medium</item>
<item name="android:textSize">16sp</item>
</style>
<style name="PokerAnalyticsTheme.TextView.RowTitle">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@color/white</item>
@ -123,6 +128,13 @@
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
</style>
<style name="PokerAnalyticsTheme.TextView.RowContent">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/white</item>
<item name="android:fontFamily">@font/roboto</item>
<item name="android:ellipsize">end</item>
</style>
<style name="PokerAnalyticsTheme.TextView.RowInfo">
<item name="android:textSize">12sp</item>
@ -147,6 +159,13 @@
<item name="android:ellipsize">end</item>
</style>
<style name="PokerAnalyticsTheme.TextView.RowValueMultilines">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@color/kaki_lighter</item>
<item name="android:fontFamily">@font/roboto</item>
<item name="android:ellipsize">end</item>
</style>
<!-- Graph legend -->
@ -266,6 +285,16 @@
<item name="android:textSize">16sp</item>
</style>
<!-- Player Row -->
<style name="PokerAnalyticsTheme.TextView.Player">
<item name="android:textColor">@color/kaki</item>
<item name="android:maxLines">1</item>
<item name="android:ellipsize">end</item>
<item name="android:textAllCaps">true</item>
<item name="android:fontFamily">@font/roboto</item>
<item name="android:textSize">32sp</item>
<item name="android:gravity">center</item>
</style>
<!-- Alert Dialog -->

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