diff --git a/app/build.gradle b/app/build.gradle
index eb89304d..feb8ce92 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -17,8 +17,8 @@ repositories {
android {
- compileSdkVersion 32
- buildToolsVersion "30.0.2"
+ compileSdkVersion 33
+ buildToolsVersion "30.0.3"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -36,7 +36,7 @@ android {
defaultConfig {
applicationId "net.pokeranalytics.android"
minSdkVersion 23
- targetSdkVersion 32
+ targetSdkVersion 33
versionCode 151
versionName "6.0.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -141,6 +141,16 @@ dependencies {
// ffmpeg for encoding video (HH export)
implementation 'com.arthenica:ffmpeg-kit-min-gpl:4.4.LTS'
+ // Camera
+ def camerax_version = "1.1.0-beta01"
+ implementation "androidx.camera:camera-core:${camerax_version}"
+ implementation "androidx.camera:camera-camera2:${camerax_version}"
+ implementation "androidx.camera:camera-lifecycle:${camerax_version}"
+ implementation "androidx.camera:camera-video:${camerax_version}"
+
+ implementation "androidx.camera:camera-view:${camerax_version}"
+ implementation "androidx.camera:camera-extensions:${camerax_version}"
+
// Instrumented Tests
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 714f4e26..e6fedda6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,13 +3,18 @@
xmlns:tools="http://schemas.android.com/tools"
package="net.pokeranalytics.android">
+
+
+
+
-
+
+
+
+
= RealmList()
+ @Ignore
+ var pictureUri: Uri? = null
+
@Ignore
override val realmObjectClass: Class = Player::class.java
@@ -63,7 +67,7 @@ open class Player : RealmObject(), NameManageable, Savable, Deletable, RowRepres
when (row) {
PlayerPropertiesRow.NAME -> this.name = value as String? ?: ""
PlayerPropertiesRow.SUMMARY -> this.summary = value as String? ?: ""
- PlayerPropertiesRow.IMAGE -> this.picture = value as String? ?: ""
+ PlayerPropertiesRow.IMAGE -> this.pictureUri = value as? Uri
}
}
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/CameraActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/CameraActivity.kt
new file mode 100644
index 00000000..29e7d0eb
--- /dev/null
+++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/CameraActivity.kt
@@ -0,0 +1,188 @@
+package net.pokeranalytics.android.ui.activity.components
+
+import android.Manifest
+import android.app.Activity
+import android.content.ContentValues
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.provider.MediaStore
+import android.widget.Toast
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.Preview
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import net.pokeranalytics.android.databinding.ActivityCameraBinding
+import timber.log.Timber
+import java.text.SimpleDateFormat
+import java.util.*
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+class CameraActivity : BaseActivity() {
+
+ companion object {
+
+ const val IMAGE_URI = "image_uri"
+
+ fun newInstanceForResult(fragment: Fragment, code: RequestCode) {
+ val intent = Intent(fragment.requireContext(), CameraActivity::class.java)
+ fragment.startActivityForResult(intent, code.value)
+ }
+
+ private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
+ private const val REQUEST_CODE_PERMISSIONS = 10
+ private val REQUIRED_PERMISSIONS =
+ mutableListOf (
+ Manifest.permission.CAMERA,
+ Manifest.permission.RECORD_AUDIO
+ ).apply {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
+ add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ }
+ }.toTypedArray()
+
+ }
+
+ private lateinit var viewBinding: ActivityCameraBinding
+
+ private lateinit var cameraExecutor: ExecutorService
+ private var imageCapture: ImageCapture? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ this.viewBinding = ActivityCameraBinding.inflate(layoutInflater)
+ setContentView(viewBinding.root)
+
+ if (allPermissionsGranted()) {
+ cameraExecutor = Executors.newSingleThreadExecutor()
+ startCamera()
+ } else {
+ ActivityCompat.requestPermissions(
+ this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
+ }
+
+ viewBinding.imageCaptureButton.setOnClickListener { takePhoto() }
+ cameraExecutor = Executors.newSingleThreadExecutor()
+
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ this.cameraExecutor.shutdown()
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ if (requestCode == REQUEST_CODE_PERMISSIONS) {
+ if (allPermissionsGranted()) {
+ startCamera()
+ } else {
+ Toast.makeText(this,
+ "Permissions not granted by the user.",
+ Toast.LENGTH_SHORT).show()
+ finish()
+ }
+ }
+ }
+
+ private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
+ ContextCompat.checkSelfPermission(
+ baseContext, it) == PackageManager.PERMISSION_GRANTED
+ }
+
+ private fun startCamera() {
+ val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
+
+ cameraProviderFuture.addListener({
+ // Used to bind the lifecycle of cameras to the lifecycle owner
+ val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
+
+ // Preview
+ val preview = Preview.Builder()
+ .build()
+ .also {
+ it.setSurfaceProvider(this.viewBinding.viewFinder.surfaceProvider)
+ }
+
+ imageCapture = ImageCapture.Builder().build()
+
+ // Select back camera as a default
+ val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
+
+ try {
+ // Unbind use cases before rebinding
+ cameraProvider.unbindAll()
+
+ // Bind use cases to camera
+ cameraProvider.bindToLifecycle(
+ this, cameraSelector, preview, imageCapture)
+
+ } catch(exc: Exception) {
+ Timber.e(exc)
+ }
+
+ }, ContextCompat.getMainExecutor(this))
+ }
+
+
+ private fun takePhoto() {
+ // Get a stable reference of the modifiable image capture use case
+ val imageCapture = imageCapture ?: return
+
+ // Create time stamped name and MediaStore entry.
+ val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
+ .format(System.currentTimeMillis())
+ val contentValues = ContentValues().apply {
+ put(MediaStore.MediaColumns.DISPLAY_NAME, name)
+ put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
+ if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
+ put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
+ }
+ }
+
+ // Create output options object which contains file + metadata
+ val outputOptions = ImageCapture.OutputFileOptions
+ .Builder(contentResolver,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ contentValues)
+ .build()
+
+ // Set up image capture listener, which is triggered after photo has
+ // been taken
+ imageCapture.takePicture(
+ outputOptions,
+ ContextCompat.getMainExecutor(this),
+ object : ImageCapture.OnImageSavedCallback {
+ override fun onError(e: ImageCaptureException) {
+ Timber.e(e)
+ }
+
+ override fun onImageSaved(output: ImageCapture.OutputFileResults) {
+
+ val msg = "Photo capture succeeded: ${output.savedUri}"
+ Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
+ Timber.d(msg)
+
+ val intent = Intent()
+ intent.putExtra(IMAGE_URI, output.savedUri.toString())
+ setResult(Activity.RESULT_OK, intent)
+
+ finish()
+ }
+ }
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt
index e91359dd..0b6e44cf 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/components/Codes.kt
@@ -16,7 +16,8 @@ enum class RequestCode(var value: Int) {
IMPORT(900),
SUBSCRIPTION(901),
CURRENCY(902),
- PERMISSION_WRITE_EXTERNAL_STORAGE(1000)
+ PERMISSION_WRITE_EXTERNAL_STORAGE(1000),
+ CAMERA(1001)
}
enum class ResultCode(var value: Int) {
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataFragment.kt
index cab56f4b..97d40a70 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataFragment.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/data/PlayerDataFragment.kt
@@ -20,7 +20,9 @@ import net.pokeranalytics.android.model.realm.Comment
import net.pokeranalytics.android.model.realm.Player
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.ui.activity.ColorPickerActivity
+import net.pokeranalytics.android.ui.activity.components.CameraActivity
import net.pokeranalytics.android.ui.activity.components.MediaActivity
+import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.extensions.showAlertDialog
@@ -72,6 +74,13 @@ class PlayerDataFragment : EditableDataFragment(), StaticRowRepresentableDataSou
player.color = if (color != Color.TRANSPARENT) color else null
rowRepresentableAdapter.refreshRow(PlayerPropertiesRow.IMAGE)
}
+
+ if (requestCode == RequestCode.CAMERA.value) {
+ val uri = data?.getStringExtra(CameraActivity.IMAGE_URI)
+ this.player.picture = uri
+ rowRepresentableAdapter.refreshRow(PlayerPropertiesRow.IMAGE)
+ }
+
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -218,7 +227,11 @@ class PlayerDataFragment : EditableDataFragment(), StaticRowRepresentableDataSou
builder.setItems(placesArray.toTypedArray()) { _, which ->
when (placesArray[which]) {
- getString(R.string.take_a_picture) -> mediaActivity?.openImageCaptureIntent(false)
+ getString(R.string.take_a_picture) -> {
+ CameraActivity.newInstanceForResult(this, RequestCode.CAMERA)
+
+// mediaActivity?.takePicture()
+ } // mediaActivity?.openImageCaptureIntent(false)
getString(R.string.library) -> mediaActivity?.openImageGalleryIntent(false)
getString(R.string.select_a_color) -> {
ColorPickerActivity.newInstanceForResult(this, REQUEST_CODE_PICK_COLOR)
diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt
index c5707d4e..dd0630d2 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/feed/NewDataMenuActivity.kt
@@ -149,11 +149,17 @@ class NewDataMenuActivity : BaseActivity() {
anim.duration = 150
anim.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
+ override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
menuContainer.visibility = View.INVISIBLE
finish()
}
+
+// override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
+// super.onAnimationEnd(animation, isReverse)
+// menuContainer.visibility = View.INVISIBLE
+// finish()
+// }
})
anim.start()
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
index 927767df..d8981d8c 100644
--- a/app/src/main/java/net/pokeranalytics/android/ui/view/PlayerImageView.kt
+++ b/app/src/main/java/net/pokeranalytics/android/ui/view/PlayerImageView.kt
@@ -2,7 +2,10 @@ package net.pokeranalytics.android.ui.view
import android.content.Context
import android.graphics.Color
+import android.graphics.ImageDecoder
import android.graphics.drawable.GradientDrawable
+import android.os.Build
+import android.provider.MediaStore
import android.util.AttributeSet
import android.util.TypedValue
import android.view.LayoutInflater
@@ -14,9 +17,11 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat.getColor
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
+import androidx.core.net.toUri
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Player
import net.pokeranalytics.android.ui.extensions.px
+import timber.log.Timber
/**
@@ -91,9 +96,33 @@ class PlayerImageView : FrameLayout {
// Picture
player.picture?.let { picture ->
- val rDrawable = RoundedBitmapDrawableFactory.create(resources, picture)
- rDrawable.isCircular = true
- this.playerImage.setImageDrawable(rDrawable)
+
+ Timber.d("picture = $picture")
+
+ val uri = picture.toUri()
+
+ context?.contentResolver?.let { resolver ->
+ val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ ImageDecoder.decodeBitmap(ImageDecoder.createSource(resolver, uri))
+ } else {
+ MediaStore.Images.Media.getBitmap(resolver, uri)
+ }
+
+ val rDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap)
+ rDrawable.isCircular = true
+ this.playerImage.setImageDrawable(rDrawable)
+
+ }
+
+// player.picture?.let { pic ->
+// pic.toUri().path?.let {
+// val rDrawable = RoundedBitmapDrawableFactory.create(resources, it)
+// rDrawable.isCircular = true
+// this.playerImage.setImageDrawable(rDrawable)
+// }
+//
+// this.playerImage.setImageURI(uri)
+// }
} ?: run {
diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml
new file mode 100644
index 00000000..71504c4b
--- /dev/null
+++ b/app/src/main/res/layout/activity_camera.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_editable_data.xml b/app/src/main/res/layout/activity_editable_data.xml
index 70454fb9..42df304b 100644
--- a/app/src/main/res/layout/activity_editable_data.xml
+++ b/app/src/main/res/layout/activity_editable_data.xml
@@ -4,4 +4,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+
+
\ No newline at end of file