diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7efe637b..11763e49 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,179 +1,185 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ package="net.pokeranalytics.android">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
index e5f41fd5..f3601912 100644
--- a/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
+++ b/app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
@@ -33,7 +33,7 @@ class PokerAnalyticsApplication : Application() {
Realm.init(this)
val realmConfiguration = RealmConfiguration.Builder()
.name(Realm.DEFAULT_REALM_NAME)
- .schemaVersion(7)
+ .schemaVersion(8)
.migration(PokerAnalyticsMigration())
.initialData(Seed(this))
.build()
diff --git a/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt b/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
index b8358605..b12a9342 100644
--- a/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
+++ b/app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
@@ -25,4 +25,5 @@ sealed class PokerAnalyticsException(message: String) : Exception(message) {
data class QueryValueMapMissingKeys(val missingKeys: List) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys")
data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow")
data class MissingFieldNameForQueryCondition(val name: String) : PokerAnalyticsException(message = "Missing fieldname for QueryCondition ${name}")
+ object InputFragmentException : PokerAnalyticsException(message = "RowEditableDelegate must be a Fragment")
}
diff --git a/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt b/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
index ba7658ff..322ea5c5 100644
--- a/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
+++ b/app/src/main/java/net/pokeranalytics/android/model/LiveData.kt
@@ -21,7 +21,8 @@ enum class LiveData : Localizable {
TRANSACTION_TYPE,
FILTER,
CUSTOM_FIELD,
- REPORT_SETUP;
+ REPORT_SETUP,
+ PLAYER;
var subType:Int? = null
@@ -38,6 +39,7 @@ enum class LiveData : Localizable {
FILTER -> Filter::class.java
CUSTOM_FIELD -> CustomField::class.java
REPORT_SETUP -> ReportSetup::class.java
+ PLAYER -> Player::class.java
}
}
@@ -78,6 +80,7 @@ enum class LiveData : Localizable {
FILTER -> R.string.filter
CUSTOM_FIELD -> R.string.custom_field
REPORT_SETUP -> R.string.custom
+ PLAYER -> R.string.player
}
}
@@ -94,6 +97,7 @@ enum class LiveData : Localizable {
FILTER -> R.string.filters
CUSTOM_FIELD -> R.string.custom_fields
REPORT_SETUP -> R.string.custom
+ PLAYER -> R.string.players
}
}
@@ -110,6 +114,7 @@ enum class LiveData : Localizable {
FILTER -> R.string.new_filter
CUSTOM_FIELD -> R.string.new_custom_field
REPORT_SETUP -> R.string.new_report
+ PLAYER -> R.string.new_friend
}
}
diff --git a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
index 687fff28..3ca6d48b 100644
--- a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
+++ b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
@@ -2,7 +2,9 @@ package net.pokeranalytics.android.model.migrations
import io.realm.DynamicRealm
import io.realm.RealmMigration
+import net.pokeranalytics.android.model.realm.Comment
import timber.log.Timber
+import java.util.*
class PokerAnalyticsMigration : RealmMigration {
@@ -151,6 +153,23 @@ class PokerAnalyticsMigration : RealmMigration {
schema.get("TransactionType")?.addField("useCount", Int::class.java)
currentVersion++
}
+
+ // Migrate to version 8
+ if (currentVersion == 7) {
+ schema.create("Comment")?.let {
+ it.addField("id", String::class.java).setRequired("id", true)
+ it.addField("content", String::class.java)
+ it.addField("date", Date::class.java)
+ }
+
+ schema.get("Player")?.let {
+ it.addField("summary", String::class.java).setRequired("summary", true)
+ it.addField("color", Int::class.java).setNullable("color", true)
+ it.addField("picture", String::class.java)
+ it.addRealmListField("comments", Comment::class.java)
+ }
+ currentVersion++
+ }
}
override fun equals(other: Any?): Boolean {
diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Comment.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Comment.kt
new file mode 100644
index 00000000..0e94c26e
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Comment.kt
@@ -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 = 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
+ }
+}
diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt
index 7323e151..267e2cb0 100644
--- a/app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt
+++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Player.kt
@@ -1,15 +1,191 @@
package net.pokeranalytics.android.model.realm
+import android.content.Context
+import io.realm.Realm
+import io.realm.RealmList
import io.realm.RealmObject
+import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
+import io.realm.kotlin.where
+import net.pokeranalytics.android.R
+import net.pokeranalytics.android.model.interfaces.*
+import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
+import net.pokeranalytics.android.ui.view.RowRepresentable
+import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
+import net.pokeranalytics.android.ui.view.RowViewType
+import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
+import net.pokeranalytics.android.ui.view.rowrepresentable.PlayerRow
+import net.pokeranalytics.android.ui.view.rowrepresentable.SeparatorRow
+import net.pokeranalytics.android.util.NULL_TEXT
+import net.pokeranalytics.android.util.extensions.isSameDay
+import net.pokeranalytics.android.util.extensions.mediumDate
import java.util.*
+import kotlin.collections.ArrayList
-open class Player : RealmObject() {
+open class Player : RealmObject(), NameManageable, Deletable, StaticRowRepresentableDataSource, RowRepresentable {
@PrimaryKey
- var id = UUID.randomUUID().toString()
+ override var id = UUID.randomUUID().toString()
// The name of the player
- var name: String = ""
+ override var name: String = ""
- }
\ No newline at end of file
+ // New fields
+ var summary: String = ""
+ var color: Int? = null
+ var picture: String? = null
+ var comments: RealmList = RealmList()
+
+ @Ignore
+ override val realmObjectClass: Class = Player::class.java
+
+ @Ignore
+ override val viewType: Int = RowViewType.ROW_PLAYER.ordinal
+
+ @Ignore
+ private var rowRepresentation: List = mutableListOf()
+
+ @Ignore
+ private var commentsToDelete: ArrayList = 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? {
+ 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 {
+ val rows = ArrayList()
+ 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()
+ 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().equalTo("id", it.id).findFirst()?.deleteFromRealm()
+ }
+ }
+ realm.close()
+ this.commentsToDelete.clear()
+ }
+
+ override fun editDescriptors(row: RowRepresentable): ArrayList? {
+
+ when (row) {
+ PlayerRow.NAME -> return row.editingDescriptors(mapOf("defaultValue" to this.name))
+ PlayerRow.SUMMARY -> return row.editingDescriptors(mapOf("defaultValue" to this.summary))
+ }
+
+ return null
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/ColorPickerActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/ColorPickerActivity.kt
new file mode 100644
index 00000000..ce8faf29
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/ColorPickerActivity.kt
@@ -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()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt
index 1ea76543..c540be6b 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/EditableDataActivity.kt
@@ -4,13 +4,14 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
-import com.crashlytics.android.Crashlytics
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
-import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
+import net.pokeranalytics.android.ui.activity.components.MediaActivity
import net.pokeranalytics.android.ui.fragment.data.*
+import java.io.File
+import java.util.*
-class EditableDataActivity : PokerAnalyticsActivity() {
+class EditableDataActivity : MediaActivity() {
enum class IntentKey(val keyName: String) {
DATA_TYPE("DATA_TYPE"),
@@ -44,6 +45,8 @@ class EditableDataActivity : PokerAnalyticsActivity() {
}
+ private var currentFragment: EditableDataFragment? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_editable_data)
@@ -58,20 +61,35 @@ class EditableDataActivity : PokerAnalyticsActivity() {
val dataType = intent.getIntExtra(IntentKey.DATA_TYPE.keyName, 0)
val primaryKey = intent.getStringExtra(IntentKey.PRIMARY_KEY.keyName)
- val fragmentManager = supportFragmentManager
- val fragmentTransaction = fragmentManager.beginTransaction()
- val fragment: EditableDataFragment = when (dataType) {
+ currentFragment = when (dataType) {
LiveData.BANKROLL.ordinal -> BankrollDataFragment()
LiveData.LOCATION.ordinal -> LocationDataFragment()
LiveData.TRANSACTION.ordinal -> TransactionDataFragment()
LiveData.CUSTOM_FIELD.ordinal -> CustomFieldDataFragment()
LiveData.TRANSACTION_TYPE.ordinal -> TransactionTypeDataFragment()
+ LiveData.PLAYER.ordinal -> PlayerDataFragment()
else -> EditableDataFragment()
}
- Crashlytics.log("creating EditableDataFragment with dataType = $dataType")
- fragment.setData(dataType, primaryKey)
- fragmentTransaction.add(R.id.container, fragment)
- fragmentTransaction.commit()
+
+ currentFragment?.let { fragment ->
+ val fragmentManager = supportFragmentManager
+ val fragmentTransaction = fragmentManager.beginTransaction()
+ fragmentTransaction.add(R.id.container, fragment)
+ fragmentTransaction.commit()
+
+ fragment.setData(dataType, primaryKey)
+ }
+
+ }
+
+ override fun isLoadingNewPictures() {
+ super.isLoadingNewPictures()
+ currentFragment?.isLoadingNewPhotos()
+ }
+
+ override fun getPictures(files: ArrayList) {
+ super.getPictures(files)
+ currentFragment?.getPhotos(files)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt
new file mode 100644
index 00000000..3a22539d
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/MediaActivity.kt
@@ -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()
+
+ 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, 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()
+ 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(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) {}
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt b/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
index 525741b6..33af950f 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/extensions/UIExtensions.kt
@@ -7,9 +7,11 @@ import android.content.res.Resources
import android.net.Uri
import android.util.TypedValue
import android.view.View
+import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatTextView
+import androidx.appcompat.widget.SearchView
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
@@ -160,4 +162,12 @@ fun View.showWithAnimation() {
fun View.addCircleRipple() = with(TypedValue()) {
context.theme.resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, this, true)
setBackgroundResource(resourceId)
+}
+
+fun SearchView.removeMargins() {
+ val searchEditFrame = findViewById(R.id.search_edit_frame)
+ val layoutParams = searchEditFrame?.layoutParams as LinearLayout.LayoutParams?
+ layoutParams?.leftMargin = 0
+ layoutParams?.rightMargin = 0
+ searchEditFrame?.layoutParams = layoutParams
}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
index 13c4a9fc..722b9dac 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
@@ -3,9 +3,8 @@ package net.pokeranalytics.android.ui.fragment
import android.app.Activity
import android.content.Intent
import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
+import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
@@ -23,10 +22,12 @@ import net.pokeranalytics.android.ui.activity.FiltersActivity
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
+import net.pokeranalytics.android.ui.extensions.removeMargins
import net.pokeranalytics.android.ui.fragment.components.DeletableItemFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
+import net.pokeranalytics.android.util.extensions.find
import net.pokeranalytics.android.util.extensions.sorted
@@ -40,6 +41,15 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
private lateinit var dataType: LiveData
private lateinit var items: RealmResults
+ private var dataListMenu: Menu? = null
+ private var searchView: SearchView? = null
+
+ var isSearchable: Boolean = false
+ set(value) {
+ field = value
+ val searchMenuItem = dataListMenu?.findItem(R.id.action_search)
+ searchMenuItem?.isVisible = value
+ }
/**
* Set fragment data
@@ -51,6 +61,11 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
setToolbarTitle(this.dataType.pluralLocalizedTitle(requireContext()))
this.items = this.retrieveItems(getRealm())
+
+ this.isSearchable = when (this.dataType) {
+ LiveData.PLAYER, LiveData.LOCATION -> true
+ else -> false
+ }
}
open fun retrieveItems(realm: Realm): RealmResults {
@@ -114,6 +129,31 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
this.recyclerView?.adapter?.notifyDataSetChanged()
}
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+
+ menu.clear()
+ inflater.inflate(R.menu.toolbar_data_list, menu)
+ this.dataListMenu = menu
+
+ val searchMenuItem = menu.findItem(R.id.action_search)
+ searchMenuItem.isVisible = isSearchable
+
+ searchView = searchMenuItem.actionView as SearchView?
+ searchView?.removeMargins()
+ searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ return false
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ filterItemsWithSearch(newText)
+ return false
+ }
+ })
+
+ super.onCreateOptionsMenu(menu, inflater)
+ }
+
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return this.items[position] as RowRepresentable
}
@@ -150,4 +190,12 @@ open class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataS
this.addButton.isVisible = showAddButton
}
+ /**
+ * Filter the items list with the given search content
+ */
+ private fun filterItemsWithSearch(searchContent: String?) {
+ this.items = getRealm().find(this.identifiableClass, searchContent)
+ dataListAdapter.notifyDataSetChanged()
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
index 1f679742..7f5d3c08 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/SettingsFragment.kt
@@ -13,6 +13,7 @@ import io.realm.Realm
import kotlinx.android.synthetic.main.fragment_settings.*
import net.pokeranalytics.android.BuildConfig
import net.pokeranalytics.android.R
+import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Currency
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.activity.*
@@ -115,6 +116,7 @@ class SettingsFragment : PokerAnalyticsFragment(), RowRepresentableDelegate, Sta
when (row) {
SettingRow.BANKROLL_REPORT -> BankrollActivity.newInstance(requireContext())
SettingRow.TOP_10 -> Top10Activity.newInstance(requireContext())
+ SettingRow.PLAYERS -> DataListActivity.newInstance(requireContext(), LiveData.PLAYER.ordinal)
SettingRow.SUBSCRIPTION -> {
if (!AppGuard.isProUser) {
BillingActivity.newInstanceForResult(this, false)
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt
index 936cb56f..fa1a95cc 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/components/PokerAnalyticsFragment.kt
@@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment
import com.crashlytics.android.Crashlytics
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
+import java.io.File
open class PokerAnalyticsFragment : Fragment() {
@@ -99,4 +100,14 @@ open class PokerAnalyticsFragment : Fragment() {
parentActivity?.supportActionBar?.setDisplayHomeAsUpEnabled(enabled)
}
+ /**
+ * Called when the user is adding new photos
+ */
+ open fun isLoadingNewPhotos() {}
+
+ /*
+ * Called when the user has selected photos
+ */
+ open fun getPhotos(files: ArrayList) {}
+
}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/PlayerDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/PlayerDataFragment.kt
new file mode 100644
index 00000000..fece704e
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/data/PlayerDataFragment.kt
@@ -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) {
+ 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? {
+ 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()
+ 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? {
+ 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
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/PlayerImageView.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/PlayerImageView.kt
new file mode 100644
index 00000000..7d732ca2
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/ui/view/PlayerImageView.kt
@@ -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
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt
index 50c9551e..2239258c 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowRepresentable.kt
@@ -19,6 +19,9 @@ interface EditDataSource {
fun editingDescriptors(map: Map): ArrayList? {
return null
}
+
+// val valueCanBeClearedWhenEditing: Boolean
+// get() { return true }
}
interface DefaultEditDataSource : EditDataSource, Localizable {
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
index 4525306d..08093edb 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
@@ -26,6 +26,7 @@ import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.extensions.getFormattedGameType
import net.pokeranalytics.android.model.realm.CustomField
+import net.pokeranalytics.android.model.realm.Player
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
@@ -59,6 +60,7 @@ enum class RowViewType(private var layoutRes: Int) {
HEADER_TITLE_VALUE(R.layout.row_header_title_value),
HEADER_TITLE_AMOUNT(R.layout.row_header_title_amount),
HEADER_TITLE_AMOUNT_BIG(R.layout.row_header_title_amount_big),
+ HEADER_SUBTITLE(R.layout.row_header_subtitle),
LOCATION_TITLE(R.layout.row_title),
INFO(R.layout.row_info),
@@ -76,6 +78,8 @@ enum class RowViewType(private var layoutRes: Int) {
TITLE_CHECK(R.layout.row_title_check),
TITLE_VALUE_CHECK(R.layout.row_title_value_check),
LOADER(R.layout.row_loader),
+ CONTENT(R.layout.row_content),
+ TITLE_SUBTITLE(R.layout.row_title_subtitle),
// Custom row
ROW_SESSION(R.layout.row_feed_session),
@@ -88,6 +92,8 @@ enum class RowViewType(private var layoutRes: Int) {
GRAPH(R.layout.row_graph),
LEGEND_DEFAULT(R.layout.row_legend_default),
LIST(R.layout.row_list),
+ ROW_PLAYER(R.layout.row_player),
+ ROW_PLAYER_IMAGE(R.layout.row_player_image),
// Separator
SEPARATOR(R.layout.row_separator);
@@ -104,7 +110,7 @@ enum class RowViewType(private var layoutRes: Int) {
// Row View Holder
HEADER_TITLE, HEADER_TITLE_VALUE, HEADER_TITLE_AMOUNT, HEADER_TITLE_AMOUNT_BIG, LOCATION_TITLE,
INFO, TITLE, TITLE_ARROW, TITLE_ICON_ARROW, TITLE_VALUE, TITLE_VALUE_ARROW, TITLE_VALUE_ACTION, TITLE_GRID,
- TITLE_SWITCH, TITLE_CHECK, TITLE_VALUE_CHECK,
+ TITLE_SWITCH, TITLE_CHECK, TITLE_VALUE_CHECK, CONTENT, TITLE_SUBTITLE, HEADER_SUBTITLE,
DATA, BOTTOM_SHEET_DATA, LOADER -> RowViewHolder(layout)
// Row Session
@@ -116,6 +122,11 @@ enum class RowViewType(private var layoutRes: Int) {
// Row Transaction
ROW_TOP_10 -> RowTop10ViewHolder(layout)
+
+ ROW_PLAYER -> RowPlayerViewHolder(layout)
+
+ ROW_PLAYER_IMAGE -> RowPlayerImageViewHolder(layout)
+
// Row Button
ROW_BUTTON -> RowButtonViewHolder(layout)
@@ -623,6 +634,47 @@ enum class RowViewType(private var layoutRes: Int) {
}
+ /**
+ * Display a player image view
+ */
+ inner class RowPlayerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
+ override fun bind(position: Int, row: RowRepresentable, adapter: RowRepresentableAdapter) {
+
+ if (row is Player) {
+ itemView.findViewById(R.id.playerImage)?.let { playerImageView ->
+ playerImageView.setPlayer(row, PlayerImageView.Size.SMALL, false)
+ }
+ itemView.findViewById(R.id.playerName)?.let { textView ->
+ textView.text = row.name
+ }
+ itemView.findViewById(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(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
*/
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/PlayerRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/PlayerRow.kt
new file mode 100644
index 00000000..44d37f87
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/PlayerRow.kt
@@ -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): ArrayList? {
+ return null
+ }
+
+
+ // override fun editingDescriptors(map: Map): ArrayList? {
+//
+//
+// }
+
+// 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)
+// }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt
index 5ed0d293..ae72f8f2 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SettingRow.kt
@@ -11,6 +11,7 @@ enum class SettingRow : RowRepresentable {
// More
BANKROLL_REPORT,
TOP_10,
+ PLAYERS,
// About
SUBSCRIPTION,
@@ -50,7 +51,7 @@ enum class SettingRow : RowRepresentable {
val rows = ArrayList()
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.reports))
- rows.addAll(arrayListOf(BANKROLL_REPORT, TOP_10))
+ rows.addAll(arrayListOf(BANKROLL_REPORT, TOP_10, PLAYERS))
rows.add(CustomizableRowRepresentable(customViewType = RowViewType.HEADER_TITLE, resId = R.string.information))
// rows.addAll(arrayListOf(VERSION, RATE_APP, CONTACT_US, BUG_REPORT))
@@ -86,6 +87,7 @@ enum class SettingRow : RowRepresentable {
return when (this) {
BANKROLL_REPORT -> R.string.bankroll
TOP_10 -> R.string.top_10
+ PLAYERS -> R.string.players
SUBSCRIPTION -> R.string.subscription
VERSION -> R.string.version
RATE_APP -> R.string.releasenote_rating
@@ -106,7 +108,7 @@ enum class SettingRow : RowRepresentable {
override val viewType: Int
get() {
return when (this) {
- BANKROLL_REPORT, TOP_10 -> RowViewType.TITLE_ICON_ARROW.ordinal
+ BANKROLL_REPORT, TOP_10, PLAYERS -> RowViewType.TITLE_ICON_ARROW.ordinal
VERSION, SUBSCRIPTION -> RowViewType.TITLE_VALUE.ordinal
LANGUAGE, CURRENCY -> RowViewType.TITLE_VALUE_ARROW.ordinal
FOLLOW_US -> RowViewType.ROW_FOLLOW_US.ordinal
@@ -119,6 +121,7 @@ enum class SettingRow : RowRepresentable {
return when(this) {
BANKROLL_REPORT -> R.drawable.ic_outline_lock
TOP_10 -> R.drawable.ic_outline_star
+ PLAYERS -> R.drawable.ic_outline_people
else -> null
}
}
diff --git a/app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt b/app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt
new file mode 100755
index 00000000..f4182e33
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/util/ImageUtils.kt
@@ -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)
+ */
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt b/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt
index 7a2b5687..de1d9451 100644
--- a/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt
+++ b/app/src/main/java/net/pokeranalytics/android/util/extensions/RealmExtensions.kt
@@ -1,16 +1,10 @@
package net.pokeranalytics.android.util.extensions
-import io.realm.Realm
-import io.realm.RealmModel
-import io.realm.RealmResults
-import io.realm.Sort
+import io.realm.*
import net.pokeranalytics.android.model.interfaces.CountableUsage
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.NameManageable
-import net.pokeranalytics.android.model.realm.Filter
-import net.pokeranalytics.android.model.realm.TournamentFeature
-import net.pokeranalytics.android.model.realm.Transaction
-import net.pokeranalytics.android.model.realm.TransactionType
+import net.pokeranalytics.android.model.realm.*
fun Realm.count(clazz: Class) : Long {
return this.where(clazz).count()
@@ -107,4 +101,22 @@ fun Realm.updateUsageCount(clazz: Class) {
}
}
+}
+
+/**
+ * Returns all entities of the [clazz] which contain the given search content
+ */
+fun < T : RealmModel> Realm.find(clazz: Class, searchContent: String?) : RealmResults {
+ 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)
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_player_color_1.xml b/app/src/main/res/drawable-xxhdpi/circle_player_color_1.xml
new file mode 100644
index 00000000..8cf572ee
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_player_color_1.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_player_color_2.xml b/app/src/main/res/drawable-xxhdpi/circle_player_color_2.xml
new file mode 100644
index 00000000..c803cf76
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_player_color_2.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_player_color_3.xml b/app/src/main/res/drawable-xxhdpi/circle_player_color_3.xml
new file mode 100644
index 00000000..5b5ab8e3
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_player_color_3.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_player_color_4.xml b/app/src/main/res/drawable-xxhdpi/circle_player_color_4.xml
new file mode 100644
index 00000000..39f8c91b
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_player_color_4.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_player_color_5.xml b/app/src/main/res/drawable-xxhdpi/circle_player_color_5.xml
new file mode 100644
index 00000000..bd3aa3ec
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_player_color_5.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_player_color_6.xml b/app/src/main/res/drawable-xxhdpi/circle_player_color_6.xml
new file mode 100644
index 00000000..9f41cab8
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_player_color_6.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_player_color_7.xml b/app/src/main/res/drawable-xxhdpi/circle_player_color_7.xml
new file mode 100644
index 00000000..68ebe9e7
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_player_color_7.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_player_color_8.xml b/app/src/main/res/drawable-xxhdpi/circle_player_color_8.xml
new file mode 100644
index 00000000..5d725ca5
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_player_color_8.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_player_color_9.xml b/app/src/main/res/drawable-xxhdpi/circle_player_color_9.xml
new file mode 100644
index 00000000..053ad739
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_player_color_9.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/circle_stroke_kaki.xml b/app/src/main/res/drawable-xxhdpi/circle_stroke_kaki.xml
new file mode 100644
index 00000000..547635d3
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/circle_stroke_kaki.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xxhdpi/ic_outline_people.xml b/app/src/main/res/drawable-xxhdpi/ic_outline_people.xml
new file mode 100644
index 00000000..1a2f6f89
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_outline_people.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable-xxhdpi/ic_outline_search.xml b/app/src/main/res/drawable-xxhdpi/ic_outline_search.xml
new file mode 100644
index 00000000..6a5ca808
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_outline_search.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_color_picker.xml b/app/src/main/res/layout/activity_color_picker.xml
new file mode 100644
index 00000000..e2942fff
--- /dev/null
+++ b/app/src/main/res/layout/activity_color_picker.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_player.xml b/app/src/main/res/layout/fragment_player.xml
new file mode 100644
index 00000000..e24000ab
--- /dev/null
+++ b/app/src/main/res/layout/fragment_player.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/row_content.xml b/app/src/main/res/layout/row_content.xml
new file mode 100644
index 00000000..49c1bf77
--- /dev/null
+++ b/app/src/main/res/layout/row_content.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/row_header_subtitle.xml b/app/src/main/res/layout/row_header_subtitle.xml
new file mode 100644
index 00000000..1e65603f
--- /dev/null
+++ b/app/src/main/res/layout/row_header_subtitle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/row_player.xml b/app/src/main/res/layout/row_player.xml
new file mode 100644
index 00000000..7f2d73c5
--- /dev/null
+++ b/app/src/main/res/layout/row_player.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/row_player_image.xml b/app/src/main/res/layout/row_player_image.xml
new file mode 100644
index 00000000..2a8a6c20
--- /dev/null
+++ b/app/src/main/res/layout/row_player_image.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/row_title_subtitle.xml b/app/src/main/res/layout/row_title_subtitle.xml
new file mode 100644
index 00000000..7e00d358
--- /dev/null
+++ b/app/src/main/res/layout/row_title_subtitle.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/view_player_image.xml b/app/src/main/res/layout/view_player_image.xml
new file mode 100644
index 00000000..5ab69c6d
--- /dev/null
+++ b/app/src/main/res/layout/view_player_image.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/toolbar_data_list.xml b/app/src/main/res/menu/toolbar_data_list.xml
new file mode 100644
index 00000000..6c0816b9
--- /dev/null
+++ b/app/src/main/res/menu/toolbar_data_list.xml
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index ec3eb74a..bea117ef 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -11,6 +11,7 @@
#8AFFFFFF
#141414
#1B1F1B
+ #808080
#e0e0e0
#40000000
@@ -43,4 +44,14 @@
#3c8c50
#71fb94
+ #00000000
+ #ffdf3e
+ #65FF82
+ #8efde7
+ #3aaeff
+ #6150ff
+ #ff56ff
+ #ff573d
+ #ff971e
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 802da918..2fc85f30 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -5,4 +5,8 @@
12sp
11sp
+
+ 48dp
+ 8dp
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 69bc6a13..63c46bef 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -108,6 +108,11 @@
- 18sp
+
+
+
+
+
+
@@ -266,6 +285,16 @@
- 16sp
+
+
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
index 45ed9d3f..43873cba 100644
--- a/app/src/main/res/xml/provider_paths.xml
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -1,6 +1,11 @@
-
+
+
+
+
\ No newline at end of file