Scan the enrollment QR in either polarity

ai-server prints the QR as block characters, which take the terminal's
foreground colour. On a dark-themed terminal that comes out as a
photographic negative, and ZXing looks only for a dark code on a light
ground, so the in-app scanner silently never matched it -- while the
phone's own camera app, which tries both, did.

Fixing it in the scanner rather than by having the server force its
colours means however the code gets displayed stops mattering: a light
terminal, a dark one, a screenshot someone inverted, a printout.

MIXED_SCAN alternates normal and inverted frames, so the cost is half the
frame rate at each polarity. Checked against the library's own
MixedDecoder off-device: the plain decoder reads the normal image and
returns nothing for the inverted one, and the mixed decoder reads both
within two frames.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQz6R4kBQcWSHBNZMgBnjL
This commit is contained in:
irisandClaude Opus 5 committed 2026-08-25 20:36:12 -04:00
1 parent ba545aa595
commit 0522123a33
2 files changed
+24 -1

No files matched your search

+7
View File
@@ -152,6 +152,13 @@ Established 2026-08-25, and it decides more than it looks like:
`"""\n-----BEGIN CERTIFICATE-----` costs Android's `CertificateFactory` `"""\n-----BEGIN CERTIFICATE-----` costs Android's `CertificateFactory`
its preamble sniff, so it tries DER instead and fails at runtime with its preamble sniff, so it tries DER instead and fails at runtime with
`ASN.1 ... DECODE_ERROR` — nowhere near the code that produced it. `ASN.1 ... DECODE_ERROR` — nowhere near the code that produced it.
- **ZXing only looks for a dark code on a light ground.** The enrollment
QR is block characters in the terminal's foreground colour, so a
dark-themed terminal renders it as a negative and the in-app scanner
silently never matches — while the phone's own camera app, which tries
both, does. The scanner asks for `Intents.Scan.MIXED_SCAN`, which
alternates normal and inverted frames; keep it that way rather than
making the server dictate the colours.
- **AGP 9 refuses `Provider`s in the source-set API**: generated sources go - **AGP 9 refuses `Provider`s in the source-set API**: generated sources go
through `androidComponents.onVariants { it.sources.java?.addGenerated through `androidComponents.onVariants { it.sources.java?.addGenerated
SourceDirectory(task, Task::outputDir) }`, which also carries the task SourceDirectory(task, Task::outputDir) }`, which also carries the task
@@ -24,6 +24,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 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
import com.journeyapps.barcodescanner.ScanOptions import com.journeyapps.barcodescanner.ScanOptions
@@ -82,7 +83,22 @@ fun SettingsScreen(
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
OutlinedButton( OutlinedButton(
onClick = { scanLauncher.launch(ScanOptions().setDesiredBarcodeFormats(ScanOptions.QR_CODE)) }, onClick = {
scanLauncher.launch(
ScanOptions()
.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
// Decode both polarities: 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 that the scanner silently never matches. Which
// way round it renders is the terminal's business, not
// something this app should depend on. MIXED_SCAN alternates
// normal and inverted frames, so it costs half the frame
// 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") }
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))