add mlkit barcode scanner

This commit is contained in:
andrekir 2022-05-17 17:29:21 -03:00
parent f20368833e
commit 5f131da50d
5 changed files with 112 additions and 45 deletions

View file

@ -175,9 +175,10 @@ dependencies {
// location services
implementation 'com.google.android.gms:play-services-location:19.0.1'
// For Google Sign-In (owner name accesss)
implementation 'com.google.android.gms:play-services-auth:20.1.0'
// ML Kit barcode scanning
implementation 'com.google.android.gms:play-services-code-scanner:16.0.0-beta1'
// Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:18.2.6'

View file

@ -97,6 +97,9 @@
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="barcode_ui"/>
<!-- we need bind job service for oreo -->
<service

View file

@ -126,7 +126,7 @@ class MainActivity : AppCompatActivity(), Logging,
const val REQUEST_ENABLE_BT = 10
const val DID_REQUEST_PERM = 11
const val RC_SIGN_IN = 12 // google signin completed
const val SELECT_DEVICE_REQUEST_CODE = 13
// const val SELECT_DEVICE_REQUEST_CODE = 13
const val CREATE_CSV_FILE = 14
}
@ -514,8 +514,7 @@ class MainActivity : AppCompatActivity(), Logging,
requestedChannelUrl = appLinkData
// if the device is connected already, process it now
if (model.isConnected.value == MeshService.ConnectionState.CONNECTED)
perhapsChangeChannel()
perhapsChangeChannel()
// We now wait for the device to connect, once connected, we ask the user if they want to switch to the new channel
}
@ -733,16 +732,16 @@ class MainActivity : AppCompatActivity(), Logging,
}
}
fun perhapsChangeChannel(url: Uri? = requestedChannelUrl) {
// If the is opening a channel URL, handle it now
if (url != null) {
private fun perhapsChangeChannel(url: Uri? = requestedChannelUrl) {
// if the device is connected already, process it now
if (url != null && model.isConnected.value == MeshService.ConnectionState.CONNECTED) {
requestedChannelUrl = null
try {
val channels = ChannelSet(url)
val primary = channels.primaryChannel
if (primary == null)
showSnackbar(R.string.channel_invalid)
else {
requestedChannelUrl = null
MaterialAlertDialogBuilder(this)
.setTitle(R.string.new_channel_rcvd)
@ -984,6 +983,15 @@ class MainActivity : AppCompatActivity(), Logging,
}
}
// Call perhapsChangeChannel() whenever [changeChannelUrl] updates with a non-null value
model.requestChannelUrl.observe(this) { url ->
url?.let {
requestedChannelUrl = url
model.clearRequestChannelUrl()
perhapsChangeChannel()
}
}
try {
bindMeshService()
} catch (ex: BindFailedException) {

View file

@ -7,6 +7,7 @@ import android.net.Uri
import android.os.RemoteException
import android.view.Menu
import androidx.core.content.edit
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -20,7 +21,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -103,6 +103,20 @@ class UIViewModel @Inject constructor(
val channels = object : MutableLiveData<ChannelSet?>(null) {
}
private val _requestChannelUrl = MutableLiveData<Uri?>(null)
val requestChannelUrl: LiveData<Uri?> get() = _requestChannelUrl
fun setRequestChannelUrl(channelUrl: Uri) {
_requestChannelUrl.value = channelUrl
}
/**
* Called immediately after activity observes requestChannelUrl
*/
fun clearRequestChannelUrl() {
_requestChannelUrl.value = null
}
var positionBroadcastSecs: Int?
get() {
radioConfig.value?.preferences?.let {

View file

@ -1,5 +1,6 @@
package com.geeksville.mesh.ui
import android.Manifest
import android.content.ActivityNotFoundException
import android.content.Intent
import android.graphics.ColorMatrix
@ -13,14 +14,15 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.ArrayAdapter
import android.widget.ImageView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.activityViewModels
import com.geeksville.analytics.DataPair
import com.geeksville.android.GeeksvilleApplication
import com.geeksville.android.Logging
import com.geeksville.android.hideKeyboard
import com.geeksville.android.isGooglePlayAvailable
import com.geeksville.mesh.AppOnlyProtos
import com.geeksville.mesh.ChannelProtos
import com.geeksville.mesh.MainActivity
import com.geeksville.mesh.R
import com.geeksville.mesh.android.hasCameraPermission
import com.geeksville.mesh.databinding.ChannelFragmentBinding
@ -31,8 +33,12 @@ import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.service.MeshService
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
import com.google.protobuf.ByteString
import com.google.zxing.integration.android.IntentIntegrator
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import dagger.hilt.android.AndroidEntryPoint
import java.security.SecureRandom
@ -65,7 +71,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
): View {
_binding = ChannelFragmentBinding.inflate(inflater, container, false)
return binding.root
}
@ -188,6 +194,52 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
}
}
private fun zxingScan() {
debug("Starting zxing QR code scanner")
val zxingScan = ScanOptions()
zxingScan.setCameraId(0)
zxingScan.setPrompt("")
zxingScan.setBeepEnabled(false)
zxingScan.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
barcodeLauncher.launch(zxingScan)
}
private fun requestPermissionAndScan() {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.camera_required)
.setMessage(R.string.why_camera_required)
.setNeutralButton(R.string.cancel) { _, _ ->
debug("Camera permission denied")
}
.setPositiveButton(getString(R.string.accept)) { _, _ ->
requestPermissionAndScanLauncher.launch(Manifest.permission.CAMERA)
}
.show()
}
private fun mlkitScan() {
debug("Starting ML Kit QR code scanner")
val options = GmsBarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_QR_CODE
)
.build()
val scanner = GmsBarcodeScanning.getClient(requireContext(), options)
scanner.startScan()
.addOnSuccessListener { barcode ->
if (barcode.rawValue != null)
model.setRequestChannelUrl(Uri.parse(barcode.rawValue))
}
.addOnFailureListener {
Snackbar.make(
requireView(),
R.string.channel_invalid,
Snackbar.LENGTH_SHORT
).show()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -195,7 +247,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
requireActivity().hideKeyboard()
}
binding.resetButton.setOnClickListener { _ ->
binding.resetButton.setOnClickListener {
// User just locked it, we should warn and then apply changes to radio
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.reset_to_defaults)
@ -211,30 +263,19 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
}
binding.scanButton.setOnClickListener {
if ((requireActivity() as MainActivity).hasCameraPermission()) {
debug("Starting QR code scanner")
val zxingScan = IntentIntegrator.forSupportFragment(this)
zxingScan.setCameraId(0)
zxingScan.setPrompt("")
zxingScan.setBeepEnabled(false)
zxingScan.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
zxingScan.initiateScan()
if (isGooglePlayAvailable(requireContext())) {
mlkitScan()
} else {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.camera_required)
.setMessage(R.string.why_camera_required)
.setNeutralButton(R.string.cancel) { _, _ ->
debug("Camera permission denied")
}
.setPositiveButton(getString(R.string.accept)) { _, _ ->
(requireActivity() as MainActivity).requestCameraPermission()
}
.show()
if (requireContext().hasCameraPermission()) {
zxingScan()
} else {
requestPermissionAndScan()
}
}
}
// Note: Do not use setOnCheckedChanged here because we don't want to be called when we programmatically disable editing
binding.editableCheckbox.setOnClickListener { _ ->
binding.editableCheckbox.setOnClickListener {
/// We use this to determine if the user tried to install a custom name
var originalName = ""
@ -299,14 +340,14 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
shareChannel()
}
model.channels.observe(viewLifecycleOwner, {
model.channels.observe(viewLifecycleOwner) {
setGUIfromModel()
})
}
// If connection state changes, we might need to enable/disable buttons
model.isConnected.observe(viewLifecycleOwner, {
model.isConnected.observe(viewLifecycleOwner) {
setGUIfromModel()
})
}
}
private fun getModemConfig(selectedChannelOptionString: String): ChannelProtos.ChannelSettings.ModemConfig {
@ -314,18 +355,18 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
if (getString(item.configRes) == selectedChannelOptionString)
return item.modemConfig
}
return ChannelProtos.ChannelSettings.ModemConfig.UNRECOGNIZED
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (result != null) {
if (result.contents != null) {
((requireActivity() as MainActivity).perhapsChangeChannel(Uri.parse(result.contents)))
}
} else {
super.onActivityResult(requestCode, resultCode, data)
private val requestPermissionAndScanLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { allowed ->
if (allowed) zxingScan()
}
// Register zxing launcher and result handler
private val barcodeLauncher = registerForActivityResult(ScanContract()) { result ->
if (result.contents != null) {
model.setRequestChannelUrl(Uri.parse(result.contents))
}
}
}