Adds Player feature

od
Laurent 6 years ago
parent b061038186
commit 6f539a277f
  1. 12
      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. 182
      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. 34
      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. 5
      app/src/main/res/xml/provider_paths.xml

@ -23,8 +23,8 @@
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.HomeActivity" android:name="net.pokeranalytics.android.ui.activity.HomeActivity"
android:launchMode="singleTop"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait"> android:screenOrientation="portrait">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -37,8 +37,8 @@
<activity <activity
android:name="net.pokeranalytics.android.ui.activity.ImportActivity" android:name="net.pokeranalytics.android.ui.activity.ImportActivity"
android:screenOrientation="portrait" android:launchMode="singleTop"
android:launchMode="singleTop"> android:screenOrientation="portrait">
<intent-filter tools:ignore="AppLinkUrlError"> <intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -160,6 +160,12 @@
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait" /> 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 <meta-data
android:name="preloaded_fonts" android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" /> android:resource="@array/preloaded_fonts" />

@ -33,7 +33,7 @@ class PokerAnalyticsApplication : Application() {
Realm.init(this) Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder() val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME) .name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(7) .schemaVersion(8)
.migration(PokerAnalyticsMigration()) .migration(PokerAnalyticsMigration())
.initialData(Seed(this)) .initialData(Seed(this))
.build() .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 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 UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow")
data class MissingFieldNameForQueryCondition(val name: String) : PokerAnalyticsException(message = "Missing fieldname for QueryCondition ${name}") 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, TRANSACTION_TYPE,
FILTER, FILTER,
CUSTOM_FIELD, CUSTOM_FIELD,
REPORT_SETUP; REPORT_SETUP,
PLAYER;
var subType:Int? = null var subType:Int? = null
@ -38,6 +39,7 @@ enum class LiveData : Localizable {
FILTER -> Filter::class.java FILTER -> Filter::class.java
CUSTOM_FIELD -> CustomField::class.java CUSTOM_FIELD -> CustomField::class.java
REPORT_SETUP -> ReportSetup::class.java REPORT_SETUP -> ReportSetup::class.java
PLAYER -> Player::class.java
} }
} }
@ -78,6 +80,7 @@ enum class LiveData : Localizable {
FILTER -> R.string.filter FILTER -> R.string.filter
CUSTOM_FIELD -> R.string.custom_field CUSTOM_FIELD -> R.string.custom_field
REPORT_SETUP -> R.string.custom REPORT_SETUP -> R.string.custom
PLAYER -> R.string.player
} }
} }
@ -94,6 +97,7 @@ enum class LiveData : Localizable {
FILTER -> R.string.filters FILTER -> R.string.filters
CUSTOM_FIELD -> R.string.custom_fields CUSTOM_FIELD -> R.string.custom_fields
REPORT_SETUP -> R.string.custom REPORT_SETUP -> R.string.custom
PLAYER -> R.string.players
} }
} }
@ -110,6 +114,7 @@ enum class LiveData : Localizable {
FILTER -> R.string.new_filter FILTER -> R.string.new_filter
CUSTOM_FIELD -> R.string.new_custom_field CUSTOM_FIELD -> R.string.new_custom_field
REPORT_SETUP -> R.string.new_report 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.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import net.pokeranalytics.android.model.realm.Comment
import timber.log.Timber import timber.log.Timber
import java.util.*
class PokerAnalyticsMigration : RealmMigration { class PokerAnalyticsMigration : RealmMigration {
@ -151,6 +153,23 @@ class PokerAnalyticsMigration : RealmMigration {
schema.get("TransactionType")?.addField("useCount", Int::class.java) schema.get("TransactionType")?.addField("useCount", Int::class.java)
currentVersion++ 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 { 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 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.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey 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 java.util.*
import kotlin.collections.ArrayList
open class Player : RealmObject() { open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresentableDataSource, RowRepresentable {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() override var id = UUID.randomUUID().toString()
// The name of the player // 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.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.crashlytics.android.Crashlytics
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData 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 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) { enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"), DATA_TYPE("DATA_TYPE"),
@ -44,6 +45,8 @@ class EditableDataActivity : PokerAnalyticsActivity() {
} }
private var currentFragment: EditableDataFragment? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_editable_data) setContentView(R.layout.activity_editable_data)
@ -58,20 +61,35 @@ class EditableDataActivity : PokerAnalyticsActivity() {
val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0) val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0)
val primaryKey = intent.getStringExtra(IntentKey.PRIMARY_KEY.keyName) val primaryKey = intent.getStringExtra(IntentKey.PRIMARY_KEY.keyName)
val fragmentManager = supportFragmentManager currentFragment = when (dataType) {
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment: EditableDataFragment = when (dataType) {
LiveData.BANKROLL.ordinal -> BankrollDataFragment() LiveData.BANKROLL.ordinal -> BankrollDataFragment()
LiveData.LOCATION.ordinal -> LocationDataFragment() LiveData.LOCATION.ordinal -> LocationDataFragment()
LiveData.TRANSACTION.ordinal -> TransactionDataFragment() LiveData.TRANSACTION.ordinal -> TransactionDataFragment()
LiveData.CUSTOM_FIELD.ordinal -> CustomFieldDataFragment() LiveData.CUSTOM_FIELD.ordinal -> CustomFieldDataFragment()
LiveData.TRANSACTION_TYPE.ordinal -> TransactionTypeDataFragment() LiveData.TRANSACTION_TYPE.ordinal -> TransactionTypeDataFragment()
LiveData.PLAYER.ordinal -> PlayerDataFragment()
else -> EditableDataFragment() else -> EditableDataFragment()
} }
Crashlytics.log("creating EditableDataFragment with dataType = $dataType")
fragment.setData(dataType, primaryKey) currentFragment?.let { fragment ->
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(R.id.container, fragment) fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit() 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.net.Uri
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.appcompat.widget.SearchView
import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
@ -161,3 +163,11 @@ fun View.addCircleRipple() = with(TypedValue()) {
context.theme.resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, this, true) context.theme.resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, this, true)
setBackgroundResource(resourceId) 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.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.*
import android.view.View import androidx.appcompat.widget.SearchView
import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager 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.LiveRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate 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.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.extensions.find
import net.pokeranalytics.android.util.extensions.sorted import net.pokeranalytics.android.util.extensions.sorted
@ -40,6 +41,15 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
private lateinit var dataType: LiveData private lateinit var dataType: LiveData
private lateinit var items: RealmResults<out Deletable> 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 * Set fragment data
@ -51,6 +61,11 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
setToolbarTitle(this.dataType.pluralLocalizedTitle(requireContext())) setToolbarTitle(this.dataType.pluralLocalizedTitle(requireContext()))
this.items = this.retrieveItems(getRealm()) 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> { open fun retrieveItems(realm: Realm): RealmResults<out Deletable> {
@ -114,6 +129,31 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
this.recyclerView?.adapter?.notifyDataSetChanged() 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? { override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return this.items[position] as RowRepresentable return this.items[position] as RowRepresentable
} }
@ -150,4 +190,12 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
this.addButton.isVisible = showAddButton 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 kotlinx.android.synthetic.main.fragment_settings.*
import net.pokeranalytics.android.BuildConfig import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Currency import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.activity.* import net.pokeranalytics.android.ui.activity.*
@ -115,6 +116,7 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
when (row) { when (row) {
SettingRow.BANKROLL_REPORT -> BankrollActivity.newInstance(requireContext()) SettingRow.BANKROLL_REPORT -> BankrollActivity.newInstance(requireContext())
SettingRow.TOP_10 -> Top10Activity.newInstance(requireContext()) SettingRow.TOP_10 -> Top10Activity.newInstance(requireContext())
SettingRow.PLAYERS -> DataListActivity.newInstance(requireContext(), LiveData.PLAYER.ordinal)
SettingRow.SUBSCRIPTION -> { SettingRow.SUBSCRIPTION -> {
if (!AppGuard.isProUser) { if (!AppGuard.isProUser) {
BillingActivity.newInstanceForResult(this, false) BillingActivity.newInstanceForResult(this, false)

@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import java.io.File
open class PokerAnalyticsFragment : Fragment() { open class PokerAnalyticsFragment : Fragment() {
@ -99,4 +100,14 @@ open class PokerAnalyticsFragment : Fragment() {
parentActivity?.supportActionBar?.setDisplayHomeAsUpEnabled(enabled) 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>? { fun editingDescriptors(map: Map<String, Any?>): ArrayList<RowRepresentableEditDescriptor>? {
return null return null
} }
// val valueCanBeClearedWhenEditing: Boolean
// get() { return true }
} }
interface DefaultEditDataSource : EditDataSource, Localizable { 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.TableSize
import net.pokeranalytics.android.model.extensions.getFormattedGameType import net.pokeranalytics.android.model.extensions.getFormattedGameType
import net.pokeranalytics.android.model.realm.CustomField 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.Session
import net.pokeranalytics.android.model.realm.Transaction import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter 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_VALUE(R.layout.row_header_title_value),
HEADER_TITLE_AMOUNT(R.layout.row_header_title_amount), HEADER_TITLE_AMOUNT(R.layout.row_header_title_amount),
HEADER_TITLE_AMOUNT_BIG(R.layout.row_header_title_amount_big), HEADER_TITLE_AMOUNT_BIG(R.layout.row_header_title_amount_big),
HEADER_SUBTITLE(R.layout.row_header_subtitle),
LOCATION_TITLE(R.layout.row_title), LOCATION_TITLE(R.layout.row_title),
INFO(R.layout.row_info), INFO(R.layout.row_info),
@ -76,6 +78,8 @@ enum class RowViewType(private var layoutRes: Int) {
TITLE_CHECK(R.layout.row_title_check), TITLE_CHECK(R.layout.row_title_check),
TITLE_VALUE_CHECK(R.layout.row_title_value_check), TITLE_VALUE_CHECK(R.layout.row_title_value_check),
LOADER(R.layout.row_loader), LOADER(R.layout.row_loader),
CONTENT(R.layout.row_content),
TITLE_SUBTITLE(R.layout.row_title_subtitle),
// Custom row // Custom row
ROW_SESSION(R.layout.row_feed_session), ROW_SESSION(R.layout.row_feed_session),
@ -88,6 +92,8 @@ enum class RowViewType(private var layoutRes: Int) {
GRAPH(R.layout.row_graph), GRAPH(R.layout.row_graph),
LEGEND_DEFAULT(R.layout.row_legend_default), LEGEND_DEFAULT(R.layout.row_legend_default),
LIST(R.layout.row_list), LIST(R.layout.row_list),
ROW_PLAYER(R.layout.row_player),
ROW_PLAYER_IMAGE(R.layout.row_player_image),
// Separator // Separator
SEPARATOR(R.layout.row_separator); SEPARATOR(R.layout.row_separator);
@ -104,7 +110,7 @@ enum class RowViewType(private var layoutRes: Int) {
// Row View Holder // Row View Holder
HEADER_TITLE, HEADER_TITLE_VALUE, HEADER_TITLE_AMOUNT, HEADER_TITLE_AMOUNT_BIG, LOCATION_TITLE, 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, 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) DATA, BOTTOM_SHEET_DATA, LOADER -> RowViewHolder(layout)
// Row Session // Row Session
@ -116,6 +122,11 @@ enum class RowViewType(private var layoutRes: Int) {
// Row Transaction // Row Transaction
ROW_TOP_10 -> RowTop10ViewHolder(layout) ROW_TOP_10 -> RowTop10ViewHolder(layout)
ROW_PLAYER -> RowPlayerViewHolder(layout)
ROW_PLAYER_IMAGE -> RowPlayerImageViewHolder(layout)
// Row Button // Row Button
ROW_BUTTON -> RowButtonViewHolder(layout) 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 * 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 // More
BANKROLL_REPORT, BANKROLL_REPORT,
TOP_10, TOP_10,
PLAYERS,
// About // About
SUBSCRIPTION, SUBSCRIPTION,
@ -50,7 +51,7 @@ enum class SettingRow : RowRepresentable {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.reports)) 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.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.information))
// rows.addAll(arrayListOf(VERSION, RATE_APP, CONTACT_US, BUG_REPORT)) // rows.addAll(arrayListOf(VERSION, RATE_APP, CONTACT_US, BUG_REPORT))
@ -86,6 +87,7 @@ enum class SettingRow : RowRepresentable {
return when (this) { return when (this) {
BANKROLL_REPORT -> R.string.bankroll BANKROLL_REPORT -> R.string.bankroll
TOP_10 -> R.string.top_10 TOP_10 -> R.string.top_10
PLAYERS -> R.string.players
SUBSCRIPTION -> R.string.subscription SUBSCRIPTION -> R.string.subscription
VERSION -> R.string.version VERSION -> R.string.version
RATE_APP -> R.string.releasenote_rating RATE_APP -> R.string.releasenote_rating
@ -106,7 +108,7 @@ enum class SettingRow : RowRepresentable {
override val viewType: Int override val viewType: Int
get() { get() {
return when (this) { 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 VERSION, SUBSCRIPTION -> RowViewType.TITLE_VALUE.ordinal
LANGUAGE, CURRENCY -> RowViewType.TITLE_VALUE_ARROW.ordinal LANGUAGE, CURRENCY -> RowViewType.TITLE_VALUE_ARROW.ordinal
FOLLOW_US -> RowViewType.ROW_FOLLOW_US.ordinal FOLLOW_US -> RowViewType.ROW_FOLLOW_US.ordinal
@ -119,6 +121,7 @@ enum class SettingRow : RowRepresentable {
return when(this) { return when(this) {
BANKROLL_REPORT -> R.drawable.ic_outline_lock BANKROLL_REPORT -> R.drawable.ic_outline_lock
TOP_10 -> R.drawable.ic_outline_star TOP_10 -> R.drawable.ic_outline_star
PLAYERS -> R.drawable.ic_outline_people
else -> null 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 package net.pokeranalytics.android.util.extensions
import io.realm.Realm import io.realm.*
import io.realm.RealmModel
import io.realm.RealmResults
import io.realm.Sort
import net.pokeranalytics.android.model.interfaces.CountableUsage import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.TournamentFeature
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.model.realm.TransactionType
fun <T : RealmModel>Realm.count(clazz: Class<T>) : Long { fun <T : RealmModel>Realm.count(clazz: Class<T>) : Long {
return this.where(clazz).count() return this.where(clazz).count()
@ -108,3 +102,21 @@ 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_light">#8AFFFFFF</color>
<color name="gray_darker">#141414</color> <color name="gray_darker">#141414</color>
<color name="gray_dark">#1B1F1B</color> <color name="gray_dark">#1B1F1B</color>
<color name="gray">#808080</color>
<color name="white_dark">#e0e0e0</color> <color name="white_dark">#e0e0e0</color>
<color name="dark_clear">#40000000</color> <color name="dark_clear">#40000000</color>
@ -43,4 +44,14 @@
<color name="chart_bar">#3c8c50</color> <color name="chart_bar">#3c8c50</color>
<color name="chart_selected_bar">#71fb94</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> </resources>

@ -5,4 +5,8 @@
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">12sp</dimen> <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="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> </resources>

@ -108,6 +108,11 @@
<item name="android:textSize">18sp</item> <item name="android:textSize">18sp</item>
</style> </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"> <style name="PokerAnalyticsTheme.TextView.RowTitle">
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
<item name="android:textColor">@color/white</item> <item name="android:textColor">@color/white</item>
@ -124,6 +129,13 @@
<item name="android:ellipsize">end</item> <item name="android:ellipsize">end</item>
</style> </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"> <style name="PokerAnalyticsTheme.TextView.RowInfo">
<item name="android:textSize">12sp</item> <item name="android:textSize">12sp</item>
<item name="android:textColor">@color/kaki_light</item> <item name="android:textColor">@color/kaki_light</item>
@ -147,6 +159,13 @@
<item name="android:ellipsize">end</item> <item name="android:ellipsize">end</item>
</style> </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 --> <!-- Graph legend -->
@ -266,6 +285,16 @@
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
</style> </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 --> <!-- Alert Dialog -->

@ -3,4 +3,9 @@
<files-path <files-path
name="files" name="files"
path="." /> path="." />
<external-path
name="external_files"
path="." />
</paths> </paths>
Loading…
Cancel
Save