parent
94305b9125
commit
b0e88d08b1
@ -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<out String>, |
||||||
|
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() |
||||||
|
} |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
<?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="match_parent"> |
||||||
|
|
||||||
|
<androidx.camera.view.PreviewView |
||||||
|
android:id="@+id/viewFinder" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" /> |
||||||
|
|
||||||
|
<Button |
||||||
|
android:id="@+id/image_capture_button" |
||||||
|
android:layout_width="110dp" |
||||||
|
android:layout_height="110dp" |
||||||
|
android:layout_marginBottom="50dp" |
||||||
|
android:layout_marginEnd="50dp" |
||||||
|
android:elevation="2dp" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||||
|
app:layout_constraintLeft_toLeftOf="parent" |
||||||
|
app:layout_constraintEnd_toStartOf="@id/vertical_centerline" /> |
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline |
||||||
|
android:id="@+id/vertical_centerline" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:orientation="vertical" |
||||||
|
app:layout_constraintGuide_percent=".50" /> |
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
||||||
Loading…
Reference in new issue