mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
add mlkit barcode scanner
This commit is contained in:
parent
f20368833e
commit
5f131da50d
5 changed files with 112 additions and 45 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue