mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Merge pull request #307 from andrekir/QRScan
Feature: Camera QR Code scanning
This commit is contained in:
commit
e0c950edae
8 changed files with 96 additions and 13 deletions
|
|
@ -50,11 +50,8 @@
|
|||
|
||||
<!-- Uart access -->
|
||||
|
||||
|
||||
<!-- the xing library will try to bring this permission in but we don't want it -->
|
||||
<uses-permission
|
||||
android:name="android.permission.CAMERA"
|
||||
tools:node="remove" />
|
||||
<!-- zxing library for QR Code scanning using camera -->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth_le"
|
||||
|
|
@ -117,6 +114,12 @@
|
|||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- zxing for QR Code scanning: lock portrait orientation -->
|
||||
<activity
|
||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
android:screenOrientation="portrait"
|
||||
tools:replace="screenOrientation" />
|
||||
|
||||
<activity
|
||||
android:name="com.geeksville.mesh.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import com.geeksville.android.Logging
|
|||
import com.geeksville.android.ServiceClient
|
||||
import com.geeksville.concurrent.handledLaunch
|
||||
import com.geeksville.mesh.android.getBackgroundPermissions
|
||||
import com.geeksville.mesh.android.getCameraPermissions
|
||||
import com.geeksville.mesh.android.getMissingPermissions
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.databinding.ActivityMainBinding
|
||||
|
|
@ -247,6 +248,8 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
return getMissingPermissions(perms)
|
||||
}
|
||||
|
||||
/** Ask the user to grant camera permission */
|
||||
fun requestCameraPermission() = requestPermission(getCameraPermissions(), false)
|
||||
|
||||
/** Ask the user to grant background location permission */
|
||||
fun requestBackgroundPermission() = requestPermission(getBackgroundPermissions(), false)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,18 @@ fun Context.getMissingPermissions(perms: List<String>) = perms.filter {
|
|||
) != PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Camera permission (or empty if we already have what we need)
|
||||
*/
|
||||
fun Context.getCameraPermissions(): List<String> {
|
||||
val perms = mutableListOf(Manifest.permission.CAMERA)
|
||||
|
||||
return getMissingPermissions(perms)
|
||||
}
|
||||
|
||||
/** @return true if the user already has camera permission */
|
||||
fun Context.hasCameraPermission() = getCameraPermissions().isEmpty()
|
||||
|
||||
/**
|
||||
* A list of missing background location permissions (or empty if we already have what we need)
|
||||
*/
|
||||
|
|
@ -41,4 +53,4 @@ fun Context.getBackgroundPermissions(): List<String> {
|
|||
}
|
||||
|
||||
/** @return true if the user already has background location permission */
|
||||
fun Context.hasBackgroundPermission() = getBackgroundPermissions().isEmpty()
|
||||
fun Context.hasBackgroundPermission() = getBackgroundPermissions().isEmpty()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.ActivityNotFoundException
|
|||
import android.content.Intent
|
||||
import android.graphics.ColorMatrix
|
||||
import android.graphics.ColorMatrixColorFilter
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.RemoteException
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -19,7 +20,9 @@ import com.geeksville.android.Logging
|
|||
import com.geeksville.android.hideKeyboard
|
||||
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
|
||||
import com.geeksville.mesh.model.Channel
|
||||
import com.geeksville.mesh.model.ChannelOption
|
||||
|
|
@ -29,6 +32,8 @@ import com.geeksville.mesh.service.MeshService
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import java.net.MalformedURLException
|
||||
import java.security.SecureRandom
|
||||
|
||||
|
||||
|
|
@ -70,6 +75,8 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
|
|||
|
||||
binding.channelOptions.isEnabled = isEditing
|
||||
binding.shareButton.isEnabled = !isEditing
|
||||
binding.resetButton.isEnabled = isEditing
|
||||
binding.scanButton.isEnabled = isEditing
|
||||
binding.channelNameView.isEnabled = isEditing
|
||||
if (isEditing) // Dim the (stale) QR code while editing...
|
||||
binding.qrView.setDim()
|
||||
|
|
@ -86,7 +93,6 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
|
|||
|
||||
// Only let buttons work if we are connected to the radio
|
||||
binding.shareButton.isEnabled = connected
|
||||
binding.resetButton.isEnabled = connected && Channel.default != channel
|
||||
|
||||
binding.editableCheckbox.isChecked = false // start locked
|
||||
if (channel != null) {
|
||||
|
|
@ -203,6 +209,28 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
|
|||
.show()
|
||||
}
|
||||
|
||||
binding.scanButton.setOnClickListener {
|
||||
if ((requireActivity() as MainActivity).hasCameraPermission()) {
|
||||
val zxingScan = IntentIntegrator.forSupportFragment(this)
|
||||
zxingScan.setCameraId(0)
|
||||
zxingScan.setPrompt("")
|
||||
zxingScan.setBeepEnabled(false)
|
||||
zxingScan.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
|
||||
zxingScan.initiateScan()
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Do not use setOnCheckedChanged here because we don't want to be called when we programmatically disable editing
|
||||
binding.editableCheckbox.setOnClickListener { _ ->
|
||||
|
||||
|
|
@ -287,4 +315,25 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
|
|||
|
||||
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) {
|
||||
Snackbar.make(binding.scanButton, R.string.channel_invalid, Snackbar.LENGTH_LONG).show()
|
||||
} else {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = ChannelSet(Uri.parse(result.contents)).getChannelUrl(false)
|
||||
startActivity(intent)
|
||||
} catch (ex: ActivityNotFoundException) {
|
||||
Snackbar.make(binding.scanButton, R.string.channel_invalid, Snackbar.LENGTH_LONG).show()
|
||||
} catch (ex: MalformedURLException) {
|
||||
Snackbar.make(binding.scanButton, R.string.channel_invalid, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,30 +98,40 @@
|
|||
android:id="@+id/resetButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:text="@string/reset"
|
||||
app:icon="@drawable/ic_twotone_public_24"
|
||||
app:layout_constraintBottom_toBottomOf="@id/bottomButtonsGuideline"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
app:layout_constraintEnd_toStartOf="@id/editableCheckbox" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/scanButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="Scan"
|
||||
app:layout_constraintBottom_toBottomOf="@id/bottomButtonsGuideline"
|
||||
app:layout_constraintStart_toEndOf="@id/editableCheckbox" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/editableCheckbox"
|
||||
android:minWidth="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:button="@drawable/sl_lock_24dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/bottomButtonsGuideline"
|
||||
app:layout_constraintStart_toEndOf="@+id/resetButton"></CheckBox>
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shareButton"
|
||||
style="@android:style/Widget.Material.ImageButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:contentDescription="@string/share"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/editableCheckbox"
|
||||
app:layout_constraintBottom_toBottomOf="@id/bottomButtonsGuideline"
|
||||
app:srcCompat="@drawable/ic_twotone_share_24" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
|
|
|
|||
|
|
@ -112,4 +112,6 @@
|
|||
<string name="cancel_no_radio">Cancelar (sem acesso ao rádio)</string>
|
||||
<string name="allow_will_show">Permitir (exibe diálogo)</string>
|
||||
<string name="provide_location_to_mesh">Fornecer localização para mesh</string>
|
||||
<string name="camera_required">Permissão da câmera</string>
|
||||
<string name="why_camera_required">Precisamos acessar a câmera para escanear códigos QR. Nenhuma foto ou video são armazenados.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -112,4 +112,6 @@
|
|||
<string name="cancel_no_radio">Cancelar (sem acesso ao rádio)</string>
|
||||
<string name="allow_will_show">Permitir (exibe diálogo)</string>
|
||||
<string name="provide_location_to_mesh">Fornecer localização para mesh</string>
|
||||
<string name="why_camera_required">Precisamos acessar a câmera para escanear códigos QR. Nenhuma foto ou video são armazenados.</string>
|
||||
<string name="camera_required">Permissão da câmera</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -116,4 +116,6 @@
|
|||
<string name="cancel_no_radio">Cancel (no radio access)</string>
|
||||
<string name="allow_will_show">Allow (will show dialog)</string>
|
||||
<string name="provide_location_to_mesh">Provide location to mesh</string>
|
||||
<string name="camera_required">Camera permission</string>
|
||||
<string name="why_camera_required">We must be granted access to the camera to read QR codes. No pictures or videos will be saved.</string>
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue