zcash-android-wallet-poc/zcash-android-wallet-app/app/src/main/java/cash/z/android/wallet/ui/fragment/ScanFragment.kt

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
}