diff --git a/app/build.gradle b/app/build.gradle
index 261a4eb12..3a832a351 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -173,9 +173,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'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2e1dd63ad..3445b3364 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -97,6 +97,9 @@
+
+ url?.let {
+ requestedChannelUrl = url
+ model.clearRequestChannelUrl()
+ perhapsChangeChannel()
+ }
+ }
+
try {
bindMeshService()
} catch (ex: BindFailedException) {
diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt
index 4536fffdb..dabdbb5e4 100644
--- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt
+++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt
@@ -107,6 +107,20 @@ class UIViewModel @Inject constructor(
private val _channels = MutableLiveData()
val channels: LiveData get() = _channels
+ private val _requestChannelUrl = MutableLiveData(null)
+ val requestChannelUrl: LiveData 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 {
diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt
index f250b7cd9..50392eb6a 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt
@@ -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,15 +14,15 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.ArrayAdapter
import android.widget.ImageView
-import androidx.activity.result.ActivityResultLauncher
+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,9 +32,11 @@ import com.geeksville.mesh.model.ChannelSet
import com.geeksville.mesh.model.UIViewModel
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.journeyapps.barcodescanner.ScanContract
-import com.journeyapps.barcodescanner.ScanIntentResult
import com.journeyapps.barcodescanner.ScanOptions
import dagger.hilt.android.AndroidEntryPoint
import java.security.SecureRandom
@@ -189,6 +192,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)
@@ -212,25 +261,14 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
}
binding.scanButton.setOnClickListener {
- if ((requireActivity() as MainActivity).hasCameraPermission()) {
- debug("Starting QR code scanner")
- val zxingScan = ScanOptions()
- zxingScan.setCameraId(0)
- zxingScan.setPrompt("")
- zxingScan.setBeepEnabled(false)
- zxingScan.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
- barcodeLauncher.launch(zxingScan)
+ 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()
+ }
}
}
@@ -315,16 +353,18 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
if (getString(item.configRes) == selectedChannelOptionString)
return item.modemConfig
}
-
return ChannelProtos.ChannelSettings.ModemConfig.UNRECOGNIZED
}
- // Register the launcher and result handler
- private val barcodeLauncher: ActivityResultLauncher = registerForActivityResult(
- ScanContract()
- ) { result: ScanIntentResult ->
+ 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) {
- ((requireActivity() as MainActivity).perhapsChangeChannel(Uri.parse(result.contents)))
+ model.setRequestChannelUrl(Uri.parse(result.contents))
}
}
}