280 lines
10 KiB
Kotlin
280 lines
10 KiB
Kotlin
package cash.z.android.wallet.ui.fragment
|
|
|
|
import android.animation.Animator
|
|
import android.content.Context
|
|
import android.content.pm.PackageManager
|
|
import android.hardware.camera2.CameraCharacteristics
|
|
import android.hardware.camera2.CameraManager
|
|
import android.media.Image
|
|
import android.os.Bundle
|
|
import android.view.LayoutInflater
|
|
import android.view.View
|
|
import android.view.ViewAnimationUtils
|
|
import android.view.ViewGroup
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.databinding.DataBindingUtil
|
|
import cash.z.android.cameraview.CameraView
|
|
import cash.z.android.wallet.R
|
|
import cash.z.android.wallet.databinding.FragmentScanBinding
|
|
import cash.z.android.wallet.extention.Toaster
|
|
import cash.z.android.wallet.ui.activity.MainActivity
|
|
import com.google.firebase.ml.vision.FirebaseVision
|
|
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode
|
|
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetectorOptions
|
|
import com.google.firebase.ml.vision.common.FirebaseVisionImage
|
|
import dagger.Module
|
|
import dagger.android.ContributesAndroidInjector
|
|
|
|
|
|
/**
|
|
* Fragment for scanning addresss, hopefully.
|
|
*/
|
|
class ScanFragment : BaseFragment() {
|
|
|
|
lateinit var binding: FragmentScanBinding
|
|
var barcodeCallback: BarcodeCallback? = null
|
|
|
|
interface BarcodeCallback {
|
|
fun onBarcodeScanned(value: String)
|
|
}
|
|
|
|
private val revealCamera = Runnable {
|
|
binding.overlayBarcodeScan.apply {
|
|
val cX = measuredWidth / 2
|
|
val cY = measuredHeight / 2
|
|
ViewAnimationUtils.createCircularReveal(this, cX, cY, 0.0f, cX.toFloat()).start()
|
|
postDelayed({
|
|
val v:View = this
|
|
v.animate().alpha(0.0f).apply { duration = 2400L }.setListener(object : Animator.AnimatorListener {
|
|
override fun onAnimationRepeat(animation: Animator?) {
|
|
}
|
|
|
|
override fun onAnimationStart(animation: Animator?) {
|
|
}
|
|
|
|
override fun onAnimationEnd(animation: Animator?) {
|
|
binding.overlayBarcodeScan.visibility = View.GONE
|
|
}
|
|
override fun onAnimationCancel(animation: Animator?) {
|
|
binding.overlayBarcodeScan.visibility = View.GONE
|
|
}
|
|
})
|
|
},500L)
|
|
}
|
|
}
|
|
|
|
private val requiredPermissions: Array<String?>
|
|
get() {
|
|
return try {
|
|
val info = mainActivity.packageManager
|
|
.getPackageInfo(mainActivity.packageName, PackageManager.GET_PERMISSIONS)
|
|
val ps = info.requestedPermissions
|
|
if (ps != null && ps.isNotEmpty()) {
|
|
ps
|
|
} else {
|
|
arrayOfNulls(0)
|
|
}
|
|
} catch (e: Exception) {
|
|
arrayOfNulls(0)
|
|
}
|
|
}
|
|
|
|
override fun onCreateView(
|
|
inflater: LayoutInflater, container: ViewGroup?,
|
|
savedInstanceState: Bundle?
|
|
): View? {
|
|
return DataBindingUtil.inflate<FragmentScanBinding>(
|
|
inflater, R.layout.fragment_scan, container, false
|
|
).let {
|
|
binding = it
|
|
it.root
|
|
}
|
|
}
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
super.onViewCreated(view, savedInstanceState)
|
|
(activity as MainActivity).let { mainActivity ->
|
|
mainActivity.setSupportActionBar(view.findViewById(R.id.toolbar))
|
|
mainActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
|
mainActivity.supportActionBar?.setTitle(R.string.destination_title_send)
|
|
}
|
|
// binding.previewCameraSource.doOnLayout {
|
|
// if (allPermissionsGranted()) {
|
|
// createCameraSource(it.width, it.height)
|
|
// } else {
|
|
// getRuntimePermissions()
|
|
// }
|
|
// }
|
|
}
|
|
|
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
super.onActivityCreated(savedInstanceState)
|
|
if(!allPermissionsGranted()) getRuntimePermissions()
|
|
|
|
|
|
// sendPresenter = SendPresenter(this, mainActivity.synchronizer)
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
binding.overlayBarcodeScan.post(revealCamera)
|
|
System.err.println("camoorah : onResume ScanFragment")
|
|
if(allPermissionsGranted()) onStartCamera()
|
|
// launch {
|
|
// sendPresenter.start()
|
|
// }
|
|
// startCameraSource()
|
|
}
|
|
|
|
override fun onPause() {
|
|
binding.cameraView.stop()
|
|
super.onPause()
|
|
// sendPresenter.stop()
|
|
// binding.previewCameraSource?.stop()
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
// cameraSource?.release()
|
|
}
|
|
|
|
/* Camera */
|
|
// private fun createCameraSource(width: Int, height: Int) {
|
|
// Toaster.short("w: $width h: $height")
|
|
// // If there's no existing cameraSource, create one.
|
|
// if (cameraSource == null) {
|
|
// cameraSource = CameraSource(mainActivity, binding.graphicOverlay)
|
|
// }
|
|
//
|
|
// try {
|
|
// cameraSource?.setMachineLearningFrameProcessor(BarcodeScanningProcessor())
|
|
// } catch (e: FirebaseMLException) {
|
|
// Log.e("temporaryBehavior", "can not create camera source")
|
|
// }
|
|
// }
|
|
|
|
/**
|
|
* Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet
|
|
* (e.g., because onResume was called before the camera source was created), this will be called
|
|
* again when the camera source is created.
|
|
*/
|
|
// private fun startCameraSource() {
|
|
// cameraSource?.let {
|
|
// try {
|
|
// binding.previewCameraSource?.start(cameraSource!!, binding.graphicOverlay)
|
|
// } catch (e: IOException) {
|
|
// Log.e("temporaryBehavior", "Unable to start camera source.", e)
|
|
// cameraSource?.release()
|
|
// cameraSource = null
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
/* Permissions */
|
|
|
|
private fun allPermissionsGranted(): Boolean {
|
|
for (permission in requiredPermissions) {
|
|
if (!isPermissionGranted(mainActivity, permission!!)) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
private fun getRuntimePermissions() {
|
|
val allNeededPermissions = arrayListOf<String>()
|
|
for (permission in requiredPermissions) {
|
|
if (!isPermissionGranted(mainActivity, permission!!)) {
|
|
allNeededPermissions.add(permission)
|
|
}
|
|
}
|
|
|
|
if (!allNeededPermissions.isEmpty()) {
|
|
requestPermissions(allNeededPermissions.toTypedArray(), CAMERA_PERMISSION_REQUEST)
|
|
}
|
|
}
|
|
|
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
if (allPermissionsGranted()) {
|
|
onStartCamera()
|
|
}
|
|
}
|
|
|
|
private fun onStartCamera() {
|
|
with(binding.cameraView) {
|
|
// workaround race conditions with google play services downloading the binaries for Firebase Vision APIs
|
|
postDelayed({
|
|
firebaseCallback = PoCallback()
|
|
start()
|
|
}, 1000L)
|
|
}
|
|
}
|
|
|
|
inner class PoCallback : CameraView.FirebaseCallback {
|
|
val options = FirebaseVisionBarcodeDetectorOptions.Builder()
|
|
.setBarcodeFormats(FirebaseVisionBarcode.FORMAT_QR_CODE)
|
|
.build()
|
|
val barcodeDetector = FirebaseVision.getInstance().getVisionBarcodeDetector(options)
|
|
var cameraId = getBackCameraId()
|
|
|
|
private fun getBackCameraId(): String {
|
|
val manager = mainActivity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
|
|
for (cameraId in manager.cameraIdList) {
|
|
val characteristics = manager.getCameraCharacteristics(cameraId)
|
|
val cOrientation = characteristics.get(CameraCharacteristics.LENS_FACING)!!
|
|
if (cOrientation == CameraCharacteristics.LENS_FACING_BACK) return cameraId
|
|
}
|
|
throw IllegalArgumentException("no rear-facing camera found!")
|
|
}
|
|
|
|
override fun onImageAvailable(image: Image) {
|
|
System.err.println("camoorah : onImageAvailable: $image width: ${image.width} height: ${image.height}")
|
|
var firebaseImage = FirebaseVisionImage.fromMediaImage(image, getRotationCompensation(cameraId, mainActivity))
|
|
barcodeDetector
|
|
.detectInImage(firebaseImage)
|
|
.addOnSuccessListener { results ->
|
|
if (results.isNotEmpty()) {
|
|
val barcode = results[0]
|
|
val value = barcode.rawValue
|
|
val message = "found: $value"
|
|
Toaster.short(message)
|
|
onScanSuccess(value!!)
|
|
// TODO: highlight the barcode
|
|
var bounds = barcode.boundingBox
|
|
var corners = barcode.cornerPoints
|
|
binding.cameraView.setBarcode(barcode)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var pendingSuccess = false
|
|
private fun onScanSuccess(value: String) {
|
|
if (!pendingSuccess) {
|
|
pendingSuccess = true
|
|
with(binding.cameraView) {
|
|
postDelayed({
|
|
barcodeCallback?.onBarcodeScanned(value)
|
|
}, 3000L)
|
|
}
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
// TODO: continue doing permissions here in a more specific, less general way
|
|
private const val CAMERA_PERMISSION_REQUEST = 1001
|
|
|
|
private fun isPermissionGranted(context: Context, permission: String): Boolean {
|
|
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
|
|
}
|
|
}
|
|
}
|
|
|
|
@Module
|
|
abstract class ScanFragmentModule {
|
|
@ContributesAndroidInjector
|
|
abstract fun contributeScanFragment(): ScanFragment
|
|
}
|