Replace mlkit with zxing QR scanner

This commit is contained in:
maxmoney21m
2023-03-10 22:06:15 +08:00
parent 9cf7cc4e5d
commit ae6cf15768
5 changed files with 86 additions and 207 deletions
+1 -11
View File
@@ -133,29 +133,19 @@ dependencies {
// For QR generation // For QR generation
implementation 'com.google.zxing:core:3.5.1' implementation 'com.google.zxing:core:3.5.1'
implementation "androidx.camera:camera-camera2:1.2.1" implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
implementation 'androidx.camera:camera-lifecycle:1.2.1'
implementation 'androidx.camera:camera-view:1.2.1'
// Markdown // Markdown
implementation "com.halilibo.compose-richtext:richtext-ui:0.16.0" implementation "com.halilibo.compose-richtext:richtext-ui:0.16.0"
implementation "com.halilibo.compose-richtext:richtext-ui-material:0.16.0" implementation "com.halilibo.compose-richtext:richtext-ui-material:0.16.0"
implementation "com.halilibo.compose-richtext:richtext-commonmark:0.16.0" implementation "com.halilibo.compose-richtext:richtext-commonmark:0.16.0"
// For QR Scanning
implementation 'com.google.mlkit:vision-common:17.3.0'
// Local Barcode Scanning model
// The idea is to make it work for degoogled phones
implementation 'com.google.mlkit:barcode-scanning:17.0.3'
// Local model for language identification // Local model for language identification
implementation 'com.google.mlkit:language-id:17.0.4' implementation 'com.google.mlkit:language-id:17.0.4'
// Google services model the translate text // Google services model the translate text
implementation 'com.google.mlkit:translate:17.0.1' implementation 'com.google.mlkit:translate:17.0.1'
// Automatic memory leak detection // Automatic memory leak detection
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
+6
View File
@@ -22,6 +22,7 @@
android:theme="@style/Theme.Amethyst" android:theme="@style/Theme.Amethyst"
android:largeHeap="true" android:largeHeap="true"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:hardwareAccelerated="true"
tools:targetApi="33"> tools:targetApi="33">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
@@ -45,6 +46,11 @@
android:name="android.app.lib_name" android:name="android.app.lib_name"
android:value="" /> android:value="" />
</activity> </activity>
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="fullSensor"
tools:replace="screenOrientation" />
</application> </application>
</manifest> </manifest>
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -34,13 +33,13 @@ fun QrCodeDrawer(contents: String, modifier: Modifier = Modifier) {
createQrCode(contents = contents) createQrCode(contents = contents)
} }
val foregroundColor = MaterialTheme.colors.onSurface val foregroundColor = Color.Black
Box( Box(
modifier = modifier modifier = modifier
.defaultMinSize(48.dp, 48.dp) .defaultMinSize(48.dp, 48.dp)
.aspectRatio(1f) .aspectRatio(1f)
.background(MaterialTheme.colors.background) .background(Color.White)
) { ) {
Canvas(modifier = Modifier.fillMaxSize()) { Canvas(modifier = Modifier.fillMaxSize()) {
// Calculate the height and width of each column/row // Calculate the height and width of each column/row
@@ -1,156 +1,57 @@
package com.vitorpamplona.amethyst.ui.qrcode package com.vitorpamplona.amethyst.ui.qrcode
import android.Manifest
import android.content.pm.PackageManager import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.runtime.Composable
import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.DisposableEffect
import androidx.camera.core.CameraSelector import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.camera.core.ImageAnalysis import androidx.compose.ui.res.stringResource
import androidx.camera.core.ImageProxy import com.journeyapps.barcodescanner.ScanContract
import androidx.camera.core.Preview import com.journeyapps.barcodescanner.ScanOptions
import androidx.camera.lifecycle.ProcessCameraProvider import com.vitorpamplona.amethyst.R
import androidx.camera.view.PreviewView import com.vitorpamplona.amethyst.service.nip19.Nip19
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.* @Composable
import androidx.compose.ui.Modifier fun QrCodeScanner(onScan: (String?) -> Unit) {
import androidx.compose.ui.platform.LocalContext val lifecycleOwner = LocalLifecycleOwner.current
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView val parseQrResult = { it: String ->
import androidx.core.content.ContextCompat try {
import androidx.lifecycle.LifecycleOwner val nip19 = Nip19.uriToRoute(it)
import com.google.mlkit.vision.barcode.BarcodeScannerOptions val startingPage = when (nip19?.type) {
import com.google.mlkit.vision.barcode.BarcodeScanning Nip19.Type.USER -> "User/${nip19.hex}"
import com.google.mlkit.vision.barcode.common.Barcode Nip19.Type.NOTE -> "Note/${nip19.hex}"
import com.google.mlkit.vision.common.InputImage else -> null
import com.vitorpamplona.amethyst.service.nip19.Nip19 }
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors if (startingPage != null) {
onScan(startingPage)
@Composable } else {
fun QrCodeScanner(onScan: (String) -> Unit) { onScan(null)
val context = LocalContext.current }
val lifecycleOwner = LocalLifecycleOwner.current } catch (e: Throwable) {
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) } // QR can be anything, do not throw errors.
onScan(null)
val cameraExecutor = Executors.newSingleThreadExecutor() }
}
var hasCameraPermission by remember {
mutableStateOf( val qrLauncher =
ContextCompat.checkSelfPermission( rememberLauncherForActivityResult(ScanContract()) {
context, if (it.contents != null) {
Manifest.permission.CAMERA parseQrResult(it.contents)
) == PackageManager.PERMISSION_GRANTED } else {
) onScan(null)
} }
}
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(), val scanOptions = ScanOptions().apply {
onResult = { granted -> setDesiredBarcodeFormats(ScanOptions.QR_CODE)
hasCameraPermission = granted setPrompt(stringResource(id = R.string.point_to_the_qr_code))
} setBeepEnabled(false)
) setOrientationLocked(false)
}
val analyzer = QRCodeAnalyzer { result ->
result?.let { DisposableEffect(lifecycleOwner) {
try { qrLauncher.launch(scanOptions)
val nip19 = Nip19.uriToRoute(it) onDispose { }
val startingPage = when (nip19?.type) { }
Nip19.Type.USER -> "User/${nip19.hex}" }
Nip19.Type.NOTE -> "Note/${nip19.hex}"
else -> null
}
if (startingPage != null) {
onScan(startingPage)
}
} catch (e: Throwable) {
// QR can be anythign. do not throw errors.
}
}
}
DisposableEffect(key1 = true) {
launcher.launch(Manifest.permission.CAMERA)
onDispose() {
cameraProviderFuture.get().unbindAll()
cameraExecutor.shutdown()
}
}
Column() {
if (hasCameraPermission) {
AndroidView(
factory = { context ->
val previewView = PreviewView(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
bindPreview(analyzer, previewView, cameraExecutor, cameraProvider, lifecycleOwner)
}, ContextCompat.getMainExecutor(context))
return@AndroidView previewView
},
modifier = Modifier.weight(1f)
)
}
}
}
fun bindPreview(
analyzer: ImageAnalysis.Analyzer,
previewView: PreviewView,
cameraExecutor: ExecutorService,
cameraProvider: ProcessCameraProvider,
lifecycleOwner: LifecycleOwner
) {
val preview = Preview.Builder().build()
val selector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
preview.setSurfaceProvider(previewView.surfaceProvider)
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(
cameraExecutor,
analyzer
)
cameraProvider.bindToLifecycle(
lifecycleOwner,
selector,
imageAnalysis,
preview
)
}
class QRCodeAnalyzer(
private val onQrCodeScanned: (result: String?) -> Unit
) : ImageAnalysis.Analyzer {
private val scanningOptions = BarcodeScannerOptions.Builder().setBarcodeFormats(Barcode.FORMAT_QR_CODE).build()
fun scanBarcodes(inputImage: InputImage) {
BarcodeScanning.getClient(scanningOptions).process(inputImage)
.addOnSuccessListener { barcodes ->
if (barcodes.isNotEmpty()) {
onQrCodeScanned(barcodes[0].displayValue)
}
}
.addOnFailureListener {
it.printStackTrace()
}
}
@androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
override fun analyze(imageProxy: ImageProxy) {
imageProxy.image?.let { image ->
val inputImage = InputImage.fromMediaImage(image, imageProxy.imageInfo.rotationDegrees)
scanBarcodes(inputImage)
}
imageProxy.close()
}
}
@@ -59,7 +59,9 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
.fillMaxSize() .fillMaxSize()
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth().padding(10.dp), modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -67,13 +69,17 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
} }
Column( Column(
modifier = Modifier.fillMaxSize().padding(horizontal = 10.dp), modifier = Modifier
.fillMaxSize()
.padding(horizontal = 10.dp),
verticalArrangement = Arrangement.SpaceBetween verticalArrangement = Arrangement.SpaceBetween
) { ) {
if (presenting) { if (presenting) {
Row( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth().padding(horizontal = 30.dp, vertical = 10.dp) modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp, vertical = 10.dp)
) { ) {
} }
@@ -107,7 +113,9 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
Row( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth().padding(horizontal = 35.dp, vertical = 10.dp) modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 35.dp, vertical = 10.dp)
) { ) {
QrCodeDrawer("nostr:${user.pubkeyNpub()}") QrCodeDrawer("nostr:${user.pubkeyNpub()}")
} }
@@ -115,7 +123,9 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
Row( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth().padding(horizontal = 30.dp, vertical = 10.dp) modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp, vertical = 10.dp)
) { ) {
Button( Button(
onClick = { presenting = false }, onClick = { presenting = false },
@@ -132,38 +142,11 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
} }
} }
} else { } else {
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { QrCodeScanner {
Text( if (it.isNullOrEmpty()) {
stringResource(R.string.point_to_the_qr_code), presenting = true
modifier = Modifier.padding(top = 7.dp), } else {
fontWeight = FontWeight.Bold, onScan(it)
fontSize = 25.sp
)
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth().padding(30.dp)
) {
QrCodeScanner(onScan)
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth().padding(horizontal = 30.dp, vertical = 10.dp)
) {
Button(
onClick = { presenting = true },
shape = RoundedCornerShape(35.dp),
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
colors = ButtonDefaults
.buttonColors(
backgroundColor = MaterialTheme.colors.primary
)
) {
Text(text = stringResource(R.string.show_qr))
} }
} }
} }