diff --git a/app/build.gradle b/app/build.gradle
index b395bd09d..ade9ce843 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -130,29 +130,19 @@ dependencies {
// For QR generation
implementation 'com.google.zxing:core:3.5.1'
- implementation "androidx.camera:camera-camera2:1.2.1"
- implementation 'androidx.camera:camera-lifecycle:1.2.1'
- implementation 'androidx.camera:camera-view:1.2.1'
+ implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
// Markdown
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-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
implementation 'com.google.mlkit:language-id:17.0.4'
// Google services model the translate text
implementation 'com.google.mlkit:translate:17.0.1'
-
// Automatic memory leak detection
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5a3417398..c22a472ac 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,6 +23,7 @@
android:theme="@style/Theme.Amethyst"
android:largeHeap="true"
android:usesCleartextTraffic="true"
+ android:hardwareAccelerated="true"
tools:targetApi="33">
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt
index 1d3aa37d4..5be23dc40 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt
@@ -1,156 +1,59 @@
-package com.vitorpamplona.amethyst.ui.qrcode
-import android.Manifest
-import android.content.pm.PackageManager
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.camera.core.CameraSelector
-import androidx.camera.core.ImageAnalysis
-import androidx.camera.core.ImageProxy
-import androidx.camera.core.Preview
-import androidx.camera.lifecycle.ProcessCameraProvider
-import androidx.camera.view.PreviewView
-import androidx.compose.foundation.layout.Column
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.LifecycleOwner
-import com.google.mlkit.vision.barcode.BarcodeScannerOptions
-import com.google.mlkit.vision.barcode.BarcodeScanning
-import com.google.mlkit.vision.barcode.common.Barcode
-import com.google.mlkit.vision.common.InputImage
-import com.vitorpamplona.amethyst.service.nip19.Nip19
-import java.util.concurrent.ExecutorService
-import java.util.concurrent.Executors
-
-@Composable
-fun QrCodeScanner(onScan: (String) -> Unit) {
- val context = LocalContext.current
- val lifecycleOwner = LocalLifecycleOwner.current
- val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
-
- val cameraExecutor = Executors.newSingleThreadExecutor()
-
- var hasCameraPermission by remember {
- mutableStateOf(
- ContextCompat.checkSelfPermission(
- context,
- Manifest.permission.CAMERA
- ) == PackageManager.PERMISSION_GRANTED
- )
- }
-
- val launcher = rememberLauncherForActivityResult(
- contract = ActivityResultContracts.RequestPermission(),
- onResult = { granted ->
- hasCameraPermission = granted
- }
- )
-
- val analyzer = QRCodeAnalyzer { result ->
- result?.let {
- try {
- val nip19 = Nip19.uriToRoute(it)
- 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()
- }
-}
+package com.vitorpamplona.amethyst.ui.qrcode
+
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.stringResource
+import com.google.zxing.client.android.Intents
+import com.journeyapps.barcodescanner.ScanContract
+import com.journeyapps.barcodescanner.ScanOptions
+import com.vitorpamplona.amethyst.R
+import com.vitorpamplona.amethyst.service.nip19.Nip19
+
+@Composable
+fun QrCodeScanner(onScan: (String?) -> Unit) {
+ val lifecycleOwner = LocalLifecycleOwner.current
+
+ val parseQrResult = { it: String ->
+ try {
+ val nip19 = Nip19.uriToRoute(it)
+ val startingPage = when (nip19?.type) {
+ Nip19.Type.USER -> "User/${nip19.hex}"
+ Nip19.Type.NOTE -> "Note/${nip19.hex}"
+ else -> null
+ }
+
+ if (startingPage != null) {
+ onScan(startingPage)
+ } else {
+ onScan(null)
+ }
+ } catch (e: Throwable) {
+ // QR can be anything, do not throw errors.
+ onScan(null)
+ }
+ }
+
+ val qrLauncher =
+ rememberLauncherForActivityResult(ScanContract()) {
+ if (it.contents != null) {
+ parseQrResult(it.contents)
+ } else {
+ onScan(null)
+ }
+ }
+
+ val scanOptions = ScanOptions().apply {
+ setDesiredBarcodeFormats(ScanOptions.QR_CODE)
+ setPrompt(stringResource(id = R.string.point_to_the_qr_code))
+ setBeepEnabled(false)
+ setOrientationLocked(false)
+ addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
+ }
+
+ DisposableEffect(lifecycleOwner) {
+ qrLauncher.launch(scanOptions)
+ onDispose { }
+ }
+}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt
index eb61f2ede..936d3f005 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/ShowQRDialog.kt
@@ -59,7 +59,9 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
.fillMaxSize()
) {
Row(
- modifier = Modifier.fillMaxWidth().padding(10.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
@@ -67,13 +69,17 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
}
Column(
- modifier = Modifier.fillMaxSize().padding(horizontal = 10.dp),
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 10.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
if (presenting) {
Row(
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(
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()}")
}
@@ -115,7 +123,9 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
Row(
horizontalArrangement = Arrangement.Center,
- modifier = Modifier.fillMaxWidth().padding(horizontal = 30.dp, vertical = 10.dp)
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 30.dp, vertical = 10.dp)
) {
Button(
onClick = { presenting = false },
@@ -132,38 +142,11 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
}
}
} else {
- Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
- Text(
- stringResource(R.string.point_to_the_qr_code),
- modifier = Modifier.padding(top = 7.dp),
- fontWeight = FontWeight.Bold,
- 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))
+ QrCodeScanner {
+ if (it.isNullOrEmpty()) {
+ presenting = true
+ } else {
+ onScan(it)
}
}
}