diff --git a/README.md b/README.md index e1b5f0c..a0afb31 100644 --- a/README.md +++ b/README.md @@ -50,18 +50,21 @@ The scanner is included by calling a single composable function ```Scanner()``` ```kotlin // basic permission handling included: ScannerWithPermissions( - onScanned = { println(it); true }, // return true to disable the scanner, false to continue scanning + onScanned = { code, type -> + println("Scanned $type: $code") + true + }, // return true to disable the scanner, false to continue scanning types = listOf(CodeType.QR), cameraPosition = CameraPosition.BACK, enableTorch = false // toggle this to enable/disable the flashlight ) // or, if you handle permissions yourself: -Scanner(onScanned = { println(it); true }, types = listOf(CodeType.QR)) +Scanner(onScanned = { code, type -> println("Scanned $type: $code"); true }, types = listOf(CodeType.QR)) ``` Check out the [sample app](./sample-app) included in the repository. # Code Types Code types supported are: -Codabar, Code39, Code93, Code128, EAN8, EAN13, ITF, UPCE, Aztec, DataMatrix, PDF417, QR \ No newline at end of file +Codabar, Code39, Code93, Code128, EAN8, EAN13, ITF, UPCE, Aztec, DataMatrix, PDF417, QR diff --git a/build-logic/convention/src/main/kotlin/org/publicvalue/convention/MavenCentralPublishConventionPlugin.kt b/build-logic/convention/src/main/kotlin/org/publicvalue/convention/MavenCentralPublishConventionPlugin.kt index 11ffe50..9d33e6a 100644 --- a/build-logic/convention/src/main/kotlin/org/publicvalue/convention/MavenCentralPublishConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/org/publicvalue/convention/MavenCentralPublishConventionPlugin.kt @@ -75,13 +75,19 @@ class MavenCentralPublishConventionPlugin : Plugin { } extensions.configure { - useInMemoryPgpKeys( - getLocalProperty("SIGNING_KEY_ID") ?: System.getenv("SIGNING_KEY_ID"), - getLocalProperty("SIGNING_KEY") ?: System.getenv("SIGNING_KEY"), - getLocalProperty("SIGNING_KEY_PASSWORD") ?: System.getenv("SIGNING_KEY_PASSWORD"), - ) - val publishing = extensions.getByType() - sign(publishing.publications) + val signingKeyId = getLocalProperty("SIGNING_KEY_ID") ?: System.getenv("SIGNING_KEY_ID") + val signingKey = getLocalProperty("SIGNING_KEY") ?: System.getenv("SIGNING_KEY") + val signingPassword = getLocalProperty("SIGNING_KEY_PASSWORD") ?: System.getenv("SIGNING_KEY_PASSWORD") + + if (!signingKey.isNullOrBlank()) { + useInMemoryPgpKeys( + signingKeyId, + signingKey, + signingPassword, + ) + val publishing = extensions.getByType() + sign(publishing.publications) + } } @@ -94,4 +100,4 @@ class MavenCentralPublishConventionPlugin : Plugin { //endregion } } -} \ No newline at end of file +} diff --git a/build.gradle.kts b/build.gradle.kts index bef807a..905dd25 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,8 @@ plugins { } subprojects { - group = "io.github.kalinjul.easyqrscan" + group = "momap.driver.easyqrscan" + version = "0.7.0-local" } nexusPublishing { diff --git a/local.properties b/local.properties index e714b91..e329c34 100644 --- a/local.properties +++ b/local.properties @@ -4,5 +4,5 @@ # Location of the SDK. This is only used by Gradle. # For customization when using a Version Control System, please read the # header note. -#Mon Sep 29 13:54:55 CEST 2025 -sdk.dir=/Users/KalinJul/Library/Android/sdk +#Thu Apr 09 10:24:21 PKT 2026 +sdk.dir=/Users/sohailmac/Library/Android/sdk diff --git a/sample-app/shared/src/commonMain/kotlin/MainView.kt b/sample-app/shared/src/commonMain/kotlin/MainView.kt index 0c02f2b..9744305 100644 --- a/sample-app/shared/src/commonMain/kotlin/MainView.kt +++ b/sample-app/shared/src/commonMain/kotlin/MainView.kt @@ -81,20 +81,20 @@ fun MainView() { val scope = rememberCoroutineScope() ScannerWithPermissions( modifier = Modifier.padding(16.dp), - onScanned = { - if (lastCode == it) { return@ScannerWithPermissions false } + onScanned = { code, _ -> + if (lastCode == code) { return@ScannerWithPermissions false } if (TimeSource.Monotonic.markNow().minus(lastSnackbar) < 1.seconds) { return@ScannerWithPermissions false } snackbarJob?.cancel() lastSnackbar = TimeSource.Monotonic.markNow() snackbarJob = scope.launch { - snackbarHostState.showSnackbar(it, duration = SnackbarDuration.Short) - if (lastCode == it) { + snackbarHostState.showSnackbar(code, duration = SnackbarDuration.Short) + if (lastCode == code) { lastCode = null } } - lastCode = it + lastCode = code false // continue scanning }, types = listOf(CodeType.QR), @@ -104,4 +104,4 @@ fun MainView() { } } } -} \ No newline at end of file +} diff --git a/scanner/src/androidMain/kotlin/BarcodeAnalyzer.kt b/scanner/src/androidMain/kotlin/BarcodeAnalyzer.kt index f9aef03..aed4148 100644 --- a/scanner/src/androidMain/kotlin/BarcodeAnalyzer.kt +++ b/scanner/src/androidMain/kotlin/BarcodeAnalyzer.kt @@ -10,7 +10,7 @@ import com.google.mlkit.vision.common.InputImage class BarcodeAnalyzer( formats: Int = Barcode.FORMAT_QR_CODE, - private val onScanned: (String) -> Boolean + private val onScanned: (String, CodeType) -> Boolean ) : ImageAnalysis.Analyzer { private val options = BarcodeScannerOptions.Builder() @@ -28,9 +28,13 @@ class BarcodeAnalyzer( ) ).addOnSuccessListener { barcode -> barcode?.takeIf { it.isNotEmpty() } - ?.mapNotNull { it.rawValue } + ?.mapNotNull { scannedCode -> + val rawValue = scannedCode.rawValue ?: return@mapNotNull null + val codeType = scannedCode.format.toCodeType() ?: return@mapNotNull null + rawValue to codeType + } ?.forEach { - if (onScanned(it)) { + if (onScanned(it.first, it.second)) { scanner.close() } } @@ -39,4 +43,4 @@ class BarcodeAnalyzer( } } } -} \ No newline at end of file +} diff --git a/scanner/src/androidMain/kotlin/Codetype+toFormat.kt b/scanner/src/androidMain/kotlin/Codetype+toFormat.kt index 95ed6d2..6607853 100644 --- a/scanner/src/androidMain/kotlin/Codetype+toFormat.kt +++ b/scanner/src/androidMain/kotlin/Codetype+toFormat.kt @@ -19,4 +19,20 @@ fun List.toFormat(): Int = map { } }.fold(0) { acc, next -> acc + next -} \ No newline at end of file +} + +fun Int.toCodeType(): CodeType? = when (this) { + Barcode.FORMAT_CODABAR -> CodeType.Codabar + Barcode.FORMAT_CODE_39 -> CodeType.Code39 + Barcode.FORMAT_CODE_93 -> CodeType.Code93 + Barcode.FORMAT_CODE_128 -> CodeType.Code128 + Barcode.FORMAT_EAN_8 -> CodeType.EAN8 + Barcode.FORMAT_EAN_13 -> CodeType.EAN13 + Barcode.FORMAT_ITF -> CodeType.ITF + Barcode.FORMAT_UPC_E -> CodeType.UPCE + Barcode.FORMAT_AZTEC -> CodeType.Aztec + Barcode.FORMAT_DATA_MATRIX -> CodeType.DataMatrix + Barcode.FORMAT_PDF417 -> CodeType.PDF417 + Barcode.FORMAT_QR_CODE -> CodeType.QR + else -> null +} diff --git a/scanner/src/androidMain/kotlin/Scanner.android.kt b/scanner/src/androidMain/kotlin/Scanner.android.kt index a0d3393..dd46529 100644 --- a/scanner/src/androidMain/kotlin/Scanner.android.kt +++ b/scanner/src/androidMain/kotlin/Scanner.android.kt @@ -17,7 +17,7 @@ import com.google.accompanist.permissions.rememberPermissionState @Composable actual fun Scanner( modifier: Modifier, - onScanned: (String) -> Boolean, + onScanned: (String, CodeType) -> Boolean, types: List, cameraPosition: CameraPosition, enableTorch: Boolean, diff --git a/scanner/src/commonMain/kotlin/Scanner.kt b/scanner/src/commonMain/kotlin/Scanner.kt index 69d4268..2439034 100644 --- a/scanner/src/commonMain/kotlin/Scanner.kt +++ b/scanner/src/commonMain/kotlin/Scanner.kt @@ -15,14 +15,14 @@ import androidx.compose.ui.unit.dp * Code Scanner * * @param types Code types to scan. - * @param onScanned Called when a code was scanned. The given lambda should return true - * if scanning was successful and scanning should be aborted. - * Return false if scanning should continue. + * @param onScanned Called when a code was scanned together with its detected code type. + * The given lambda should return true if scanning was successful and + * scanning should be aborted. Return false if scanning should continue. */ @Composable expect fun Scanner( modifier: Modifier = Modifier, - onScanned: (String) -> Boolean, + onScanned: (String, CodeType) -> Boolean, types: List, cameraPosition: CameraPosition = CameraPosition.BACK, enableTorch: Boolean, @@ -32,16 +32,16 @@ expect fun Scanner( * Code Scanner with permission handling. * * @param types Code types to scan. - * @param onScanned Called when a code was scanned. The given lambda should return true - * if scanning was successful and scanning should be aborted. - * Return false if scanning should continue. + * @param onScanned Called when a code was scanned together with its detected code type. + * The given lambda should return true if scanning was successful and + * scanning should be aborted. Return false if scanning should continue. * @param permissionText Text to show if permission was denied. * @param openSettingsLabel Label to show on the "Go to settings" Button */ @Composable fun ScannerWithPermissions( modifier: Modifier = Modifier, - onScanned: (String) -> Boolean, + onScanned: (String, CodeType) -> Boolean, types: List, cameraPosition: CameraPosition = CameraPosition.BACK, enableTorch: Boolean, @@ -72,15 +72,15 @@ fun ScannerWithPermissions( * Code Scanner with permission handling. * * @param types Code types to scan. - * @param onScanned Called when a code was scanned. The given lambda should return true - * if scanning was successful and scanning should be aborted. - * Return false if scanning should continue. + * @param onScanned Called when a code was scanned together with its detected code type. + * The given lambda should return true if scanning was successful and + * scanning should be aborted. Return false if scanning should continue. * @param permissionDeniedContent Content to show if permission was denied. */ @Composable fun ScannerWithPermissions( modifier: Modifier = Modifier, - onScanned: (String) -> Boolean, + onScanned: (String, CodeType) -> Boolean, types: List, cameraPosition: CameraPosition, enableTorch: Boolean, @@ -99,4 +99,4 @@ fun ScannerWithPermissions( } else { permissionDeniedContent(permissionState) } -} \ No newline at end of file +} diff --git a/scanner/src/iosMain/kotlin/Codetype+toFormat.kt b/scanner/src/iosMain/kotlin/Codetype+toFormat.kt index c83eb50..4249f0c 100644 --- a/scanner/src/iosMain/kotlin/Codetype+toFormat.kt +++ b/scanner/src/iosMain/kotlin/Codetype+toFormat.kt @@ -9,11 +9,12 @@ import platform.AVFoundation.AVMetadataObjectTypeDataMatrixCode import platform.AVFoundation.AVMetadataObjectTypeEAN13Code import platform.AVFoundation.AVMetadataObjectTypeEAN8Code import platform.AVFoundation.AVMetadataObjectTypeITF14Code +import platform.AVFoundation.AVMetadataObjectType import platform.AVFoundation.AVMetadataObjectTypePDF417Code import platform.AVFoundation.AVMetadataObjectTypeQRCode import platform.AVFoundation.AVMetadataObjectTypeUPCECode -fun List.toFormat(): List = map { +fun List.toFormat(): List = map { when(it) { CodeType.Codabar -> if (iosVersionIsMin(15,4)) { AVMetadataObjectTypeCodabarCode } else error("AVMetadataObjectTypeCodabarCode not available on iOS ${iosVersion()}") CodeType.Code39 -> AVMetadataObjectTypeCode39Code @@ -28,4 +29,20 @@ fun List.toFormat(): List CodeType.PDF417 -> AVMetadataObjectTypePDF417Code CodeType.QR -> AVMetadataObjectTypeQRCode } -} \ No newline at end of file +} + +fun AVMetadataObjectType.toCodeType(): CodeType? = when (this) { + AVMetadataObjectTypeCodabarCode -> CodeType.Codabar + AVMetadataObjectTypeCode39Code -> CodeType.Code39 + AVMetadataObjectTypeCode93Code -> CodeType.Code93 + AVMetadataObjectTypeCode128Code -> CodeType.Code128 + AVMetadataObjectTypeEAN8Code -> CodeType.EAN8 + AVMetadataObjectTypeEAN13Code -> CodeType.EAN13 + AVMetadataObjectTypeITF14Code -> CodeType.ITF + AVMetadataObjectTypeUPCECode -> CodeType.UPCE + AVMetadataObjectTypeAztecCode -> CodeType.Aztec + AVMetadataObjectTypeDataMatrixCode -> CodeType.DataMatrix + AVMetadataObjectTypePDF417Code -> CodeType.PDF417 + AVMetadataObjectTypeQRCode -> CodeType.QR + else -> null +} diff --git a/scanner/src/iosMain/kotlin/Scanner.ios.kt b/scanner/src/iosMain/kotlin/Scanner.ios.kt index 46ae491..492bc5e 100644 --- a/scanner/src/iosMain/kotlin/Scanner.ios.kt +++ b/scanner/src/iosMain/kotlin/Scanner.ios.kt @@ -20,7 +20,7 @@ import platform.UIKit.UIApplicationOpenSettingsURLString @Composable actual fun Scanner( modifier: Modifier, - onScanned: (String) -> Boolean, // return true to abort scanning + onScanned: (String, CodeType) -> Boolean, // return true to abort scanning types: List, cameraPosition: CameraPosition, enableTorch: Boolean, @@ -39,9 +39,7 @@ actual fun Scanner( } UiScannerView( modifier = modifier, - onScanned = { - onScanned(it) - }, + onScanned = onScanned, allowedMetadataTypes = types.toFormat(), cameraPosition = cameraPosition, onStarted = { @@ -81,4 +79,4 @@ class IosMutableCameraPermissionState: MutableCameraPermissionState() { fun getCameraPermissionStatus(): CameraPermissionStatus { val authorizationStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo) return if (authorizationStatus == AVAuthorizationStatusAuthorized) CameraPermissionStatus.Granted else CameraPermissionStatus.Denied -} \ No newline at end of file +} diff --git a/scanner/src/iosMain/kotlin/ScannerView.kt b/scanner/src/iosMain/kotlin/ScannerView.kt index 02999eb..bfd183a 100644 --- a/scanner/src/iosMain/kotlin/ScannerView.kt +++ b/scanner/src/iosMain/kotlin/ScannerView.kt @@ -51,7 +51,7 @@ fun UiScannerView( // https://developer.apple.com/documentation/avfoundation/avmetadataobjecttype?language=objc allowedMetadataTypes: List, cameraPosition: CameraPosition, - onScanned: (String) -> Boolean, + onScanned: (String, CodeType) -> Boolean, onStarted: () -> Unit, ) { val coordinator = remember { @@ -105,7 +105,7 @@ class ScannerPreviewView(private val coordinator: ScannerCameraCoordinator): UIV @OptIn(ExperimentalForeignApi::class) class ScannerCameraCoordinator( - val onScanned: (String) -> Boolean, + val onScanned: (String, CodeType) -> Boolean, val onStarted: () -> Unit, val cameraPosition: CameraPosition ): AVCaptureMetadataOutputObjectsDelegateProtocol, NSObject() { @@ -181,11 +181,13 @@ class ScannerCameraCoordinator( override fun captureOutput(output: platform.AVFoundation.AVCaptureOutput, didOutputMetadataObjects: List<*>, fromConnection: platform.AVFoundation.AVCaptureConnection) { val metadataObject = didOutputMetadataObjects.firstOrNull() as? AVMetadataMachineReadableCodeObject - metadataObject?.stringValue?.let { onFound(it) } + val code = metadataObject?.stringValue ?: return + val codeType = metadataObject.type.toCodeType() ?: return + onFound(code, codeType) } - fun onFound(code: String) { - val stopScanning = onScanned(code) + fun onFound(code: String, codeType: CodeType) { + val stopScanning = onScanned(code, codeType) if (stopScanning) { captureSession.stopRunning() } diff --git a/scanner/src/jvmMain/kotlin/Scanner.jvm.kt b/scanner/src/jvmMain/kotlin/Scanner.jvm.kt index 04d9d42..9c1b1b1 100644 --- a/scanner/src/jvmMain/kotlin/Scanner.jvm.kt +++ b/scanner/src/jvmMain/kotlin/Scanner.jvm.kt @@ -7,10 +7,10 @@ import androidx.compose.ui.Modifier @Composable actual fun Scanner( modifier: Modifier, - onScanned: (String) -> Boolean, + onScanned: (String, CodeType) -> Boolean, types: List, cameraPosition: CameraPosition, enableTorch: Boolean, ) { Text("Scanner not implemented for JVM") -} \ No newline at end of file +}