Hold the camera permission before the scanner opens
The library's capture activity asks for the camera itself, and opens the camera without waiting for the answer. The first scan on a fresh install therefore comes up as a live preview with "Sorry, the Android camera encountered a problem. You may need to restart the device." over it, and works on the second try. Nothing is wrong with the camera, so nothing should say there is. Asking before launching it means the activity always starts with the permission already held. A denial now says what to do instead of leaving a dialog blaming the device. The scan options move to one function while there are two callers -- the button when the permission is already held, and the permission result when it has just been granted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQz6R4kBQcWSHBNZMgBnjL
This commit is contained in:
1 parent
bf82166736
commit
f53a1ca214
1 file changed
+50
-17
@@ -1,5 +1,7 @@
|
|||||||
package com.example.aiapp
|
package com.example.aiapp
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -24,6 +26,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import com.google.zxing.client.android.Intents
|
import com.google.zxing.client.android.Intents
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanIntentResult
|
import com.journeyapps.barcodescanner.ScanIntentResult
|
||||||
@@ -62,6 +65,17 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val requestCamera = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { granted ->
|
||||||
|
if (granted) {
|
||||||
|
scanLauncher.launch(enrollmentScanOptions())
|
||||||
|
} else {
|
||||||
|
error = "Scanning needs the camera. Grant it in the system settings, " +
|
||||||
|
"or type the host, port and token in below."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(Modifier.fillMaxSize().padding(16.dp)) {
|
Column(Modifier.fillMaxSize().padding(16.dp)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||||
Text(
|
Text(
|
||||||
@@ -84,23 +98,21 @@ fun SettingsScreen(
|
|||||||
|
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scanLauncher.launch(
|
// Hold the camera permission before the scanner starts.
|
||||||
ScanOptions()
|
// Letting its activity ask on our behalf is what the
|
||||||
.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
// library does by default, and it opens the camera without
|
||||||
.setCaptureActivity(EnrollmentScanActivity::class.java)
|
// waiting for the answer: the first-ever scan comes up as
|
||||||
// Follow the phone, not the library's landscape pin.
|
// a live preview with "Sorry, the Android camera
|
||||||
.setOrientationLocked(false)
|
// encountered a problem" over it, and works on the second
|
||||||
// Decode both polarities: ZXing otherwise looks only for a
|
// try. Nothing is wrong with the camera, so nothing should
|
||||||
// dark code on a light ground, and ai-server's QR is block
|
// say there is.
|
||||||
// characters in the terminal's foreground colour -- so on a
|
if (context.checkSelfPermission(Manifest.permission.CAMERA)
|
||||||
// dark-themed terminal it comes out as a photographic
|
== PackageManager.PERMISSION_GRANTED
|
||||||
// negative that the scanner silently never matches. Which
|
) {
|
||||||
// way round it renders is the terminal's business, not
|
scanLauncher.launch(enrollmentScanOptions())
|
||||||
// something this app should depend on. MIXED_SCAN alternates
|
} else {
|
||||||
// normal and inverted frames, so it costs half the frame
|
requestCamera.launch(Manifest.permission.CAMERA)
|
||||||
// rate at each polarity and nothing else.
|
}
|
||||||
.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) { Text("Scan QR code") }
|
) { Text("Scan QR code") }
|
||||||
@@ -152,3 +164,24 @@ fun SettingsScreen(
|
|||||||
}) { Text("Save") }
|
}) { Text("Save") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How the enrollment QR is scanned, in one place because two callers reach
|
||||||
|
* it -- straight from the button when the camera permission is already
|
||||||
|
* held, and from the permission result when it has just been granted.
|
||||||
|
*
|
||||||
|
* MIXED_SCAN is the load-bearing part: ZXing otherwise looks only for a
|
||||||
|
* dark code on a light ground, and ai-server's QR is block characters in
|
||||||
|
* the terminal's foreground colour, so on a dark-themed terminal it comes
|
||||||
|
* out as a photographic negative the scanner silently never matches. Which
|
||||||
|
* way round it renders is the terminal's business, not something this app
|
||||||
|
* should depend on. The mixed decoder alternates normal and inverted
|
||||||
|
* frames, costing half the frame rate at each polarity and nothing else.
|
||||||
|
*/
|
||||||
|
private fun enrollmentScanOptions(): ScanOptions =
|
||||||
|
ScanOptions()
|
||||||
|
.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
||||||
|
.setCaptureActivity(EnrollmentScanActivity::class.java)
|
||||||
|
// Follow the phone, not the library's landscape pin.
|
||||||
|
.setOrientationLocked(false)
|
||||||
|
.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
|
||||||
Reference in new issue
Block a user