From 545b317c50f0eadc00d4a082eb81ca8069e8f1fb Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 31 Oct 2023 19:08:08 +0000 Subject: [PATCH 001/174] feat: initial structure of OSBarcodeLib-Android References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- settings.gradle | 2 +- .../libtemplateplaceholder/MainActivity.kt | 11 ----------- .../barcode/controller/OSBRCDController.kt | 9 +++++++++ .../barcode/model/OSBRCDScanParameters.kt | 10 ++++++++++ .../libtemplateplaceholder/ExampleUnitTest.kt | 17 ----------------- .../outsystems/plugins/barcode/ScanCodeTests.kt | 9 +++++++++ 6 files changed, 29 insertions(+), 29 deletions(-) delete mode 100644 src/main/java/organizationidplaceholder/libtemplateplaceholder/MainActivity.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBRCDController.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/model/OSBRCDScanParameters.kt delete mode 100644 src/test/java/organizationidplaceholder/libtemplateplaceholder/ExampleUnitTest.kt create mode 100644 src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt diff --git a/settings.gradle b/settings.gradle index c50e98e..d2151be 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = "LibTemplatePlaceholder" \ No newline at end of file +rootProject.name = "OSBarcodeLib" \ No newline at end of file diff --git a/src/main/java/organizationidplaceholder/libtemplateplaceholder/MainActivity.kt b/src/main/java/organizationidplaceholder/libtemplateplaceholder/MainActivity.kt deleted file mode 100644 index a129d33..0000000 --- a/src/main/java/organizationidplaceholder/libtemplateplaceholder/MainActivity.kt +++ /dev/null @@ -1,11 +0,0 @@ -package organizationidplaceholder.libtemplateplaceholder - -import androidx.appcompat.app.AppCompatActivity -import android.os.Bundle - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBRCDController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBRCDController.kt new file mode 100644 index 0000000..8a73c4f --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBRCDController.kt @@ -0,0 +1,9 @@ +package com.outsystems.plugins.barcode.controller + +class OSBRCDController { + + fun scanCode() { + //TODO implement scanCode feature + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBRCDScanParameters.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBRCDScanParameters.kt new file mode 100644 index 0000000..db66917 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBRCDScanParameters.kt @@ -0,0 +1,10 @@ +package com.outsystems.plugins.barcode.model + +data class OSBRCDScanParameters( + val scanInstructions: String?, + val cameraDirection: Int?, + val scanOrientation: Int?, + val scanButton: Boolean?, + val scanButtonText: String?, + val scanningLibrary: String? +) \ No newline at end of file diff --git a/src/test/java/organizationidplaceholder/libtemplateplaceholder/ExampleUnitTest.kt b/src/test/java/organizationidplaceholder/libtemplateplaceholder/ExampleUnitTest.kt deleted file mode 100644 index 9ac43a9..0000000 --- a/src/test/java/organizationidplaceholder/libtemplateplaceholder/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package organizationidplaceholder.libtemplateplaceholder - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt new file mode 100644 index 0000000..d72e724 --- /dev/null +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -0,0 +1,9 @@ +package com.outsystems.plugins.barcode + +import org.junit.Test + +class ScanCodeTests { + + @Test + fun scanCodeTest() { } +} \ No newline at end of file From 8ab59856d1a69830222ee93e3687121cfde13588 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 2 Nov 2023 10:30:16 +0000 Subject: [PATCH 002/174] feat: update SDKs, Gradle and Kotlin versions to match MABS 10 Context: MABS 10 brings these updates (https://github.com/OutSystems/private-NativeShell-Template/blob/9c1989499ebc1578dc39e167240856fe229ba52c/CHANGELOG.md?plain=1#L48C147-L48C198), so our Android libraries should be using the same versions. References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- build.gradle | 14 ++++++++------ gradle/wrapper/gradle-wrapper.properties | 2 +- src/main/AndroidManifest.xml | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 0802323..8a6bd32 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = "1.5.21" + ext.kotlin_version = "1.9.10" ext.jacocoVersion = '0.8.7' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:8.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jacoco:org.jacoco.core:$jacocoVersion" } @@ -17,11 +17,13 @@ apply plugin: "kotlin-android" apply plugin: "jacoco" android { - compileSdk 32 + + namespace "com.outsystems.plugins.barcode" + compileSdk 34 defaultConfig { minSdk 26 - targetSdk 32 + targetSdk 34 versionCode 1 versionName "1.0" @@ -45,8 +47,8 @@ android { task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) { reports { - xml.enabled = true - html.enabled = true + //xml.enabled = true + //html.enabled = true } def fileFilter = ['**/BuildConfig.*', '**/Manifest*.*'] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8a7aeff..df49134 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Apr 08 08:58:08 WEST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME \ No newline at end of file diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 4045313..a3ac88e 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="com.outsystems.plugins.barcode"> Date: Fri, 3 Nov 2023 13:04:41 +0000 Subject: [PATCH 003/174] feat: first version of ScanBarcode feature using zxing Context: This PR includes a first version of the scanBarcode feature, which uses CameraX, Jetpack Compose, and ZXing to implement the scanner. References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- build.gradle | 33 +++++++ src/main/AndroidManifest.xml | 5 + .../controller/OSBARCBarcodeAnalyzer.kt | 64 +++++++++++++ .../barcode/controller/OSBARCController.kt | 43 +++++++++ .../barcode/controller/OSBRCDController.kt | 9 -- ...nParameters.kt => OSBARCScanParameters.kt} | 2 +- .../barcode/view/OSBARCScannerActivity.kt | 94 +++++++++++++++++++ .../plugins/barcode/view/ui.theme/Color.kt | 11 +++ .../plugins/barcode/view/ui.theme/Theme.kt | 70 ++++++++++++++ .../plugins/barcode/view/ui.theme/Type.kt | 34 +++++++ src/main/res/values/strings.xml | 1 + 11 files changed, 356 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt delete mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBRCDController.kt rename src/main/kotlin/com/outsystems/plugins/barcode/model/{OSBRCDScanParameters.kt => OSBARCScanParameters.kt} (87%) create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Type.kt diff --git a/build.gradle b/build.gradle index 8a6bd32..8021aa8 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,17 @@ android { "outputs/code-coverage/connected/*coverage.ec" ])) } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion '1.5.3' + } + packaging { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } } repositories { @@ -75,7 +86,29 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' + implementation platform('androidx.compose:compose-bom:2023.03.00') + implementation 'androidx.compose.ui:ui' + implementation 'androidx.compose.ui:ui-graphics' + implementation 'androidx.compose.ui:ui-tooling-preview' + implementation 'androidx.compose.material3:material3' + implementation platform('androidx.compose:compose-bom:2023.03.00') + implementation platform('androidx.compose:compose-bom:2023.03.00') testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + implementation "androidx.activity:activity-compose:1.8.0" + implementation "androidx.camera:camera-camera2:1.3.0" + implementation 'androidx.camera:camera-lifecycle:1.3.0' + implementation 'androidx.camera:camera-view:1.3.0' + implementation 'com.google.zxing:core:3.4.1' + androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') + androidTestImplementation 'androidx.compose.ui:ui-test-junit4' + androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') + androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') + debugImplementation 'androidx.compose.ui:ui-tooling' + debugImplementation 'androidx.compose.ui:ui-test-manifest' + + } diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index a3ac88e..5c82f99 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -9,6 +9,11 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.LibTemplatePlaceholder"> + diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt new file mode 100644 index 0000000..70a90f8 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -0,0 +1,64 @@ +package com.outsystems.plugins.barcode.controller + +import android.graphics.Bitmap +import android.graphics.Matrix +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType +import com.google.zxing.MultiFormatReader +import com.google.zxing.RGBLuminanceSource +import com.google.zxing.common.HybridBinarizer +import java.lang.Exception +import java.nio.ByteBuffer + +class OSBARCBarcodeAnalyzer( + private val onBarcodeScanned: (String) -> Unit +): ImageAnalysis.Analyzer { + + override fun analyze(image: ImageProxy) { + try { + // calculate rotation (necessary for 1D barcodes) + val rotationDegrees = image.imageInfo.rotationDegrees + var imageBitmap = image.toBitmap() + + // Rotate the image if it's in portrait mode (rotation = 90 or 270 degrees) + if (rotationDegrees == 90 || rotationDegrees == 270) { + // Create a matrix for rotation + val matrix = Matrix() + matrix.postRotate(rotationDegrees.toFloat()) + + // Rotate the image + imageBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, imageBitmap.width, imageBitmap.height, matrix, true) + } + + val width = imageBitmap.width + val height = imageBitmap.height + val pixels = IntArray(width * height) + imageBitmap.getPixels(pixels, 0, width, 0, 0, width, height) + + val source = RGBLuminanceSource(width, height, pixels) + val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) + val result = MultiFormatReader().apply { + setHints( + mapOf( + DecodeHintType.TRY_HARDER to arrayListOf(true) + ) + ) + }.decode(binaryBitmap) + onBarcodeScanned(result.text) + } catch (e: Exception) { + e.printStackTrace() + } finally { + image.close() + } + } + + private fun ByteBuffer.toByteArray(): ByteArray { + rewind() + return ByteArray(remaining()).also { + get(it) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt new file mode 100644 index 0000000..81040c7 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -0,0 +1,43 @@ +package com.outsystems.plugins.barcode.controller + +import android.app.Activity +import android.content.Intent +import com.outsystems.plugins.barcode.model.OSBARCScanParameters +import com.outsystems.plugins.barcode.view.OSBARCScannerActivity + +class OSBARCController { + + companion object { + private const val BACK_CAMERA = 1 + private const val FRONT_CAMERA = 2 + private const val SCAN_REQUEST_CODE = 222 + } + + fun scanCode(activity: Activity, parameters: OSBARCScanParameters) { + /* + val integrator = IntentIntegrator(activity); + integrator.setOrientationLocked(false); + + if (parameters.cameraDirection == BACK_CAMERA) { + integrator.setCameraId(0); + } else if (parameters.cameraDirection == FRONT_CAMERA) { + integrator.setCameraId(1); + } + + integrator.captureActivity = OSBARCScannerActivity::class.java + //integrator.captureActivity = OSBARCScannerActivityXML::class.java + integrator.addExtra("SCAN_INSTRUCTIONS", parameters.scanInstructions) + integrator.addExtra("SCAN_ORIENTATION", parameters.scanOrientation) + //integrator.addExtra("SCAN_LINE", scanLine) + integrator.addExtra("SCAN_BUTTON", parameters.scanButton) + integrator.addExtra("SCAN_TEXT", parameters.scanButtonText) + integrator.initiateScan() + + */ + + val scanningIntent = Intent(activity, OSBARCScannerActivity::class.java) + activity.startActivityForResult(scanningIntent, SCAN_REQUEST_CODE) + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBRCDController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBRCDController.kt deleted file mode 100644 index 8a73c4f..0000000 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBRCDController.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.outsystems.plugins.barcode.controller - -class OSBRCDController { - - fun scanCode() { - //TODO implement scanCode feature - } - -} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBRCDScanParameters.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt similarity index 87% rename from src/main/kotlin/com/outsystems/plugins/barcode/model/OSBRCDScanParameters.kt rename to src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt index db66917..ef362de 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBRCDScanParameters.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt @@ -1,6 +1,6 @@ package com.outsystems.plugins.barcode.model -data class OSBRCDScanParameters( +data class OSBARCScanParameters( val scanInstructions: String?, val cameraDirection: Int?, val scanOrientation: Int?, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt new file mode 100644 index 0000000..335b82f --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -0,0 +1,94 @@ +package com.outsystems.plugins.barcode.view + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST +import androidx.compose.ui.Modifier +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer +import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme +import java.lang.Exception + +class OSBARCScannerActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + BarcodeScannerTheme { + + val lifecycleOwner = LocalLifecycleOwner.current + val context = LocalContext.current + val cameraProviderFuture = remember { + ProcessCameraProvider.getInstance(context) + } + var barcode by remember { + mutableStateOf("") + } + + Column ( + modifier = Modifier.fillMaxSize() + ) { + AndroidView( + factory = { context -> + val previewView = PreviewView(context) + val preview = Preview.Builder().build() + val selector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + preview.setSurfaceProvider(previewView.surfaceProvider) + val imageAnalysis = ImageAnalysis.Builder() + .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) + .build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(context), + OSBARCBarcodeAnalyzer { result -> + barcode = result + } + ) + try { + cameraProviderFuture.get().bindToLifecycle( + lifecycleOwner, + selector, + preview, + imageAnalysis + ) + } catch (e: Exception) { + e.printStackTrace() + } + previewView + }, + modifier = Modifier.weight(1f) + ) + Text( + text = "Barcode text is $barcode", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier + .fillMaxWidth() + .padding(64.dp) + ) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt new file mode 100644 index 0000000..12c1a50 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt @@ -0,0 +1,11 @@ +package com.outsystems.plugins.barcode.view.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt new file mode 100644 index 0000000..c96bd85 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt @@ -0,0 +1,70 @@ +package com.outsystems.plugins.barcode.view.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun BarcodeScannerTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Type.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Type.kt new file mode 100644 index 0000000..551c753 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Type.kt @@ -0,0 +1,34 @@ +package com.outsystems.plugins.barcode.view.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 23a95d7..beefedb 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ LibTemplatePlaceholder + OSBARCScannerActivity \ No newline at end of file From 12b101b5c47d3f41dba994eebf43f6da0f4ee813 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 3 Nov 2023 19:07:08 +0000 Subject: [PATCH 004/174] feat: add permission request for the camera References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- src/main/AndroidManifest.xml | 2 +- .../barcode/controller/OSBARCController.kt | 37 ++--- .../barcode/view/OSBARCScannerActivity.kt | 135 +++++++++++------- 3 files changed, 99 insertions(+), 75 deletions(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 5c82f99..d4bc912 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - + - val previewView = PreviewView(context) - val preview = Preview.Builder().build() - val selector = CameraSelector.Builder() - .requireLensFacing(CameraSelector.LENS_FACING_BACK) - .build() - preview.setSurfaceProvider(previewView.surfaceProvider) - val imageAnalysis = ImageAnalysis.Builder() - .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) - .build() - imageAnalysis.setAnalyzer( - ContextCompat.getMainExecutor(context), - OSBARCBarcodeAnalyzer { result -> - barcode = result - } - ) - try { - cameraProviderFuture.get().bindToLifecycle( - lifecycleOwner, - selector, - preview, - imageAnalysis - ) - } catch (e: Exception) { - e.printStackTrace() - } - previewView - }, - modifier = Modifier.weight(1f) - ) - Text( - text = "Barcode text is $barcode", - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier - .fillMaxWidth() - .padding(64.dp) - ) - } + // permissions + val requestPermissionLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + // do nothing, continue + } else { + this.setResult(CAMERA_PERMISSION_DENIED_RESULT_CODE) + this.finish() } } + SideEffect { + requestPermissionLauncher.launch(Manifest.permission.CAMERA) + } + + // rest of the UI + val cameraProviderFuture = remember { + ProcessCameraProvider.getInstance(context) + } + var barcode by remember { + mutableStateOf("") + } + + Column ( + modifier = Modifier.fillMaxSize() + ) { + AndroidView( + factory = { context -> + val previewView = PreviewView(context) + val preview = Preview.Builder().build() + val selector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + preview.setSurfaceProvider(previewView.surfaceProvider) + val imageAnalysis = ImageAnalysis.Builder() + .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) + .build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(context), + OSBARCBarcodeAnalyzer { result -> + barcode = result + } + ) + try { + cameraProviderFuture.get().bindToLifecycle( + lifecycleOwner, + selector, + preview, + imageAnalysis + ) + } catch (e: Exception) { + e.printStackTrace() + } + previewView + }, + modifier = Modifier.weight(1f) + ) + Text( + text = "Barcode text is $barcode", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier + .fillMaxWidth() + .padding(64.dp) + ) + } } + } \ No newline at end of file From 08efb6a8b965b5645aee7c243820d90ef9fc102b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 11:25:20 +0000 Subject: [PATCH 005/174] feat: add OSBARCError class for library errors References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .../plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt | 8 -------- .../com/outsystems/plugins/barcode/model/OSBARCError.kt | 6 ++++++ 2 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index 70a90f8..7af1212 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -10,7 +10,6 @@ import com.google.zxing.MultiFormatReader import com.google.zxing.RGBLuminanceSource import com.google.zxing.common.HybridBinarizer import java.lang.Exception -import java.nio.ByteBuffer class OSBARCBarcodeAnalyzer( private val onBarcodeScanned: (String) -> Unit @@ -54,11 +53,4 @@ class OSBARCBarcodeAnalyzer( } } - private fun ByteBuffer.toByteArray(): ByteArray { - rewind() - return ByteArray(remaining()).also { - get(it) - } - } - } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt new file mode 100644 index 0000000..371e634 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt @@ -0,0 +1,6 @@ +package com.outsystems.plugins.barcode.model + +enum class OSBARCError(val code: Int, val description: String) { + CAMERA_PERMISSION_DENIED_ERROR(1, "Couldn't access camera. Check your camera permissions and try again."), + INVALID_PARAMETERS_ERROR(2, "Barcode parameters are invalid.") +} \ No newline at end of file From 8e9c7e54ebd7b9bd6049e605681b181c5b8b0e89 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 11:36:15 +0000 Subject: [PATCH 006/174] refactor: include missing parameters in OSBARCScanParameters References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .../outsystems/plugins/barcode/model/OSBARCScanParameters.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt index ef362de..12b54d2 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt @@ -5,6 +5,7 @@ data class OSBARCScanParameters( val cameraDirection: Int?, val scanOrientation: Int?, val scanButton: Boolean?, - val scanButtonText: String?, - val scanningLibrary: String? + val scanText: String?, + val hint: Int?, + val androidScanningLibrary: String? ) \ No newline at end of file From 914b50c888ad51ee277e7873c8b29727e5cb7b46 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 12:50:46 +0000 Subject: [PATCH 007/174] feat: properly handle all possible results of scanning feature Context: We need to properly handle all possible outcomes of the scanning feature: success, cancelled, or some error that happened while scanning. References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .../controller/OSBARCBarcodeAnalyzer.kt | 11 ++++- .../barcode/controller/OSBARCController.kt | 37 +++++++++++++++- .../plugins/barcode/model/OSBARCError.kt | 4 +- .../barcode/view/OSBARCScannerActivity.kt | 42 ++++++++++--------- 4 files changed, 70 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index 7af1212..f8c0920 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -2,6 +2,7 @@ package com.outsystems.plugins.barcode.controller import android.graphics.Bitmap import android.graphics.Matrix +import android.util.Log import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import com.google.zxing.BinaryBitmap @@ -12,9 +13,14 @@ import com.google.zxing.common.HybridBinarizer import java.lang.Exception class OSBARCBarcodeAnalyzer( - private val onBarcodeScanned: (String) -> Unit + private val onBarcodeScanned: (String) -> Unit, + private val onScanningError: () -> Unit ): ImageAnalysis.Analyzer { + companion object { + private const val LOG_TAG = "OSBARCBarcodeAnalyzer" + } + override fun analyze(image: ImageProxy) { try { // calculate rotation (necessary for 1D barcodes) @@ -47,7 +53,8 @@ class OSBARCBarcodeAnalyzer( }.decode(binaryBitmap) onBarcodeScanned(result.text) } catch (e: Exception) { - e.printStackTrace() + e.message?.let { Log.e(LOG_TAG, it) } + onScanningError } finally { image.close() } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt index 03b82f0..c50334d 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -2,6 +2,7 @@ package com.outsystems.plugins.barcode.controller import android.app.Activity import android.content.Intent +import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.OSBARCScannerActivity @@ -17,6 +18,9 @@ class OSBARCController { private const val SCAN_BUTTON = "SCAN_BUTTON" private const val SCAN_BUTTON_TEXT = "SCAN_BUTTON_TEXT" private const val SCAN_LIBRARY = "SCAN_LIBRARY" + private const val SCAN_RESULT = "scanResult" + private const val CAMERA_PERMISSION_DENIED_RESULT_CODE = 1 + private const val SCANNING_EXCEPTION_RESULT_CODE = 2 } fun scanCode(activity: Activity, parameters: OSBARCScanParameters) { @@ -25,10 +29,39 @@ class OSBARCController { putExtra(CAMERA_DIRECTION, parameters.cameraDirection) putExtra(SCAN_ORIENTATION, parameters.scanOrientation) putExtra(SCAN_BUTTON, parameters.scanButton) - putExtra(SCAN_BUTTON_TEXT, parameters.scanButtonText) - putExtra(SCAN_LIBRARY, parameters.scanningLibrary) + putExtra(SCAN_BUTTON_TEXT, parameters.scanText) + putExtra(SCAN_LIBRARY, parameters.androidScanningLibrary) } activity.startActivityForResult(scanningIntent, SCAN_REQUEST_CODE) } + fun handleActivityResult( + requestCode: Int, + resultCode: Int, + intent: Intent?, + onSuccess: (String) -> Unit, + onError: (OSBARCError) -> Unit + ) { + when (requestCode) { + SCAN_REQUEST_CODE -> { + when (resultCode) { + Activity.RESULT_OK -> { + val result = intent?.extras?.getString(SCAN_RESULT) + if (result.isNullOrEmpty()) { + onError(OSBARCError.SCANNING_GENERAL_ERROR) + return + } + onSuccess(result) + } + Activity.RESULT_CANCELED -> + onError(OSBARCError.SCAN_CANCELLED_ERROR) + CAMERA_PERMISSION_DENIED_RESULT_CODE -> + onError(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR) + SCANNING_EXCEPTION_RESULT_CODE -> + onError(OSBARCError.SCANNING_GENERAL_ERROR) + } + } + } + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt index 371e634..ef020c6 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt @@ -2,5 +2,7 @@ package com.outsystems.plugins.barcode.model enum class OSBARCError(val code: Int, val description: String) { CAMERA_PERMISSION_DENIED_ERROR(1, "Couldn't access camera. Check your camera permissions and try again."), - INVALID_PARAMETERS_ERROR(2, "Barcode parameters are invalid.") + INVALID_PARAMETERS_ERROR(2, "Barcode parameters are invalid."), + SCAN_CANCELLED_ERROR(3, "Barcode scanning was cancelled."), + SCANNING_GENERAL_ERROR(4, "There was an error scanning the barcode.") } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 884931a..c090f60 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -1,7 +1,9 @@ package com.outsystems.plugins.barcode.view import android.Manifest +import android.content.Intent import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.camera.core.CameraSelector @@ -11,7 +13,6 @@ import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -21,14 +22,9 @@ import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.compose.ui.Modifier import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.SideEffect import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer @@ -38,7 +34,11 @@ import java.lang.Exception class OSBARCScannerActivity : ComponentActivity() { companion object { - private const val CAMERA_PERMISSION_DENIED_RESULT_CODE = 101 + private const val SCAN_SUCCESS_RESULT_CODE = -1 + private const val CAMERA_PERMISSION_DENIED_RESULT_CODE = 1 + private const val SCANNING_EXCEPTION_RESULT_CODE = 2 + private const val SCAN_RESULT = "scanResult" + private const val LOG_TAG = "OSBARCScannerActivity" } override fun onCreate(savedInstanceState: Bundle?) { @@ -95,9 +95,19 @@ class OSBARCScannerActivity : ComponentActivity() { .build() imageAnalysis.setAnalyzer( ContextCompat.getMainExecutor(context), - OSBARCBarcodeAnalyzer { result -> - barcode = result - } + OSBARCBarcodeAnalyzer( + { result -> + barcode = result + val resultIntent = Intent() + resultIntent.putExtra(SCAN_RESULT, result) + setResult(SCAN_SUCCESS_RESULT_CODE, resultIntent) + finish() + }, + { + setResult(SCANNING_EXCEPTION_RESULT_CODE) + finish() + } + ) ) try { cameraProviderFuture.get().bindToLifecycle( @@ -107,20 +117,14 @@ class OSBARCScannerActivity : ComponentActivity() { imageAnalysis ) } catch (e: Exception) { - e.printStackTrace() + e.message?.let { Log.e(LOG_TAG, it) } + setResult(SCANNING_EXCEPTION_RESULT_CODE) + finish() } previewView }, modifier = Modifier.weight(1f) ) - Text( - text = "Barcode text is $barcode", - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier - .fillMaxWidth() - .padding(64.dp) - ) } } From fcfe53d3dc74ced5da37e09bfdd36413bdf7bb01 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 13:04:43 +0000 Subject: [PATCH 008/174] refactor: multiple refactors References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .../barcode/controller/OSBARCBarcodeAnalyzer.kt | 10 +++++----- .../plugins/barcode/controller/OSBARCController.kt | 2 ++ .../plugins/barcode/view/OSBARCScannerActivity.kt | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index f8c0920..0a4c154 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -23,20 +23,20 @@ class OSBARCBarcodeAnalyzer( override fun analyze(image: ImageProxy) { try { - // calculate rotation (necessary for 1D barcodes) - val rotationDegrees = image.imageInfo.rotationDegrees var imageBitmap = image.toBitmap() - // Rotate the image if it's in portrait mode (rotation = 90 or 270 degrees) + // rotate the image if it's in portrait mode (rotation = 90 or 270 degrees) + val rotationDegrees = image.imageInfo.rotationDegrees if (rotationDegrees == 90 || rotationDegrees == 270) { - // Create a matrix for rotation + // create a matrix for rotation val matrix = Matrix() matrix.postRotate(rotationDegrees.toFloat()) - // Rotate the image + // actually rotate the image imageBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, imageBitmap.width, imageBitmap.height, matrix, true) } + // scan image using zxing val width = imageBitmap.width val height = imageBitmap.height val pixels = IntArray(width * height) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt index c50334d..c7dd2a9 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -17,6 +17,7 @@ class OSBARCController { private const val SCAN_ORIENTATION = "SCAN_ORIENTATION" private const val SCAN_BUTTON = "SCAN_BUTTON" private const val SCAN_BUTTON_TEXT = "SCAN_BUTTON_TEXT" + private const val SCAN_HINT = "SCAN_HINT" private const val SCAN_LIBRARY = "SCAN_LIBRARY" private const val SCAN_RESULT = "scanResult" private const val CAMERA_PERMISSION_DENIED_RESULT_CODE = 1 @@ -30,6 +31,7 @@ class OSBARCController { putExtra(SCAN_ORIENTATION, parameters.scanOrientation) putExtra(SCAN_BUTTON, parameters.scanButton) putExtra(SCAN_BUTTON_TEXT, parameters.scanText) + putExtra(SCAN_HINT, parameters.hint) putExtra(SCAN_LIBRARY, parameters.androidScanningLibrary) } activity.startActivityForResult(scanningIntent, SCAN_REQUEST_CODE) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index c090f60..c441037 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -87,7 +87,7 @@ class OSBARCScannerActivity : ComponentActivity() { val previewView = PreviewView(context) val preview = Preview.Builder().build() val selector = CameraSelector.Builder() - .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .requireLensFacing(CameraSelector.LENS_FACING_BACK) // temporary .build() preview.setSurfaceProvider(previewView.surfaceProvider) val imageAnalysis = ImageAnalysis.Builder() From 874837d365a3cbf7114781474db55f9f2e6169d2 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 14:07:51 +0000 Subject: [PATCH 009/174] refactor: remove unnecessary constants References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .../outsystems/plugins/barcode/controller/OSBARCController.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt index c7dd2a9..53d5a4c 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -9,8 +9,6 @@ import com.outsystems.plugins.barcode.view.OSBARCScannerActivity class OSBARCController { companion object { - private const val BACK_CAMERA = 1 - private const val FRONT_CAMERA = 2 private const val SCAN_REQUEST_CODE = 112 private const val SCAN_INSTRUCTIONS = "SCAN_INSTRUCTIONS" private const val CAMERA_DIRECTION = "CAMERA_DIRECTION" From cf1c7214a7c56ef2fd2ba18d63d03cddec44a999 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 14:18:08 +0000 Subject: [PATCH 010/174] feat: setup Azure pipeline for library Context: This pipeline will be used to build the library and publish the version defined in the pom.xml file in our Azure repository. References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- azure-pipelines.yml | 73 +++++++++++++++++++++++++++++++++++++++++++++ pom.xml | 11 +++++++ 2 files changed, 84 insertions(+) create mode 100644 azure-pipelines.yml create mode 100644 pom.xml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..3172c9c --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,73 @@ +# Android +# Build your Android project with Gradle. +# Add steps that test, sign, and distribute the APK, save build artifacts, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/android + +parameters: +- name: publishBuild + displayName: Publish new build? + type: boolean + default: false + +trigger: +- main + +pool: + vmImage: ubuntu-latest + +steps: +- task: SonarCloudPrepare@1 + inputs: + SonarCloud: 'SonarCloud' + organization: 'outsystemsrd' + scannerMode: 'CLI' + configMode: 'file' +- task: Gradle@2 + displayName: Build Project + inputs: + workingDirectory: '' + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx4096M' + javaHomeOption: 'JDKVersion' + jdkVersionOption: '1.11' + jdkArchitectureOption: 'x64' + publishJUnitResults: true + testResultsFiles: '**/TEST-*.xml' + tasks: 'clean build' +- task: CmdLine@2 + displayName: Tests + inputs: + script: | + ./gradlew jacocoTestReport +- task: PublishCodeCoverageResults@1 + inputs: + codeCoverageTool: "JaCoCo" + summaryFileLocation: $(System.DefaultWorkingDirectory)/**/jacocoTestReport/jacocoTestReport.xml + pathToSources: $(System.DefaultWorkingDirectory)/src/main/kotlin + reportDirectory: $(System.DefaultWorkingDirectory)/**/jacocoTestReport/html/ + additionalCodeCoverageFiles: $(System.DefaultWorkingDirectory)/**/jacoco/*.exec + failIfCoverageEmpty: true +- task: CmdLine@2 + displayName: Validate pom.xml + inputs: + script: | + /usr/bin/mvn -version + /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom +- task: MavenAuthenticate@0 + displayName: Authenticate in public repo + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + inputs: + artifactsFeeds: 'PublicArtifactRepository' +- task: Bash@3 + displayName: Deploy file + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + inputs: + targetType: 'inline' + script: | + /usr/bin/mvn deploy:deploy-file \ + -DpomFile=/home/vsts/work/1/s/pom.xml \ + -DgeneratePom=true \ + -Dfile=build/outputs/aar/OSBarcodeLib-release.aar \ + -Dpackaging=aar \ + -DrepositoryId=PublicArtifactRepository \ + -Durl=https://pkgs.dev.azure.com/OutSystemsRD/9e79bc5b-69b2-4476-9ca5-d67594972a52/_packaging/PublicArtifactRepository/maven/v1 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..20acd37 --- /dev/null +++ b/pom.xml @@ -0,0 +1,11 @@ + + + + 4.0.0 + com.github.outsystems + osbarcode-android + 0.0.1 + From a7a0f22fca36f4be6ac5c51c08f6b22c2f63b1f7 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 14:27:28 +0000 Subject: [PATCH 011/174] fix: use java 17 instead of 11 Context: The version of the Android Gradle plugin we're using, that matches the one set by MABSA 10, requires Java 17 to run. References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3172c9c..32823d0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,7 +29,7 @@ steps: gradleWrapperFile: 'gradlew' gradleOptions: '-Xmx4096M' javaHomeOption: 'JDKVersion' - jdkVersionOption: '1.11' + jdkVersionOption: '1.17' jdkArchitectureOption: 'x64' publishJUnitResults: true testResultsFiles: '**/TEST-*.xml' From 774694e9e6db48c9d62ae1a35f0b75be3bbb8638 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 14:31:28 +0000 Subject: [PATCH 012/174] fix: remove unused activity References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- src/main/AndroidManifest.xml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index d4bc912..fec30f6 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -14,15 +14,6 @@ android:exported="false" android:label="@string/title_activity_osbarcscanner" android:theme="@style/Theme.LibTemplatePlaceholder" /> - - - - - - - \ No newline at end of file From c9b124229ac238428895641be3ad9fd8e12b7ad8 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 14:38:23 +0000 Subject: [PATCH 013/174] refactor: remove unnecessary steps Context: We use SonarCloud for test coverage, so these steps are unnecessary. References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- azure-pipelines.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 32823d0..b3a40bf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -34,19 +34,6 @@ steps: publishJUnitResults: true testResultsFiles: '**/TEST-*.xml' tasks: 'clean build' -- task: CmdLine@2 - displayName: Tests - inputs: - script: | - ./gradlew jacocoTestReport -- task: PublishCodeCoverageResults@1 - inputs: - codeCoverageTool: "JaCoCo" - summaryFileLocation: $(System.DefaultWorkingDirectory)/**/jacocoTestReport/jacocoTestReport.xml - pathToSources: $(System.DefaultWorkingDirectory)/src/main/kotlin - reportDirectory: $(System.DefaultWorkingDirectory)/**/jacocoTestReport/html/ - additionalCodeCoverageFiles: $(System.DefaultWorkingDirectory)/**/jacoco/*.exec - failIfCoverageEmpty: true - task: CmdLine@2 displayName: Validate pom.xml inputs: From 350488e2e38916406dd67e6b718d0a2e212b3779 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 14:59:41 +0000 Subject: [PATCH 014/174] refactor: remove unnecessary resources References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .../drawable-v24/ic_launcher_foreground.xml | 30 ---- .../res/drawable/ic_launcher_background.xml | 170 ------------------ src/main/res/layout/activity_main.xml | 18 -- .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 1404 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 2898 -> 0 bytes src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 982 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 1772 -> 0 bytes src/main/res/mipmap-xhdpi/ic_launcher.webp | Bin 1900 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 3918 -> 0 bytes src/main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 2884 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 5914 -> 0 bytes src/main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 3844 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 7778 -> 0 bytes src/main/res/values-night/themes.xml | 16 -- src/main/res/values/colors.xml | 10 -- src/main/res/values/strings.xml | 4 - src/main/res/values/themes.xml | 16 -- 19 files changed, 274 deletions(-) delete mode 100644 src/main/res/drawable-v24/ic_launcher_foreground.xml delete mode 100644 src/main/res/drawable/ic_launcher_background.xml delete mode 100644 src/main/res/layout/activity_main.xml delete mode 100644 src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 src/main/res/mipmap-xxxhdpi/ic_launcher.webp delete mode 100644 src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp delete mode 100644 src/main/res/values-night/themes.xml delete mode 100644 src/main/res/values/colors.xml delete mode 100644 src/main/res/values/strings.xml delete mode 100644 src/main/res/values/themes.xml diff --git a/src/main/res/drawable-v24/ic_launcher_foreground.xml b/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d1..0000000 --- a/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/res/drawable/ic_launcher_background.xml b/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9..0000000 --- a/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/res/layout/activity_main.xml b/src/main/res/layout/activity_main.xml deleted file mode 100644 index 4fc2444..0000000 --- a/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index eca70cf..0000000 --- a/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index eca70cf..0000000 --- a/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/main/res/mipmap-hdpi/ic_launcher.webp b/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78ecd372343283f4157dcfd918ec5165bb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG diff --git a/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2898 zcmV-Y3$650Nk&FW3jhFDMM6+kP&il$0000G0000-002h-06|PpNWB9900E$G+qN-D z+81ABX7q?;bwx%xBg?kcwr$(C-Tex-ZCkHUw(Y9#+`E5-zuONG5fgw~E2WDng@Bc@ z24xy+R1n%~6xI#u9vJ8zREI)sb<&Il(016}Z~V1n^PU3-_H17A*Bf^o)&{_uBv}Py zulRfeE8g(g6HFhk_?o_;0@tz?1I+l+Y#Q*;RVC?(ud`_cU-~n|AX-b`JHrOIqn(-t&rOg-o`#C zh0LPxmbOAEb;zHTu!R3LDh1QO zZTf-|lJNUxi-PpcbRjw3n~n-pG;$+dIF6eqM5+L();B2O2tQ~|p{PlpNcvDbd1l%c zLtXn%lu(3!aNK!V#+HNn_D3lp z2%l+hK-nsj|Bi9;V*WIcQRTt5j90A<=am+cc`J zTYIN|PsYAhJ|=&h*4wI4ebv-C=Be#u>}%m;a{IGmJDU`0snWS&$9zdrT(z8#{OZ_Y zxwJx!ZClUi%YJjD6Xz@OP8{ieyJB=tn?>zaI-4JN;rr`JQbb%y5h2O-?_V@7pG_+y z(lqAsqYr!NyVb0C^|uclHaeecG)Sz;WV?rtoqOdAAN{j%?Uo%owya(F&qps@Id|Of zo@~Y-(YmfB+chv^%*3g4k3R0WqvuYUIA+8^SGJ{2Bl$X&X&v02>+0$4?di(34{pt* zG=f#yMs@Y|b&=HyH3k4yP&goF2LJ#tBLJNNDo6lG06r}ghC-pC4Q*=x3;|+W04zte zAl>l4kzUBQFYF(E`KJy?ZXd1tnfbH+Z~SMmA21KokJNs#eqcXWKUIC>{TuoKe^vhF z);H)o`t9j~`$h1D`#bxe@E`oE`cM9w(@)5Bp8BNukIwM>wZHfd0S;5bcXA*5KT3bj zc&_~`&{z7u{Et!Z_k78H75gXf4g8<_ul!H$eVspPeU3j&&Au=2R*Zp#M9$9s;fqwgzfiX=E_?BwVcfx3tG9Q-+<5fw z%Hs64z)@Q*%s3_Xd5>S4dg$s>@rN^ixeVj*tqu3ZV)biDcFf&l?lGwsa zWj3rvK}?43c{IruV2L`hUU0t^MemAn3U~x3$4mFDxj=Byowu^Q+#wKRPrWywLjIAp z9*n}eQ9-gZmnd9Y0WHtwi2sn6n~?i#n9VN1B*074_VbZZ=WrpkMYr{RsI ztM_8X1)J*DZejxkjOTRJ&a*lrvMKBQURNP#K)a5wIitfu(CFYV4FT?LUB$jVwJSZz zNBFTWg->Yk0j&h3e*a5>B=-xM7dE`IuOQna!u$OoxLlE;WdrNlN)1 z7**de7-hZ!(%_ZllHBLg`Ir#|t>2$*xVOZ-ADZKTN?{(NUeLU9GbuG-+Axf*AZ-P1 z0ZZ*fx+ck4{XtFsbcc%GRStht@q!m*ImssGwuK+P@%gEK!f5dHymg<9nSCXsB6 zQ*{<`%^bxB($Z@5286^-A(tR;r+p7B%^%$N5h%lb*Vlz-?DL9x;!j<5>~kmXP$E}m zQV|7uv4SwFs0jUervsxVUm>&9Y3DBIzc1XW|CUZrUdb<&{@D5yuLe%Xniw^x&{A2s z0q1+owDSfc3Gs?ht;3jw49c#mmrViUfX-yvc_B*wY|Lo7; zGh!t2R#BHx{1wFXReX*~`NS-LpSX z#TV*miO^~B9PF%O0huw!1Zv>^d0G3$^8dsC6VI!$oKDKiXdJt{mGkyA`+Gwd4D-^1qtNTUK)`N*=NTG-6}=5k6suNfdLt*dt8D| z%H#$k)z#ZRcf|zDWB|pn<3+7Nz>?WW9WdkO5(a^m+D4WRJ9{wc>Y}IN)2Kbgn;_O? zGqdr&9~|$Y0tP=N(k7^Eu;iO*w+f%W`20BNo)=Xa@M_)+o$4LXJyiw{F?a633SC{B zl~9FH%?^Rm*LVz`lkULs)%idDX^O)SxQol(3jDRyBVR!7d`;ar+D7do)jQ}m`g$TevUD5@?*P8)voa?kEe@_hl{_h8j&5eB-5FrYW&*FHVt$ z$kRF9Nstj%KRzpjdd_9wO=4zO8ritN*NPk_9avYrsF(!4))tm{Ga#OY z(r{0buexOzu7+rw8E08Gxd`LTOID{*AC1m*6Nw@osfB%0oBF5sf<~wH1kL;sd zo)k6^VyRFU`)dt*iX^9&QtWbo6yE8XXH?`ztvpiOLgI3R+=MOBQ9=rMVgi<*CU%+d1PQQ0a1U=&b0vkF207%xU0ssI2 diff --git a/src/main/res/mipmap-mdpi/ic_launcher.webp b/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d64e58ba64d180ce43ee13bf9a17835fbca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!TQj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i diff --git a/src/main/res/mipmap-xhdpi/ic_launcher.webp b/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a3070fe34c611c42c0d3ad3013a0dce358be0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? diff --git a/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3918 zcmV-U53%r4Nk&FS4*&pHMM6+kP&il$0000G0001A003VA06|PpNSy@$00HoY|G(*G z+qV7x14$dSO^Re!iqt-AAIE9iwr$(CZQJL$blA4B`>;C3fBY6Q8_YSjb2%a=fc}4E zrSzssacq<^nmW|Rs93PJni30R<8w<(bK_$LO4L?!_OxLl$}K$MUEllnMK|rg=f3;y z*?;3j|Nh>)p0JQ3A~rf(MibH2r+)3cyV1qF&;8m{w-S*y+0mM){KTK^M5}ksc`qX3 zy>rf^b>~l>SSHds8(I@hz3&PD@LmEs4&prkT=BjsBCXTMhN$_)+kvnl0bLKW5rEsj z*d#KXGDB4P&>etx0X+`R19yC=LS)j!mgs5M0L~+o-T~Jl!p!AJxnGAhV%~rhYUL4hlWhgES3Kb5oA&X z{}?3OBSS-{!v$nCIGj->(-TAG)8LR{htr41^gxsT8yqt2@DEG6Yl`Uma3Nd4;YUoW zTbkYl3CMU5ypMF3EIkYmWL|*BknM`0+Kq6CpvO(y$#j94e+q{vI{Zp8cV_6RK!`&C zob$*5Q|$IZ09dW=L!V zw@#2wviu|<#3lgGE8GEhcx+zBt`} zOwP8j9X%^f7i_bth4PiJ$LYtFJSCN$3xwDN;8mr*B;CJwBP2G0TMq0uNt7S^DO_wE zepk!Wrn#Z#03j{`c*Rf~y3o7?J}w?tEELRUR2cgxB*Y{LzA#pxHgf}q?u5idu>077 zd^=p)`nA}6e`|@`p?u}YU66PP_MA}Zqqe!c{nK&z%Jwq1N4e_q<#4g^xaz=ao;u|6 zwpRcW2Lax=ZGbx=Q*HhlJ`Ns#Y*r0*%!T?P*TTiX;rb)$CGLz=rSUum$)3Qyv{BL2 zO*=OI2|%(Yz~`pNEOnLp>+?T@glq-DujlIp?hdJeZ7ctP4_OKx|5@EOps3rr(pWzg zK4d3&oN-X2qN(d_MkfwB4I)_)!I_6nj2iA9u^pQ{;GckGLxBGrJUM2Wdda!k)Y>lq zmjws>dVQ*vW9lvEMkiN3wE-__6OWD0txS&Qn0n22cyj4Q*8(nG4!G{6OOwNvsrPIL zCl-$W9UwkEUVuLwyD%|inbOF*xMODZ4VMEVAq_zUxZ+K#Gdqf!DW$5f)?7UNOFMz! zrB~tuu=6X2FE(p^iqgxr+?ZK;=yz`e;C$#_@D9Lj-+TDVOrva>(#*PVbaHO>A)mhl z07OJWCqYC60518$!&c`eNBcBW%GnfaQ*$eazV^2_AW?j)h;J1nUjN(I9=0+!RVx~% z3@Tf!P0TE+98jA?WceK-}A1% zW!K)lyKcGqy#M~})315-A#2NXQ`?6NR#Apo=S!oF=JfpX>iR*49ec{7AN$xxpK{D$ z2d%Fz&rdfSqourN$~Y^NFIMV1CZ?J*bMx~H3k&meGtH@q9ra2vZxmA$S(#jaaj-g4 ztJmxG+DLV<*q<|sDXPp$X>E)#S}Vm&sRaO5P&goh2><}FEdZSXDqsL$06sAkh(e+v zAsBhKSRexgwg6tIy~GFJzaTxXD(}|+0eOwFDA%rn`X;MVwDHT9=4=g%OaJ9s%3b9>9EUTnnp0t;2Zpa{*>mk~hZqItE_!dQ zOtC>8`$l|mV43Jbudf0N6&&X;{=z}Zi}d1`2qmJ}i|0*GsulD3>GgQXHN)pkR6sf1 z?5ZU%&xtL}oH;YiAA)d*^Ndw2T$+Mjuzyzz@-SM`9df7LqTxLuIwC~S0092~+=qYv z@*ja;?Wt!T!{U?c*Z0YtGe)XbI&y-?B&G2$`JDM)(dIV9G`Sc#6?sI60de6kv+)Qb zUW~2|WjvJq3TA8`0+sWA3zRhY9a~ow)O~&StBkG2{*{TGiY~S8ep{V&Vo2l<6LWsu z^#p0-v*t2?3&aA1)ozu|%efSR=XnpX$lvTeRdKlvM!@|pM5p2w3u-6 zU>}t2xiYLS+{|%C65AzX+23Mtlq?BS&YdYcYsVjoiE&rT>;Necn6l^K)T^lmE`5u{ zm1i+-a-gc;Z&v-{;8r)z6NYfBUv+=_L}ef}qa9FX01)+Aaf+;xj(mL6|JUzGJR1|fnanb%?BPPIp>SCjP|8qE5qJ{=n5ZGw?81z3(k;pzH%1CtlX50{E7h)$h{qGKfzC`e2o`*IqA#tjA z`Fz&^%$b9F*N`)U-#6>a)Z`55`$Dd0cfcs0$d13^ONrdCu9xcv_=n#WQo8stcz3jP9|2EvdI-RhJM3%Q%oM&!OlShM|0 z?gz?wHZSnm45njLtsz8PVT1S&jAlbKg5kVam$p16=EK@Sj4EP0OtH zmJDmdc^v)x>56Qg_wmYHz6h)>kl_h$>0@J!ypv%APmjZTAQVLy6Fu50RGY&JAVNhx zrF_qG6`x9MkT;1SFWo$)l{M$;3qUDn9JwE}z zRl#E_bDRJFii61kPgBybIgp8dNW!Cc1b*^YYk-#oWLJvtM_v^hQx~9?8LD4VFFxBF z3MlrsSC%f9Oupn*ctPL0U1fwfX?`tRhPD{PSLFPQOmIt$mDy0SgpNVvHS+f#Do>h1Gn?LZU9(KaN>Q_=Y*_T zvtD7%_u^^+{g`0VGzg(VZrpVQ6Ub5M=tI_p7T93R8@3Zulu3|#{iNcu!oiHxZ4Rf*( zfmiN$$ru(*_Zqn=`Gq#OuHRTSwp7uH_SokR&|)RuW5yo=Z|_4?qU-JU+tpt>!B&Is z@N(=SG;bpVc;AO@zbmMM zScqq1)b-ZQIrs={oD}|?6y{$HNB1U0^LsBh8JI&3!GBZxOXI<}&5-$lgkAaYqhOTb z?2vEnZ$-kk;*M_17(upJF3%+iH*s0-r{vttXVB2OUwI1s^+G(Ft(U8gYFXC}#P&E^ z>T@C^tS`Z7{6HT4_nF~n>JlZtk5&qDBl6r|^kzQYe`wq!C)n@$c>WOPA61NDFj<<6 zGW71NMMhwAl!U-yqrq2xrSFqRCI8acw7?}3j;ynxo*-b7Co;g5r%^j=H@9({PXXBf z@r>U>>N;E)81wx`B4f%{PB~MHka_);%kBCb(d|Jy5!MqJ%2p`t&@L)4$T2j&-WHvG zv3(uyA_gwqNu(k?jQTtv3dgPKRZoH8prxe7>pQBW5L&dpumS&5Ld2?(sCpJjvc4L5 zEnh&?91WVm)ZdTj=fjJ$pPDdgAttLXuke+?KdKxu*;kTC(r!tQk6;gxj4h%FdHAt(^M3YvYj(!tOeN)+Hvj6+< zzyJRG?^lZfWuR#t!tUKP&(?%3v&Zd$R2YN>lB(Lq`OInY48%4%yTv2 zYe1{G`3)(PDEio5Y@-I5tUf`c%%OCJMtSW56g3iEg%3`$7XSJJHyA z<|7&N)5Xrlgv~%BO24eFd;Hd;uiK%D`EdK|quUeRZDqbh9l)%j%J#0lfrZumvA<_w zu&=AVvdChf6}eqh(bUz`(`Ue*p01{fBAcTgKyDYLs_I+YyJEk+rM@avU~>fB$n)HS zM7pfJydu`i%gfS<{PF94kZDv$t>06sAkheDzu40NJ$5CMW%n^Lls?8^p^QGWURbKu3ZduZQZ((s2? zzE`}<{;Zt7<$C|9R8A~DJ~@%x>TfP zF>TX8)@v|t)q4GjRt<}5s6hLHwRel7>V@&r-O|Av(yh;Q1A{E>Ir>p+%dHD|=l+lT zpr(Dg&>#Nu=!)6bCLr-ZS%|;h)Ij$+e@r8_{qO19QvDe=&1tmpY*0lcA^Cc-#{9fQ z<~$*<&P$Q<_jy#<$40PMofM7aQ}C=jphI`4kLg}Z7CIN#26D{-4v-_CA-LiE@(%{y!BzsU%gG`Q?sjLUf%qFSl0y)2#ae*+EI>s|i`d^V$Dn)qmzqRq6VJRY|{4ujsIU%#bnqU6MR&-1I_43=|5(6Jr;Jvert) zE?S|Tmn}Tv<-??sxV5@9t}3D=>YZ0JrQe$CO~|EY=Lj9RM&4svQHPQL6%pV5fPFiH zfXDx;l@~et{*{U*#c#Dvzu)|znDO7$#CRx)Z&yp-}SrD{&|(MQtfUz~n35@RLfUy=aqrhCX0M}J_r5QsK~NmRCR|Nm&L z41UdsLjWxSUlL41r^0K&nCCK>fdR-!MYjFg(z9_mF^C|#ZQw?`)f6uVzF^`bRnVY& zo}@M06J&_+>w9@jpaO4snmU;0t-(zYW1qVBHtuD!d?%?AtN7Plp><-1Y8Rqb20ZaP zTCgn*-Sri4Q8Xn>=gNaWQ57%!D35UkA@ksOlPB*Dvw}t02ENAqw|kFhn%ZyyW%+t{ zNdM!uqEM^;2}f+tECHbwLmH*!nZVrb$-az%t50Y2pg(HqhvY-^-lb}>^6l{$jOI6} zo_kBzj%8aX|6H5M0Y<)7pzz_wLkIpRm!;PzY)9+24wk2&TT{w--phDGDCOz{cN_ca zpnm7`$oDy=HX%0i-`769*0M6(e5j-?(?24%)<)&46y0e&6@HCDZAm9W6Ib#Y#BF6- z=30crHGg+RRTe%VBC>T00OV6F+gQDAK38Ne3N9bm|62tPccBJi)5{B z4zc^Db72XiBd}v$CF|yU{Z=M|DZ%-(XarYNclODlb1Kz1_EKLy(NSLCN`eUl(rBCL zT*jx@wNvze0|TSqgE(QArOZU)_?qH(sj#TwzElLs9q)(0u!_P|R%Cy_0JFQxgGV>1 zz4?_uq<8_gM0`c*Hh|;UMz~vrg1gQXp{ufg`hM_qU;U>+zmvc5blCLSq@PrEBSGR# z&8=2Z4uXN`F3p73ueD1l{s{k$WipAvSh5W7ABe?4)t;r@V?y`bNB5FvBuE|0VRTb< zM1Hn^?DSsJY+sX@T5xW=#>T9VEV|?<(=6|ge$X6Sb05!LFdjDcoq*gM(Zq=t;_)Le&jyt(&9jzR73noru`a# zN*<`KwGa^gZU3-)MSLF0aFag#f0<>E(bYTeHmtdbns#|I)-$)mJ`q9ctQ8g0=ET?| zdO}eZ*b_p>ygRTtR^5Ggdam=Zb5wmd{}np+Jn1d_=M`~P=M67jj})fH4ztb5yQqQW z^C|C&^LHAK-u+ooIK)yM)QM?t;|<{P;;{`p=BclzAN#JzL4jCwXkQB1Dy{=^KR`=~ zTrr)y7eiYBzSNs_DvO=4A6#EgGS-zY%Vi)N*Yb`U;6o}KR}dq{r9pT5wqZ@3NOE8- z9-(}D|Nc5732CSYQbL)!gPQ#RbD8BhK3dl{sUuPvei0tkvnJBxDEAYTesU8H$)g(Plra{VH(v3u^CO1~(+ zU0O7#)jaS4{NcwA+LuSm&VBcX2#Im3xg)W}ySNw%->orn1taZ&+d)}8gJTqA!u|5P z{yv?zol_3|(1(%M(EVU=cp?L`{Pi|ixk{U)*guFML3P!OSlz;zGA#T+E@8@cgQ_mv1o7RSU=Zo_82F?&&2r;WE z@wk}JHYEZ9nYUc(Vv~iTCa3u8e4q(yq<29VoNbKk|`mq%I6u)My=gPIDuUb&lzf4`MEA9^g8u z)vp8|$$HE9m_BTV?lOosIGa4jud=jIbw)O2eCMfyw2*S8?hjWw^nqws$O*M$3I1)x zR0PWFb3$ySOcGTe1dz%N0l;RPc`x%05FtT^f^j{YCP}*Q=lvp4$ZXrTZQHhO+w%wJn3c8j%+5C3UAFD&%8dBl_qi9D5g8fry}6Ev z2_Q~)5^N$!IU`BPh1O|=BxQ#*C5*}`lluC515$lxc-vNC)IgW=K|=z7o%cWFpndn= zX}f{`!VK02_kU+Q5a3m37J;c} zTzbxteE{GNf?yLt5X=Bzc-mio^Up0nunMCgp*ZJ;%MJvPM3QK)BryP(_v@ei4UvHr z6+sbCifQaOkL6-;5fL8$W($zZ_;CZp305C;~$hhRquZr-r)jjd1z z31%ZK{-(`P#|Um_Sivn@p$-vz46uqT>QG0B1w9znfS9A8PB2LaHdzA|_)yjXVR*l{ zkcu3@vEf7bxH0nkh`q?8FmoO_Ucui*>_a~P?qQrlZ9@+D7%MTpSnztpylXrt5!-k8_QPB?YL8Kx_On8WD zgT+111d(Op$^$&KLAN5+@?>f7F4~wFi(8TL8+szgVmcMDTp5l&k6~=rA{Dt}!gb^r zSWY<)M7D|Z2P0cEodj6E42PV>&>DFmQpgt)E-|#sSUU@uKed+F680H@<;-x{p|nuH4!_mn85rx>wz;0mPi2ZkL#k6;sznu?cXh!T0S>{w6 zL^gvR05NY64l*<+_L>On$rjx9!US;l;LX6@z}yi#2XHh)F@Oo+l)h%fq$v}DNmF2> zfs^_t0)3N-W<9-N?uedVv{)-J0W5mh#29QM5R5h&KuiRM=0Zvnf#lF=K#WlCgc#9c zS;qvh(P$!_a8JwyhI^ZJV2k+B6Z^64?w|1?5gyo6y{}923CRZfYVe1#?F% z7h2SUiNO3;T#JUOyovSs@@C1GtwipycA=*x5{BpIZ_#GCMuV8XK=x;qCNy{d7?wA~ zC+=vjls;ci&zW=6$H~4^K%v{p}Ab?U%C6Z4p%eC<3ExqU$XR<}LLF67A$Sr20DR_pJ3yeBa~ z^sw{V0FI5;UpwXsScYuhbqGQ`YQ25;6p6W^+tgL&;Ml;>S3CGpSZ>VrTn0m1$y$HU z&65)I!c?oREz};c=nLCliriqQX->4uivHTgd${GqeAlf*!P^B|jkU|*IdNP(&6C>4 zqOW$)Nw9nvjy^&`?E|gotDV{JmJ9Q~vuhy<`^C4XIUDt|j4o6rK^e8_(=YqC zuaR6TRVf@tUFHB079o4MBIh{M~4>WwnGgesQH*3?w(RA%hCZ*7)b!aNV=yOQ%o_Y=Lt0Sl*(9^jfRnC210Om$=y>*o|3z} zAR&vAdrB#mWoaB0fJSw9xw|Am$fzK>rx-~R#7IFSAwdu_EI|SRfB*yl0w8oX09H^q zAjl2?0I)v*odGJ40FVGaF&2qJq9Gv`>V>2r0|c`GX8h>CX8eHcOy>S0@<;M3<_6UM z7yCEpug5NZL!H_0>Hg_HasQGxR`rY&Z{geOy?N92Z z{lER^um|$*?*G63*njwc(R?NT)Bei*3jVzR>FWUDb^gKhtL4A=kE_1p-%Fo2`!8M} z(0AjuCiS;G{?*^1tB-uY%=)SRx&D)pK4u@>f6@KPe3}2j_har$>HqzH;UCR^ssFD0 z7h+VLO4o@_Yt>>AeaZKUxqyvxWCAjKB>qjQ30UA)#w z&=RmdwlT`7a8J8Yae=7*c8XL|{@%wA8uvCqfsNX^?UZsS>wX}QD{K}ad4y~iO*p%4 z_cS{u7Ek%?WV6em2(U9#d8(&JDirb^u~7wK4+xP$iiI6IlD|a&S)6o=kG;59N|>K1 zn(0mUqbG3YIY7dQd+*4~)`!S9m7H6HP6YcKHhBc#b%1L}VIisp%;TckEkcu0>lo@u995$<*Em;XNodjTiCdC%R+TX|_ZR#|1`RR|`^@Teh zl#w@8fI1FTx2Dy+{blUT{`^kY*V-AZUd?ZZqCS4gW(kY5?retkLbF=>p=59Nl|=sf zo1Pc|{{N4>5nt#627ylGF`3n>X%`w%bw-Y~zWM_{Si$dc82|=YhISal{N7OY?O`C4 zD|qb}6nLWJ`hUyL+E>-;ricg9J@ZNYP(x(Sct&OI$Y!QWr*=^VN;G3#i>^1n4e#Je zOVhbFbLpXVu*16enDM+ic;97@R~u&kh__kgP#!R`*rQEnA+_dLkNP~L`0alC|J;c; zeiK=s8;BsLE)KbG3BD&Br@(Ha@SBT&$?xX`=$;eeel=|R_dIr6-Ro?=HEjnsJ_b`1 zK6Yg^-6;^2aW!xeTK)A~3Rm|L^FCHB_I>jIju7ZGo&N_1*QHkxH2!!%@o4iZ?vntS;&zJdPe1dH#04YD93A44o-MpfD zP{rn_aq>U%RDvC2+bp;xPlsOzauIi3*Lf42`jVKKZCRuKdYhi>FDuL2l=v{$BCN#Q6796s%r-AG$Q^t(3c@ zD?w0UhYr11@feiyl9kY_@H8~|xlmO<8PfQmj1!$@WieW@VxR@Psxfe-v9WCi1+f>F4VL?0O~K7T?m4-u|pSkBpUJZZe*16_wAp zSYZ@;k`3;W3UHKUWc8QeI}0jH5Ly=cGWQPw(Kr2fm=-5L(d`lcXofy8tJY3@Tuadz zYWXR{mW7XT!RF#RVCe%}=tM*O6!AD3^(!8un~opNI%Uko7$5t@<8+?; zTxDys(MyyGsUjtSu9$+|_-t!U3fVb1dkK?l`17<+jfl=hrBHnDSV>^R1=TnQeyqbW z>ov#l%!1|S!1>8UUxIdhQq`_klcHVx0{?#>K3#$4GlXncwldt!g17TcvKq-jo_996 z>oA=tH9CqRl6Yw?Uc`am!V?lHJbizOJaVaScf1UP5e7Dbgabq=b!B~T&_F6?ooU>w%x0A zH~&MHJ=q`fCH{U<7MDXE4SD32cDZA)WJeWkllJ`UspWaS#eDe^kg^oU_A14UE9zG-a^g{xaXf$})Wik>gT zl#dkzGr(;h0JZDuFn(+k8wNq?PZ5grQ<+sM?wBGt@JnH6v0#or-5wBQWKU~(S_> zkE!tc*ZJ1Y&*p(xX84POb3cClRMd!^qJ#CAZfIepEj-<`VURS_yCz0(?*Ixcj4 z-!zV1_QZhpm=0<;*(nm+F>T=)o?ep@CK5I%g^VAA+RB25ab?7)A~z~egru=I1S|@v zH7tXV!0wmGS^qj#e+MY;C5eUjEAp$Y?LDkS^QPZ}8WN85?r$u<-Epi;yZ1|J2J`se z$D6DpH~2F=eI0B&=UFAUnJvZAmClJlK)sutJ?M>xpZiWV&0=G4MZP+x+p>EX=HbCz zxls%Mw?*u^;LbHWIWCyq+yi)`GmFn9J112CZda_u@YIP%i;srFg_paU02Ifij*7}l z&CF-(3|>*a|+vbNR`^RP=9G?ymEJ0Z~)d&c*UE$UMepZ zcITr{0WqhxkjUnM15js_gW=e3Uh|y6ZReaXHIz-=p`x5VvB&rH9y>Amv@^WmXFEw) zQXYrk3feir=a{jMQ+wDIkkFnZ$k{sJakHn*?u za%4b!00ev8NVLM1TY=cl?KB&55BY_MU-sg?c>=Dbz_W{(Z~c?HJi*XpYL)C6Bd8WH zt+v-#0&o~@t4qESi*)+eW%@VD0|o^yF)n0hME$UtXF$*Lvh}7sso{`|pn*JDIy5^Fm3s$5*zEE=?u5<=l8FJc3r%+H} zdfoNl2J0^~!-*mOL5o-x32|e0Im*E!yY7F7E5N)W3>+v_LBydlEx?4$RL5f2oYRD# zaR0wv(-p~wO0eLDl3K=%`{5+0Gd$ktO=W)gWlGZJ0`K z$_RNA=ckrfa;H0KA~dR^p�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp zwO~NVXwXCl;jE<}>%@xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m z!GmimlG$=wgXCnr6j@m<1gAL46#T~5Bnm=2{^@>|t&`9mkEPddj zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@9A|X;;IA>8!oX+y(BKJ$EzV* znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE? zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG) zaZ7j)dq!O}{?#8Y7~7i6fHh4{`pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m# z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU zuSa6v^kXi0xPs?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q w)2!4MKjfSAeNo!9>IaV6aUZ*?W>} zs4%E?srLW`CJh0GCIK@hTkrW7A15Iu%N&?Q^$0+!{Tv&|t^Y@u%!L zglTg&?Q5q#ijZ;&HBQ?FNPp;k3J5!&{^+SGq?AX~SiOM9jJMRpyP?RCr@z38AQyy&WRMaC;n4una$~nJKSp?q|s8F00c9?Q! zY_ovvjTFm+DeQM^LXJ#v0}6HRt3R1%5PT*}W!k8BEM;Jrj8dIceFo2fhzTqaB3KKk zGlCLI)gU25(#u6ch6GeB1k@eHq7l{EHXv0n6xE#ws#ri}08kkCf8hUt{|Ejb`2YW* zvg}0nSSX1m=76s?sZhRY$K=3dpJ+y*eDULGnL2}4>4nvW^7_<~wIM_5fjvwt4h1|g z)g0Z6ZFq9j<~9~b8((~TN{Z?ZQfw|is&Xp~AC61sj;xItKyCHdI|tCMC_LbXF>~vR z=w6V3^H=W4CbAgR4#xw}ETTwu2guW~=Crl@SMXv85jQ=%y!s^?m4PI0My7MWICO;- z175jm%&PcPWh8QdOU(#8bp4!N7ET-+)N}N2zk2)8ch|4Q&lPFNQgT-thu053`r*h3 z_8dI@G;`zn;lH$zX3RzIk`E8~`J=BBdR}qD%n@vVG1834)!pS1Y?zVkJGtsa(sB~y zNfMYKsOJb%5J(0ivK8d+l2D2y&5X!cg3BG!AJ}910|_${nF}sC1QF^nLIhzXk-Y#x z0)&1iK!O;Og0Ky!;`b~v%b$`S4E&fB)1NB4v@8wr( z&+NX4e^&o)ecb=)dd~C!{(1e6t?&9j{l8%U*k4)?`(L3;Qjw z#w7FS+U(94MaJKS!J9O8^$)36_J8;thW#2$y9i{bB{?M{QS_inZIJ!jwqAbfXYVd$ zQ5fC$6Nc9hFi8m^;oI-%C#BS|c8vy+@{jx6hFcf^_;2VRgkoN(0h!_VSGmgNPRsxI z8$rTo0LaYq-H5i&gtj81=&xU?H-Y2==G@uQV7E`@+2E9XQW@{&j`?EOktk|Ho{HU>ZqDzvgjwBmdex z&uZNd2C1h{{}2k6Ys9$*nFP3;K%u!MhW`uZy7Sn`1M1zs@Es&;z*Z>Gsh@-3Fe6pE zQD2@cqF((NrRevgvLsvM_8;;iNyJ5nyPyy?e!kvKjGj`6diRFBEe49Oa7wwkJFV7Z z$YT&DWloYu-H?3<0BKn9L&JYDT-SK~*6c5pi18P26$JESKRYj{T7Zk6KiRJcbvOO*{P56Q6s8msbeI3>|j>K9}Q9UBeq*inXKemCm`-<5|-$ZyN4u$(3 z&HcvqehFD%5Yrmykg-^d`=BSa8(i=>ZoC77^mWY{evp(km@aHqhUECBz76YiR+VYK zY_avFC~V3$=`6C4JhfHAQ@DZtUOwH`L;oYX6zK0-uI^?hS$ALfq}A7evR;ohJHij} zHSZdW?EKv9U1s4oD*<(0oQ*;MaQ6@cvGL zuHCPgm_NhVsgp^sfr*ia^Db}swo1?O(_Q2)y+S$CBm+g=9wCOUPbz(x)_GbaKa@A7 zuI&!ynLiZRT#V%_y_-D`0Z5lT*auoe{(U5NylTzFSJW()W-#F6*&A`LNO1bV#Y;QJ zSbLBnp|B^dtK|KIWC|No>JjWBWE@n7O)x{&^E(WMeMvp57#qA8m* zeTow*U@_86B#Fm*rxyYu5PRWaWHx8y> z*qmHEp(AMDl0v)ij(AY8fnH=~ZwwjVAbu*m5;xPfidh@ov6d8g zfJsi&!QyK53Es%sC39ts;54V68koALD4b|%tNHW0bIkZAJKa=W&FomJSEDT>W1xIX z1x%Z>AvNIsSPLcn3RTcHXb@KB?cuM)=x6fcIx>&(GxqZ8w3p#jJ(GVgc*`c0HG}dv zIop&Qim!K1NFwic%07KcjWgHBPUkq7f~lj;TPqVGTiT#cUeim>;nY`>h@a*S{qQex zQ`z62WK|Mj)Y{tfF{;T4P;c8$Q|KU?Joh zIkA^z%X7z|r>4aTh@|StTi!-r1D!g=zb#3d#{{&K3CqE$Iz-UH<%37c zRfkO`&uM%#AD3PHv`g5t0e^O%nVL0d{Xlx^EjEC3#skF@`zl-7PF^0oxW)1!C!JxR zWvuAHH?)61FKA1QeT*_sY7;_Id#!GmV4n`MO{~sv}VLSK` zXRw=Y=Clz*00B(5y^K;gCZMAzjT5+c3IC=)l(9VIDdatpxj3y89WwI|bH&$!ZEvp` zPR!T@#!(|KfI-w?!&+7$N3F6>tD{YO4Qg$d_`nNEdfVCha9vaPn0jI0`)`@*72hq! zpU5ND^P*RoEkbD5o#az(-g=Y)L>HH>Oc%}$ zT3Rs_ih0;4+Lv4Y;@Iv(;fUbQ=i-G(#>vghec~*j(I#r|5mqFiJBpzi&hzEcD{u$< zRsm0BVYn=pT;0>R(itW|*D&;O%bOc7et9ACaH#J>z3A1A~6fdP>pmbM%xzm4>|;c_?B+%sl;Qs2{t!60$^u zH1t@9^6>;?!FuusnISi$f5CL&;z?EqJN$FBuWDA#D5`cy_UvCFIVvf{c?4N0teh;d zET$7aVbj08KTQS!x?Nd1Is8q8qFzs}a=!@nJ;7FSfCY^T@D-gpw`w<6e#X3+;O}1h z$%I!M)0bg|EKUA04Qjn@+x{Rj8vt6Wn!R|3A92z}^$KfF5(#CWr4y#~re1CN4i4w0 z#GsypBR{xA3Er7sgAi(|}1-W?s~n$7?K|9WL8kpVfw-;#b9 z+mn;=ep!162U5R>_t}fOt~tE?s#m( zO-S$7>Ay6*hHdZ)7_oU915WYYCIX;hFI-U2EWYX!pllONr@Q--2o~`!isi6vTPLJ4@(|o=%NHYjo0_S&q*UQIROw@*N-By@PaQ&;YxFZ0aR zX&}LeOEz);#m~Hwm^VAY8DK}b$F4bo{jMN?d!lxKPhNklzr^Cd`0f4oJr^z=I|l`* zm8AHm*fPV`0=lF3Pnnp}&J0N1X@}-D94YvmUabFrLGSnTz7Mu^21F#O5tN#CuY9Vh zUZBH=ez%h*wkf0hBtXJh1SN3d+IF{gzT7lp)j}n?03lt;XSQRAh7qd&v;RwTYDuQ# zbI2*r<>?x-G0@hM{;%{VBD7nLKt~D`T~-HAt5;h%i0_=Ifs=yHma5dhJ+QMG?Ux(a z|E?1CMy1!~oA`FP!k~iG=t&5#>bVdz=peT8HMB6Y)#7PpETtNryT^+Rv3vpJaF^zP z{H}0-LyV9Fu21ID%wO9f1IKlFr1p4c{o-?03vyB-tr5duk^&L$;m_|f$vs`^Sl{j2 z95}oY{LlY+=ZS%J+tZoXCd0*sSU7w^gjovXn+g7uyra5{cU49@yHf#Z^Jl-$9cIfo z+AJuxH$VLb=#+uBbVmUjnx zxb1pZ@-O9=AIk4@S)m6fJ2?{HrNYwwnL3a45muuNjr;6$O`bGEM0T4A2_S$t=86*- zcO+0mywg*j#A4mU}enR_!cGmIYQ;qwfchWtFEXL)AK%*;=j znYne+hS4EMy3S)C*mZ1KI>!+)0V@9!N6H$Y}~MJ{rYuf zz^KljIWvFi-?#?V@LPR&c6Nn{!=XM z>}-h$S76;$H{E{Y%@^zlmOl^efBwa%UU+jJD9UVukQ3ti_kH-?H*RC0?M1W%FCvMB zM_+v6fk$6X2sx)-p~B3&Kl{nscK}pNLM*qjtpaf9>AU{-iPKQZR8yCg!TY}Qg*(;) z)gdvCcB%kppZc$VdvsK@)3l1{&DG!d_6OHOS`y=ITLEVu`unSKA2E%JD*DVX{LJ}K z9l>hMRDqxQh0lnpGHpVYneX}eA3Pt|2v%=q;rt)``R|#bDyB)OXY&vI_@|*}h}G?^ z@aZ4_!7cQPX`!fW_?{oT1NTwHs#l5L-0`E|y@48<3Q^HFf8=Idi zpJYD%1MkII!~|7I^WGo)IF=?{>ACnjJ_WUi39C}!Q{QnheVJqeKKqq5^o5CBde(g9 zvw$X6^jz_^E2$wSw4!q5*RG(C2_^XO$HBn_55vbl44OnTTRwRaePP0vo{K)U1#99& z<>rq7V&V(<&@I%MFoN5zrY}sz=(*-L&}1QQ*a%`u25h{cFj===17eB_uGuzG&byQ< zrm8BJZl4r_E$3k|Wo6FW0-6M7>qac5uFQsQcmkLWGfeH74S3Z_rJ!jgN++!@i=HW8 zkyjI(oPH-+-N#Qc^-mpNO`bc6r=2-<%&Wy5K1vfFJB(L_IkpS6fY^NmuL8qsgj>MD zn~BHH9WM~32_3vd=W&B)k7F9q%stJx+b_L_X-4zr^LVUMCmyCTA3sWtkvsmME?Xiy z?xOSfB=_$oY06~J-HcCq&)qcW{j;uP;?Dm}=hkq?zh&n!;m((-G-u_t|6x399Q;>A zgNpxoJNj{u|MFDH7Rhq@FCAl0dE|ddnl!oh9{Lq?@JDoR6L;C941IK`ISfdE$4S zE0AUQ8+2|Ncl_q5QkSp#AODp~(^mfP&%Au@@|TBQwoP`UU+V{6u8|)6ZA{~uKmQ*M zmrMTDU8S~8Eqi{^v0Ug&5Upcm#y7Z1(RbgZAG8jB$eRwCspQ)>5;U)oGZ&E5aeR*K z8Yt`Y0$G))Yd(Y3KH}tA4`-_QmNke5hU_|nq=xtyjwW(_o?itz>B>WM&^63bNdQ)k@-IgDHW*RW$Xo9#RzrTrCn7L2H{9Amq|qNg@#eZY=|P zCoI?2s+L)zsM%WX(NbVEY^`C>lFjIBYmJ6@DKJ0ZT4&F&WHW!dwa%QzOG!?jY_2(S zDcEzZbz*2Q!43|z))9yOP9X1Xt%DXzwY(3tl-TR=Qb_MbZYRrooh;dYYmS!U_as1(=YVB?Q_A|tNu5Ut&_q3jbfDM zoFxT^uEuH`nX3*sB%K?GuHUkweYReBwnHqh3P)~`+s3+Tj!rDA1e)8vuBv5J*IsxC zkd^~b(aGzArj08{>cnzOuy04C+C`}gb|Yz-1avxeWzev3NzcHbz_&4W@QCr$z3~w=8Ua- z`;vfG1~BP8CyLb=F7t1am~ph_#|O%$khSJ9%Vtcn)YmpgQxF?xM^_Vb+5fnpB^W0I`f%X8gb9#X{Q-yJG0{Z56aWeI&zPxnf5pdJA38bM`cYnS#x)% z`n1tFf$i)W-hGm(f9mde^=X@NcV_lFb=P`4&CI&H=IArijGwdCk&X@uQ$5xmj!~^? z#$ROCI)V-~t%L%GS#wo@U27ddR`4`3)WoB{R-4snfNrfee|kI8^bu#yDgYqOwas9# zmcb`3!kRJ`Cr=_tq)8aMt{aGtUZsqwVlj6DgCGre>AEt&x8H_in!x@uwgExIh|-mA zjdaC(29~CTVSaaF7HPbql&*9Uo8P@f)>LqCXclr}peS7_1BQ28u9PO8Eq1@`l3q9o zkfKCaO2?T?ZyA6loW<#9_c^O=m<&h}CA!ineAD@=(gbq`vyT|tiJ6#^B1$P;;qax` z55k&Q?wEh#87niLo*+n4L@65J(Nz~=Ya%7^(miLb(E>A3B@|Jjl;FU&D>o|9#7PJH z?|ago!o;WC^h=|T7PVBg(DAB}72cyUS zb(f>Bwbr!F1eTCO5fpj<{PqhY5>143p?~5ZA5H40);=@M#MYvrB6gqHbU_!GSY??i z%s=>-ciA4*zOOZHds0a(kWewZ4h(k8h(ua7HX)Au&mY~H8KY6(_cb$_&fA@QjIW-*heP3%$d!m5^AdnT}`12qA^c@!g3DOwZ5WwE2?)-yU z!)Vx#Mtxt?FzFTwK!77sy7)sMzUd->w4^bxtpM2j!b1pjgyk zGKwWGeb4)^zjy{9Es&PU1}gwg?|J#L$KJB7ett9@4M%-nGtIQr0>Fl@8-yh`-+1ed zS6r}(MeSvgSoFmH*_WPu@i?}!AB~2?;i&IxrkNg~cQ9Som98tcq)k^|eeER|Zl77t za-TVUc;DNvzVXJ%w52+#weN?+;i#{f#!Oc&z?81*N>^e~ltRS%ZI@lR{rs()HmqG! zx*}ZrI-EZ}ckJMiy>A^oofwDfC~IH)z8{VHKGT@#E5I(Ll&+MnMCl>~AV7+>Gi%mF zkU1QlKASdR0B80!YhP<$Ywi0?W2Ux45oPfxv9QolWzJPD^weBfvo4SONxP35106sAmh(e+vAs0GboFD@PvNs)jNPvarhW}0YliZEg{Gazv z+JDIpoojRVPr<*C|BTq<`6ga{5q^8^!|0cxe=rZ!zxH3%f5ZO0cQ*Z<^$Yt2{|Ek0 zyT|*F+CO@K;(owBKtGg!S^xj-Z~rga2m6nxKl9J=fBSuNKW_dLKWhJKeg^-Xe`^1? z`TyJj)8E!#>_3Y?uKrwqq3LJ#SGU>AzUO|6`nR^u&3FNN_jGOc zw)Nw`wr3yIKhgcee6IaN=ws>M{6677%)hPwx&HzC(f&u~&)6@b2kNRzBDQAP0*H73 zq%McOmRk{B3i47qRe=DA*$&odrbEJZ*pV9XXa&p@wlW~@Yfs>V{yiTtplMhgM*-Bz zsSnlq&pG;z0OUN%$~$3=g1UF+G*>+17eRbBf3=y79J}KR8owon@$1Z7MIrvvWWH)34nK2SD)GsrJ{l z1Cl#oVo3A8qY3e=aF)qzms~FG#2$LzT=gs&aVMOj>(%{y<&O0cG!nCiESl~x=^dF{ zKvj8F1K8Ng171wwM5Fh4KoQw`_c6#y$(5cAm7e}~nJ#A*fx+c9;y#&W!#VukR)ugk zKp3=+;Ut+IYn%m+r4d*<`L2h%aDnX5}^!5R|H;(34AoVWjRx(msBZvk;rCI*|~ zdOijqI@9Z{Vu!~jvHW{lBa$rnl4+!s_5sfK3bCGk-B%iDe&@-}+%fOKU|(9?V1 zHE8&@4z)Kx!RAvAs z!Wic9=o#(bg?kc-G68-m(jZ`^=XGUXb)}t(%&~sjFnV^sEX%hSy6UKC4iOhgV=BHV z2w`4g7Y=s#Vu2B_?#VQ|hP39@eArgfX>-0S+dd&^mx0*wp}>)x;c4RUgxz%;oNe?& z-7-lJ@Y^2^C;=qJsxx5|xF)*pTGhch2B&kxtn;f!7=gznk}I3}Dh}(CoMXgA5-p&kS202!l?!fT3t|HG*rIP~mS* z$Wjo}jq3}z$Qq!9yrtd3fM0N629ZM?LU$nv@Tv9b7I;D|;0H2dsA~g7Z7zp1| zB)XmrkMgF6OQr|R)HHD^TE{Y#j!~SR?b`Xt3Qs`B+x<hxexYeAjMUWdZ-*n9%(1)Wb(n2U<><7&9dwGJmrob)4%H? zlQ%z+L-^$dFhhH|@u$%97Qz?*Ynh2VG@q|?8vY&L74&fs&_b&3$x&Oyjl~LQDRRap zJU4U*R+(2Dd!G+lh8!V{pT_UJn+^1Qg6$` zqkNm(a#hWyc6SP+p5=C4HL8-m`pO`5o~`-LI?_h5CsH?F_%?nDodmz&pWR20WTpJE z?N|wSzLjMUK8E)a2tI}Lf;+;*M|h3Y(U#>)g1>zk9|Hd}oZAa2 zLYBWBoSW!Ts!RwXr^8h+U*@{9{zqS^iH)Op<;r`Uw~nc}<^$V~_i%$GFjaG?X1@E|M`h)nekvFKt`Dh-f>@|0-`Xoq)o` zx;JmzDfOV9qCx|EVpogEe0LK~tGS?5$$L_i6P$P6wIsCQaP_;d{{N=iV@+8LI}o#( zvo*Ejy=IIn{rdIQh1&q-{EuohpVOjJ^Q3lD*YTp37$^RRgn8ihpdu5{Ct%5-KO!VL zcNB6dUajXI9jkm-P|i3~GB-A(X`P1Oqqb$tcku)UJw0w3GeUijb__#QT4j%64z%EeB7S?jlWwx_7&+EEvB|6N=kV}DwnyAlX=?j`) zmU#!$*^@NIu#n_d7;WoJV@*Fbv9|yJO4;n|BNF2xy(54RyB>t~8lUOUW$&2%Nwi1y zx6JxW88>U2$#qhl^6KUbtmg9}D0o5vYDT7kWJthLGkpGnN4T>{St^_EU>4;DmLF9o zr|LqsA8_MoNLQ=}w?8u!ziSZ@PC#Y<#9uJFo-ozVo6D;<8j^1$c|qAE3ZTE5i~zmE z$BU5lw6l=EWsg^y^;8>r9qH{xfL|~PZYK#md$zZ0?o11gV<*WSW~cgy2GYGQir%wf zt4iW8D+;s*;RGrmd(-T<@2&j(Cb9xhV*l-x`TpK`xq|7p?5R%5*s!69?2c!cC*VY* z2DE^9pvOPLU!1e}wA8S8opcTJ3`NB>hY=JQnL~QFXR4K8A$BqJnoEB$wn-%u@E6Mh zCfMF4kusv3N!(aHC}4)Xs^xoOwXd%e^6pi5|DZo=Q25j+6HlJ^7FodH6y1bMROR^q zGu6)fopS`h%Sw<;ZH%TEPf+#81-#_v+@8nlR0jLcIDKQtLleOC)6yLZgC!D9X3GgS zohwU{v$jl=quD#Go^hB{`@Qw*a%`(^jyT~=q^bWgGzRj;|12J55HWdCWV}EB|K=%N z3Nq-qxJJ`>^|1MNN+q}zTB&ooE3j==AgK@^UW<^oSbeALa2peF)Th6{@sj0KyMNHZ zksk1+MXN2tv+22A%cQOGpS9)77(uP9mh+!5T5ERLvF@b}$+WvXM45Z?-kCa)fb~f1 znVbTD$Gx-0Zxc`0D@YgHakge6SL0H`-vN_x?AP0>iGH0_EE&=v83hMJgaKAI0jJXm zVxVz;X<$v6WW7}fxROO7vr#YLP;;lij5VrX{;>7kK6TtOH&6|Ar^xo>00%+u$C4@# z>!jOt6*3><171+WxoZnKDTzJtDRw+T030;yI}~uV@9fCnei^I*j>Bp&mzP2d=FPb_ zCM*l_+$LDR3B*a!A$g#>xsrZvw0lckxmMg>0aQd7tPyN=t{dgXb;Ie+T8{fZH=gdu zM7Rg9c(kg(Jg0?ARRRl=AONFKrvFj)lTY$KfT%6^6s`mk*ABGhsce*LsoD>K{z_M2 ziPpnu+lw22PfF!CoId^6n*G4H(Ix+#+N{C(da7t1BYMGEaE#PdpOLxsVD5riQXHp@OX;`S`8VnpM~)I920w~<3|mo0 zf8~Az`*?2?H&gZ&*K&bRkV@qzvMlRHXys8*Ze2+1c?5o!^+$&MHxB@4Ee5cke52R! zmn7AZtY6ST%ixgU5)%$%QcwHj7Es-Qu^kLAPwy%7pGBw_4Q9#da^W2$}axNHr03)_nw z5?yuNmXrI5HgS46)c5&}B)Tts49oU92>3xBLLy}FMUW=84DQbVq^;7_e7|(Sdz|&J z73N+M`rc2rt*oSWu#7S{*s~nH6HRHJS1SmzeXk|;CA)FI4bat3<%}nkB%;;?=F>B7ms9QSxv#@+69;@>QaR?REYX4&)=itG>rM{<{A79Rmk)`5ON#GL`*KX%}Ihk3w(RtM-WLt z?f&FLF}4N^yE!(pZ&Yj&Bc`~K0@4_}*0Om?wN|}4WJ>WL;G^H2*QpgEkGA~OET-Km zkwz|5{6dnz1U<2Pe9DNL>3g5FEIvp1jzP&2K#z~j%g6!7B;^zF+o95?fV{3mnB8*RMhCDNp>Am-3e@jNfMj?jHV$MWjk!DDKP zkAz$Y?Sr)!GUOX}qTQ5aMh|wq1uq}~joWyKl=b_LboM#wi{CMuz5x6BKlA-qy++cM01D3b7`uD z#l6M4pI;JCypO8JZ6?U&wNxR!{4oB_ zlV!x9+-&Qy6{%MQ{~yoZGkKiTSC`YS_j22~G;xUV855g2&C(zm^V!(wpcm@zn{%!g z4}JGo(sGZ1O~to-}le

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s diff --git a/src/main/res/values-night/themes.xml b/src/main/res/values-night/themes.xml deleted file mode 100644 index 45f3135..0000000 --- a/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml deleted file mode 100644 index f8c6127..0000000 --- a/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml deleted file mode 100644 index beefedb..0000000 --- a/src/main/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - LibTemplatePlaceholder - OSBARCScannerActivity - \ No newline at end of file diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml deleted file mode 100644 index fb27870..0000000 --- a/src/main/res/values/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file From b15c4ea49269b548a36174a8e5bcd73511b10104 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 15:01:01 +0000 Subject: [PATCH 015/174] chore: raise lib version to 0.0.2 References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 20acd37..fac8e81 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.1 + 0.0.2 From 228ce2e5f82c35d879bc0b6dfc76742bef7fc061 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 15:07:26 +0000 Subject: [PATCH 016/174] fix: remove references to resources from AndroidManifest.xml References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- src/main/AndroidManifest.xml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index fec30f6..6282671 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -4,16 +4,10 @@ + android:supportsRtl="true"> + android:exported="false" /> \ No newline at end of file From 4601e7ee32bd10e50c122844a2d41810d364513c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 15:53:03 +0000 Subject: [PATCH 017/174] chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- CHANGELOG.md | 12 ++++++++++++ src/main/AndroidManifest.xml | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6dca7d8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +The changes documented here do not include those from the original repository. + +## [Unreleased] + +### 06-11-2023 +Android - First implementation of the scan barcode feature using zxing (https://outsystemsrd.atlassian.net/browse/RMET-2758) \ No newline at end of file diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 6282671..a8956d8 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -9,5 +9,4 @@ android:name=".view.OSBARCScannerActivity" android:exported="false" /> - \ No newline at end of file From 9874dc63a22fe57490fad11d94624281d479c849 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 15:56:56 +0000 Subject: [PATCH 018/174] chore: raise lib version to 0.0.3 References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- azure-pipelines.yml | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b3a40bf..dca55d8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | diff --git a/pom.xml b/pom.xml index fac8e81..74e4574 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.2 + 0.0.3 From 3013f5642a7632d03bad4e4d91724bafe9c25048 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:01:37 +0000 Subject: [PATCH 019/174] chore: raise java-version to use 17 instead of 11. Context: The version of the Android Gradle plugin the lib uses, which matches the one from MABS 10, requires Java 17 to run. References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .github/workflows/github_actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index eb6bd6b..9ad4861 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: '11' + java-version: '17' - name: Bundle Install run: bundle install From 73e9d224dd63ae23d85218419133cca147430117 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:14:09 +0000 Subject: [PATCH 020/174] chore: set sonar.projectKey Context: This is a required step in the SonarCloud configuration. References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index f860a85..9931ef8 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ # Organization and project keys are displayed in the right sidebar of the project homepage sonar.organization=outsystemsrd -sonar.projectKey=OutSystems_LibTemplatePlaceholder-Android +sonar.projectKey=OutSystems_OSBarcodeLib-Android sonar.host.url=https://sonarcloud.io sonar.language=kotlin From 0726e8345e48008f517fd45af19dfc8509d2e79e Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:22:23 +0000 Subject: [PATCH 021/174] refactor: include TODO comment References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .../kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index d72e724..831c8ac 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -5,5 +5,7 @@ import org.junit.Test class ScanCodeTests { @Test - fun scanCodeTest() { } + fun scanCodeTest() { + // TODO implement unit tests + } } \ No newline at end of file From 318cfff006298d5edb911ac1a9b0257a3c283911 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:26:36 +0000 Subject: [PATCH 022/174] refactor: remove unnecessary fields from AndroidManifest.xml References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- pom.xml | 2 +- src/main/AndroidManifest.xml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 74e4574..c0053cd 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.3 + 0.0.4 diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index a8956d8..46c545d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -2,9 +2,7 @@ - + From b7415d740d5feb6dc051a5803e02668fd6dfe9c3 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:31:01 +0000 Subject: [PATCH 023/174] misc: comment lines to generate new lib version on Azure --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dca55d8..b3a40bf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | From 15ba9b0936d0e2a3e342f8fa10cde0d4ead9fb4a Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:31:45 +0000 Subject: [PATCH 024/174] refactor: change from TODO to actual comment --- src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 831c8ac..b468341 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -6,6 +6,6 @@ class ScanCodeTests { @Test fun scanCodeTest() { - // TODO implement unit tests + // temporarily empty } } \ No newline at end of file From 9948474a0a227574685c0f3e8faea9ab9649c8d6 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:41:55 +0000 Subject: [PATCH 025/174] Finish initialisation by running the generator script. --- fastlane/Appfile | 2 +- scripts/generator_script.sh | 57 ------------------- .../osbarcodelib}/ExampleInstrumentedTest.kt | 4 +- 3 files changed, 3 insertions(+), 60 deletions(-) delete mode 100644 scripts/generator_script.sh rename src/androidTest/java/{organizationidplaceholder/libtemplateplaceholder => com.outsystems.plugins.barcode/osbarcodelib}/ExampleInstrumentedTest.kt (78%) diff --git a/fastlane/Appfile b/fastlane/Appfile index 920122f..b378394 100644 --- a/fastlane/Appfile +++ b/fastlane/Appfile @@ -1,2 +1,2 @@ json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one -package_name("organizationidplaceholder.libtemplateplaceholder") # e.g. com.krausefx.app +package_name("com.outsystems.plugins.barcode.osbarcodelib") # e.g. com.krausefx.app diff --git a/scripts/generator_script.sh b/scripts/generator_script.sh deleted file mode 100644 index 6e2ff57..0000000 --- a/scripts/generator_script.sh +++ /dev/null @@ -1,57 +0,0 @@ -### Welcome the user to this awesome tool - -printf "Welcome to the Android Library Generator!!\n\n" - -remoteURL=$(git config --get remote.origin.url) -remoteURL=$(echo $remoteURL | perl -F/ -wane 'print $F[-1]') -remoteURL=$(echo ${remoteURL%-*}) - -### Inform the user of the library name to be used -printf "Considering the repository name you gave while creating it, we will use '$remoteURL' as the Library's name." - -### Ask the user for the package identifier. Continue to ask while the input doesn't match the correct format. -while true -do - printf "\nPlease write the desired package identifier. This should be composed only by lowercase letters, numbers and '.' (e.g. 'com.outsystems').\n" - read organisationId - result=$(echo $organisationId | awk '/^[a-z0-9]+([\.]?[a-z0-9]+)*?$/') - - if ! [ -z "$result" ]; then - break - else - printf "Format not valid. Please try again." - fi -done - -### Delete current file -currentFile=$(basename "$0") - -printf "\nThe '$currentFile' file will be removed as it should only be executed once.\n\n" - -rm -f $currentFile - -### Proceed to change all necessary placeholders -cd .. - -LC_CTYPE=C && LANG=C && find . -type f -exec sed -e "s/LibTemplatePlaceholder/$remoteURL/g" -i '' '{}' ';' -LC_CTYPE=C && LANG=C && find . -depth -name '*LibTemplatePlaceholder*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E "s/(.*)LibTemplatePlaceholder/\1$remoteURL/")"; done - -LC_CTYPE=C && LANG=C && find . -type f -exec sed -e "s/organizationidplaceholder/$organisationId/g" -i '' '{}' ';' -LC_CTYPE=C && LANG=C && find . -depth -name '*organizationidplaceholder*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E "s/(.*)organizationidplaceholder/\1$organisationId/")"; done - -### Convert $remoteURL to lowercase -lowercaseRemoteURL=$(echo "$remoteURL" | tr '[:upper:]' '[:lower:]') - -LC_CTYPE=C && LANG=C && find . -type f -exec sed -e "s/libtemplateplaceholder/$lowercaseRemoteURL/g" -i '' '{}' ';' -LC_CTYPE=C && LANG=C && find . -depth -name '*libtemplateplaceholder*' -print0|while IFS= read -rd '' f; do mv -i "$f" "$(echo "$f"|sed -E "s/(.*)libtemplateplaceholder/\1$lowercaseRemoteURL/")"; done - -### Commit and push -rm -f .git/index -git reset -git add . -git commit -m "Finish initialisation by running the generator script." -git push - -### Close -printf "\n###Applause###\n" -printf "Looks like everything's done. Enjoy!\n\n" diff --git a/src/androidTest/java/organizationidplaceholder/libtemplateplaceholder/ExampleInstrumentedTest.kt b/src/androidTest/java/com.outsystems.plugins.barcode/osbarcodelib/ExampleInstrumentedTest.kt similarity index 78% rename from src/androidTest/java/organizationidplaceholder/libtemplateplaceholder/ExampleInstrumentedTest.kt rename to src/androidTest/java/com.outsystems.plugins.barcode/osbarcodelib/ExampleInstrumentedTest.kt index 72150fb..9d5999c 100644 --- a/src/androidTest/java/organizationidplaceholder/libtemplateplaceholder/ExampleInstrumentedTest.kt +++ b/src/androidTest/java/com.outsystems.plugins.barcode/osbarcodelib/ExampleInstrumentedTest.kt @@ -1,4 +1,4 @@ -package organizationidplaceholder.libtemplateplaceholder +package com.outsystems.plugins.barcode.osbarcodelib import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -19,6 +19,6 @@ class ExampleInstrumentedTest { fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("organizationidplaceholder.libtemplateplaceholder", appContext.packageName) + assertEquals("com.outsystems.plugins.barcode.osbarcodelib", appContext.packageName) } } \ No newline at end of file From afa9a8f06eb87cda9d1441d7ec34087c20413ca0 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:45:54 +0000 Subject: [PATCH 026/174] chore: remove unnecessary test file References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .../osbarcodelib/ExampleInstrumentedTest.kt | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 src/androidTest/java/com.outsystems.plugins.barcode/osbarcodelib/ExampleInstrumentedTest.kt diff --git a/src/androidTest/java/com.outsystems.plugins.barcode/osbarcodelib/ExampleInstrumentedTest.kt b/src/androidTest/java/com.outsystems.plugins.barcode/osbarcodelib/ExampleInstrumentedTest.kt deleted file mode 100644 index 9d5999c..0000000 --- a/src/androidTest/java/com.outsystems.plugins.barcode/osbarcodelib/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.outsystems.plugins.barcode.osbarcodelib - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.outsystems.plugins.barcode.osbarcodelib", appContext.packageName) - } -} \ No newline at end of file From 7437aa8d96778e2e6ded7462b3adee843e623750 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:50:33 +0000 Subject: [PATCH 027/174] misc: remove comments --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b3a40bf..dca55d8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | From 4724ab6ff90db542da22e0a099a51268f1e34bc7 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 16:57:20 +0000 Subject: [PATCH 028/174] chore: update action versions for GitHub action YAML References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- .github/workflows/github_actions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index 9ad4861..4d494d1 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v3 @@ -32,7 +32,7 @@ jobs: run: bundle exec fastlane coverage - name: Setup sonarqube - uses: warchant/setup-sonar-scanner@v3 + uses: warchant/setup-sonar-scanner@v7 - name: Send to Sonarcloud run: bundle exec fastlane sonarqube From 9e5f4bd0185564ad4cb50d1dfff728a32c24976b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 17:06:42 +0000 Subject: [PATCH 029/174] chore: set files to be excluded from test coverage and code duplication Context: SonarCloud needs to be configured to look at the files where we want coverage to be analyzed. References: https://outsystemsrd.atlassian.net/browse/RMET-2758 --- sonar-project.properties | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 9931ef8..5a511a1 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,4 +7,10 @@ sonar.language=kotlin # Defining path to coverage file sonar.coverage.jacoco.xmlReportPaths=**/jacocoTestReport/jacocoTestReport.xml -sonar.junit.reportPaths=**/test-results/**/*.xml \ No newline at end of file +sonar.junit.reportPaths=**/test-results/**/*.xml + +# Removing tests, mocks and code not testable by unit tests from the coverage estimation +sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/controller/*.kt,**/view/*.kt + +# Removing tests from code duplication checks +sonar.cpd.exclusions=**/*Tests.kt \ No newline at end of file From a34fd17ea8722e2bd945c34a43b9144994aae762 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 17:14:21 +0000 Subject: [PATCH 030/174] chore: include missing file in excluded files list --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 5a511a1..e2e2719 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -10,7 +10,7 @@ sonar.coverage.jacoco.xmlReportPaths=**/jacocoTestReport/jacocoTestReport.xml sonar.junit.reportPaths=**/test-results/**/*.xml # Removing tests, mocks and code not testable by unit tests from the coverage estimation -sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/controller/*.kt,**/view/*.kt +sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/controller/*.kt,**/view/*.kt,Theme.kt # Removing tests from code duplication checks sonar.cpd.exclusions=**/*Tests.kt \ No newline at end of file From 3bd7a412b6a58250f7861ef12bd890a3f3efdfac Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 6 Nov 2023 17:20:36 +0000 Subject: [PATCH 031/174] chore: include missing file in excluded files list --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index e2e2719..0752a6c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -10,7 +10,7 @@ sonar.coverage.jacoco.xmlReportPaths=**/jacocoTestReport/jacocoTestReport.xml sonar.junit.reportPaths=**/test-results/**/*.xml # Removing tests, mocks and code not testable by unit tests from the coverage estimation -sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/controller/*.kt,**/view/*.kt,Theme.kt +sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/controller/*.kt,**/view/*.kt,**/*Theme.kt,**/ui.theme/*.kt # Removing tests from code duplication checks sonar.cpd.exclusions=**/*Tests.kt \ No newline at end of file From ce0bb59eab5d2085b336793dffb0dce6919a6319 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 7 Nov 2023 16:07:58 +0000 Subject: [PATCH 032/174] feat: implement barcode scanning with ML Kit Context: Since we're now introducing the usage of ML Kit to scan barcodes, our library now offers two options for reading barcodes. Because of that, we used the Factory design pattern to obtain the correct class library instance for each run of the scan barcode feature - ZXing or ML Kit. References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- build.gradle | 1 + .../controller/OSBARCBarcodeAnalyzer.kt | 49 ++++----------- .../barcode/controller/OSBARCMLKitWrapper.kt | 59 +++++++++++++++++ .../controller/OSBARCScanLibraryFactory.kt | 31 +++++++++ .../controller/OSBARCScanLibraryInterface.kt | 12 ++++ .../barcode/controller/OSBARCZXingWrapper.kt | 63 +++++++++++++++++++ .../plugins/barcode/model/OSBARCError.kt | 4 +- .../barcode/view/OSBARCScannerActivity.kt | 1 + 8 files changed, 181 insertions(+), 39 deletions(-) create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt diff --git a/build.gradle b/build.gradle index 8021aa8..5f0805a 100644 --- a/build.gradle +++ b/build.gradle @@ -103,6 +103,7 @@ dependencies { implementation 'androidx.camera:camera-lifecycle:1.3.0' implementation 'androidx.camera:camera-view:1.3.0' implementation 'com.google.zxing:core:3.4.1' + implementation 'com.google.mlkit:barcode-scanning:17.2.0' androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') androidTestImplementation 'androidx.compose.ui:ui-test-junit4' androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index 0a4c154..f4542ab 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -1,18 +1,12 @@ package com.outsystems.plugins.barcode.controller -import android.graphics.Bitmap -import android.graphics.Matrix import android.util.Log import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy -import com.google.zxing.BinaryBitmap -import com.google.zxing.DecodeHintType -import com.google.zxing.MultiFormatReader -import com.google.zxing.RGBLuminanceSource -import com.google.zxing.common.HybridBinarizer import java.lang.Exception class OSBARCBarcodeAnalyzer( + private val scanLibrary: String, private val onBarcodeScanned: (String) -> Unit, private val onScanningError: () -> Unit ): ImageAnalysis.Analyzer { @@ -23,40 +17,19 @@ class OSBARCBarcodeAnalyzer( override fun analyze(image: ImageProxy) { try { - var imageBitmap = image.toBitmap() - - // rotate the image if it's in portrait mode (rotation = 90 or 270 degrees) - val rotationDegrees = image.imageInfo.rotationDegrees - if (rotationDegrees == 90 || rotationDegrees == 270) { - // create a matrix for rotation - val matrix = Matrix() - matrix.postRotate(rotationDegrees.toFloat()) - - // actually rotate the image - imageBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, imageBitmap.width, imageBitmap.height, matrix, true) - } - - // scan image using zxing - val width = imageBitmap.width - val height = imageBitmap.height - val pixels = IntArray(width * height) - imageBitmap.getPixels(pixels, 0, width, 0, 0, width, height) - - val source = RGBLuminanceSource(width, height, pixels) - val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) - val result = MultiFormatReader().apply { - setHints( - mapOf( - DecodeHintType.TRY_HARDER to arrayListOf(true) - ) - ) - }.decode(binaryBitmap) - onBarcodeScanned(result.text) + val scanningLib : OSBARCScanLibraryInterface = OSBARCScanLibraryFactory.createScanLibraryWrapper(scanLibrary) + scanningLib.scanBarcode( + image, + { + onBarcodeScanned(it) + }, + { + onScanningError + } + ) } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } onScanningError - } finally { - image.close() } } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt new file mode 100644 index 0000000..5cebb3f --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt @@ -0,0 +1,59 @@ +package com.outsystems.plugins.barcode.controller + +import android.util.Log +import androidx.camera.core.ImageProxy +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.common.InputImage +import com.outsystems.plugins.barcode.model.OSBARCError +import kotlinx.coroutines.runBlocking + +class OSBARCMLKitWrapper: OSBARCScanLibraryInterface { + + companion object { + private const val LOG_TAG = "OSBARCMLKitWrapper" + } + + override fun scanBarcode( + imageProxy: ImageProxy, + onSuccess: (String) -> Unit, + onError: (OSBARCError) -> Unit + ) { + try { + val options = BarcodeScannerOptions.Builder() + .enableAllPotentialBarcodes() + .build() + val scanner = BarcodeScanning.getClient(options) + val mediaImage = imageProxy.image + + if (mediaImage != null) { + val image = InputImage.fromMediaImage( + mediaImage, + imageProxy.imageInfo.rotationDegrees + ) + runBlocking { + scanner.process(image) + .addOnSuccessListener { barcodes -> + var result: String? = null + if (barcodes.isNotEmpty()) { + result = barcodes.first().rawValue + } + if (!result.isNullOrEmpty()) { + onSuccess(result) + } + } + .addOnFailureListener { e -> + e.message?.let { Log.e(LOG_TAG, it) } + onError(OSBARCError.MLKIT_LIBRARY_ERROR) + } + } + } + } catch (e: Exception) { + e.message?.let { Log.e(LOG_TAG, it) } + onError(OSBARCError.MLKIT_LIBRARY_ERROR) + } finally { + imageProxy.close() + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt new file mode 100644 index 0000000..597a972 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt @@ -0,0 +1,31 @@ +package com.outsystems.plugins.barcode.controller + +class OSBARCScanLibraryFactory { + + companion object { + private const val LIBRARY_ZXING = "zxing" + private const val LIBRARY_MLKIT = "mlkit" + + fun createScanLibraryWrapper(scanLibrary: String): OSBARCScanLibraryInterface { + return when (scanLibrary) { + LIBRARY_ZXING -> { + createZXingWrapper() + } + LIBRARY_MLKIT -> { + createMLKitWrapper() + } + else -> { + createZXingWrapper() + } + } + } + + private fun createZXingWrapper(): OSBARCZXingWrapper { + return OSBARCZXingWrapper() + } + + private fun createMLKitWrapper(): OSBARCMLKitWrapper { + return OSBARCMLKitWrapper() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt new file mode 100644 index 0000000..b08a7eb --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt @@ -0,0 +1,12 @@ +package com.outsystems.plugins.barcode.controller + +import androidx.camera.core.ImageProxy +import com.outsystems.plugins.barcode.model.OSBARCError + +interface OSBARCScanLibraryInterface { + fun scanBarcode( + imageProxy: ImageProxy, + onSuccess: (String) -> Unit, + onError: (OSBARCError) -> Unit + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt new file mode 100644 index 0000000..6fc224a --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -0,0 +1,63 @@ +package com.outsystems.plugins.barcode.controller + +import android.graphics.Bitmap +import android.graphics.Matrix +import android.util.Log +import androidx.camera.core.ImageProxy +import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType +import com.google.zxing.MultiFormatReader +import com.google.zxing.RGBLuminanceSource +import com.google.zxing.common.HybridBinarizer +import com.outsystems.plugins.barcode.model.OSBARCError + +class OSBARCZXingWrapper: OSBARCScanLibraryInterface { + + companion object { + private const val LOG_TAG = "OSBARCZXingWrapper" + } + + override fun scanBarcode( + imageProxy: ImageProxy, + onSuccess: (String) -> Unit, + onError: (OSBARCError) -> Unit + ) { + try { + var imageBitmap = imageProxy.toBitmap() + + // rotate the image if it's in portrait mode (rotation = 90 or 270 degrees) + val rotationDegrees = imageProxy.imageInfo.rotationDegrees + if (rotationDegrees == 90 || rotationDegrees == 270) { + // create a matrix for rotation + val matrix = Matrix() + matrix.postRotate(rotationDegrees.toFloat()) + + // actually rotate the image + imageBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, imageBitmap.width, imageBitmap.height, matrix, true) + } + + // scan image using zxing + val width = imageBitmap.width + val height = imageBitmap.height + val pixels = IntArray(width * height) + imageBitmap.getPixels(pixels, 0, width, 0, 0, width, height) + + val source = RGBLuminanceSource(width, height, pixels) + val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) + val result = MultiFormatReader().apply { + setHints( + mapOf( + DecodeHintType.TRY_HARDER to arrayListOf(true) + ) + ) + }.decode(binaryBitmap) + onSuccess(result.text) + } catch (e: Exception) { + e.message?.let { Log.e(LOG_TAG, it) } + onError(OSBARCError.ZXING_LIBRARY_ERROR) + } finally { + imageProxy.close() + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt index ef020c6..b0c9006 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt @@ -4,5 +4,7 @@ enum class OSBARCError(val code: Int, val description: String) { CAMERA_PERMISSION_DENIED_ERROR(1, "Couldn't access camera. Check your camera permissions and try again."), INVALID_PARAMETERS_ERROR(2, "Barcode parameters are invalid."), SCAN_CANCELLED_ERROR(3, "Barcode scanning was cancelled."), - SCANNING_GENERAL_ERROR(4, "There was an error scanning the barcode.") + SCANNING_GENERAL_ERROR(4, "There was an error scanning the barcode."), + ZXING_LIBRARY_ERROR(5, "There was an error scanning the barcode with ZXing."), + MLKIT_LIBRARY_ERROR(6, "There was an error scanning the barcode with ML Kit.") } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index c441037..6dc55c3 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -96,6 +96,7 @@ class OSBARCScannerActivity : ComponentActivity() { imageAnalysis.setAnalyzer( ContextCompat.getMainExecutor(context), OSBARCBarcodeAnalyzer( + "zxing", // temporary { result -> barcode = result val resultIntent = Intent() From ba1dd4229c9476b3a712449b7c9f048d91ef6fe3 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 7 Nov 2023 17:39:58 +0000 Subject: [PATCH 033/174] feat: handle all possible errors References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../barcode/controller/OSBARCBarcodeAnalyzer.kt | 5 +++-- .../plugins/barcode/controller/OSBARCController.kt | 10 ++++++---- .../barcode/controller/OSBARCMLKitWrapper.kt | 5 +++-- .../barcode/controller/OSBARCZXingWrapper.kt | 14 +++++++++++++- .../plugins/barcode/view/OSBARCScannerActivity.kt | 9 ++++----- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index f4542ab..d2a02ac 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -3,12 +3,13 @@ package com.outsystems.plugins.barcode.controller import android.util.Log import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy +import com.outsystems.plugins.barcode.model.OSBARCError import java.lang.Exception class OSBARCBarcodeAnalyzer( private val scanLibrary: String, private val onBarcodeScanned: (String) -> Unit, - private val onScanningError: () -> Unit + private val onScanningError: (OSBARCError) -> Unit ): ImageAnalysis.Analyzer { companion object { @@ -24,7 +25,7 @@ class OSBARCBarcodeAnalyzer( onBarcodeScanned(it) }, { - onScanningError + onScanningError(it) } ) } catch (e: Exception) { diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt index 53d5a4c..4b3f14d 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -18,8 +18,6 @@ class OSBARCController { private const val SCAN_HINT = "SCAN_HINT" private const val SCAN_LIBRARY = "SCAN_LIBRARY" private const val SCAN_RESULT = "scanResult" - private const val CAMERA_PERMISSION_DENIED_RESULT_CODE = 1 - private const val SCANNING_EXCEPTION_RESULT_CODE = 2 } fun scanCode(activity: Activity, parameters: OSBARCScanParameters) { @@ -55,10 +53,14 @@ class OSBARCController { } Activity.RESULT_CANCELED -> onError(OSBARCError.SCAN_CANCELLED_ERROR) - CAMERA_PERMISSION_DENIED_RESULT_CODE -> + OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code -> onError(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR) - SCANNING_EXCEPTION_RESULT_CODE -> + OSBARCError.SCANNING_GENERAL_ERROR.code -> onError(OSBARCError.SCANNING_GENERAL_ERROR) + OSBARCError.ZXING_LIBRARY_ERROR.code -> + onError(OSBARCError.ZXING_LIBRARY_ERROR) + OSBARCError.MLKIT_LIBRARY_ERROR.code -> + onError(OSBARCError.MLKIT_LIBRARY_ERROR) } } } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt index 5cebb3f..b0bfe1a 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt @@ -46,13 +46,14 @@ class OSBARCMLKitWrapper: OSBARCScanLibraryInterface { e.message?.let { Log.e(LOG_TAG, it) } onError(OSBARCError.MLKIT_LIBRARY_ERROR) } + .addOnCompleteListener { + imageProxy.close() + } } } } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } onError(OSBARCError.MLKIT_LIBRARY_ERROR) - } finally { - imageProxy.close() } } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt index 6fc224a..28ce2b4 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -7,6 +7,7 @@ import androidx.camera.core.ImageProxy import com.google.zxing.BinaryBitmap import com.google.zxing.DecodeHintType import com.google.zxing.MultiFormatReader +import com.google.zxing.NotFoundException import com.google.zxing.RGBLuminanceSource import com.google.zxing.common.HybridBinarizer import com.outsystems.plugins.barcode.model.OSBARCError @@ -33,7 +34,15 @@ class OSBARCZXingWrapper: OSBARCScanLibraryInterface { matrix.postRotate(rotationDegrees.toFloat()) // actually rotate the image - imageBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, imageBitmap.width, imageBitmap.height, matrix, true) + imageBitmap = Bitmap.createBitmap( + imageBitmap, + 0, + 0, + imageBitmap.width, + imageBitmap.height, + matrix, + true + ) } // scan image using zxing @@ -52,6 +61,9 @@ class OSBARCZXingWrapper: OSBARCScanLibraryInterface { ) }.decode(binaryBitmap) onSuccess(result.text) + } catch (e: NotFoundException) { + // keep trying + e.message?.let { Log.d(LOG_TAG, it) } } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } onError(OSBARCError.ZXING_LIBRARY_ERROR) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 6dc55c3..129864e 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer +import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import java.lang.Exception @@ -35,8 +36,6 @@ class OSBARCScannerActivity : ComponentActivity() { companion object { private const val SCAN_SUCCESS_RESULT_CODE = -1 - private const val CAMERA_PERMISSION_DENIED_RESULT_CODE = 1 - private const val SCANNING_EXCEPTION_RESULT_CODE = 2 private const val SCAN_RESULT = "scanResult" private const val LOG_TAG = "OSBARCScannerActivity" } @@ -63,7 +62,7 @@ class OSBARCScannerActivity : ComponentActivity() { if (isGranted) { // do nothing, continue } else { - this.setResult(CAMERA_PERMISSION_DENIED_RESULT_CODE) + this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) this.finish() } } @@ -105,7 +104,7 @@ class OSBARCScannerActivity : ComponentActivity() { finish() }, { - setResult(SCANNING_EXCEPTION_RESULT_CODE) + setResult(it.code) finish() } ) @@ -119,7 +118,7 @@ class OSBARCScannerActivity : ComponentActivity() { ) } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } - setResult(SCANNING_EXCEPTION_RESULT_CODE) + setResult(OSBARCError.SCANNING_GENERAL_ERROR.code) finish() } previewView From 7ccc9ca1736d64e4727db63f79b25203c993831a Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 7 Nov 2023 17:44:45 +0000 Subject: [PATCH 034/174] chore: raise lib version to 0.0.5 References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- azure-pipelines.yml | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dca55d8..b3a40bf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | diff --git a/pom.xml b/pom.xml index c0053cd..a8fb6da 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.4 + 0.0.5 From 906b3a921d18d7c66d1463cdd40d380cd6bf1baf Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 7 Nov 2023 17:51:36 +0000 Subject: [PATCH 035/174] fix: include missing annotation for ImageProxy method References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../plugins/barcode/controller/OSBARCMLKitWrapper.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt index b0bfe1a..b036877 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt @@ -1,6 +1,8 @@ package com.outsystems.plugins.barcode.controller import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.core.ExperimentalGetImage import androidx.camera.core.ImageProxy import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.BarcodeScanning @@ -14,7 +16,7 @@ class OSBARCMLKitWrapper: OSBARCScanLibraryInterface { private const val LOG_TAG = "OSBARCMLKitWrapper" } - override fun scanBarcode( + @OptIn(ExperimentalGetImage::class) override fun scanBarcode( imageProxy: ImageProxy, onSuccess: (String) -> Unit, onError: (OSBARCError) -> Unit From efc485de409d6826dad3c319b74ec96c21a842cb Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 7 Nov 2023 18:27:42 +0000 Subject: [PATCH 036/174] chore: add documentation using KDoc Context: Documentation was added using the official format for Kotlin code documentation - KDoc: https://kotlinlang.org/docs/kotlin-doc.html. References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../controller/OSBARCBarcodeAnalyzer.kt | 10 ++++++++++ .../barcode/controller/OSBARCController.kt | 18 ++++++++++++++++++ .../barcode/controller/OSBARCMLKitWrapper.kt | 10 ++++++++++ .../controller/OSBARCScanLibraryFactory.kt | 16 ++++++++++++++++ .../controller/OSBARCScanLibraryInterface.kt | 3 +++ .../barcode/controller/OSBARCZXingWrapper.kt | 10 ++++++++++ .../plugins/barcode/model/OSBARCError.kt | 3 +++ .../barcode/model/OSBARCScanParameters.kt | 3 +++ .../barcode/view/OSBARCScannerActivity.kt | 12 ++++++++++++ 9 files changed, 85 insertions(+) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index d2a02ac..215acf4 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -6,6 +6,10 @@ import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.model.OSBARCError import java.lang.Exception +/** + * This class is responsible for implementing the ImageAnalysis.Analyzer interface, + * and overriding its analyze() method to scan for barcodes in the image frames. + */ class OSBARCBarcodeAnalyzer( private val scanLibrary: String, private val onBarcodeScanned: (String) -> Unit, @@ -16,6 +20,12 @@ class OSBARCBarcodeAnalyzer( private const val LOG_TAG = "OSBARCBarcodeAnalyzer" } + /** + * Overrides the analyze() method from ImageAnalysis.Analyzer, + * calling the respective implementation of OSBARCScanLibraryInterface + * to scan for barcodes in the image. + * @param image - ImageProxy object that represents the image to be analyzed. + */ override fun analyze(image: ImageProxy) { try { val scanningLib : OSBARCScanLibraryInterface = OSBARCScanLibraryFactory.createScanLibraryWrapper(scanLibrary) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt index 4b3f14d..fdc9b38 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -6,6 +6,10 @@ import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.OSBARCScannerActivity +/** + * This class is responsible for implementing the Controller + * of the library, following the MVC pattern + */ class OSBARCController { companion object { @@ -20,6 +24,12 @@ class OSBARCController { private const val SCAN_RESULT = "scanResult" } + /** + * Scans barcodes, opening the device's camera and using scanning libraries. + * To do that, it launches the OSBARCScannerActivity activity. + * @param activity - used to open the scanner activity, its onActivityResult() will be called after scanning the barcode. + * @param parameters - object that contains all the barcode parameters to be used when scanning. + */ fun scanCode(activity: Activity, parameters: OSBARCScanParameters) { val scanningIntent = Intent(activity, OSBARCScannerActivity::class.java).apply { putExtra(SCAN_INSTRUCTIONS, parameters.scanInstructions) @@ -33,6 +43,14 @@ class OSBARCController { activity.startActivityForResult(scanningIntent, SCAN_REQUEST_CODE) } + /** + * Handles the result of calling the scanCode feature. + * @param requestCode - the code identifying the request. + * @param resultCode - the code identifying the result of the request. + * @param intent - the resulting intent from the operation. + * @param onSuccess - The code to be executed if the operation was successful. + * @param onError - The code to be executed if the operation was not successful. + */ fun handleActivityResult( requestCode: Int, resultCode: Int, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt index b036877..e3f27e5 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt @@ -10,12 +10,22 @@ import com.google.mlkit.vision.common.InputImage import com.outsystems.plugins.barcode.model.OSBARCError import kotlinx.coroutines.runBlocking +/** + * Helper class that implements the OSBARCScanLibraryInterface + * to scan an image using the ML Kit library. + */ class OSBARCMLKitWrapper: OSBARCScanLibraryInterface { companion object { private const val LOG_TAG = "OSBARCMLKitWrapper" } + /** + * Scans an image looking for barcodes, using the ML Kit library. + * @param imageProxy - ImageProxy object that represents the image to be analyzed. + * @param onSuccess - The code to be executed if the operation was successful. + * @param onError - The code to be executed if the operation was not successful. + */ @OptIn(ExperimentalGetImage::class) override fun scanBarcode( imageProxy: ImageProxy, onSuccess: (String) -> Unit, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt index 597a972..6944ec7 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt @@ -1,11 +1,19 @@ package com.outsystems.plugins.barcode.controller +/** + * A factory class to create instances of OSBARCScanLibraryInterface. + */ class OSBARCScanLibraryFactory { companion object { private const val LIBRARY_ZXING = "zxing" private const val LIBRARY_MLKIT = "mlkit" + /** + * Creates and returns OSPMTGatewayInterface instance. + * @param scanLibrary - String to identify which library to use + * @return the newly created OSBARCScanLibraryInterface instance. + */ fun createScanLibraryWrapper(scanLibrary: String): OSBARCScanLibraryInterface { return when (scanLibrary) { LIBRARY_ZXING -> { @@ -20,10 +28,18 @@ class OSBARCScanLibraryFactory { } } + /** + * Creates and returns a OSBARCZXingWrapper instance. + * @return the newly created OSBARCZXingWrapper instance. + */ private fun createZXingWrapper(): OSBARCZXingWrapper { return OSBARCZXingWrapper() } + /** + * Creates and returns a OSBARCMLKitWrapper instance. + * @return the newly created OSBARCMLKitWrapper instance. + */ private fun createMLKitWrapper(): OSBARCMLKitWrapper { return OSBARCMLKitWrapper() } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt index b08a7eb..c890482 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt @@ -3,6 +3,9 @@ package com.outsystems.plugins.barcode.controller import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.model.OSBARCError +/** + * Interface that provides the signature of the scanBarcode method + */ interface OSBARCScanLibraryInterface { fun scanBarcode( imageProxy: ImageProxy, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt index 28ce2b4..40e9af0 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -12,12 +12,22 @@ import com.google.zxing.RGBLuminanceSource import com.google.zxing.common.HybridBinarizer import com.outsystems.plugins.barcode.model.OSBARCError +/** + * Helper class that implements the OSBARCScanLibraryInterface + * to scan an image using the ZXing library. + */ class OSBARCZXingWrapper: OSBARCScanLibraryInterface { companion object { private const val LOG_TAG = "OSBARCZXingWrapper" } + /** + * Scans an image looking for barcodes, using the ZXing library. + * @param imageProxy - ImageProxy object that represents the image to be analyzed. + * @param onSuccess - The code to be executed if the operation was successful. + * @param onError - The code to be executed if the operation was not successful. + */ override fun scanBarcode( imageProxy: ImageProxy, onSuccess: (String) -> Unit, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt index b0c9006..45c2785 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt @@ -1,5 +1,8 @@ package com.outsystems.plugins.barcode.model +/** + * Enum class that holds the library's errors. + */ enum class OSBARCError(val code: Int, val description: String) { CAMERA_PERMISSION_DENIED_ERROR(1, "Couldn't access camera. Check your camera permissions and try again."), INVALID_PARAMETERS_ERROR(2, "Barcode parameters are invalid."), diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt index 12b54d2..a5dc9a1 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt @@ -1,5 +1,8 @@ package com.outsystems.plugins.barcode.model +/** + * Data class that represents the object with the scan parameters. + */ data class OSBARCScanParameters( val scanInstructions: String?, val cameraDirection: Int?, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 129864e..5bd3107 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -32,6 +32,11 @@ import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import java.lang.Exception +/** + * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. + * It is also responsible for opening a camera stream using CameraX, and calling the class that + * implements the ImageAnalysis.Analyzer interface. + */ class OSBARCScannerActivity : ComponentActivity() { companion object { @@ -40,6 +45,9 @@ class OSBARCScannerActivity : ComponentActivity() { private const val LOG_TAG = "OSBARCScannerActivity" } + /** + * Overrides the onCreate method from Activity, setting the UI of the screen + */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -50,6 +58,10 @@ class OSBARCScannerActivity : ComponentActivity() { } } + /** + * Composable function, responsible for declaring the UI of the screen, + * as well as creating an instance of OSBARCBarcodeAnalyzer for image analysis. + */ @Composable fun ScanScreen() { val lifecycleOwner = LocalLifecycleOwner.current From 7ab0e7f25d6660984695381fdb46171e008d0012 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 8 Nov 2023 13:12:46 +0000 Subject: [PATCH 037/174] fix: use kotlin 1.6.20 because of MABS 9 Context: Because of MABS 9 builds, we need to compile the library with Kotlin 1.6.20 and not 1.9.10. If we only needed to support MABS 10, we could use 1.9.10. Because of this, we needed to downgrade some our the gradle dependencies. One of these dependencies is 'androidx.camera:camera-core', which we had to downgrade. For that reason, we cannot use method ImageProxy.toBitmap any longer and had to implement our own version of it. References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- build.gradle | 43 ++++++++++++++----- pom.xml | 2 +- .../barcode/controller/OSBARCZXingWrapper.kt | 42 +++++++++++++++++- .../plugins/barcode/ScanCodeTests.kt | 20 +++++++++ 4 files changed, 94 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 5f0805a..75ebf3b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = "1.9.10" + ext.kotlin_version = "1.6.20" ext.jacocoVersion = '0.8.7' repositories { google() @@ -66,7 +66,7 @@ android { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.5.3' + kotlinCompilerExtensionVersion '1.2.0-alpha08' } packaging { resources { @@ -85,31 +85,52 @@ dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' + + //implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + + implementation "androidx.compose.ui:ui:1.0.5" + implementation "androidx.compose.material:material:1.0.5" + implementation "androidx.compose.ui:ui-tooling-preview:1.0.5" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' + implementation "androidx.activity:activity-compose:1.4.0" + implementation 'androidx.compose.material3:material3:1.0.0' + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.5" + debugImplementation "androidx.compose.ui:ui-tooling:1.0.5" + + /* implementation platform('androidx.compose:compose-bom:2023.03.00') implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui-graphics' implementation 'androidx.compose.ui:ui-tooling-preview' implementation 'androidx.compose.material3:material3' + implementation platform('androidx.compose:compose-bom:2023.03.00') implementation platform('androidx.compose:compose-bom:2023.03.00') - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + */ + + + implementation "androidx.camera:camera-camera2:1.0.2" + implementation 'androidx.camera:camera-lifecycle:1.0.2' + implementation 'androidx.camera:camera-view:1.0.0-alpha31' + + implementation 'androidx.camera:camera-core:1.0.0' + - implementation "androidx.activity:activity-compose:1.8.0" - implementation "androidx.camera:camera-camera2:1.3.0" - implementation 'androidx.camera:camera-lifecycle:1.3.0' - implementation 'androidx.camera:camera-view:1.3.0' implementation 'com.google.zxing:core:3.4.1' implementation 'com.google.mlkit:barcode-scanning:17.2.0' + + /* androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') androidTestImplementation 'androidx.compose.ui:ui-test-junit4' androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') debugImplementation 'androidx.compose.ui:ui-tooling' debugImplementation 'androidx.compose.ui:ui-test-manifest' + */ } diff --git a/pom.xml b/pom.xml index a8fb6da..75c48c4 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.5 + 0.0.6 diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt index 40e9af0..c174b84 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -1,7 +1,11 @@ package com.outsystems.plugins.barcode.controller import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.ImageFormat import android.graphics.Matrix +import android.graphics.Rect +import android.graphics.YuvImage import android.util.Log import androidx.camera.core.ImageProxy import com.google.zxing.BinaryBitmap @@ -11,6 +15,8 @@ import com.google.zxing.NotFoundException import com.google.zxing.RGBLuminanceSource import com.google.zxing.common.HybridBinarizer import com.outsystems.plugins.barcode.model.OSBARCError +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer /** * Helper class that implements the OSBARCScanLibraryInterface @@ -33,8 +39,9 @@ class OSBARCZXingWrapper: OSBARCScanLibraryInterface { onSuccess: (String) -> Unit, onError: (OSBARCError) -> Unit ) { + try { - var imageBitmap = imageProxy.toBitmap() + var imageBitmap = imageProxyToBitmap(imageProxy) // rotate the image if it's in portrait mode (rotation = 90 or 270 degrees) val rotationDegrees = imageProxy.imageInfo.rotationDegrees @@ -80,6 +87,39 @@ class OSBARCZXingWrapper: OSBARCScanLibraryInterface { } finally { imageProxy.close() } + + } + + // Function to convert ImageProxy to Bitmap + private fun imageProxyToBitmap(image: ImageProxy): Bitmap { + + // get image data + val planes = image.planes + val yBuffer: ByteBuffer = planes[0].buffer + val uBuffer: ByteBuffer = planes[1].buffer + val vBuffer: ByteBuffer = planes[2].buffer + + val imageWidth = image.width + val imageHeight = image.height + + val ySize = yBuffer.remaining() + val uSize = uBuffer.remaining() + val vSize = vBuffer.remaining() + + // use byte arrays for image data + val data = ByteArray(ySize + uSize + vSize) + yBuffer.get(data, 0, ySize) + uBuffer.get(data, ySize, uSize) + vBuffer.get(data, ySize + uSize, vSize) + + // Create a YUV image + val yuvImage = YuvImage(data, ImageFormat.NV21, imageWidth, imageHeight, null) + + // Convert YUV to Bitmap + val out = ByteArrayOutputStream() + yuvImage.compressToJpeg(Rect(0, 0, imageWidth, imageHeight), 100, out) + val imageBytes = out.toByteArray() + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) } } \ No newline at end of file diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index b468341..bc422c9 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -8,4 +8,24 @@ class ScanCodeTests { fun scanCodeTest() { // temporarily empty } + + @Test + fun givenZXingParamWhenScanBarcodeThenZXingWrapperCalled() { + // temporarily empty + } + + @Test + fun givenMLKitParamWhenScanBarcodeThenMLKitWrapperCalled() { + // temporarily empty + } + + @Test + fun givenLibParamEmptyWhenScanBarcodeThenZXingWrapperCalled() { + // temporarily empty + } + + @Test + fun givenAnyOtherLibWhenScanBarcodeThenZXingWrapperCalled() { + // temporarily empty + } } \ No newline at end of file From 604d8f94af28943137a69972709082cb50f165c1 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 8 Nov 2023 19:16:49 +0000 Subject: [PATCH 038/174] feat: implement first part of unit tests References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- build.gradle | 9 + sonar-project.properties | 2 +- .../plugins/barcode/ScanCodeTests.kt | 160 +++++++++++++++++- 3 files changed, 166 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 75ebf3b..eeee5d1 100644 --- a/build.gradle +++ b/build.gradle @@ -73,6 +73,10 @@ android { excludes += '/META-INF/{AL2.0,LGPL2.1}' } } + + testOptions { + unitTests.returnDefaultValues = true + } } repositories { @@ -132,5 +136,10 @@ dependencies { debugImplementation 'androidx.compose.ui:ui-test-manifest' */ + testImplementation "org.mockito:mockito-core:4.3.0" + testImplementation 'org.mockito:mockito-inline:4.3.0' + testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0' + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2" } diff --git a/sonar-project.properties b/sonar-project.properties index 0752a6c..72f2b57 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -10,7 +10,7 @@ sonar.coverage.jacoco.xmlReportPaths=**/jacocoTestReport/jacocoTestReport.xml sonar.junit.reportPaths=**/test-results/**/*.xml # Removing tests, mocks and code not testable by unit tests from the coverage estimation -sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/controller/*.kt,**/view/*.kt,**/*Theme.kt,**/ui.theme/*.kt +sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/view/*.kt,**/*Theme.kt,**/ui.theme/*.kt # Removing tests from code duplication checks sonar.cpd.exclusions=**/*Tests.kt \ No newline at end of file diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index bc422c9..5a6675e 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -1,17 +1,169 @@ package com.outsystems.plugins.barcode +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import com.outsystems.plugins.barcode.controller.OSBARCController +import com.outsystems.plugins.barcode.model.OSBARCError +import com.outsystems.plugins.barcode.model.OSBARCScanParameters +import org.junit.Assert.assertEquals +import org.junit.Assert.fail +import org.junit.Before import org.junit.Test +import org.mockito.Mockito class ScanCodeTests { + private lateinit var mockActivity: Activity + private lateinit var mockIntent: Intent + private lateinit var mockBundle: Bundle + + companion object { + private const val SCAN_REQUEST_CODE = 112 + private const val SCAN_RESULT = "scanResult" + private const val RESULT_CODE = "myCode" + } + + @Before + fun before() { + mockActivity = Mockito.mock(Activity::class.java) + mockIntent = Mockito.mock(Intent::class.java) + mockBundle = Mockito.mock(Bundle::class.java) + } + @Test - fun scanCodeTest() { - // temporarily empty + fun givenParametersCorrectWhenScanBarcodeThenSuccess() { + val barcodeController = OSBARCController() + val parameters = OSBARCScanParameters( + "Scan the barcode", + 1, + 1, + false, + "", + 1, + "zxing" + ) + barcodeController.scanCode(mockActivity, parameters) } @Test - fun givenZXingParamWhenScanBarcodeThenZXingWrapperCalled() { - // temporarily empty + fun givenResultOKAndBarcodeValidWhenHandleScanResultThenSuccess() { + Mockito.doReturn(mockBundle).`when`(mockIntent).extras + Mockito.doReturn(RESULT_CODE).`when`(mockBundle).getString(SCAN_RESULT) + + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, Activity.RESULT_OK, mockIntent, + { + assertEquals(RESULT_CODE, it) + }, + { + fail() + } + ) + } + + @Test + fun givenResultOKAndBarcodeNullWhenHandleScanResultThenScanningError() { + Mockito.doReturn(mockBundle).`when`(mockIntent).extras + Mockito.doReturn(null).`when`(mockBundle).getString(SCAN_RESULT) + + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, Activity.RESULT_OK, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ) + } + + @Test + fun givenResultOKAndBarcodeEmptyWhenHandleScanResultThenScanningError() { + Mockito.doReturn(mockBundle).`when`(mockIntent).extras + Mockito.doReturn("").`when`(mockBundle).getString(SCAN_RESULT) + + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, Activity.RESULT_OK, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ) + } + + @Test + fun givenResultCancelledAndBarcodeEmptyWhenHandleScanResultThenCancelledError() { + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, Activity.RESULT_CANCELED, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.SCAN_CANCELLED_ERROR.code, it.code) + assertEquals(OSBARCError.SCAN_CANCELLED_ERROR.description, it.description) + } + ) + } + + @Test + fun givenCameraPermissionDeniedWhenHandleScanResultThenPermissionDeniedError() { + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code, it.code) + assertEquals(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.description, it.description) + } + ) + } + + @Test + fun givenScanningErrorWhenHandleScanResultThenScanningError() { + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, OSBARCError.SCANNING_GENERAL_ERROR.code, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ) + } + + @Test + fun givenZXingErrorWhenHandleScanResultThenZXingError() { + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, OSBARCError.ZXING_LIBRARY_ERROR.code, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.code, it.code) + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.description, it.description) + } + ) + } + + @Test + fun givenMLKitErrorWhenHandleScanResultThenMLKitError() { + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, OSBARCError.MLKIT_LIBRARY_ERROR.code, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.MLKIT_LIBRARY_ERROR.code, it.code) + assertEquals(OSBARCError.MLKIT_LIBRARY_ERROR.description, it.description) + } + ) } @Test From 6f976f8f5485b2af32c3137ea60a45e48d103eb8 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 10:22:45 +0000 Subject: [PATCH 039/174] feat: implement unit tests for BarcodeAnalyzer References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../controller/OSBARCBarcodeAnalyzer.kt | 7 +- .../barcode/view/OSBARCScannerActivity.kt | 3 +- .../plugins/barcode/ScanCodeTests.kt | 94 +++++++++++++++++-- .../barcode/mocks/OSBARCScanLibraryMock.kt | 30 ++++++ 4 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCScanLibraryMock.kt diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index 215acf4..8384578 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -11,7 +11,7 @@ import java.lang.Exception * and overriding its analyze() method to scan for barcodes in the image frames. */ class OSBARCBarcodeAnalyzer( - private val scanLibrary: String, + private val scanLibrary: OSBARCScanLibraryInterface, private val onBarcodeScanned: (String) -> Unit, private val onScanningError: (OSBARCError) -> Unit ): ImageAnalysis.Analyzer { @@ -28,8 +28,7 @@ class OSBARCBarcodeAnalyzer( */ override fun analyze(image: ImageProxy) { try { - val scanningLib : OSBARCScanLibraryInterface = OSBARCScanLibraryFactory.createScanLibraryWrapper(scanLibrary) - scanningLib.scanBarcode( + scanLibrary.scanBarcode( image, { onBarcodeScanned(it) @@ -40,7 +39,7 @@ class OSBARCBarcodeAnalyzer( ) } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } - onScanningError + onScanningError(OSBARCError.SCANNING_GENERAL_ERROR) } } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 5bd3107..59c3990 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer +import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import java.lang.Exception @@ -107,7 +108,7 @@ class OSBARCScannerActivity : ComponentActivity() { imageAnalysis.setAnalyzer( ContextCompat.getMainExecutor(context), OSBARCBarcodeAnalyzer( - "zxing", // temporary + OSBARCScanLibraryFactory.createScanLibraryWrapper("zxing"), // temporary { result -> barcode = result val resultIntent = Intent() diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 5a6675e..bbcb2bd 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -3,7 +3,11 @@ package com.outsystems.plugins.barcode import android.app.Activity import android.content.Intent import android.os.Bundle +import androidx.camera.core.ImageProxy +import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCController +import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory +import com.outsystems.plugins.barcode.mocks.OSBARCScanLibraryMock import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import org.junit.Assert.assertEquals @@ -167,17 +171,95 @@ class ScanCodeTests { } @Test - fun givenMLKitParamWhenScanBarcodeThenMLKitWrapperCalled() { - // temporarily empty + fun givenScanLibrarySuccessWhenScanBarcodeThenSuccess() { + val mockImageProxy = Mockito.mock(ImageProxy::class.java) + val scanLibMock = OSBARCScanLibraryMock().apply { + success = true + resultCode = RESULT_CODE + } + OSBARCBarcodeAnalyzer(scanLibMock, + { + assertEquals(RESULT_CODE, it) + }, + { + fail() + } + ).analyze(mockImageProxy) } @Test - fun givenLibParamEmptyWhenScanBarcodeThenZXingWrapperCalled() { - // temporarily empty + fun givenScanLibraryGeneralErrorWhenScanBarcodeThenGeneralError() { + val mockImageProxy = Mockito.mock(ImageProxy::class.java) + val scanLibMock = OSBARCScanLibraryMock().apply { + success = false + exception = false + error = OSBARCError.SCANNING_GENERAL_ERROR + } + OSBARCBarcodeAnalyzer(scanLibMock, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ).analyze(mockImageProxy) } @Test - fun givenAnyOtherLibWhenScanBarcodeThenZXingWrapperCalled() { - // temporarily empty + fun givenScanLibraryZXingErrorWhenScanBarcodeThenZxingError() { + val mockImageProxy = Mockito.mock(ImageProxy::class.java) + val scanLibMock = OSBARCScanLibraryMock().apply { + success = false + exception = false + error = OSBARCError.ZXING_LIBRARY_ERROR + } + OSBARCBarcodeAnalyzer(scanLibMock, + { + fail() + }, + { + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.code, it.code) + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.description, it.description) + } + ).analyze(mockImageProxy) } + + @Test + fun givenScanLibraryMLKitErrorWhenScanBarcodeThenMLKitError() { + val mockImageProxy = Mockito.mock(ImageProxy::class.java) + val scanLibMock = OSBARCScanLibraryMock().apply { + success = false + exception = false + error = OSBARCError.MLKIT_LIBRARY_ERROR + } + OSBARCBarcodeAnalyzer(scanLibMock, + { + fail() + }, + { + assertEquals(OSBARCError.MLKIT_LIBRARY_ERROR.code, it.code) + assertEquals(OSBARCError.MLKIT_LIBRARY_ERROR.description, it.description) + } + ).analyze(mockImageProxy) + } + + @Test + fun givenScanLibraryExceptionWhenScanBarcodeThenGeneralError() { + val mockImageProxy = Mockito.mock(ImageProxy::class.java) + val scanLibMock = OSBARCScanLibraryMock().apply { + success = false + exception = true + } + OSBARCBarcodeAnalyzer(scanLibMock, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ).analyze(mockImageProxy) + } + } \ No newline at end of file diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCScanLibraryMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCScanLibraryMock.kt new file mode 100644 index 0000000..edb0f85 --- /dev/null +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCScanLibraryMock.kt @@ -0,0 +1,30 @@ +package com.outsystems.plugins.barcode.mocks + +import androidx.camera.core.ImageProxy +import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryInterface +import com.outsystems.plugins.barcode.model.OSBARCError + +class OSBARCScanLibraryMock: OSBARCScanLibraryInterface { + + var resultCode = "" + var success = true + var exception = false + var error: OSBARCError = OSBARCError.SCANNING_GENERAL_ERROR + override fun scanBarcode( + imageProxy: ImageProxy, + onSuccess: (String) -> Unit, + onError: (OSBARCError) -> Unit + ) { + if (success) { + onSuccess("myCode") + } + else if (!exception) { + onError(error) + } + else { + throw Exception() + } + + } + +} \ No newline at end of file From 986754a55980ee92c01fce1b2a35a0f36d57a3c4 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 11:07:29 +0000 Subject: [PATCH 040/174] chore: update files to be excluded from test coverage References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 72f2b57..d69e8b6 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -10,7 +10,7 @@ sonar.coverage.jacoco.xmlReportPaths=**/jacocoTestReport/jacocoTestReport.xml sonar.junit.reportPaths=**/test-results/**/*.xml # Removing tests, mocks and code not testable by unit tests from the coverage estimation -sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/view/*.kt,**/*Theme.kt,**/ui.theme/*.kt +sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/view/*.kt,**/*Theme.kt,**/ui.theme/*.kt,**/*Wrapper.kt,**/*Factory.kt,**/*Interface.kt # Removing tests from code duplication checks sonar.cpd.exclusions=**/*Tests.kt \ No newline at end of file From 90bd79736378f0db8de05471343a2f3388545f29 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 11:09:45 +0000 Subject: [PATCH 041/174] misc: uncomment lines --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b3a40bf..dca55d8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | From 796fe53ebe3ba41682647d6a32ba55b60245a0ce Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 11:14:36 +0000 Subject: [PATCH 042/174] chore: raise lib version References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 75c48c4..de78565 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.6 + 0.0.7 From 9f34a75236def726a85b47746a8922141fc4c5e1 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 11:21:40 +0000 Subject: [PATCH 043/174] refactor: remove unused import References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index bbcb2bd..5027a46 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -6,7 +6,6 @@ import android.os.Bundle import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCController -import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.mocks.OSBARCScanLibraryMock import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters From 1a4263b1725973ef00aab6652ebcceb0ae7fc19b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 11:24:25 +0000 Subject: [PATCH 044/174] feat: make interface functional Context: An interface that declares only a single function should be marked as function interface. Function interfaces can be instantiated from lambda expressions directly and are, therefore, more comfortable to use. More info here: https://kotlinlang.org/docs/fun-interfaces.html References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../plugins/barcode/controller/OSBARCScanLibraryInterface.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt index c890482..4fdb5a4 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt @@ -6,7 +6,7 @@ import com.outsystems.plugins.barcode.model.OSBARCError /** * Interface that provides the signature of the scanBarcode method */ -interface OSBARCScanLibraryInterface { +fun interface OSBARCScanLibraryInterface { fun scanBarcode( imageProxy: ImageProxy, onSuccess: (String) -> Unit, From 13cbdee4b7cc8b8a77db23ab3e4b88918abd62e7 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 11:36:36 +0000 Subject: [PATCH 045/174] feat: add sonarqube configuration References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- build.gradle | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index eeee5d1..398cdf1 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,18 @@ buildscript { } } +plugins { + id "org.sonarqube" version "3.5.0.2730" +} + +sonarqube { + properties { + property "sonar.projectKey", "OutSystems_OSBarcodeLib-Android" + property "sonar.organization", "outsystemsrd" + property "sonar.host.url", "https://sonarcloud.io" + } +} + apply plugin: "com.android.library" apply plugin: "kotlin-android" apply plugin: "jacoco" @@ -52,8 +64,8 @@ android { } def fileFilter = ['**/BuildConfig.*', '**/Manifest*.*'] - def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debugUnitTest", excludes: fileFilter) - def mainSrc = "${project.projectDir}/src/main/java" + def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter) + def mainSrc = "${project.projectDir}/src/main/kotlin" sourceDirectories.setFrom(files([mainSrc])) classDirectories.setFrom(files([debugTree])) From 2be69f3fe9a8767d1fc4f8a652c72f554006038c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 11:45:56 +0000 Subject: [PATCH 046/174] feat: enable jacoco reports References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 398cdf1..2c07913 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.1' + classpath 'com.android.tools.build:gradle:7.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jacoco:org.jacoco.core:$jacocoVersion" } @@ -59,8 +59,8 @@ android { task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) { reports { - //xml.enabled = true - //html.enabled = true + xml.enabled = true + html.enabled = true } def fileFilter = ['**/BuildConfig.*', '**/Manifest*.*'] From d8cf2ed2015e5f95ed571ffa442372c37ab4b20c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 11:55:11 +0000 Subject: [PATCH 047/174] misc: test with different gradle version --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2c07913..a502ad3 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:8.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jacoco:org.jacoco.core:$jacocoVersion" } @@ -80,6 +80,7 @@ android { composeOptions { kotlinCompilerExtensionVersion '1.2.0-alpha08' } + packaging { resources { excludes += '/META-INF/{AL2.0,LGPL2.1}' From 842aad6a95e2ba6afd25de662a4cc2f10106be65 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 12:05:12 +0000 Subject: [PATCH 048/174] test: test using new way of setting enabled in reports References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a502ad3..09742dd 100644 --- a/build.gradle +++ b/build.gradle @@ -59,8 +59,10 @@ android { task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) { reports { - xml.enabled = true - html.enabled = true + //xml.enabled = true + //html.enabled = true + xml.getRequired().set(true) + html.getRequired().set(true) } def fileFilter = ['**/BuildConfig.*', '**/Manifest*.*'] From 786278ab72cb581f4ea4da553a717ff781ffd8d6 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 12:16:51 +0000 Subject: [PATCH 049/174] feat: use new way of setting reports enabled for sonarqube Context: For Gradle 8, we can't use xml.enabled any longer. This was removed in Gradle 8. More info: https://sonarsource.atlassian.net/browse/SONARGRADL-84 References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 09742dd..055f318 100644 --- a/build.gradle +++ b/build.gradle @@ -59,8 +59,6 @@ android { task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) { reports { - //xml.enabled = true - //html.enabled = true xml.getRequired().set(true) html.getRequired().set(true) } From 9c8543ca4781a60b51b46aa084fcf8519fd061b7 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 12:26:53 +0000 Subject: [PATCH 050/174] feat: add more test cases References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../plugins/barcode/ScanCodeTests.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 5027a46..4162b7a 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -65,6 +65,53 @@ class ScanCodeTests { ) } + @Test + fun givenIntentNullWhenHandleScanResultThenSuccess() { + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, Activity.RESULT_OK, null, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ) + } + + @Test + fun givenBundleNullWhenHandleScanResultThenSuccess() { + Mockito.doReturn(null).`when`(mockIntent).extras + + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, Activity.RESULT_OK, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ) + } + + @Test + fun givenStringNullWhenHandleScanResultThenSuccess() { + Mockito.doReturn(mockBundle).`when`(mockIntent).extras + Mockito.doReturn(null).`when`(mockBundle).getString(SCAN_RESULT) + + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, Activity.RESULT_OK, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ) + } + @Test fun givenResultOKAndBarcodeNullWhenHandleScanResultThenScanningError() { Mockito.doReturn(mockBundle).`when`(mockIntent).extras From 3d75a203f1647bfe00e8dd9f2375040e5bb67120 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 12:49:18 +0000 Subject: [PATCH 051/174] feat: add more test cases References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../barcode/controller/OSBARCController.kt | 10 +++++ .../plugins/barcode/model/OSBARCError.kt | 12 +++--- .../plugins/barcode/ScanCodeTests.kt | 42 +++++++++++++++++-- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt index fdc9b38..bf30f12 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -2,6 +2,7 @@ package com.outsystems.plugins.barcode.controller import android.app.Activity import android.content.Intent +import android.util.Log import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.OSBARCScannerActivity @@ -22,6 +23,7 @@ class OSBARCController { private const val SCAN_HINT = "SCAN_HINT" private const val SCAN_LIBRARY = "SCAN_LIBRARY" private const val SCAN_RESULT = "scanResult" + private const val LOG_TAG = "OSBARCController" } /** @@ -79,8 +81,16 @@ class OSBARCController { onError(OSBARCError.ZXING_LIBRARY_ERROR) OSBARCError.MLKIT_LIBRARY_ERROR.code -> onError(OSBARCError.MLKIT_LIBRARY_ERROR) + else -> { + Log.d(LOG_TAG, "Invalid result code") + onError(OSBARCError.SCANNING_GENERAL_ERROR) + } } } + else -> { + Log.d(LOG_TAG, "Invalid request code") + onError(OSBARCError.SCANNING_GENERAL_ERROR) + } } } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt index 45c2785..65a9c21 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt @@ -4,10 +4,10 @@ package com.outsystems.plugins.barcode.model * Enum class that holds the library's errors. */ enum class OSBARCError(val code: Int, val description: String) { - CAMERA_PERMISSION_DENIED_ERROR(1, "Couldn't access camera. Check your camera permissions and try again."), - INVALID_PARAMETERS_ERROR(2, "Barcode parameters are invalid."), - SCAN_CANCELLED_ERROR(3, "Barcode scanning was cancelled."), - SCANNING_GENERAL_ERROR(4, "There was an error scanning the barcode."), - ZXING_LIBRARY_ERROR(5, "There was an error scanning the barcode with ZXing."), - MLKIT_LIBRARY_ERROR(6, "There was an error scanning the barcode with ML Kit.") + CAMERA_PERMISSION_DENIED_ERROR(7, "Scanning cancelled due to missing camera permissions."), + INVALID_PARAMETERS_ERROR(10, "Scanning parameters are invalid."), + SCAN_CANCELLED_ERROR(6, "Scanning cancelled."), + SCANNING_GENERAL_ERROR(4, "Error while trying to scan code."), + ZXING_LIBRARY_ERROR(11, "There was an error scanning the barcode with ZXing."), + MLKIT_LIBRARY_ERROR(12, "There was an error scanning the barcode with ML Kit.") } \ No newline at end of file diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 4162b7a..449bd4b 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -23,6 +23,8 @@ class ScanCodeTests { companion object { private const val SCAN_REQUEST_CODE = 112 + private const val INVALID_REQUEST_CODE = 113 + private const val INVALID_RESULT_CODE = 9 private const val SCAN_RESULT = "scanResult" private const val RESULT_CODE = "myCode" } @@ -66,7 +68,7 @@ class ScanCodeTests { } @Test - fun givenIntentNullWhenHandleScanResultThenSuccess() { + fun givenIntentNullWhenHandleScanResultThenGeneralError() { val barcodeController = OSBARCController() barcodeController.handleActivityResult(SCAN_REQUEST_CODE, Activity.RESULT_OK, null, { @@ -80,7 +82,7 @@ class ScanCodeTests { } @Test - fun givenBundleNullWhenHandleScanResultThenSuccess() { + fun givenBundleNullWhenHandleScanResultThenGeneralError() { Mockito.doReturn(null).`when`(mockIntent).extras val barcodeController = OSBARCController() @@ -96,7 +98,7 @@ class ScanCodeTests { } @Test - fun givenStringNullWhenHandleScanResultThenSuccess() { + fun givenStringNullWhenHandleScanResultThenGeneralError() { Mockito.doReturn(mockBundle).`when`(mockIntent).extras Mockito.doReturn(null).`when`(mockBundle).getString(SCAN_RESULT) @@ -112,6 +114,40 @@ class ScanCodeTests { ) } + @Test + fun givenInvalidRequestCodeWhenHandleScanResultThenGeneralError() { + Mockito.doReturn(mockBundle).`when`(mockIntent).extras + Mockito.doReturn(null).`when`(mockBundle).getString(SCAN_RESULT) + + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(INVALID_REQUEST_CODE, Activity.RESULT_OK, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ) + } + + @Test + fun givenInvalidResultCodeWhenHandleScanResultThenGeneralError() { + Mockito.doReturn(mockBundle).`when`(mockIntent).extras + Mockito.doReturn(null).`when`(mockBundle).getString(SCAN_RESULT) + + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, INVALID_RESULT_CODE, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) + assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) + } + ) + } + @Test fun givenResultOKAndBarcodeNullWhenHandleScanResultThenScanningError() { Mockito.doReturn(mockBundle).`when`(mockIntent).extras From d80caf869aa07518bf4c1631ba4e97d317d82039 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 12:52:08 +0000 Subject: [PATCH 052/174] refactor: remove duplicated test References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../outsystems/plugins/barcode/ScanCodeTests.kt | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 449bd4b..270c8fb 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -148,23 +148,6 @@ class ScanCodeTests { ) } - @Test - fun givenResultOKAndBarcodeNullWhenHandleScanResultThenScanningError() { - Mockito.doReturn(mockBundle).`when`(mockIntent).extras - Mockito.doReturn(null).`when`(mockBundle).getString(SCAN_RESULT) - - val barcodeController = OSBARCController() - barcodeController.handleActivityResult(SCAN_REQUEST_CODE, Activity.RESULT_OK, mockIntent, - { - fail() - }, - { - assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.code, it.code) - assertEquals(OSBARCError.SCANNING_GENERAL_ERROR.description, it.description) - } - ) - } - @Test fun givenResultOKAndBarcodeEmptyWhenHandleScanResultThenScanningError() { Mockito.doReturn(mockBundle).`when`(mockIntent).extras From 54e297a34453565526610ddf0e95697c99974618 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 15:54:13 +0000 Subject: [PATCH 053/174] refactor: refactor code and add more unit tests Context: We're resorting to Helper classes to isolate library specific code (ML Kit and ZXing). References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- sonar-project.properties | 2 +- .../barcode/controller/OSBARCMLKitWrapper.kt | 47 ++-- .../controller/OSBARCScanLibraryFactory.kt | 25 +- .../barcode/controller/OSBARCZXingWrapper.kt | 54 ++--- .../controller/helper/OSBARCMLKitHelper.kt | 58 +++++ .../helper/OSBARCMLKitHelperInterface.kt | 18 ++ .../controller/helper/OSBARCZXingHelper.kt | 89 +++++++ .../helper/OSBARCZXingHelperInterface.kt | 18 ++ .../barcode/view/OSBARCScannerActivity.kt | 8 +- .../plugins/barcode/ScanCodeTests.kt | 227 ++++++++++++++++++ .../barcode/mocks/OSBARCMLKitHelperMock.kt | 40 +++ .../barcode/mocks/OSBARCZXingHelperMock.kt | 38 +++ 12 files changed, 542 insertions(+), 82 deletions(-) create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelperInterface.kt create mode 100644 src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt create mode 100644 src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt diff --git a/sonar-project.properties b/sonar-project.properties index d69e8b6..41ba837 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -10,7 +10,7 @@ sonar.coverage.jacoco.xmlReportPaths=**/jacocoTestReport/jacocoTestReport.xml sonar.junit.reportPaths=**/test-results/**/*.xml # Removing tests, mocks and code not testable by unit tests from the coverage estimation -sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/view/*.kt,**/*Theme.kt,**/ui.theme/*.kt,**/*Wrapper.kt,**/*Factory.kt,**/*Interface.kt +sonar.coverage.exclusions=**/*Tests.kt,**/*Mock.kt,**/model/*.kt,**/view/*.kt,**/*Theme.kt,**/ui.theme/*.kt,**/*Interface.kt,**/helper/*.kt # Removing tests from code duplication checks sonar.cpd.exclusions=**/*Tests.kt \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt index e3f27e5..8208c6f 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt @@ -4,17 +4,14 @@ import android.util.Log import androidx.annotation.OptIn import androidx.camera.core.ExperimentalGetImage import androidx.camera.core.ImageProxy -import com.google.mlkit.vision.barcode.BarcodeScannerOptions -import com.google.mlkit.vision.barcode.BarcodeScanning -import com.google.mlkit.vision.common.InputImage +import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelperInterface import com.outsystems.plugins.barcode.model.OSBARCError -import kotlinx.coroutines.runBlocking /** - * Helper class that implements the OSBARCScanLibraryInterface + * Wrapper class that implements the OSBARCScanLibraryInterface * to scan an image using the ML Kit library. */ -class OSBARCMLKitWrapper: OSBARCScanLibraryInterface { +class OSBARCMLKitWrapper(private val helper: OSBARCMLKitHelperInterface): OSBARCScanLibraryInterface { companion object { private const val LOG_TAG = "OSBARCMLKitWrapper" @@ -32,36 +29,22 @@ class OSBARCMLKitWrapper: OSBARCScanLibraryInterface { onError: (OSBARCError) -> Unit ) { try { - val options = BarcodeScannerOptions.Builder() - .enableAllPotentialBarcodes() - .build() - val scanner = BarcodeScanning.getClient(options) val mediaImage = imageProxy.image - if (mediaImage != null) { - val image = InputImage.fromMediaImage( - mediaImage, - imageProxy.imageInfo.rotationDegrees - ) - runBlocking { - scanner.process(image) - .addOnSuccessListener { barcodes -> - var result: String? = null - if (barcodes.isNotEmpty()) { - result = barcodes.first().rawValue - } - if (!result.isNullOrEmpty()) { - onSuccess(result) - } + helper.decodeImage(imageProxy, mediaImage, + { barcodes -> + var result: String? = null + if (barcodes.isNotEmpty()) { + result = barcodes.first().rawValue } - .addOnFailureListener { e -> - e.message?.let { Log.e(LOG_TAG, it) } - onError(OSBARCError.MLKIT_LIBRARY_ERROR) + if (!result.isNullOrEmpty()) { + onSuccess(result) } - .addOnCompleteListener { - imageProxy.close() - } - } + }, + { + onError(OSBARCError.MLKIT_LIBRARY_ERROR) + } + ) } } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt index 6944ec7..ea90c7c 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt @@ -1,5 +1,8 @@ package com.outsystems.plugins.barcode.controller +import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelperInterface +import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelperInterface + /** * A factory class to create instances of OSBARCScanLibraryInterface. */ @@ -14,16 +17,22 @@ class OSBARCScanLibraryFactory { * @param scanLibrary - String to identify which library to use * @return the newly created OSBARCScanLibraryInterface instance. */ - fun createScanLibraryWrapper(scanLibrary: String): OSBARCScanLibraryInterface { + fun createScanLibraryWrapper( + scanLibrary: String, + zxingHelper: OSBARCZXingHelperInterface, + mlkitHelper: OSBARCMLKitHelperInterface + ): OSBARCScanLibraryInterface { return when (scanLibrary) { LIBRARY_ZXING -> { - createZXingWrapper() + createZXingWrapper(zxingHelper) } + LIBRARY_MLKIT -> { - createMLKitWrapper() + createMLKitWrapper(mlkitHelper) } + else -> { - createZXingWrapper() + createZXingWrapper(zxingHelper) } } } @@ -32,16 +41,16 @@ class OSBARCScanLibraryFactory { * Creates and returns a OSBARCZXingWrapper instance. * @return the newly created OSBARCZXingWrapper instance. */ - private fun createZXingWrapper(): OSBARCZXingWrapper { - return OSBARCZXingWrapper() + private fun createZXingWrapper(helper: OSBARCZXingHelperInterface): OSBARCZXingWrapper { + return OSBARCZXingWrapper(helper) } /** * Creates and returns a OSBARCMLKitWrapper instance. * @return the newly created OSBARCMLKitWrapper instance. */ - private fun createMLKitWrapper(): OSBARCMLKitWrapper { - return OSBARCMLKitWrapper() + private fun createMLKitWrapper(helper: OSBARCMLKitHelperInterface): OSBARCMLKitWrapper { + return OSBARCMLKitWrapper(helper) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt index c174b84..c5dfd9c 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -1,28 +1,21 @@ package com.outsystems.plugins.barcode.controller import android.graphics.Bitmap -import android.graphics.BitmapFactory import android.graphics.ImageFormat -import android.graphics.Matrix import android.graphics.Rect import android.graphics.YuvImage import android.util.Log import androidx.camera.core.ImageProxy -import com.google.zxing.BinaryBitmap -import com.google.zxing.DecodeHintType -import com.google.zxing.MultiFormatReader -import com.google.zxing.NotFoundException -import com.google.zxing.RGBLuminanceSource -import com.google.zxing.common.HybridBinarizer +import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelperInterface import com.outsystems.plugins.barcode.model.OSBARCError import java.io.ByteArrayOutputStream import java.nio.ByteBuffer /** - * Helper class that implements the OSBARCScanLibraryInterface + * Wrapper class that implements the OSBARCScanLibraryInterface * to scan an image using the ZXing library. */ -class OSBARCZXingWrapper: OSBARCScanLibraryInterface { +class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBARCScanLibraryInterface { companion object { private const val LOG_TAG = "OSBARCZXingWrapper" @@ -39,27 +32,13 @@ class OSBARCZXingWrapper: OSBARCScanLibraryInterface { onSuccess: (String) -> Unit, onError: (OSBARCError) -> Unit ) { - try { var imageBitmap = imageProxyToBitmap(imageProxy) // rotate the image if it's in portrait mode (rotation = 90 or 270 degrees) val rotationDegrees = imageProxy.imageInfo.rotationDegrees if (rotationDegrees == 90 || rotationDegrees == 270) { - // create a matrix for rotation - val matrix = Matrix() - matrix.postRotate(rotationDegrees.toFloat()) - - // actually rotate the image - imageBitmap = Bitmap.createBitmap( - imageBitmap, - 0, - 0, - imageBitmap.width, - imageBitmap.height, - matrix, - true - ) + imageBitmap = helper.rotateBitmap(imageBitmap, rotationDegrees) } // scan image using zxing @@ -68,26 +47,20 @@ class OSBARCZXingWrapper: OSBARCScanLibraryInterface { val pixels = IntArray(width * height) imageBitmap.getPixels(pixels, 0, width, 0, 0, width, height) - val source = RGBLuminanceSource(width, height, pixels) - val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) - val result = MultiFormatReader().apply { - setHints( - mapOf( - DecodeHintType.TRY_HARDER to arrayListOf(true) - ) - ) - }.decode(binaryBitmap) - onSuccess(result.text) - } catch (e: NotFoundException) { - // keep trying - e.message?.let { Log.d(LOG_TAG, it) } + helper.decodeImage(pixels, width, height, + { + onSuccess(it) + }, + { + onError(OSBARCError.ZXING_LIBRARY_ERROR) + } + ) } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } onError(OSBARCError.ZXING_LIBRARY_ERROR) } finally { imageProxy.close() } - } // Function to convert ImageProxy to Bitmap @@ -119,7 +92,8 @@ class OSBARCZXingWrapper: OSBARCScanLibraryInterface { val out = ByteArrayOutputStream() yuvImage.compressToJpeg(Rect(0, 0, imageWidth, imageHeight), 100, out) val imageBytes = out.toByteArray() - return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + //return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + return helper.bitmapFromImageBytes(imageBytes) } } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt new file mode 100644 index 0000000..dd4aa34 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt @@ -0,0 +1,58 @@ +package com.outsystems.plugins.barcode.controller.helper + +import android.media.Image +import android.util.Log +import androidx.camera.core.ImageProxy +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 kotlinx.coroutines.runBlocking + +/** + * Helper class that implements the OSBARCMLKitHelperInterface + * to scan an image using the ML Kit library. + * It encapsulates all the code related with the ML Kit library. + */ +class OSBARCMLKitHelper: OSBARCMLKitHelperInterface { + companion object { + private const val LOG_TAG = "OSBARCMLKitHelper" + } + + /** + * Scans an image looking for barcodes, using the ML Kit library. + * @param imageProxy - ImageProxy object that represents the image to be analyzed. + * @param mediaImage - Image object that represents the image to be analyzed. + * @param onSuccess - The code to be executed if the operation was successful. + * @param onError - The code to be executed if the operation was not successful. + */ + override fun decodeImage( + imageProxy: ImageProxy, + mediaImage: Image, + onSuccess: (MutableList) -> Unit, + onError: () -> Unit + ) { + val options = BarcodeScannerOptions.Builder() + .enableAllPotentialBarcodes() + .build() + val scanner = BarcodeScanning.getClient(options) + val image = InputImage.fromMediaImage( + mediaImage, + imageProxy.imageInfo.rotationDegrees + ) + runBlocking { + scanner.process(image) + .addOnSuccessListener { barcodes -> + onSuccess(barcodes) + } + .addOnFailureListener { e -> + e.message?.let { Log.e(LOG_TAG, it) } + onError() + } + .addOnCompleteListener { + imageProxy.close() + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt new file mode 100644 index 0000000..068d400 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt @@ -0,0 +1,18 @@ +package com.outsystems.plugins.barcode.controller.helper + +import android.media.Image +import androidx.camera.core.ImageProxy +import com.google.mlkit.vision.barcode.common.Barcode + +/** + * Interface that provides the signature of the type's methods. + */ +fun interface OSBARCMLKitHelperInterface { + fun decodeImage( + imageProxy: ImageProxy, + mediaImage: Image, + onSuccess: (MutableList) -> Unit, + onError: () -> Unit + ) + +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt new file mode 100644 index 0000000..06c0aa0 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt @@ -0,0 +1,89 @@ +package com.outsystems.plugins.barcode.controller.helper + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Matrix +import android.util.Log +import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType +import com.google.zxing.MultiFormatReader +import com.google.zxing.NotFoundException +import com.google.zxing.RGBLuminanceSource +import com.google.zxing.common.HybridBinarizer + +/** + * Helper class that implements the OSBARCZXingHelperInterface + * to scan an image using the ZXing library. + * It encapsulates all the code related with the ZXing library. + */ +class OSBARCZXingHelper: OSBARCZXingHelperInterface { + + companion object { + private const val LOG_TAG = "OSBARCZXingHelper" + } + + /** + * Converts a ByteArray into a Bitmap using BitmapFactory + * @param imageBytes - ByteArray to convert + * @return the resulting bitmap. + */ + override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + } + + /** + * Rotates a bitmap, provided with the rotation degrees. + * @param bitmap - Bitmap object to rotate + * @param rotationDegrees - degrees to rotate the image. + * @return the resulting bitmap. + */ + override fun rotateBitmap(bitmap: Bitmap, rotationDegrees: Int): Bitmap { + // create a matrix for rotation + val matrix = Matrix() + matrix.postRotate(rotationDegrees.toFloat()) + + // actually rotate the image + return Bitmap.createBitmap( + bitmap, + 0, + 0, + bitmap.width, + bitmap.height, + matrix, + true + ) + } + + /** + * Scans an image looking for barcodes, using the ZXing library. + * @param pixels - IntArray that represents the image to be analyzed. + * @param onSuccess - The code to be executed if the operation was successful. + * @param onError - The code to be executed if the operation was not successful. + */ + override fun decodeImage( + pixels: IntArray, width: Int, height: Int, + onSuccess: (String) -> Unit, + onError: () -> Unit + ) { + try { + val source = RGBLuminanceSource(width, height, pixels) + val binaryBitmap = BinaryBitmap(HybridBinarizer(source)) + val result = MultiFormatReader().apply { + setHints( + mapOf( + DecodeHintType.TRY_HARDER to arrayListOf(true) + ) + ) + }.decode(binaryBitmap) + onSuccess(result.text) + } catch (e: NotFoundException) { + // keep trying + e.message?.let { Log.d(LOG_TAG, it) } + } catch (e: Exception) { + e.message?.let { Log.d(LOG_TAG, it) } + onError() + } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelperInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelperInterface.kt new file mode 100644 index 0000000..ac65746 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelperInterface.kt @@ -0,0 +1,18 @@ +package com.outsystems.plugins.barcode.controller.helper + +import android.graphics.Bitmap + +/** + * Interface that provides the signature of the type's methods. + */ +interface OSBARCZXingHelperInterface { + fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap + fun rotateBitmap(bitmap: Bitmap, rotationDegrees: Int): Bitmap + fun decodeImage( + pixels: IntArray, + width: Int, + height: Int, + onSuccess: (String) -> Unit, + onError: () -> Unit + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 59c3990..c4ae3e0 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -29,6 +29,8 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory +import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper +import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import java.lang.Exception @@ -108,7 +110,11 @@ class OSBARCScannerActivity : ComponentActivity() { imageAnalysis.setAnalyzer( ContextCompat.getMainExecutor(context), OSBARCBarcodeAnalyzer( - OSBARCScanLibraryFactory.createScanLibraryWrapper("zxing"), // temporary + OSBARCScanLibraryFactory.createScanLibraryWrapper( + "zxing", + OSBARCZXingHelper(), + OSBARCMLKitHelper() + ), // temporary { result -> barcode = result val resultIntent = Intent() diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 270c8fb..c267333 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -2,10 +2,16 @@ package com.outsystems.plugins.barcode import android.app.Activity import android.content.Intent +import android.media.Image import android.os.Bundle +import androidx.camera.core.ImageInfo import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCController +import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory +import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelperInterface +import com.outsystems.plugins.barcode.mocks.OSBARCMLKitHelperMock +import com.outsystems.plugins.barcode.mocks.OSBARCZXingHelperMock import com.outsystems.plugins.barcode.mocks.OSBARCScanLibraryMock import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters @@ -14,12 +20,18 @@ import org.junit.Assert.fail import org.junit.Before import org.junit.Test import org.mockito.Mockito +import java.nio.ByteBuffer class ScanCodeTests { private lateinit var mockActivity: Activity private lateinit var mockIntent: Intent private lateinit var mockBundle: Bundle + private lateinit var mockImageProxy: ImageProxy + private lateinit var mockPlaneProxy: ImageProxy.PlaneProxy + private lateinit var mockByteBuffer: ByteBuffer + private lateinit var mockImageInfo: ImageInfo + private lateinit var planes: Array companion object { private const val SCAN_REQUEST_CODE = 112 @@ -34,6 +46,17 @@ class ScanCodeTests { mockActivity = Mockito.mock(Activity::class.java) mockIntent = Mockito.mock(Intent::class.java) mockBundle = Mockito.mock(Bundle::class.java) + mockImageProxy = Mockito.mock(ImageProxy::class.java) + mockPlaneProxy = Mockito.mock(ImageProxy.PlaneProxy::class.java) + mockByteBuffer = Mockito.mock(ByteBuffer::class.java) + mockImageInfo = Mockito.mock(ImageInfo::class.java) + planes = arrayOf(mockPlaneProxy, mockPlaneProxy, mockPlaneProxy) + Mockito.doReturn(planes).`when`(mockImageProxy).planes + Mockito.doReturn(mockByteBuffer).`when`(mockPlaneProxy).buffer + Mockito.doReturn(30).`when`(mockImageProxy).width + Mockito.doReturn(30).`when`(mockImageProxy).height + Mockito.doReturn(30).`when`(mockByteBuffer).remaining() + Mockito.doReturn(mockImageInfo).`when`(mockImageProxy).imageInfo } @Test @@ -327,4 +350,208 @@ class ScanCodeTests { ).analyze(mockImageProxy) } + @Test + fun givenImage90DegreesWhenZXingScanThenSuccess() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "zxing", + OSBARCZXingHelperMock().apply { scanResult = SCAN_RESULT }, + OSBARCMLKitHelperMock() + ) + + Mockito.doReturn(90).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases + + wrapper.scanBarcode(mockImageProxy, + { + assertEquals(SCAN_RESULT, it) + }, + { + fail() + } + ) + } + + @Test + fun givenImage270DegreesWhenZXingScanThenSuccess() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "zxing", + OSBARCZXingHelperMock().apply { scanResult = SCAN_RESULT }, + OSBARCMLKitHelperMock() + ) + + Mockito.doReturn(270).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases + + wrapper.scanBarcode(mockImageProxy, + { + assertEquals(SCAN_RESULT, it) + }, + { + fail() + } + ) + } + + @Test + fun givenImage0DegreesWhenZXingScanThenSuccess() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "zxing", + OSBARCZXingHelperMock().apply { scanResult = SCAN_RESULT }, + OSBARCMLKitHelperMock() + ) + + Mockito.doReturn(0).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases + + wrapper.scanBarcode(mockImageProxy, + { + assertEquals(SCAN_RESULT, it) + }, + { + fail() + } + ) + } + + @Test + fun givenErrorWhenZXingScanThenZXingError() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "zxing", + OSBARCZXingHelperMock().apply { + success = false + exception = false + }, + OSBARCMLKitHelperMock() + ) + + Mockito.doReturn(0).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases + + wrapper.scanBarcode(mockImageProxy, + { + fail() + }, + { + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.code, it.code) + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.description, it.description) + } + ) + } + + @Test + fun givenExceptionWhenZXingScanThenZXingError() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "zxing", + OSBARCZXingHelperMock().apply { + success = false + exception = true + }, + OSBARCMLKitHelperMock() + ) + + Mockito.doReturn(0).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases + + wrapper.scanBarcode(mockImageProxy, + { + fail() + }, + { + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.code, it.code) + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.description, it.description) + } + ) + } + + @Test + fun givenSuccessWhenMLKitScanThenSuccess() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "mlkit", + OSBARCZXingHelperMock(), + OSBARCMLKitHelperMock().apply { + scanResult = SCAN_RESULT + success = true + } + ) + + val mockMediaImage = Mockito.mock(Image::class.java) + Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image + + wrapper.scanBarcode(mockImageProxy, + { + assertEquals(SCAN_RESULT, it) + }, + { + fail() + } + ) + } + + @Test + fun givenBarcodesEmptyWhenMLKitScanThenDoNothing() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "mlkit", + OSBARCZXingHelperMock(), + OSBARCMLKitHelperMock().apply { + barcodesEmpty = true + } + ) + + val mockMediaImage = Mockito.mock(Image::class.java) + Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image + + wrapper.scanBarcode(mockImageProxy, + { + // do nothing + }, + { + // do nothing + } + ) + } + + @Test + fun givenErrorWhenMLKitScanThenMLKitError() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "mlkit", + OSBARCZXingHelperMock(), + OSBARCMLKitHelperMock().apply { + success = false + } + ) + + val mockMediaImage = Mockito.mock(Image::class.java) + Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image + + wrapper.scanBarcode(mockImageProxy, + { + fail() + }, + { + assertEquals(OSBARCError.MLKIT_LIBRARY_ERROR.code, it.code) + assertEquals(OSBARCError.MLKIT_LIBRARY_ERROR.description, it.description) + } + ) + } + + @Test + fun givenExceptionWhenMLKitScanThenMLKitError() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "mlkit", + OSBARCZXingHelperMock(), + OSBARCMLKitHelperMock().apply { + success = false + exception = true + } + ) + + val mockMediaImage = Mockito.mock(Image::class.java) + Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image + + wrapper.scanBarcode(mockImageProxy, + { + fail() + }, + { + assertEquals(OSBARCError.MLKIT_LIBRARY_ERROR.code, it.code) + assertEquals(OSBARCError.MLKIT_LIBRARY_ERROR.description, it.description) + } + ) + } + } \ No newline at end of file diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt new file mode 100644 index 0000000..7e3945e --- /dev/null +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt @@ -0,0 +1,40 @@ +package com.outsystems.plugins.barcode.mocks + +import android.media.Image +import androidx.camera.core.ImageProxy +import com.google.mlkit.vision.barcode.common.Barcode +import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelperInterface +import com.outsystems.plugins.barcode.model.OSBARCError +import org.mockito.Mockito + +class OSBARCMLKitHelperMock: OSBARCMLKitHelperInterface { + + var success = true + var scanResult = "" + var exception = false + var barcodesEmpty = true + override fun decodeImage( + imageProxy: ImageProxy, + mediaImage: Image, + onSuccess: (MutableList) -> Unit, + onError: () -> Unit + ) { + val mockBarcode = Mockito.mock(Barcode::class.java) + + if (barcodesEmpty) { + onSuccess(mutableListOf()) + return + } + + if (success) { + Mockito.doReturn(scanResult).`when`(mockBarcode).rawValue + onSuccess(mutableListOf(mockBarcode)) + } + else if (!exception) { + onError() + } + else { + throw Exception() + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt new file mode 100644 index 0000000..96a5818 --- /dev/null +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt @@ -0,0 +1,38 @@ +package com.outsystems.plugins.barcode.mocks + +import android.graphics.Bitmap +import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelperInterface +import com.outsystems.plugins.barcode.model.OSBARCError +import org.mockito.Mockito + +class OSBARCZXingHelperMock: OSBARCZXingHelperInterface { + + var scanResult = "" + var success = true + var exception = false + override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { + return Mockito.mock(Bitmap::class.java) + } + + override fun rotateBitmap(bitmap: Bitmap, rotationDegrees: Int): Bitmap { + return Mockito.mock(Bitmap::class.java) + } + + override fun decodeImage( + pixels: IntArray, + width: Int, + height: Int, + onSuccess: (String) -> Unit, + onError: () -> Unit + ) { + if (success) { + onSuccess(scanResult) + } + else if (!exception) { + onError() + } + else { + throw Exception() + } + } +} \ No newline at end of file From 039cfe1fc7acba05883303f9898feb27f85bdc96 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 15:59:32 +0000 Subject: [PATCH 054/174] feat: add missing unit test References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../plugins/barcode/ScanCodeTests.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index c267333..429baa7 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -554,4 +554,29 @@ class ScanCodeTests { ) } + @Test + fun givenWrongLibraryWhenScanThenZXingIsUsed() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "wrongLibrary", + OSBARCZXingHelperMock().apply { + success = false + exception = false + }, + OSBARCMLKitHelperMock() + ) + + val mockMediaImage = Mockito.mock(Image::class.java) + Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image + + wrapper.scanBarcode(mockImageProxy, + { + fail() + }, + { + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.code, it.code) + assertEquals(OSBARCError.ZXING_LIBRARY_ERROR.description, it.description) + } + ) + } + } \ No newline at end of file From f3f4f4b7bc36d0403a4077ca5b1799f38ed78aae Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 16:06:37 +0000 Subject: [PATCH 055/174] feat: add missing test cases References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../barcode/controller/OSBARCZXingWrapper.kt | 1 - .../plugins/barcode/ScanCodeTests.kt | 24 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt index c5dfd9c..ba0a614 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -92,7 +92,6 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR val out = ByteArrayOutputStream() yuvImage.compressToJpeg(Rect(0, 0, imageWidth, imageHeight), 100, out) val imageBytes = out.toByteArray() - //return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) return helper.bitmapFromImageBytes(imageBytes) } diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 429baa7..7e0cc57 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -9,7 +9,6 @@ import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCController import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory -import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelperInterface import com.outsystems.plugins.barcode.mocks.OSBARCMLKitHelperMock import com.outsystems.plugins.barcode.mocks.OSBARCZXingHelperMock import com.outsystems.plugins.barcode.mocks.OSBARCScanLibraryMock @@ -32,6 +31,7 @@ class ScanCodeTests { private lateinit var mockByteBuffer: ByteBuffer private lateinit var mockImageInfo: ImageInfo private lateinit var planes: Array + private lateinit var mockException: Exception companion object { private const val SCAN_REQUEST_CODE = 112 @@ -51,12 +51,14 @@ class ScanCodeTests { mockByteBuffer = Mockito.mock(ByteBuffer::class.java) mockImageInfo = Mockito.mock(ImageInfo::class.java) planes = arrayOf(mockPlaneProxy, mockPlaneProxy, mockPlaneProxy) + mockException = Mockito.mock(Exception::class.java) Mockito.doReturn(planes).`when`(mockImageProxy).planes Mockito.doReturn(mockByteBuffer).`when`(mockPlaneProxy).buffer Mockito.doReturn(30).`when`(mockImageProxy).width Mockito.doReturn(30).`when`(mockImageProxy).height Mockito.doReturn(30).`when`(mockByteBuffer).remaining() Mockito.doReturn(mockImageInfo).`when`(mockImageProxy).imageInfo + Mockito.doReturn("errorMessage").`when`(mockException).message } @Test @@ -579,4 +581,24 @@ class ScanCodeTests { ) } + @Test + fun givenMediaImageNullWhenMLKitScanThenDoNothing() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "mlkit", + OSBARCZXingHelperMock(), + OSBARCMLKitHelperMock() + ) + + Mockito.doReturn(null).`when`(mockImageProxy).image + + wrapper.scanBarcode(mockImageProxy, + { + // do nothing + }, + { + // do nothing + } + ) + } + } \ No newline at end of file From 453d5119b19bf0297dcddc1ced8d8f8d54fd96f4 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 16:14:02 +0000 Subject: [PATCH 056/174] feat: add missing test cases https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../plugins/barcode/ScanCodeTests.kt | 51 +++++++++++++++++++ .../barcode/mocks/OSBARCMLKitHelperMock.kt | 2 +- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 7e0cc57..92cb826 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -468,6 +468,7 @@ class ScanCodeTests { OSBARCMLKitHelperMock().apply { scanResult = SCAN_RESULT success = true + barcodesEmpty = false } ) @@ -484,6 +485,56 @@ class ScanCodeTests { ) } + @Test + fun givenResultEmptyWhenMLKitScanThenDoNothing() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "mlkit", + OSBARCZXingHelperMock(), + OSBARCMLKitHelperMock().apply { + scanResult = "" + success = true + barcodesEmpty = false + } + ) + + val mockMediaImage = Mockito.mock(Image::class.java) + Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image + + wrapper.scanBarcode(mockImageProxy, + { + // do nothing + }, + { + // do nothing + } + ) + } + + @Test + fun givenResultNullWhenMLKitScanThenDoNothing() { + val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( + "mlkit", + OSBARCZXingHelperMock(), + OSBARCMLKitHelperMock().apply { + scanResult = null + success = true + barcodesEmpty = false + } + ) + + val mockMediaImage = Mockito.mock(Image::class.java) + Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image + + wrapper.scanBarcode(mockImageProxy, + { + // do nothing + }, + { + // do nothing + } + ) + } + @Test fun givenBarcodesEmptyWhenMLKitScanThenDoNothing() { val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt index 7e3945e..d27b3dd 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt @@ -10,7 +10,7 @@ import org.mockito.Mockito class OSBARCMLKitHelperMock: OSBARCMLKitHelperInterface { var success = true - var scanResult = "" + var scanResult: String? = null var exception = false var barcodesEmpty = true override fun decodeImage( From d585d819590e5a03d184459f58192ebd24b53686 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 16:23:12 +0000 Subject: [PATCH 057/174] fix: fixing unit tests and refactor References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../com/outsystems/plugins/barcode/ScanCodeTests.kt | 10 ++++++++-- .../plugins/barcode/mocks/OSBARCMLKitHelperMock.kt | 1 - .../plugins/barcode/mocks/OSBARCZXingHelperMock.kt | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 92cb826..fca881b 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -565,6 +565,7 @@ class ScanCodeTests { OSBARCZXingHelperMock(), OSBARCMLKitHelperMock().apply { success = false + barcodesEmpty = false } ) @@ -590,6 +591,7 @@ class ScanCodeTests { OSBARCMLKitHelperMock().apply { success = false exception = true + barcodesEmpty = false } ) @@ -615,7 +617,9 @@ class ScanCodeTests { success = false exception = false }, - OSBARCMLKitHelperMock() + OSBARCMLKitHelperMock().apply { + barcodesEmpty = false + } ) val mockMediaImage = Mockito.mock(Image::class.java) @@ -637,7 +641,9 @@ class ScanCodeTests { val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( "mlkit", OSBARCZXingHelperMock(), - OSBARCMLKitHelperMock() + OSBARCMLKitHelperMock().apply { + barcodesEmpty = false + } ) Mockito.doReturn(null).`when`(mockImageProxy).image diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt index d27b3dd..820cfde 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt @@ -4,7 +4,6 @@ import android.media.Image import androidx.camera.core.ImageProxy import com.google.mlkit.vision.barcode.common.Barcode import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelperInterface -import com.outsystems.plugins.barcode.model.OSBARCError import org.mockito.Mockito class OSBARCMLKitHelperMock: OSBARCMLKitHelperInterface { diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt index 96a5818..2e15ed0 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt @@ -2,7 +2,6 @@ package com.outsystems.plugins.barcode.mocks import android.graphics.Bitmap import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelperInterface -import com.outsystems.plugins.barcode.model.OSBARCError import org.mockito.Mockito class OSBARCZXingHelperMock: OSBARCZXingHelperInterface { From 8354095b41cf3fe60a0fa9ec71f7193e2fab6111 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 16:31:48 +0000 Subject: [PATCH 058/174] chore: raise lib version References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- azure-pipelines.yml | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dca55d8..b3a40bf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | diff --git a/pom.xml b/pom.xml index de78565..949418c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.7 + 0.0.8 From 8ea7937682702f052cedbe686205b949b0917cd2 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 16:37:44 +0000 Subject: [PATCH 059/174] refactor: use correct log type References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../plugins/barcode/controller/helper/OSBARCZXingHelper.kt | 2 +- .../kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt index 06c0aa0..89c0017 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt @@ -80,7 +80,7 @@ class OSBARCZXingHelper: OSBARCZXingHelperInterface { // keep trying e.message?.let { Log.d(LOG_TAG, it) } } catch (e: Exception) { - e.message?.let { Log.d(LOG_TAG, it) } + e.message?.let { Log.e(LOG_TAG, it) } onError() } diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index fca881b..e010410 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -31,7 +31,6 @@ class ScanCodeTests { private lateinit var mockByteBuffer: ByteBuffer private lateinit var mockImageInfo: ImageInfo private lateinit var planes: Array - private lateinit var mockException: Exception companion object { private const val SCAN_REQUEST_CODE = 112 @@ -51,14 +50,12 @@ class ScanCodeTests { mockByteBuffer = Mockito.mock(ByteBuffer::class.java) mockImageInfo = Mockito.mock(ImageInfo::class.java) planes = arrayOf(mockPlaneProxy, mockPlaneProxy, mockPlaneProxy) - mockException = Mockito.mock(Exception::class.java) Mockito.doReturn(planes).`when`(mockImageProxy).planes Mockito.doReturn(mockByteBuffer).`when`(mockPlaneProxy).buffer Mockito.doReturn(30).`when`(mockImageProxy).width Mockito.doReturn(30).`when`(mockImageProxy).height Mockito.doReturn(30).`when`(mockByteBuffer).remaining() Mockito.doReturn(mockImageInfo).`when`(mockImageProxy).imageInfo - Mockito.doReturn("errorMessage").`when`(mockException).message } @Test From 9848bdcfce8484f126b5fc7c3a15ac94cf637555 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 16:48:22 +0000 Subject: [PATCH 060/174] refactor: remove commented dependencies References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- azure-pipelines.yml | 4 ++-- build.gradle | 28 ---------------------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b3a40bf..dca55d8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | diff --git a/build.gradle b/build.gradle index 055f318..27550e2 100644 --- a/build.gradle +++ b/build.gradle @@ -98,13 +98,9 @@ repositories { } dependencies { - implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' - - //implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation "androidx.compose.ui:ui:1.0.5" implementation "androidx.compose.material:material:1.0.5" implementation "androidx.compose.ui:ui-tooling-preview:1.0.5" @@ -118,41 +114,17 @@ dependencies { androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.5" debugImplementation "androidx.compose.ui:ui-tooling:1.0.5" - /* - implementation platform('androidx.compose:compose-bom:2023.03.00') - implementation 'androidx.compose.ui:ui' - implementation 'androidx.compose.ui:ui-graphics' - implementation 'androidx.compose.ui:ui-tooling-preview' - implementation 'androidx.compose.material3:material3' - - implementation platform('androidx.compose:compose-bom:2023.03.00') - implementation platform('androidx.compose:compose-bom:2023.03.00') - */ - implementation "androidx.camera:camera-camera2:1.0.2" implementation 'androidx.camera:camera-lifecycle:1.0.2' implementation 'androidx.camera:camera-view:1.0.0-alpha31' - implementation 'androidx.camera:camera-core:1.0.0' - - implementation 'com.google.zxing:core:3.4.1' implementation 'com.google.mlkit:barcode-scanning:17.2.0' - /* - androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') - androidTestImplementation 'androidx.compose.ui:ui-test-junit4' - androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') - androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') - debugImplementation 'androidx.compose.ui:ui-tooling' - debugImplementation 'androidx.compose.ui:ui-test-manifest' - */ - testImplementation "org.mockito:mockito-core:4.3.0" testImplementation 'org.mockito:mockito-inline:4.3.0' testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2" - } From 9ce39b7ba57c7e8181f4175bdd8a6081decd86e0 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 16:50:23 +0000 Subject: [PATCH 061/174] chore: set mlkit as default References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- azure-pipelines.yml | 4 ++-- pom.xml | 2 +- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dca55d8..b3a40bf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | diff --git a/pom.xml b/pom.xml index 949418c..c4cfc8f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.8 + 0.0.9 diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index c4ae3e0..6b04d4d 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -111,7 +111,7 @@ class OSBARCScannerActivity : ComponentActivity() { ContextCompat.getMainExecutor(context), OSBARCBarcodeAnalyzer( OSBARCScanLibraryFactory.createScanLibraryWrapper( - "zxing", + "mlkit", OSBARCZXingHelper(), OSBARCMLKitHelper() ), // temporary From b4ea84f9e8df8758fdf1ad4f5f19331babbb4edb Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 16:59:31 +0000 Subject: [PATCH 062/174] misc: remove comments --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b3a40bf..dca55d8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | From f38b5a5980b1d373c6f6741f857bce13a71ad725 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 18:45:29 +0000 Subject: [PATCH 063/174] feat: use the SCAN_LIBRARY parameter Context: This parameter should be used to decide which library to use. References: https://outsystemsrd.atlassian.net/browse/RMET-2895 --- azure-pipelines.yml | 4 ++-- pom.xml | 2 +- .../plugins/barcode/controller/OSBARCScanLibraryFactory.kt | 2 -- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 3 ++- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index dca55d8..b3a40bf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | diff --git a/pom.xml b/pom.xml index c4cfc8f..1b73b7c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.9 + 0.0.10 diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt index ea90c7c..0b43660 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt @@ -26,11 +26,9 @@ class OSBARCScanLibraryFactory { LIBRARY_ZXING -> { createZXingWrapper(zxingHelper) } - LIBRARY_MLKIT -> { createMLKitWrapper(mlkitHelper) } - else -> { createZXingWrapper(zxingHelper) } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 6b04d4d..0849bf7 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -46,6 +46,7 @@ class OSBARCScannerActivity : ComponentActivity() { private const val SCAN_SUCCESS_RESULT_CODE = -1 private const val SCAN_RESULT = "scanResult" private const val LOG_TAG = "OSBARCScannerActivity" + private const val SCAN_LIBRARY = "SCAN_LIBRARY" } /** @@ -111,7 +112,7 @@ class OSBARCScannerActivity : ComponentActivity() { ContextCompat.getMainExecutor(context), OSBARCBarcodeAnalyzer( OSBARCScanLibraryFactory.createScanLibraryWrapper( - "mlkit", + intent.extras?.getString(SCAN_LIBRARY) ?: "", OSBARCZXingHelper(), OSBARCMLKitHelper() ), // temporary From 58c279220c83873173bb6b0a5c43028d806e3321 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 9 Nov 2023 19:29:33 +0000 Subject: [PATCH 064/174] feat: first version of AlertDialog for app's permissions References: https://outsystemsrd.atlassian.net/browse/RMET-2895 --- .../barcode/view/OSBARCScannerActivity.kt | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 0849bf7..ee03890 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -2,6 +2,7 @@ package com.outsystems.plugins.barcode.view import android.Manifest import android.content.Intent +import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity @@ -22,9 +23,15 @@ import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.compose.ui.Modifier import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer @@ -70,6 +77,7 @@ class OSBARCScannerActivity : ComponentActivity() { fun ScanScreen() { val lifecycleOwner = LocalLifecycleOwner.current val context = LocalContext.current + var permissionGiven by remember { mutableStateOf(true) } // permissions val requestPermissionLauncher = rememberLauncherForActivityResult( @@ -77,15 +85,31 @@ class OSBARCScannerActivity : ComponentActivity() { ) { isGranted: Boolean -> if (isGranted) { // do nothing, continue + permissionGiven = true } else { - this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) - this.finish() + //this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) + //this.finish() + val s = "" + permissionGiven = false } } SideEffect { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } + if (!permissionGiven) { + AlertDialogExample( + onDismissRequest = { + val s = "s" + }, + onConfirmation = { + val s = "" + }, + dialogTitle = "Camera Access Not Enabled", + dialogText = "To continue, please go to the Settings app and enable it." + ) + } + // rest of the UI val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) @@ -148,4 +172,58 @@ class OSBARCScannerActivity : ComponentActivity() { } } + @Composable + fun AlertDialogExample( + onDismissRequest: () -> Unit, + onConfirmation: () -> Unit, + dialogTitle: String, + dialogText: String, + ) { + var dialogOpen by remember { mutableStateOf(true) } + val context = LocalContext.current + + if (dialogOpen) { + AlertDialog( + title = { + Text( + text = dialogTitle, + fontSize = 20.sp + ) + }, + text = { + Text(text = dialogText) + }, + onDismissRequest = { + //onDismissRequest() + dialogOpen = false + }, + confirmButton = { + TextButton( + onClick = { + //onConfirmation() + dialogOpen = false + val intent = Intent().apply { + action = android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.fromParts("package", context.packageName, null) + } + context.startActivity(intent) + } + ) { + Text("Settings") + } + }, + dismissButton = { + TextButton( + onClick = { + //onDismissRequest() + dialogOpen = false + } + ) { + Text("Ok") + } + } + ) + } + } + } \ No newline at end of file From a6a59fee516ced26108dd452a21ea1c7f1037ef2 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 10 Nov 2023 10:03:42 +0000 Subject: [PATCH 065/174] chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dca7d8..cf278b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,5 +8,8 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 09-11-2023 +Android - Scan barcode feature using ML Kit (https://outsystemsrd.atlassian.net/browse/RMET-2894) + ### 06-11-2023 Android - First implementation of the scan barcode feature using zxing (https://outsystemsrd.atlassian.net/browse/RMET-2758) \ No newline at end of file From 2d598bb757839889cb6fefc88cb2bde1e02aae33 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 10 Nov 2023 10:16:37 +0000 Subject: [PATCH 066/174] refactor: remove unnecessary code References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../controller/OSBARCScanLibraryFactory.kt | 32 +++---------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt index ea90c7c..b2aecfc 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt @@ -22,35 +22,11 @@ class OSBARCScanLibraryFactory { zxingHelper: OSBARCZXingHelperInterface, mlkitHelper: OSBARCMLKitHelperInterface ): OSBARCScanLibraryInterface { - return when (scanLibrary) { - LIBRARY_ZXING -> { - createZXingWrapper(zxingHelper) - } - - LIBRARY_MLKIT -> { - createMLKitWrapper(mlkitHelper) - } - - else -> { - createZXingWrapper(zxingHelper) - } + return if (scanLibrary == LIBRARY_MLKIT) { + OSBARCMLKitWrapper(mlkitHelper) + } else { + OSBARCZXingWrapper(zxingHelper) } } - - /** - * Creates and returns a OSBARCZXingWrapper instance. - * @return the newly created OSBARCZXingWrapper instance. - */ - private fun createZXingWrapper(helper: OSBARCZXingHelperInterface): OSBARCZXingWrapper { - return OSBARCZXingWrapper(helper) - } - - /** - * Creates and returns a OSBARCMLKitWrapper instance. - * @return the newly created OSBARCMLKitWrapper instance. - */ - private fun createMLKitWrapper(helper: OSBARCMLKitHelperInterface): OSBARCMLKitWrapper { - return OSBARCMLKitWrapper(helper) - } } } \ No newline at end of file From 0e008f796ea06f5385b3abe96da5935d312007c8 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 10 Nov 2023 10:33:02 +0000 Subject: [PATCH 067/174] refactor: add more comments to code References: https://outsystemsrd.atlassian.net/browse/RMET-2894 --- .../barcode/controller/OSBARCZXingWrapper.kt | 17 +++++++++++++--- .../controller/helper/OSBARCZXingHelper.kt | 20 +++++++++++-------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt index ba0a614..966a171 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -45,7 +45,15 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR val width = imageBitmap.width val height = imageBitmap.height val pixels = IntArray(width * height) - imageBitmap.getPixels(pixels, 0, width, 0, 0, width, height) + imageBitmap.getPixels( + pixels, + 0, // first index to write into pixels + width, + 0, // x coordinate of the first pixel to read + 0, // y coordinate of the first pixel to read + width, + height + ) helper.decodeImage(pixels, width, height, { @@ -72,9 +80,11 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR val uBuffer: ByteBuffer = planes[1].buffer val vBuffer: ByteBuffer = planes[2].buffer + // get image width and height val imageWidth = image.width val imageHeight = image.height + // calculate image data size val ySize = yBuffer.remaining() val uSize = uBuffer.remaining() val vSize = vBuffer.remaining() @@ -85,10 +95,11 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR uBuffer.get(data, ySize, uSize) vBuffer.get(data, ySize + uSize, vSize) - // Create a YUV image + // create a YUV image + // ImageFormat.NV21 used because it's efficient and widely supported val yuvImage = YuvImage(data, ImageFormat.NV21, imageWidth, imageHeight, null) - // Convert YUV to Bitmap + // convert YUV to Bitmap val out = ByteArrayOutputStream() yuvImage.compressToJpeg(Rect(0, 0, imageWidth, imageHeight), 100, out) val imageBytes = out.toByteArray() diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt index 89c0017..884077f 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt @@ -28,7 +28,11 @@ class OSBARCZXingHelper: OSBARCZXingHelperInterface { * @return the resulting bitmap. */ override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { - return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + return BitmapFactory.decodeByteArray( + imageBytes, + 0, // use 0 in the offset to decode from the beginning of imageBytes + imageBytes.size // use byte array size as length because we want to decode the whole image + ) } /** @@ -45,12 +49,12 @@ class OSBARCZXingHelper: OSBARCZXingHelperInterface { // actually rotate the image return Bitmap.createBitmap( bitmap, - 0, - 0, - bitmap.width, - bitmap.height, - matrix, - true + 0, // 0 is the x coordinate of the first pixel in source bitmap + 0, // 0 is the y coordinate of the first pixel in source bitmap + bitmap.width, // number of pixels in each row + bitmap.height, // number of rows + matrix, // matrix to be used for rotation + true // true states that source bitmap should be filtered using matrix (rotation) ) } @@ -77,7 +81,7 @@ class OSBARCZXingHelper: OSBARCZXingHelperInterface { }.decode(binaryBitmap) onSuccess(result.text) } catch (e: NotFoundException) { - // keep trying + // keep trying, no barcode was found in this camera frame e.message?.let { Log.d(LOG_TAG, it) } } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } From 4f3e2ae5b2b66cb70da966d1c21f642e32416c2f Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 10 Nov 2023 14:41:20 +0000 Subject: [PATCH 068/174] chore: bring changes from feat/RMET-2894/scan-code-mlkit References: https://outsystemsrd.atlassian.net/browse/RMET-2895 --- CHANGELOG.md | 3 ++ .../controller/OSBARCScanLibraryFactory.kt | 30 +++---------------- .../barcode/controller/OSBARCZXingWrapper.kt | 17 +++++++++-- .../controller/helper/OSBARCZXingHelper.kt | 20 ++++++++----- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dca7d8..cf278b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,5 +8,8 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 09-11-2023 +Android - Scan barcode feature using ML Kit (https://outsystemsrd.atlassian.net/browse/RMET-2894) + ### 06-11-2023 Android - First implementation of the scan barcode feature using zxing (https://outsystemsrd.atlassian.net/browse/RMET-2758) \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt index 0b43660..b2aecfc 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryFactory.kt @@ -22,33 +22,11 @@ class OSBARCScanLibraryFactory { zxingHelper: OSBARCZXingHelperInterface, mlkitHelper: OSBARCMLKitHelperInterface ): OSBARCScanLibraryInterface { - return when (scanLibrary) { - LIBRARY_ZXING -> { - createZXingWrapper(zxingHelper) - } - LIBRARY_MLKIT -> { - createMLKitWrapper(mlkitHelper) - } - else -> { - createZXingWrapper(zxingHelper) - } + return if (scanLibrary == LIBRARY_MLKIT) { + OSBARCMLKitWrapper(mlkitHelper) + } else { + OSBARCZXingWrapper(zxingHelper) } } - - /** - * Creates and returns a OSBARCZXingWrapper instance. - * @return the newly created OSBARCZXingWrapper instance. - */ - private fun createZXingWrapper(helper: OSBARCZXingHelperInterface): OSBARCZXingWrapper { - return OSBARCZXingWrapper(helper) - } - - /** - * Creates and returns a OSBARCMLKitWrapper instance. - * @return the newly created OSBARCMLKitWrapper instance. - */ - private fun createMLKitWrapper(helper: OSBARCMLKitHelperInterface): OSBARCMLKitWrapper { - return OSBARCMLKitWrapper(helper) - } } } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt index ba0a614..966a171 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -45,7 +45,15 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR val width = imageBitmap.width val height = imageBitmap.height val pixels = IntArray(width * height) - imageBitmap.getPixels(pixels, 0, width, 0, 0, width, height) + imageBitmap.getPixels( + pixels, + 0, // first index to write into pixels + width, + 0, // x coordinate of the first pixel to read + 0, // y coordinate of the first pixel to read + width, + height + ) helper.decodeImage(pixels, width, height, { @@ -72,9 +80,11 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR val uBuffer: ByteBuffer = planes[1].buffer val vBuffer: ByteBuffer = planes[2].buffer + // get image width and height val imageWidth = image.width val imageHeight = image.height + // calculate image data size val ySize = yBuffer.remaining() val uSize = uBuffer.remaining() val vSize = vBuffer.remaining() @@ -85,10 +95,11 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR uBuffer.get(data, ySize, uSize) vBuffer.get(data, ySize + uSize, vSize) - // Create a YUV image + // create a YUV image + // ImageFormat.NV21 used because it's efficient and widely supported val yuvImage = YuvImage(data, ImageFormat.NV21, imageWidth, imageHeight, null) - // Convert YUV to Bitmap + // convert YUV to Bitmap val out = ByteArrayOutputStream() yuvImage.compressToJpeg(Rect(0, 0, imageWidth, imageHeight), 100, out) val imageBytes = out.toByteArray() diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt index 89c0017..884077f 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt @@ -28,7 +28,11 @@ class OSBARCZXingHelper: OSBARCZXingHelperInterface { * @return the resulting bitmap. */ override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { - return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + return BitmapFactory.decodeByteArray( + imageBytes, + 0, // use 0 in the offset to decode from the beginning of imageBytes + imageBytes.size // use byte array size as length because we want to decode the whole image + ) } /** @@ -45,12 +49,12 @@ class OSBARCZXingHelper: OSBARCZXingHelperInterface { // actually rotate the image return Bitmap.createBitmap( bitmap, - 0, - 0, - bitmap.width, - bitmap.height, - matrix, - true + 0, // 0 is the x coordinate of the first pixel in source bitmap + 0, // 0 is the y coordinate of the first pixel in source bitmap + bitmap.width, // number of pixels in each row + bitmap.height, // number of rows + matrix, // matrix to be used for rotation + true // true states that source bitmap should be filtered using matrix (rotation) ) } @@ -77,7 +81,7 @@ class OSBARCZXingHelper: OSBARCZXingHelperInterface { }.decode(binaryBitmap) onSuccess(result.text) } catch (e: NotFoundException) { - // keep trying + // keep trying, no barcode was found in this camera frame e.message?.let { Log.d(LOG_TAG, it) } } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } From cbfbf00935c5d3bcc117aa2dacd5ea0a3192e258 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 10 Nov 2023 14:48:12 +0000 Subject: [PATCH 069/174] chore: revert changes in "feat: first version of AlertDialog for app's permissions" This reverts commit 58c279220c83873173bb6b0a5c43028d806e3321. References: https://outsystemsrd.atlassian.net/browse/RMET-2895 --- .../barcode/view/OSBARCScannerActivity.kt | 82 +------------------ 1 file changed, 2 insertions(+), 80 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index ee03890..0849bf7 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -2,7 +2,6 @@ package com.outsystems.plugins.barcode.view import android.Manifest import android.content.Intent -import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity @@ -23,15 +22,9 @@ import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.compose.ui.Modifier import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.SideEffect -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer @@ -77,7 +70,6 @@ class OSBARCScannerActivity : ComponentActivity() { fun ScanScreen() { val lifecycleOwner = LocalLifecycleOwner.current val context = LocalContext.current - var permissionGiven by remember { mutableStateOf(true) } // permissions val requestPermissionLauncher = rememberLauncherForActivityResult( @@ -85,31 +77,15 @@ class OSBARCScannerActivity : ComponentActivity() { ) { isGranted: Boolean -> if (isGranted) { // do nothing, continue - permissionGiven = true } else { - //this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) - //this.finish() - val s = "" - permissionGiven = false + this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) + this.finish() } } SideEffect { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } - if (!permissionGiven) { - AlertDialogExample( - onDismissRequest = { - val s = "s" - }, - onConfirmation = { - val s = "" - }, - dialogTitle = "Camera Access Not Enabled", - dialogText = "To continue, please go to the Settings app and enable it." - ) - } - // rest of the UI val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) @@ -172,58 +148,4 @@ class OSBARCScannerActivity : ComponentActivity() { } } - @Composable - fun AlertDialogExample( - onDismissRequest: () -> Unit, - onConfirmation: () -> Unit, - dialogTitle: String, - dialogText: String, - ) { - var dialogOpen by remember { mutableStateOf(true) } - val context = LocalContext.current - - if (dialogOpen) { - AlertDialog( - title = { - Text( - text = dialogTitle, - fontSize = 20.sp - ) - }, - text = { - Text(text = dialogText) - }, - onDismissRequest = { - //onDismissRequest() - dialogOpen = false - }, - confirmButton = { - TextButton( - onClick = { - //onConfirmation() - dialogOpen = false - val intent = Intent().apply { - action = android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS - data = Uri.fromParts("package", context.packageName, null) - } - context.startActivity(intent) - } - ) { - Text("Settings") - } - }, - dismissButton = { - TextButton( - onClick = { - //onDismissRequest() - dialogOpen = false - } - ) { - Text("Ok") - } - } - ) - } - } - } \ No newline at end of file From 7fb23cdfc55240aff524e2ed76d8330143420a8f Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 10 Nov 2023 14:54:18 +0000 Subject: [PATCH 070/174] chore: remove commented lines References: https://outsystemsrd.atlassian.net/browse/RMET-2895 --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b3a40bf..dca55d8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,12 +42,12 @@ steps: /usr/bin/mvn -f /home/vsts/work/1/s/pom.xml help:effective-pom - task: MavenAuthenticate@0 displayName: Authenticate in public repo - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: artifactsFeeds: 'PublicArtifactRepository' - task: Bash@3 displayName: Deploy file - #condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) + condition: or(${{parameters.publishBuild}}, eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: targetType: 'inline' script: | From 111d378fa3c3dc30b130004cca3b1eab9ea963af Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 10 Nov 2023 15:38:28 +0000 Subject: [PATCH 071/174] chore: update changelog References: feat/RMET-2895/use-both-libraries --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf278b0..34b6235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 10-11-2023 +Android - Use both libraries dynamically (https://outsystemsrd.atlassian.net/browse/RMET-2895) + ### 09-11-2023 Android - Scan barcode feature using ML Kit (https://outsystemsrd.atlassian.net/browse/RMET-2894) From 67ea0b5256708fd8426c777e4697f9424de50cb1 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 10 Nov 2023 15:52:02 +0000 Subject: [PATCH 072/174] chore: raise lib version References: https://outsystemsrd.atlassian.net/browse/RMET-2895 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1b73b7c..4427616 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.10 + 0.0.11 From 87182c8ad90a1e1a3f6b3bdeaf791479c8c0c846 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 13 Nov 2023 11:56:21 +0000 Subject: [PATCH 073/174] feat: use camera direction to decide camera facing Context: Instead of passing each field of OSBARCScanParameters in an extra through the intent, we might as well pass the whole object. To do that, it has to be made a Serializable. References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- .../barcode/controller/OSBARCController.kt | 23 +++++-------------- .../barcode/model/OSBARCScanParameters.kt | 4 +++- .../barcode/view/OSBARCScannerActivity.kt | 13 +++++++---- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt index bf30f12..d4ec498 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -15,13 +15,7 @@ class OSBARCController { companion object { private const val SCAN_REQUEST_CODE = 112 - private const val SCAN_INSTRUCTIONS = "SCAN_INSTRUCTIONS" - private const val CAMERA_DIRECTION = "CAMERA_DIRECTION" - private const val SCAN_ORIENTATION = "SCAN_ORIENTATION" - private const val SCAN_BUTTON = "SCAN_BUTTON" - private const val SCAN_BUTTON_TEXT = "SCAN_BUTTON_TEXT" - private const val SCAN_HINT = "SCAN_HINT" - private const val SCAN_LIBRARY = "SCAN_LIBRARY" + private const val SCAN_PARAMETERS = "SCAN_PARAMETERS" private const val SCAN_RESULT = "scanResult" private const val LOG_TAG = "OSBARCController" } @@ -33,16 +27,11 @@ class OSBARCController { * @param parameters - object that contains all the barcode parameters to be used when scanning. */ fun scanCode(activity: Activity, parameters: OSBARCScanParameters) { - val scanningIntent = Intent(activity, OSBARCScannerActivity::class.java).apply { - putExtra(SCAN_INSTRUCTIONS, parameters.scanInstructions) - putExtra(CAMERA_DIRECTION, parameters.cameraDirection) - putExtra(SCAN_ORIENTATION, parameters.scanOrientation) - putExtra(SCAN_BUTTON, parameters.scanButton) - putExtra(SCAN_BUTTON_TEXT, parameters.scanText) - putExtra(SCAN_HINT, parameters.hint) - putExtra(SCAN_LIBRARY, parameters.androidScanningLibrary) - } - activity.startActivityForResult(scanningIntent, SCAN_REQUEST_CODE) + activity.startActivityForResult( + Intent( + activity, OSBARCScannerActivity::class.java + ).putExtra(SCAN_PARAMETERS, parameters), SCAN_REQUEST_CODE + ) } /** diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt index a5dc9a1..ed0244d 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt @@ -1,5 +1,7 @@ package com.outsystems.plugins.barcode.model +import java.io.Serializable + /** * Data class that represents the object with the scan parameters. */ @@ -11,4 +13,4 @@ data class OSBARCScanParameters( val scanText: String?, val hint: Int?, val androidScanningLibrary: String? -) \ No newline at end of file +) : Serializable \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 0849bf7..964dfec 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -32,6 +32,7 @@ import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError +import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import java.lang.Exception @@ -46,9 +47,11 @@ class OSBARCScannerActivity : ComponentActivity() { private const val SCAN_SUCCESS_RESULT_CODE = -1 private const val SCAN_RESULT = "scanResult" private const val LOG_TAG = "OSBARCScannerActivity" - private const val SCAN_LIBRARY = "SCAN_LIBRARY" + private const val SCAN_PARAMETERS = "SCAN_PARAMETERS" } + private lateinit var parameters: OSBARCScanParameters + /** * Overrides the onCreate method from Activity, setting the UI of the screen */ @@ -57,7 +60,7 @@ class OSBARCScannerActivity : ComponentActivity() { setContent { BarcodeScannerTheme { - ScanScreen() + ScanScreen(intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters) } } } @@ -67,7 +70,7 @@ class OSBARCScannerActivity : ComponentActivity() { * as well as creating an instance of OSBARCBarcodeAnalyzer for image analysis. */ @Composable - fun ScanScreen() { + fun ScanScreen(parameters: OSBARCScanParameters) { val lifecycleOwner = LocalLifecycleOwner.current val context = LocalContext.current @@ -102,7 +105,7 @@ class OSBARCScannerActivity : ComponentActivity() { val previewView = PreviewView(context) val preview = Preview.Builder().build() val selector = CameraSelector.Builder() - .requireLensFacing(CameraSelector.LENS_FACING_BACK) // temporary + .requireLensFacing(if (parameters.cameraDirection == 2) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) .build() preview.setSurfaceProvider(previewView.surfaceProvider) val imageAnalysis = ImageAnalysis.Builder() @@ -112,7 +115,7 @@ class OSBARCScannerActivity : ComponentActivity() { ContextCompat.getMainExecutor(context), OSBARCBarcodeAnalyzer( OSBARCScanLibraryFactory.createScanLibraryWrapper( - intent.extras?.getString(SCAN_LIBRARY) ?: "", + parameters.androidScanningLibrary ?: "", OSBARCZXingHelper(), OSBARCMLKitHelper() ), // temporary From 629e4f315bbf34bef7f5b5cb6f3375d20ba06c04 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 13 Nov 2023 12:10:51 +0000 Subject: [PATCH 074/174] chore: raise lib version to 0.0.12 References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4427616..ecaadcf 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.11 + 0.0.12 From 920574d0b2dbc279193f75ecb4440ff0b9c83d7b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 13 Nov 2023 12:11:35 +0000 Subject: [PATCH 075/174] chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b6235..933ef9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 13-11-2023 +Android - Select Camera (Back or Front) (https://outsystemsrd.atlassian.net/browse/RMET-2764) + ### 10-11-2023 Android - Use both libraries dynamically (https://outsystemsrd.atlassian.net/browse/RMET-2895) From 5f9ad8604c0ea4cd3384de5b8b223ce19e5b85cb Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 13 Nov 2023 13:53:02 +0000 Subject: [PATCH 076/174] refactor: use constant to check camera direction value Context: Makes the code more readable. References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 964dfec..9ecbb6e 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -48,6 +48,7 @@ class OSBARCScannerActivity : ComponentActivity() { private const val SCAN_RESULT = "scanResult" private const val LOG_TAG = "OSBARCScannerActivity" private const val SCAN_PARAMETERS = "SCAN_PARAMETERS" + private const val CAM_DIRECTION_FRONT = 2 } private lateinit var parameters: OSBARCScanParameters @@ -105,7 +106,7 @@ class OSBARCScannerActivity : ComponentActivity() { val previewView = PreviewView(context) val preview = Preview.Builder().build() val selector = CameraSelector.Builder() - .requireLensFacing(if (parameters.cameraDirection == 2) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) + .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) .build() preview.setSurfaceProvider(previewView.surfaceProvider) val imageAnalysis = ImageAnalysis.Builder() From 4333275c639f853a84409398441b8a5e6b99acc6 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 13 Nov 2023 14:10:35 +0000 Subject: [PATCH 077/174] refactor: remove unused variable References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 9ecbb6e..31298c2 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -51,8 +51,6 @@ class OSBARCScannerActivity : ComponentActivity() { private const val CAM_DIRECTION_FRONT = 2 } - private lateinit var parameters: OSBARCScanParameters - /** * Overrides the onCreate method from Activity, setting the UI of the screen */ From a3f575581f1c5b60d6ac383779f2034471c4c88d Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 13 Nov 2023 17:45:39 +0000 Subject: [PATCH 078/174] feat: show alert dialog when camera permission is denied Context: Following the UI/UX design, we should present this dialog when the permission to the camera is denied, so that the user can more easily navigate to the settings. References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- .../barcode/view/OSBARCScannerActivity.kt | 99 +++++++++++++++++-- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 9ecbb6e..c4d6973 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -1,7 +1,10 @@ package com.outsystems.plugins.barcode.view import android.Manifest +import android.content.Context import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity @@ -22,9 +25,13 @@ import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.compose.ui.Modifier import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.SideEffect import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer @@ -43,6 +50,9 @@ import java.lang.Exception */ class OSBARCScannerActivity : ComponentActivity() { + private var permissionRequestCount = 0 + private var showDialog by mutableStateOf(false) + companion object { private const val SCAN_SUCCESS_RESULT_CODE = -1 private const val SCAN_RESULT = "scanResult" @@ -51,8 +61,6 @@ class OSBARCScannerActivity : ComponentActivity() { private const val CAM_DIRECTION_FRONT = 2 } - private lateinit var parameters: OSBARCScanParameters - /** * Overrides the onCreate method from Activity, setting the UI of the screen */ @@ -66,6 +74,13 @@ class OSBARCScannerActivity : ComponentActivity() { } } + override fun onResume() { + super.onResume() + if (!hasCameraPermission(this.applicationContext)) { + showDialog = true + } + } + /** * Composable function, responsible for declaring the UI of the screen, * as well as creating an instance of OSBARCBarcodeAnalyzer for image analysis. @@ -74,20 +89,33 @@ class OSBARCScannerActivity : ComponentActivity() { fun ScanScreen(parameters: OSBARCScanParameters) { val lifecycleOwner = LocalLifecycleOwner.current val context = LocalContext.current + var permissionGiven by remember { mutableStateOf(true) } // permissions val requestPermissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> if (isGranted) { - // do nothing, continue + permissionGiven = true + showDialog = false } else { - this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) - this.finish() + permissionGiven = false + showDialog = true } } SideEffect { - requestPermissionLauncher.launch(Manifest.permission.CAMERA) + if (permissionRequestCount == 0) { + permissionRequestCount++ + requestPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } + + if (!permissionGiven) { + CameraPermissionRequiredDialog( + showDialog, + dialogTitle = "Camera Access Not Enabled", + dialogText = "To continue, please go to the Settings app and enable it." + ) } // rest of the UI @@ -152,4 +180,63 @@ class OSBARCScannerActivity : ComponentActivity() { } } + @Composable + fun CameraPermissionRequiredDialog( + shouldShowDialog: Boolean, + dialogTitle: String, + dialogText: String, + ) { + var dialogOpen by remember { mutableStateOf(true) } + val context = LocalContext.current + + if (shouldShowDialog) { + AlertDialog( + title = { + Text( + text = dialogTitle, + fontSize = 20.sp + ) + }, + text = { + Text(text = dialogText) + }, + onDismissRequest = { + dialogOpen = false + }, + confirmButton = { + TextButton( + onClick = { + showDialog = false + val intent = Intent().apply { + action = android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.fromParts("package", context.packageName, null) + } + context.startActivity(intent) + } + ) { + Text("Settings") + } + }, + dismissButton = { + TextButton( + onClick = { + showDialog = false + this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) + this.finish() + } + ) { + Text("Ok") + } + } + ) + } + } + + private fun hasCameraPermission(context: Context): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + } + } \ No newline at end of file From a3370cc5c2a6b63ed9b04554b0f0a69a9a350ff2 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 13 Nov 2023 18:24:10 +0000 Subject: [PATCH 079/174] refactor: use separate file for dialog References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- .../view/OSBARCCameraPermissionDialog.kt | 51 +++++++++++++ .../barcode/view/OSBARCScannerActivity.kt | 72 ++++--------------- 2 files changed, 64 insertions(+), 59 deletions(-) create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt new file mode 100644 index 0000000..bd32264 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt @@ -0,0 +1,51 @@ +package com.outsystems.plugins.barcode.view + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.sp + +@Composable +fun CameraPermissionRequiredDialog( + onDismissRequest: () -> Unit, + onConfirmation: () -> Unit, + shouldShowDialog: Boolean, + dialogTitle: String, + dialogText: String, +) { + if (shouldShowDialog) { + AlertDialog( + title = { + Text( + text = dialogTitle, + fontSize = 20.sp + ) + }, + text = { + Text(text = dialogText) + }, + onDismissRequest = { + onDismissRequest() + }, + confirmButton = { + TextButton( + onClick = { + onConfirmation() + } + ) { + Text("Settings") + } + }, + dismissButton = { + TextButton( + onClick = { + onDismissRequest() + } + ) { + Text("Ok") + } + } + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index c4d6973..c916b14 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle +import android.provider.Settings import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -25,13 +26,9 @@ import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.compose.ui.Modifier import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.SideEffect import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer @@ -76,9 +73,7 @@ class OSBARCScannerActivity : ComponentActivity() { override fun onResume() { super.onResume() - if (!hasCameraPermission(this.applicationContext)) { - showDialog = true - } + showDialog = !hasCameraPermission(this.applicationContext) } /** @@ -112,6 +107,17 @@ class OSBARCScannerActivity : ComponentActivity() { if (!permissionGiven) { CameraPermissionRequiredDialog( + { + this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) + this.finish() + }, + { + val intent = Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.fromParts("package", context.packageName, null) + } + context.startActivity(intent) + }, showDialog, dialogTitle = "Camera Access Not Enabled", dialogText = "To continue, please go to the Settings app and enable it." @@ -180,58 +186,6 @@ class OSBARCScannerActivity : ComponentActivity() { } } - @Composable - fun CameraPermissionRequiredDialog( - shouldShowDialog: Boolean, - dialogTitle: String, - dialogText: String, - ) { - var dialogOpen by remember { mutableStateOf(true) } - val context = LocalContext.current - - if (shouldShowDialog) { - AlertDialog( - title = { - Text( - text = dialogTitle, - fontSize = 20.sp - ) - }, - text = { - Text(text = dialogText) - }, - onDismissRequest = { - dialogOpen = false - }, - confirmButton = { - TextButton( - onClick = { - showDialog = false - val intent = Intent().apply { - action = android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS - data = Uri.fromParts("package", context.packageName, null) - } - context.startActivity(intent) - } - ) { - Text("Settings") - } - }, - dismissButton = { - TextButton( - onClick = { - showDialog = false - this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) - this.finish() - } - ) { - Text("Ok") - } - } - ) - } - } - private fun hasCameraPermission(context: Context): Boolean { return ContextCompat.checkSelfPermission( context, From 1aca15bfa05f944c579c4d92c6413fd4d1967a5b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 13 Nov 2023 18:24:36 +0000 Subject: [PATCH 080/174] refactor: remove unused variable References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index c916b14..ce3b35b 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -128,9 +128,6 @@ class OSBARCScannerActivity : ComponentActivity() { val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) } - var barcode by remember { - mutableStateOf("") - } Column ( modifier = Modifier.fillMaxSize() @@ -155,7 +152,6 @@ class OSBARCScannerActivity : ComponentActivity() { OSBARCMLKitHelper() ), // temporary { result -> - barcode = result val resultIntent = Intent() resultIntent.putExtra(SCAN_RESULT, result) setResult(SCAN_SUCCESS_RESULT_CODE, resultIntent) From 73a7f6123d739feec2062d3e6216d9455531e03b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 13 Nov 2023 18:27:10 +0000 Subject: [PATCH 081/174] chore: raise lib version to 0.0.13 References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ecaadcf..63abe3d 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.12 + 0.0.13 From 2781a655a344d3f1f01ad07c355c92d50c7b0a22 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 09:13:29 +0000 Subject: [PATCH 082/174] refactor: include buttons text as parameters References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- .../plugins/barcode/view/OSBARCCameraPermissionDialog.kt | 6 ++++-- .../plugins/barcode/view/OSBARCScannerActivity.kt | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt index bd32264..4e31b4a 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt @@ -13,6 +13,8 @@ fun CameraPermissionRequiredDialog( shouldShowDialog: Boolean, dialogTitle: String, dialogText: String, + confirmButtonText: String, + dismissButtonText: String ) { if (shouldShowDialog) { AlertDialog( @@ -34,7 +36,7 @@ fun CameraPermissionRequiredDialog( onConfirmation() } ) { - Text("Settings") + Text(confirmButtonText) } }, dismissButton = { @@ -43,7 +45,7 @@ fun CameraPermissionRequiredDialog( onDismissRequest() } ) { - Text("Ok") + Text(dismissButtonText) } } ) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index ce3b35b..a92545b 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -120,7 +120,9 @@ class OSBARCScannerActivity : ComponentActivity() { }, showDialog, dialogTitle = "Camera Access Not Enabled", - dialogText = "To continue, please go to the Settings app and enable it." + dialogText = "To continue, please go to the Settings app and enable it.", + "Settings", + "Ok" ) } From 51a077116ccaf81a42f98d6f5f18e5432339d480 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 09:14:04 +0000 Subject: [PATCH 083/174] chore: raise lib version to 0.0.14 References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 63abe3d..c59e8ec 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.13 + 0.0.14 From edf6224884e09451d8f48c154e9d6e5626900d13 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 09:15:17 +0000 Subject: [PATCH 084/174] chore: remove comment References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index a92545b..5ad10d2 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -152,7 +152,7 @@ class OSBARCScannerActivity : ComponentActivity() { parameters.androidScanningLibrary ?: "", OSBARCZXingHelper(), OSBARCMLKitHelper() - ), // temporary + ), { result -> val resultIntent = Intent() resultIntent.putExtra(SCAN_RESULT, result) From a521f9cacdf09738993b7cd8c62e45f9469389d2 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 09:21:42 +0000 Subject: [PATCH 085/174] chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 933ef9e..21b75e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 13-11-2023 +Android - Implement AlertDialog to settings (https://outsystemsrd.atlassian.net/browse/RMET-2764) + ### 13-11-2023 Android - Select Camera (Back or Front) (https://outsystemsrd.atlassian.net/browse/RMET-2764) From 1a9b4fe18d980b56684a6bb8b2a4eba34d090020 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 09:33:06 +0000 Subject: [PATCH 086/174] refactor: use named parameters References: https://outsystemsrd.atlassian.net/browse/RMET-2764 --- .../plugins/barcode/view/OSBARCScannerActivity.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 5ad10d2..ec5d34c 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -107,22 +107,22 @@ class OSBARCScannerActivity : ComponentActivity() { if (!permissionGiven) { CameraPermissionRequiredDialog( - { + onDismissRequest = { this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) this.finish() }, - { + onConfirmation = { val intent = Intent().apply { action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS data = Uri.fromParts("package", context.packageName, null) } context.startActivity(intent) }, - showDialog, + shouldShowDialog = showDialog, dialogTitle = "Camera Access Not Enabled", dialogText = "To continue, please go to the Settings app and enable it.", - "Settings", - "Ok" + confirmButtonText = "Settings", + dismissButtonText = "Ok" ) } From 1de666fbd072a88a983aa62f999a23d14d6fc8c1 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 15:07:19 +0000 Subject: [PATCH 087/174] feat: implement flashlight Context: The flashlight button should only be presented if the flashlight is available in the camera that is being used (front or back). References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../barcode/view/OSBARCScannerActivity.kt | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index ec5d34c..e2b156b 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -4,29 +4,37 @@ import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.hardware.camera2.CameraAccessException +import android.hardware.camera2.CameraCharacteristics +import android.hardware.camera2.CameraManager import android.net.Uri import android.os.Bundle import android.provider.Settings import android.util.Log import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent +import androidx.activity.result.contract.ActivityResultContracts +import androidx.camera.core.Camera import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.Button +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.compose.ui.Modifier -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.runtime.SideEffect import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView @@ -38,7 +46,6 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme -import java.lang.Exception /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -46,7 +53,8 @@ import java.lang.Exception * implements the ImageAnalysis.Analyzer interface. */ class OSBARCScannerActivity : ComponentActivity() { - + private lateinit var camera: Camera + private lateinit var selector: CameraSelector private var permissionRequestCount = 0 private var showDialog by mutableStateOf(false) @@ -64,9 +72,14 @@ class OSBARCScannerActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters + selector = CameraSelector.Builder() + .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) + .build() + setContent { BarcodeScannerTheme { - ScanScreen(intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters) + ScanScreen(parameters) } } } @@ -138,9 +151,6 @@ class OSBARCScannerActivity : ComponentActivity() { factory = { context -> val previewView = PreviewView(context) val preview = Preview.Builder().build() - val selector = CameraSelector.Builder() - .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) - .build() preview.setSurfaceProvider(previewView.surfaceProvider) val imageAnalysis = ImageAnalysis.Builder() .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) @@ -166,7 +176,7 @@ class OSBARCScannerActivity : ComponentActivity() { ) ) try { - cameraProviderFuture.get().bindToLifecycle( + camera = cameraProviderFuture.get().bindToLifecycle( lifecycleOwner, selector, preview, @@ -181,6 +191,28 @@ class OSBARCScannerActivity : ComponentActivity() { }, modifier = Modifier.weight(1f) ) + + if (isFlashAvailable(context)) { + TorchButton() + } + } + } + + @Composable + fun TorchButton() { + var isFlashlightOn by remember { mutableStateOf(false) } + + Button( + onClick = { + toggleFlashlight(isFlashlightOn) + isFlashlightOn = !isFlashlightOn + }, + modifier = Modifier, + ) { + Icon( + imageVector = Icons.Default.Info, + contentDescription = "Torch" + ) } } @@ -191,4 +223,25 @@ class OSBARCScannerActivity : ComponentActivity() { ) == PackageManager.PERMISSION_GRANTED } + private fun toggleFlashlight(isFlashlightOn: Boolean) { + try { + camera.cameraControl.enableTorch(!isFlashlightOn) + } catch (e: CameraAccessException) { + e.message?.let { Log.e(LOG_TAG, it) } + } catch (e: Exception) { + e.message?.let { Log.e(LOG_TAG, it) } + } + } + + private fun isFlashAvailable(context: Context): Boolean { + val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager + val cameraId = cameraManager.cameraIdList.find { + val characteristics = cameraManager.getCameraCharacteristics(it) + val facing = characteristics.get(CameraCharacteristics.LENS_FACING) + facing == selector.lensFacing + } + if (cameraId.isNullOrEmpty()) return false + return cameraManager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true + } + } \ No newline at end of file From 2b024786903fe66db13e45fbb38d69ca51dd239c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 16:16:28 +0000 Subject: [PATCH 088/174] refactor: use hasFlashUnit to determine if camera has flash Context: Our previous way of determining if the camera had a flashlight available was using the "isFlashAvailable" helper method. This method required a SuppressLint("RestrictedApi") annotation because of "selector.lensFacing". We should avoid using this specific annotation, and method "selector.lensFacing" isn't supposed to be used. References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../barcode/view/OSBARCScannerActivity.kt | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index e2b156b..cbb633d 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -5,8 +5,6 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.hardware.camera2.CameraAccessException -import android.hardware.camera2.CameraCharacteristics -import android.hardware.camera2.CameraManager import android.net.Uri import android.os.Bundle import android.provider.Settings @@ -144,6 +142,17 @@ class OSBARCScannerActivity : ComponentActivity() { ProcessCameraProvider.getInstance(context) } + try { + camera = cameraProviderFuture.get().bindToLifecycle( + lifecycleOwner, + selector + ) + } catch (e: Exception) { + e.message?.let { Log.e(LOG_TAG, it) } + setResult(OSBARCError.SCANNING_GENERAL_ERROR.code) + finish() + } + Column ( modifier = Modifier.fillMaxSize() ) { @@ -192,7 +201,7 @@ class OSBARCScannerActivity : ComponentActivity() { modifier = Modifier.weight(1f) ) - if (isFlashAvailable(context)) { + if (camera.cameraInfo.hasFlashUnit()) { TorchButton() } } @@ -233,15 +242,4 @@ class OSBARCScannerActivity : ComponentActivity() { } } - private fun isFlashAvailable(context: Context): Boolean { - val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager - val cameraId = cameraManager.cameraIdList.find { - val characteristics = cameraManager.getCameraCharacteristics(it) - val facing = characteristics.get(CameraCharacteristics.LENS_FACING) - facing == selector.lensFacing - } - if (cameraId.isNullOrEmpty()) return false - return cameraManager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true - } - } \ No newline at end of file From b6f998c9862f26ca3fc599e3979eee15a16da171 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 16:20:32 +0000 Subject: [PATCH 089/174] chore: raise lib version to 0.0.15 References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c59e8ec..fa3f3c5 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.14 + 0.0.15 From a377eb685aebc739af7739bb97d74d9107dfb678 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 16:44:48 +0000 Subject: [PATCH 090/174] refactor: remove extra line References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index cbb633d..c3b313a 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -210,7 +210,6 @@ class OSBARCScannerActivity : ComponentActivity() { @Composable fun TorchButton() { var isFlashlightOn by remember { mutableStateOf(false) } - Button( onClick = { toggleFlashlight(isFlashlightOn) From ac6c8bc310c3d3e00798cd5ae73d46316c7c5b75 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 17:47:50 +0000 Subject: [PATCH 091/174] feat: update button according to flashlight state Context: We need to use different images to represent the on and off state. References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../barcode/view/OSBARCScannerActivity.kt | 24 ++++++++++++++----- src/main/res/drawable/flash_off.xml | 10 ++++++++ src/main/res/drawable/flash_on.xml | 10 ++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 src/main/res/drawable/flash_off.xml create mode 100644 src/main/res/drawable/flash_on.xml diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index c3b313a..569d8e6 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -20,12 +20,12 @@ import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Info +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Button -import androidx.compose.material3.Icon +import androidx.compose.material3.ButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue @@ -33,10 +33,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.painterResource import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat +import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper @@ -210,18 +213,27 @@ class OSBARCScannerActivity : ComponentActivity() { @Composable fun TorchButton() { var isFlashlightOn by remember { mutableStateOf(false) } + val onIcon = painterResource(id = R.drawable.flash_on) + val offIcon = painterResource(id = R.drawable.flash_off) + Button( onClick = { toggleFlashlight(isFlashlightOn) isFlashlightOn = !isFlashlightOn }, modifier = Modifier, + colors = ButtonDefaults.buttonColors( + containerColor = if (isFlashlightOn) Color.White else Color.Black + ), + shape = CircleShape ) { - Icon( - imageVector = Icons.Default.Info, - contentDescription = "Torch" + val icon = if (isFlashlightOn) onIcon else offIcon + Image( + painter = icon, + contentDescription = null ) } + } private fun hasCameraPermission(context: Context): Boolean { diff --git a/src/main/res/drawable/flash_off.xml b/src/main/res/drawable/flash_off.xml new file mode 100644 index 0000000..d6342f5 --- /dev/null +++ b/src/main/res/drawable/flash_off.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/main/res/drawable/flash_on.xml b/src/main/res/drawable/flash_on.xml new file mode 100644 index 0000000..9ffd696 --- /dev/null +++ b/src/main/res/drawable/flash_on.xml @@ -0,0 +1,10 @@ + + + From 6ceaecd91830253ab1fa5b18923c680a4997908a Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 18:14:01 +0000 Subject: [PATCH 092/174] refactor: replace Column with Box Context: We want to overlay Buttons and more elements on top of the Camera view. To do that, we need to use the Box composable that is designed for that. References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../barcode/view/OSBARCScannerActivity.kt | 59 ++++++++----------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 569d8e6..1bea316 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -21,7 +21,7 @@ import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Button @@ -32,6 +32,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -39,7 +40,6 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat -import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper @@ -47,6 +47,7 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme +import com.outsystemsenterprise.enmobile11dev.BarcodeSampleAppNew.R /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -156,9 +157,7 @@ class OSBARCScannerActivity : ComponentActivity() { finish() } - Column ( - modifier = Modifier.fillMaxSize() - ) { + Box(modifier = Modifier.fillMaxSize()) { AndroidView( factory = { context -> val previewView = PreviewView(context) @@ -200,39 +199,33 @@ class OSBARCScannerActivity : ComponentActivity() { finish() } previewView - }, - modifier = Modifier.weight(1f) + } ) if (camera.cameraInfo.hasFlashUnit()) { - TorchButton() + var isFlashlightOn by remember { mutableStateOf(false) } + val onIcon = painterResource(id = R.drawable.flash_on) + val offIcon = painterResource(id = R.drawable.flash_off) + + Button( + onClick = { + toggleFlashlight(isFlashlightOn) + isFlashlightOn = !isFlashlightOn + }, + modifier = Modifier.align(Alignment.BottomEnd), + colors = ButtonDefaults.buttonColors( + containerColor = if (isFlashlightOn) Color.White else Color.Black + ), + shape = CircleShape + ) { + val icon = if (isFlashlightOn) onIcon else offIcon + Image( + painter = icon, + contentDescription = null + ) + } } } - } - - @Composable - fun TorchButton() { - var isFlashlightOn by remember { mutableStateOf(false) } - val onIcon = painterResource(id = R.drawable.flash_on) - val offIcon = painterResource(id = R.drawable.flash_off) - - Button( - onClick = { - toggleFlashlight(isFlashlightOn) - isFlashlightOn = !isFlashlightOn - }, - modifier = Modifier, - colors = ButtonDefaults.buttonColors( - containerColor = if (isFlashlightOn) Color.White else Color.Black - ), - shape = CircleShape - ) { - val icon = if (isFlashlightOn) onIcon else offIcon - Image( - painter = icon, - contentDescription = null - ) - } } From dd4896be4f416dcd4a368fdb19475664a08159a5 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 18:14:25 +0000 Subject: [PATCH 093/174] chore: raise lib version to 0.0.16 References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fa3f3c5..7942fc2 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.15 + 0.0.16 From 8ae91a80da78a7f5082961ca2e952586f081cb37 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 18:16:05 +0000 Subject: [PATCH 094/174] fix: properly import R class References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 1bea316..a35dc88 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat +import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper @@ -47,7 +48,6 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme -import com.outsystemsenterprise.enmobile11dev.BarcodeSampleAppNew.R /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -206,7 +206,7 @@ class OSBARCScannerActivity : ComponentActivity() { var isFlashlightOn by remember { mutableStateOf(false) } val onIcon = painterResource(id = R.drawable.flash_on) val offIcon = painterResource(id = R.drawable.flash_off) - + Button( onClick = { toggleFlashlight(isFlashlightOn) From c2f405cb0a7bbc492fb0ed8d4be5ed8a1423fc16 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 18:30:05 +0000 Subject: [PATCH 095/174] refactor: use helper method for flashlight button References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../barcode/view/OSBARCScannerActivity.kt | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index a35dc88..b7101e8 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -32,7 +32,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -203,32 +202,36 @@ class OSBARCScannerActivity : ComponentActivity() { ) if (camera.cameraInfo.hasFlashUnit()) { - var isFlashlightOn by remember { mutableStateOf(false) } - val onIcon = painterResource(id = R.drawable.flash_on) - val offIcon = painterResource(id = R.drawable.flash_off) - - Button( - onClick = { - toggleFlashlight(isFlashlightOn) - isFlashlightOn = !isFlashlightOn - }, - modifier = Modifier.align(Alignment.BottomEnd), - colors = ButtonDefaults.buttonColors( - containerColor = if (isFlashlightOn) Color.White else Color.Black - ), - shape = CircleShape - ) { - val icon = if (isFlashlightOn) onIcon else offIcon - Image( - painter = icon, - contentDescription = null - ) - } + TorchButton() } } } + @Composable + fun TorchButton() { + var isFlashlightOn by remember { mutableStateOf(false) } + val onIcon = painterResource(id = R.drawable.flash_on) + val offIcon = painterResource(id = R.drawable.flash_off) + + Button( + onClick = { + toggleFlashlight(isFlashlightOn) + isFlashlightOn = !isFlashlightOn + }, + colors = ButtonDefaults.buttonColors( + containerColor = if (isFlashlightOn) Color.White else Color.Black + ), + shape = CircleShape + ) { + val icon = if (isFlashlightOn) onIcon else offIcon + Image( + painter = icon, + contentDescription = null + ) + } + } + private fun hasCameraPermission(context: Context): Boolean { return ContextCompat.checkSelfPermission( context, From d5056155baa300a88b1da04eeba9ce325a5bcc67 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 18:33:07 +0000 Subject: [PATCH 096/174] chore: raise lib version to 0.0.17 References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7942fc2..387af7f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.16 + 0.0.17 From 9ef7d46f8c8550f2d7c9afe25fbfdfff262eb59f Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 19:00:20 +0000 Subject: [PATCH 097/174] chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21b75e5..3ad77c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 14-11-2023 +Android - Implement Torch Button to settings (https://outsystemsrd.atlassian.net/browse/RMET-2759) + ### 13-11-2023 Android - Implement AlertDialog to settings (https://outsystemsrd.atlassian.net/browse/RMET-2764) From 41f141bc009d796cfc77131727b7b42971803af4 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 19:17:17 +0000 Subject: [PATCH 098/174] test: use theme to remove action bar from activity References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- src/main/AndroidManifest.xml | 3 ++- src/main/res/values/styles.xml | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/main/res/values/styles.xml diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 46c545d..5220d30 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + android:exported="false" + android:theme="@style/AppTheme.NoActionBar"/> \ No newline at end of file diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml new file mode 100644 index 0000000..ce8f742 --- /dev/null +++ b/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file From b39c2a152cd8d6d0a37480f62fc4e5853a210cd0 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 19:17:37 +0000 Subject: [PATCH 099/174] chore: raise lib version to 0.0.18 References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 387af7f..f7c004d 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.17 + 0.0.18 From 195ce5ff237e7f5e25db30055a989a5448fae5f6 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 19:31:23 +0000 Subject: [PATCH 100/174] Revert "test: use theme to remove action bar from activity" This reverts commit 41f141bc009d796cfc77131727b7b42971803af4. --- src/main/AndroidManifest.xml | 3 +-- src/main/res/values/styles.xml | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 src/main/res/values/styles.xml diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 5220d30..46c545d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -5,7 +5,6 @@ + android:exported="false" /> \ No newline at end of file diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml deleted file mode 100644 index ce8f742..0000000 --- a/src/main/res/values/styles.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file From e7718a4814f10e39e326775da3c7d6390efa5377 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 19:34:25 +0000 Subject: [PATCH 101/174] fix: hide action bar References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index b7101e8..3d9421c 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -73,6 +73,8 @@ class OSBARCScannerActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + actionBar?.hide() + val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters selector = CameraSelector.Builder() .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) From c1283372985750129f7793499f711213e8d1e3d6 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 19:34:44 +0000 Subject: [PATCH 102/174] chore: raise lib version to 0.0.19 References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f7c004d..5577903 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.18 + 0.0.19 From 8394489dbea08b349402e5c0f0a674be69a8cbaa Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 20:08:20 +0000 Subject: [PATCH 103/174] Revert "fix: hide action bar" This reverts commit e7718a4814f10e39e326775da3c7d6390efa5377. --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 3d9421c..b7101e8 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -73,8 +73,6 @@ class OSBARCScannerActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - actionBar?.hide() - val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters selector = CameraSelector.Builder() .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) From d0b8c3702cdc21f6e9079088590243701f57624c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 20:26:49 +0000 Subject: [PATCH 104/174] feat: hide action bar (title bar) and fill the whole screen with Camera stream References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index b7101e8..41714cc 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -72,6 +72,7 @@ class OSBARCScannerActivity : ComponentActivity() { */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + actionBar?.hide() val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters selector = CameraSelector.Builder() @@ -198,7 +199,8 @@ class OSBARCScannerActivity : ComponentActivity() { finish() } previewView - } + }, + modifier = Modifier.fillMaxSize() ) if (camera.cameraInfo.hasFlashUnit()) { From b4a9247048ec38768a8c6ce8a76b0ff8d4c65f4d Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 20:27:08 +0000 Subject: [PATCH 105/174] chore: raise lib version to 0.0.20 References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5577903..e0e1c63 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.19 + 0.0.20 From 47bb7afeb0018197c3bc33d7ed30722236186de9 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 14 Nov 2023 20:33:53 +0000 Subject: [PATCH 106/174] misc: add extra line References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 41714cc..5a078b4 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -206,6 +206,7 @@ class OSBARCScannerActivity : ComponentActivity() { if (camera.cameraInfo.hasFlashUnit()) { TorchButton() } + } } From 8914520db4f16064009fc91bdb217bfce257846c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 15 Nov 2023 08:18:07 +0000 Subject: [PATCH 107/174] refactor: remove extra lines References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 5a078b4..608b4b8 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -206,9 +206,7 @@ class OSBARCScannerActivity : ComponentActivity() { if (camera.cameraInfo.hasFlashUnit()) { TorchButton() } - } - } @Composable From 5ae992cbec9448c0a02ddfe876d18ab8bfa9fe4d Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 15 Nov 2023 09:32:57 +0000 Subject: [PATCH 108/174] refactor: only use one catch Context: Since both catches do the exact same thing, we might as well only catch "Exception", that will include "CameraAccessException". References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 608b4b8..70bfaae 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -243,8 +243,6 @@ class OSBARCScannerActivity : ComponentActivity() { private fun toggleFlashlight(isFlashlightOn: Boolean) { try { camera.cameraControl.enableTorch(!isFlashlightOn) - } catch (e: CameraAccessException) { - e.message?.let { Log.e(LOG_TAG, it) } } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } } From 4d5fc6a28f8430d62acdbb38aa732f941a3ec92d Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 15 Nov 2023 09:38:49 +0000 Subject: [PATCH 109/174] refactor: remove unused import References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 70bfaae..841ffe8 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -4,7 +4,6 @@ import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.hardware.camera2.CameraAccessException import android.net.Uri import android.os.Bundle import android.provider.Settings From 38251b3e2a4f254eebdab517b2bb01438af95f3a Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 15 Nov 2023 09:46:56 +0000 Subject: [PATCH 110/174] fix: only set isFlashlightOn if enableTorch goes OK Context: If "enableTorch" throws an exception, we don't want to alter the value of "isFlashlightOn" because we failed to alter the flashlight's state. References: https://outsystemsrd.atlassian.net/browse/RMET-2759 --- .../barcode/view/OSBARCScannerActivity.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 841ffe8..7dc815c 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -216,8 +216,12 @@ class OSBARCScannerActivity : ComponentActivity() { Button( onClick = { - toggleFlashlight(isFlashlightOn) - isFlashlightOn = !isFlashlightOn + try { + camera.cameraControl.enableTorch(!isFlashlightOn) + isFlashlightOn = !isFlashlightOn + } catch (e: Exception) { + e.message?.let { Log.e(LOG_TAG, it) } + } }, colors = ButtonDefaults.buttonColors( containerColor = if (isFlashlightOn) Color.White else Color.Black @@ -239,12 +243,4 @@ class OSBARCScannerActivity : ComponentActivity() { ) == PackageManager.PERMISSION_GRANTED } - private fun toggleFlashlight(isFlashlightOn: Boolean) { - try { - camera.cameraControl.enableTorch(!isFlashlightOn) - } catch (e: Exception) { - e.message?.let { Log.e(LOG_TAG, it) } - } - } - } \ No newline at end of file From dda6fbb5dd3bf0b84bdac531168906f7f729318a Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 15 Nov 2023 10:29:42 +0000 Subject: [PATCH 111/174] feat: show scan instructions if they exist Context: If there are scan instructions in the scan parameters, we present them in a Text composable (https://developer.android.com/jetpack/compose/text). References: https://outsystemsrd.atlassian.net/browse/RMET-2761 --- pom.xml | 2 +- .../plugins/barcode/view/OSBARCScannerActivity.kt | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0e1c63..a1e5a64 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.20 + 0.0.21 diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 7dc815c..dc96f01 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -25,12 +25,14 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -205,6 +207,14 @@ class OSBARCScannerActivity : ComponentActivity() { if (camera.cameraInfo.hasFlashUnit()) { TorchButton() } + + if (!parameters.scanInstructions.isNullOrEmpty()) { + Text( + text = parameters.scanInstructions, + modifier = Modifier.align(Alignment.Center), + color = Color.White + ) + } } } From cdfc854f59217a218f9388a28c3ead517f8efbe4 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 15 Nov 2023 10:52:01 +0000 Subject: [PATCH 112/174] chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2761 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad77c6..e42cbbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 14-11-2023 +Android - Implement Scan Instructions (https://outsystemsrd.atlassian.net/browse/RMET-2761) + ### 14-11-2023 Android - Implement Torch Button to settings (https://outsystemsrd.atlassian.net/browse/RMET-2759) From b983a4efb45f131047b967da8bb55b761ff9b313 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 15 Nov 2023 12:59:06 +0000 Subject: [PATCH 113/174] fix: align text to the center Context: This way, when the text has to be presented in more than 1 line, it is centered. References: https://outsystemsrd.atlassian.net/browse/RMET-2761 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index dc96f01..1c6b828 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.R @@ -212,7 +213,8 @@ class OSBARCScannerActivity : ComponentActivity() { Text( text = parameters.scanInstructions, modifier = Modifier.align(Alignment.Center), - color = Color.White + color = Color.White, + textAlign = TextAlign.Center ) } } From cc12910a91e139ac2843815331a1a7b2096f899c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 15 Nov 2023 13:02:32 +0000 Subject: [PATCH 114/174] chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2761 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a1e5a64..f984167 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.21 + 0.0.22 From c0e7bf9f84a6445ceb0149a0876ad6e3d9917625 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 15 Nov 2023 13:41:30 +0000 Subject: [PATCH 115/174] feat: add scan button and close button References: https://outsystemsrd.atlassian.net/browse/RMET-2762 --- .../barcode/model/OSBARCScanParameters.kt | 4 +- .../barcode/view/OSBARCScannerActivity.kt | 47 +++++++++++++++++++ .../plugins/barcode/view/ui.theme/Color.kt | 4 +- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt index ed0244d..3219f0b 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt @@ -9,8 +9,8 @@ data class OSBARCScanParameters( val scanInstructions: String?, val cameraDirection: Int?, val scanOrientation: Int?, - val scanButton: Boolean?, - val scanText: String?, + val scanButton: Boolean, + val scanText: String, val hint: Int?, val androidScanningLibrary: String? ) : Serializable \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 1c6b828..13a9d93 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -23,8 +23,11 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect @@ -35,6 +38,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource @@ -49,6 +53,7 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme +import com.outsystems.plugins.barcode.view.ui.theme.CustomGray /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -205,10 +210,30 @@ class OSBARCScannerActivity : ComponentActivity() { modifier = Modifier.fillMaxSize() ) + // close button + Button( + onClick = { + //setResult(OSBARCError.SCAN_CANCELLED_ERROR.code) + //finish() + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent + ), + modifier = Modifier.align(Alignment.TopEnd) + ) { + Icon( + imageVector = Icons.Default.Clear, + contentDescription = null, + tint = CustomGray + ) + } + + // flashlight button if (camera.cameraInfo.hasFlashUnit()) { TorchButton() } + // text with scan instructions if (!parameters.scanInstructions.isNullOrEmpty()) { Text( text = parameters.scanInstructions, @@ -217,6 +242,28 @@ class OSBARCScannerActivity : ComponentActivity() { textAlign = TextAlign.Center ) } + + // scan button to turn on scanning when used + if (parameters.scanButton) { + Button( + onClick = { + // turn on scanning, which should be disabled + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.DarkGray + ), + shape = RectangleShape, + modifier = Modifier.align(Alignment.BottomCenter) + ) { + Text( + text = parameters.scanText, + color = Color.White, + textAlign = TextAlign.Center + + ) + } + } + } } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt index 12c1a50..546ed22 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt @@ -8,4 +8,6 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val Pink40 = Color(0xFF7D5260) + +val CustomGray = Color(0xFFB3BAC4) \ No newline at end of file From c72512432f8446975c89529b6e18204f555c492c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 16 Nov 2023 09:50:13 +0000 Subject: [PATCH 116/174] feat: finish activity with SCAN_CANCELLED_ERROR on close button click References: https://outsystemsrd.atlassian.net/browse/RMET-2761 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 13a9d93..457eeda 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -213,8 +213,8 @@ class OSBARCScannerActivity : ComponentActivity() { // close button Button( onClick = { - //setResult(OSBARCError.SCAN_CANCELLED_ERROR.code) - //finish() + setResult(OSBARCError.SCAN_CANCELLED_ERROR.code) + finish() }, colors = ButtonDefaults.buttonColors( containerColor = Color.Transparent From b9245ebbb1d1bcf31a82011f7e32ef13193a645f Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 16 Nov 2023 10:08:56 +0000 Subject: [PATCH 117/174] feat: only send barcode result when scanning Context: When there's a scan button, we only want to start scanning when the button in clicked. For now, this behaviour is "faked" by us only sending the barcode result when the button in clicked. References: https://outsystemsrd.atlassian.net/browse/RMET-2762 --- .../barcode/view/OSBARCScannerActivity.kt | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 457eeda..e2ce0ff 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -45,7 +45,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat -import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper @@ -53,6 +52,7 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme +import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.view.ui.theme.CustomGray /** @@ -65,6 +65,7 @@ class OSBARCScannerActivity : ComponentActivity() { private lateinit var selector: CameraSelector private var permissionRequestCount = 0 private var showDialog by mutableStateOf(false) + private var scanning = true companion object { private const val SCAN_SUCCESS_RESULT_CODE = -1 @@ -82,6 +83,9 @@ class OSBARCScannerActivity : ComponentActivity() { actionBar?.hide() val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters + + scanning = !parameters.scanButton + selector = CameraSelector.Builder() .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) .build() @@ -182,14 +186,18 @@ class OSBARCScannerActivity : ComponentActivity() { OSBARCMLKitHelper() ), { result -> - val resultIntent = Intent() - resultIntent.putExtra(SCAN_RESULT, result) - setResult(SCAN_SUCCESS_RESULT_CODE, resultIntent) - finish() + if (scanning) { + val resultIntent = Intent() + resultIntent.putExtra(SCAN_RESULT, result) + setResult(SCAN_SUCCESS_RESULT_CODE, resultIntent) + finish() + } }, { - setResult(it.code) - finish() + if (scanning) { + setResult(it.code) + finish() + } } ) ) @@ -247,7 +255,7 @@ class OSBARCScannerActivity : ComponentActivity() { if (parameters.scanButton) { Button( onClick = { - // turn on scanning, which should be disabled + scanning = true }, colors = ButtonDefaults.buttonColors( containerColor = Color.DarkGray From 64d8bb0fc255685c82c76ceb3180ff03d62c84cb Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 16 Nov 2023 11:41:45 +0000 Subject: [PATCH 118/174] refactor: add comments for context References: https://outsystemsrd.atlassian.net/browse/RMET-2762 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index e2ce0ff..4e197ef 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -186,6 +186,7 @@ class OSBARCScannerActivity : ComponentActivity() { OSBARCMLKitHelper() ), { result -> + // we only want to process the scan result if scanning is active if (scanning) { val resultIntent = Intent() resultIntent.putExtra(SCAN_RESULT, result) @@ -194,6 +195,7 @@ class OSBARCScannerActivity : ComponentActivity() { } }, { + // we only want to process the scan result if scanning is active if (scanning) { setResult(it.code) finish() From 4c74aa9eb958ff58d526274750e9279383bc2709 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 16 Nov 2023 12:02:12 +0000 Subject: [PATCH 119/174] refactor: remove extra line References: https://outsystemsrd.atlassian.net/browse/RMET-2762 --- .../com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 4e197ef..c48e7b7 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -269,7 +269,6 @@ class OSBARCScannerActivity : ComponentActivity() { text = parameters.scanText, color = Color.White, textAlign = TextAlign.Center - ) } } From 1537623818a1c1946dd885275a8c789826ed87cd Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 16 Nov 2023 12:05:56 +0000 Subject: [PATCH 120/174] chore: udpate changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2762 --- CHANGELOG.md | 5 ++++- pom.xml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e42cbbc..2bf8243 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,10 @@ The changes documented here do not include those from the original repository. ## [Unreleased] -### 14-11-2023 +### 16-11-2023 +Android - Implement Scan Button (https://outsystemsrd.atlassian.net/browse/RMET-2762) + +### 15-11-2023 Android - Implement Scan Instructions (https://outsystemsrd.atlassian.net/browse/RMET-2761) ### 14-11-2023 diff --git a/pom.xml b/pom.xml index f984167..121f322 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.22 + 0.0.23 From ccc1ea3603447b7a3e2db49619c74c2f0d8e7d8b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 16 Nov 2023 12:09:38 +0000 Subject: [PATCH 121/174] refactor: remove extra lines References: https://outsystemsrd.atlassian.net/browse/RMET-2762 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index c48e7b7..17c1a1d 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -83,9 +83,7 @@ class OSBARCScannerActivity : ComponentActivity() { actionBar?.hide() val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters - scanning = !parameters.scanButton - selector = CameraSelector.Builder() .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) .build() From ecc9feaed1a152809cfa351ee215b4e0f969bea1 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 17 Nov 2023 09:28:08 +0000 Subject: [PATCH 122/174] refactor: use helper methods to process scan result References: https://outsystemsrd.atlassian.net/browse/RMET-2762 --- .../barcode/view/OSBARCScannerActivity.kt | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 17c1a1d..5c9487f 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -52,8 +52,8 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme -import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.view.ui.theme.CustomGray +import com.outsystems.plugins.barcode.R /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -184,20 +184,10 @@ class OSBARCScannerActivity : ComponentActivity() { OSBARCMLKitHelper() ), { result -> - // we only want to process the scan result if scanning is active - if (scanning) { - val resultIntent = Intent() - resultIntent.putExtra(SCAN_RESULT, result) - setResult(SCAN_SUCCESS_RESULT_CODE, resultIntent) - finish() - } + processReadSuccess(result) }, { - // we only want to process the scan result if scanning is active - if (scanning) { - setResult(it.code) - finish() - } + processReadError(it) } ) ) @@ -309,4 +299,22 @@ class OSBARCScannerActivity : ComponentActivity() { ) == PackageManager.PERMISSION_GRANTED } + private fun processReadSuccess(result: String) { + // we only want to process the scan result if scanning is active + if (scanning) { + val resultIntent = Intent() + resultIntent.putExtra(SCAN_RESULT, result) + setResult(SCAN_SUCCESS_RESULT_CODE, resultIntent) + finish() + } + } + + private fun processReadError(error: OSBARCError) { + // we only want to process the scan result if scanning is active + if (scanning) { + setResult(error.code) + finish() + } + } + } \ No newline at end of file From 22c1b22ceb47ef0ef0acd8aad3716255e8e63938 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 17 Nov 2023 09:58:53 +0000 Subject: [PATCH 123/174] refactor: handle all conditions to show dialog in the dialog component Context: This refactor was necessary to reduce the cognitive complexity of the ScanScreen method, as pointed out by SonarCloud. References: https://outsystemsrd.atlassian.net/browse/RMET-2762 --- .../view/OSBARCCameraPermissionDialog.kt | 3 +- .../barcode/view/OSBARCScannerActivity.kt | 39 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt index 4e31b4a..b18aee6 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCCameraPermissionDialog.kt @@ -10,13 +10,14 @@ import androidx.compose.ui.unit.sp fun CameraPermissionRequiredDialog( onDismissRequest: () -> Unit, onConfirmation: () -> Unit, + permissionGiven: Boolean, shouldShowDialog: Boolean, dialogTitle: String, dialogText: String, confirmButtonText: String, dismissButtonText: String ) { - if (shouldShowDialog) { + if (!permissionGiven && shouldShowDialog) { AlertDialog( title = { Text( diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 5c9487f..babd712 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -129,26 +129,25 @@ class OSBARCScannerActivity : ComponentActivity() { } } - if (!permissionGiven) { - CameraPermissionRequiredDialog( - onDismissRequest = { - this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) - this.finish() - }, - onConfirmation = { - val intent = Intent().apply { - action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS - data = Uri.fromParts("package", context.packageName, null) - } - context.startActivity(intent) - }, - shouldShowDialog = showDialog, - dialogTitle = "Camera Access Not Enabled", - dialogText = "To continue, please go to the Settings app and enable it.", - confirmButtonText = "Settings", - dismissButtonText = "Ok" - ) - } + CameraPermissionRequiredDialog( + onDismissRequest = { + this.setResult(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code) + this.finish() + }, + onConfirmation = { + val intent = Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.fromParts("package", context.packageName, null) + } + context.startActivity(intent) + }, + permissionGiven = permissionGiven, + shouldShowDialog = showDialog, + dialogTitle = "Camera Access Not Enabled", + dialogText = "To continue, please go to the Settings app and enable it.", + confirmButtonText = "Settings", + dismissButtonText = "Ok" + ) // rest of the UI val cameraProviderFuture = remember { From 5b073c7667562e935773c68d71f455faed62e96f Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 17 Nov 2023 12:00:41 +0000 Subject: [PATCH 124/174] refactor: use correct term for comment References: https://outsystemsrd.atlassian.net/browse/RMET-2762 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index babd712..d6eb15d 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -309,7 +309,7 @@ class OSBARCScannerActivity : ComponentActivity() { } private fun processReadError(error: OSBARCError) { - // we only want to process the scan result if scanning is active + // we only want to process the scan error if scanning is active if (scanning) { setResult(error.code) finish() From 7f555b804580641c25a4a1361714334de4492ebc Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 17 Nov 2023 15:35:21 +0000 Subject: [PATCH 125/174] feat: lock orientation if portrait or landscape Context: When "parameters.scanOrientation" is "ORIENTATION_PORTRAIT" or "ORIENTATION_LANDSCAPE", it should be locked to the corresponding orientation. The activity is already adaptive by default, so we don't need to worry about that case. References: https://outsystemsrd.atlassian.net/browse/RMET-2763 --- .../plugins/barcode/view/OSBARCScannerActivity.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index d6eb15d..873c0d7 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -3,6 +3,7 @@ package com.outsystems.plugins.barcode.view import android.Manifest import android.content.Context import android.content.Intent +import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle @@ -73,6 +74,8 @@ class OSBARCScannerActivity : ComponentActivity() { private const val LOG_TAG = "OSBARCScannerActivity" private const val SCAN_PARAMETERS = "SCAN_PARAMETERS" private const val CAM_DIRECTION_FRONT = 2 + private const val ORIENTATION_PORTRAIT = 1 + private const val ORIENTATION_LANDSCAPE = 2 } /** @@ -81,8 +84,15 @@ class OSBARCScannerActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) actionBar?.hide() - val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters + + // possibly lock orientation, the screen is adaptive by default + if (parameters.scanOrientation == ORIENTATION_PORTRAIT) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } else if (parameters.scanOrientation == ORIENTATION_LANDSCAPE) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } + scanning = !parameters.scanButton selector = CameraSelector.Builder() .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) From 733f2f8d7475770b7cd1808f9f95a082ddbefa04 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 17 Nov 2023 15:37:37 +0000 Subject: [PATCH 126/174] chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2763 --- CHANGELOG.md | 3 +++ pom.xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bf8243..9fa5604 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 17-11-2023 +Android - Implement Scan Orientation (https://outsystemsrd.atlassian.net/browse/RMET-2763) + ### 16-11-2023 Android - Implement Scan Button (https://outsystemsrd.atlassian.net/browse/RMET-2762) diff --git a/pom.xml b/pom.xml index 121f322..9eef316 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.23 + 0.0.24 From f81f970659a4e5fb1d2fb055feb27ca8ee68d553 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 17 Nov 2023 16:26:53 +0000 Subject: [PATCH 127/174] chore: update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fa5604..3da9b0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The changes documented here do not include those from the original repository. ## [Unreleased] ### 17-11-2023 -Android - Implement Scan Orientation (https://outsystemsrd.atlassian.net/browse/RMET-2763) +Android - Implement Scan Orientation (Adaptive, Portrait, or Landscape) (https://outsystemsrd.atlassian.net/browse/RMET-2763) ### 16-11-2023 Android - Implement Scan Button (https://outsystemsrd.atlassian.net/browse/RMET-2762) From 46327e6e111ca2f92623885af14fa75cc61dd8cd Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 17 Nov 2023 16:47:12 +0000 Subject: [PATCH 128/174] chore: refactor changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2763 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3da9b0d..7d8725d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The changes documented here do not include those from the original repository. ## [Unreleased] ### 17-11-2023 -Android - Implement Scan Orientation (Adaptive, Portrait, or Landscape) (https://outsystemsrd.atlassian.net/browse/RMET-2763) +Android - Implement Scan Orientation (Portrait, Landscape, or Adaptive) (https://outsystemsrd.atlassian.net/browse/RMET-2763) ### 16-11-2023 Android - Implement Scan Button (https://outsystemsrd.atlassian.net/browse/RMET-2762) From 970a629da73611f557a3be3b1022405fd7ddecaf Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Sun, 19 Nov 2023 16:47:47 +0000 Subject: [PATCH 129/174] My Signed Commit Signed-off-by: Alexandre Jacinto From 68c06fad5b08dfb9261f79bf98aa9b83a1b0f39c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Sun, 19 Nov 2023 16:52:34 +0000 Subject: [PATCH 130/174] test: testing another signed commit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d8725d..7bad48c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The changes documented here do not include those from the original repository. ## [Unreleased] ### 17-11-2023 -Android - Implement Scan Orientation (Portrait, Landscape, or Adaptive) (https://outsystemsrd.atlassian.net/browse/RMET-2763) +Android - Implement Scan Orientation (Portrait, Landscape, Adaptive) (https://outsystemsrd.atlassian.net/browse/RMET-2763) ### 16-11-2023 Android - Implement Scan Button (https://outsystemsrd.atlassian.net/browse/RMET-2762) From b483b11db456c4a9bd0d6bec43c4921869e781b2 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 20 Nov 2023 14:12:15 +0000 Subject: [PATCH 131/174] refactor: use helper functions for composables Context: We should use a @Composable function for each element on the screen. References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../barcode/view/OSBARCScannerActivity.kt | 108 +++++++++++------- .../plugins/barcode/view/ui.theme/Color.kt | 5 +- src/main/res/drawable/flash_off.xml | 27 +++-- src/main/res/drawable/flash_on.xml | 23 ++-- 4 files changed, 103 insertions(+), 60 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 873c0d7..e90c044 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -20,10 +20,12 @@ import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.Button @@ -39,11 +41,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer @@ -54,7 +56,8 @@ import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import com.outsystems.plugins.barcode.view.ui.theme.CustomGray -import com.outsystems.plugins.barcode.R +import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackground +import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBorder /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -218,63 +221,48 @@ class OSBARCScannerActivity : ComponentActivity() { ) // close button - Button( - onClick = { - setResult(OSBARCError.SCAN_CANCELLED_ERROR.code) - finish() - }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ), - modifier = Modifier.align(Alignment.TopEnd) - ) { - Icon( - imageVector = Icons.Default.Clear, - contentDescription = null, - tint = CustomGray - ) - } + CloseButton(modifier = Modifier.align(Alignment.TopEnd)) // flashlight button if (camera.cameraInfo.hasFlashUnit()) { - TorchButton() + TorchButton(modifier = Modifier.align(Alignment.BottomEnd)) } // text with scan instructions if (!parameters.scanInstructions.isNullOrEmpty()) { - Text( - text = parameters.scanInstructions, - modifier = Modifier.align(Alignment.Center), - color = Color.White, - textAlign = TextAlign.Center - ) + ScanInstructions(modifier = Modifier.align(Alignment.Center), scanInstructions = parameters.scanInstructions) } // scan button to turn on scanning when used if (parameters.scanButton) { - Button( - onClick = { - scanning = true - }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.DarkGray - ), - shape = RectangleShape, - modifier = Modifier.align(Alignment.BottomCenter) - ) { - Text( - text = parameters.scanText, - color = Color.White, - textAlign = TextAlign.Center - ) - } + ScanButton(modifier = Modifier.align(Alignment.BottomCenter), scanButtonText = parameters.scanText) } } } @Composable - fun TorchButton() { + fun CloseButton(modifier: Modifier) { + Button( + onClick = { + setResult(OSBARCError.SCAN_CANCELLED_ERROR.code) + finish() + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent + ), + modifier = modifier + ) { + Icon( + imageVector = Icons.Default.Clear, + contentDescription = null, + tint = CustomGray + ) + } + } + + @Composable + fun TorchButton(modifier: Modifier) { var isFlashlightOn by remember { mutableStateOf(false) } val onIcon = painterResource(id = R.drawable.flash_on) val offIcon = painterResource(id = R.drawable.flash_off) @@ -289,9 +277,10 @@ class OSBARCScannerActivity : ComponentActivity() { } }, colors = ButtonDefaults.buttonColors( - containerColor = if (isFlashlightOn) Color.White else Color.Black + containerColor = Color.Transparent ), - shape = CircleShape + shape = CircleShape, + modifier = modifier ) { val icon = if (isFlashlightOn) onIcon else offIcon Image( @@ -301,6 +290,37 @@ class OSBARCScannerActivity : ComponentActivity() { } } + @Composable + fun ScanInstructions(modifier: Modifier, scanInstructions: String) { + Text( + text = scanInstructions, + modifier = modifier, + color = Color.White, + textAlign = TextAlign.Center + ) + } + + @Composable + fun ScanButton(modifier: Modifier, scanButtonText: String) { + Button( + onClick = { + scanning = true + }, + colors = ButtonDefaults.buttonColors( + containerColor = ButtonsBackground + ), + shape = RoundedCornerShape(4.dp), + border = BorderStroke(width = 1.dp, color = ButtonsBorder), + modifier = modifier + ) { + Text( + text = scanButtonText, + color = Color.White, + textAlign = TextAlign.Center + ) + } + } + private fun hasCameraPermission(context: Context): Boolean { return ContextCompat.checkSelfPermission( context, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt index 546ed22..0bdfdaa 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt @@ -10,4 +10,7 @@ val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) val Pink40 = Color(0xFF7D5260) -val CustomGray = Color(0xFFB3BAC4) \ No newline at end of file +val CustomGray = Color(0xFFB3BAC4) + +val ButtonsBackground = Color(0x1AFFFFFF) +val ButtonsBorder = Color(0xFF4F575E) \ No newline at end of file diff --git a/src/main/res/drawable/flash_off.xml b/src/main/res/drawable/flash_off.xml index d6342f5..645e9ae 100644 --- a/src/main/res/drawable/flash_off.xml +++ b/src/main/res/drawable/flash_off.xml @@ -1,10 +1,23 @@ + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + + + + + android:pathData="M24,0.726L24,0.726A23.274,23.274 0,0 1,47.274 24L47.274,24A23.274,23.274 0,0 1,24 47.274L24,47.274A23.274,23.274 0,0 1,0.726 24L0.726,24A23.274,23.274 0,0 1,24 0.726z" + android:strokeWidth="1.45156" + android:fillColor="#00000000" + android:strokeColor="#4F575E"/> diff --git a/src/main/res/drawable/flash_on.xml b/src/main/res/drawable/flash_on.xml index 9ffd696..a38132f 100644 --- a/src/main/res/drawable/flash_on.xml +++ b/src/main/res/drawable/flash_on.xml @@ -1,10 +1,17 @@ - + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + + + + From 67dcfff319767e7a61e5886bddcb06aa32613d63 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 20 Nov 2023 17:28:14 +0000 Subject: [PATCH 132/174] feat: align scan and flashlight buttons Context: These two buttons should be aligned, with the scan button at the center, and the flashlight button at the right-most corner. This commit also includes some refactoring on the Close button. References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../barcode/view/OSBARCScannerActivity.kt | 42 ++++++++++++------- src/main/res/drawable/close.xml | 9 ++++ 2 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 src/main/res/drawable/close.xml diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index e90c044..426d151 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -24,10 +24,10 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon @@ -48,6 +48,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat +import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper @@ -55,9 +56,9 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme -import com.outsystems.plugins.barcode.view.ui.theme.CustomGray import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackground import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBorder +import com.outsystems.plugins.barcode.view.ui.theme.CustomGray /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -178,7 +179,9 @@ class OSBARCScannerActivity : ComponentActivity() { finish() } - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier.fillMaxSize() + ) { AndroidView( factory = { context -> val previewView = PreviewView(context) @@ -221,23 +224,32 @@ class OSBARCScannerActivity : ComponentActivity() { ) // close button - CloseButton(modifier = Modifier.align(Alignment.TopEnd)) - - // flashlight button - if (camera.cameraInfo.hasFlashUnit()) { - TorchButton(modifier = Modifier.align(Alignment.BottomEnd)) - } + CloseButton( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(top = 16.dp, end = 16.dp) + ) // text with scan instructions if (!parameters.scanInstructions.isNullOrEmpty()) { ScanInstructions(modifier = Modifier.align(Alignment.Center), scanInstructions = parameters.scanInstructions) } - // scan button to turn on scanning when used - if (parameters.scanButton) { - ScanButton(modifier = Modifier.align(Alignment.BottomCenter), scanButtonText = parameters.scanText) + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .padding(start = 16.dp, top = 32.dp, end = 16.dp, bottom = 32.dp) + ) { + // scan button to turn on scanning when used + if (parameters.scanButton) { + ScanButton(modifier = Modifier.align(Alignment.Center)/*.align(Alignment.BottomCenter)*/, scanButtonText = parameters.scanText) + } + // flashlight button + if (camera.cameraInfo.hasFlashUnit()) { + TorchButton(modifier = Modifier.align(Alignment.CenterEnd)/*.align(Alignment.BottomEnd)*/) + } } - } } @@ -254,7 +266,7 @@ class OSBARCScannerActivity : ComponentActivity() { modifier = modifier ) { Icon( - imageVector = Icons.Default.Clear, + painter = painterResource(id = R.drawable.close), contentDescription = null, tint = CustomGray ) diff --git a/src/main/res/drawable/close.xml b/src/main/res/drawable/close.xml new file mode 100644 index 0000000..92e1a8a --- /dev/null +++ b/src/main/res/drawable/close.xml @@ -0,0 +1,9 @@ + + + From afa889edc709c8b94fd9bb3a0137b8664d654ddf Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 20 Nov 2023 17:33:03 +0000 Subject: [PATCH 133/174] refactor: remove commented out code References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 426d151..6886611 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -243,11 +243,11 @@ class OSBARCScannerActivity : ComponentActivity() { ) { // scan button to turn on scanning when used if (parameters.scanButton) { - ScanButton(modifier = Modifier.align(Alignment.Center)/*.align(Alignment.BottomCenter)*/, scanButtonText = parameters.scanText) + ScanButton(modifier = Modifier.align(Alignment.Center), scanButtonText = parameters.scanText) } // flashlight button if (camera.cameraInfo.hasFlashUnit()) { - TorchButton(modifier = Modifier.align(Alignment.CenterEnd)/*.align(Alignment.BottomEnd)*/) + TorchButton(modifier = Modifier.align(Alignment.CenterEnd)) } } } From da509d040c64a170608f432bbd0cb17164bbe8a0 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 20 Nov 2023 18:08:28 +0000 Subject: [PATCH 134/174] feat: make scanning view full screen Context: The scanning view should be full screen, as in the Figma design. References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../plugins/barcode/view/OSBARCScannerActivity.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 6886611..317fc65 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -9,6 +9,7 @@ import android.net.Uri import android.os.Bundle import android.provider.Settings import android.util.Log +import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent @@ -48,6 +49,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat +import androidx.core.view.WindowCompat import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory @@ -87,7 +89,6 @@ class OSBARCScannerActivity : ComponentActivity() { */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - actionBar?.hide() val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters // possibly lock orientation, the screen is adaptive by default @@ -107,6 +108,8 @@ class OSBARCScannerActivity : ComponentActivity() { ScanScreen(parameters) } } + + makeViewFullscreen() } override fun onResume() { @@ -358,4 +361,12 @@ class OSBARCScannerActivity : ComponentActivity() { } } + private fun makeViewFullscreen() { + // hide the action bar + actionBar?.hide() + // set full screen + WindowCompat.setDecorFitsSystemWindows(window, false) + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + } + } \ No newline at end of file From 2c038ce3322a9908532379c802bb7467d6e2f9dc Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 21 Nov 2023 14:37:37 +0000 Subject: [PATCH 135/174] feat: first version of scan frame References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../barcode/view/OSBARCScannerActivity.kt | 111 +++++++++++++++++- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 317fc65..b9ffbd4 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -22,11 +22,22 @@ import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.view.PreviewView import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.paddingFromBaseline +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button @@ -41,10 +52,21 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.ClipOp import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextMeasurer +import androidx.compose.ui.text.drawText import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -183,7 +205,8 @@ class OSBARCScannerActivity : ComponentActivity() { } Box( - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize(), ) { AndroidView( factory = { context -> @@ -226,6 +249,87 @@ class OSBARCScannerActivity : ComponentActivity() { modifier = Modifier.fillMaxSize() ) + /* + val configuration = LocalConfiguration.current + val screenHeight = configuration.screenHeightDp.dp + val screenWidth = configuration.screenWidthDp.dp + + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color.Black.copy(alpha = 0.6f)), + verticalArrangement = Arrangement.Center + ) { + + // text with scan instructions + if (!parameters.scanInstructions.isNullOrEmpty()) { + ScanInstructions(modifier = Modifier + .align(Alignment.CenterHorizontally) + //.padding(bottom = 64.dp) + ,scanInstructions = parameters.scanInstructions) + } + + // create the semi-transparent effect + Canvas( + modifier = Modifier + //.fillMaxSize(), + .fillMaxWidth() + .height(screenHeight / 1.3f), + onDraw = { + val circlePath = Path().apply { + addRect( + Rect(center, size.width / 4) + ) + } + clipPath(circlePath, clipOp = ClipOp.Difference) { + drawRect(SolidColor(Color.Black.copy(alpha = 0.6f))) + } + }) + + } + + */ + + // create the semi-transparent effect + Canvas(modifier = Modifier.fillMaxSize(), onDraw = { + val circlePath = Path().apply { + addRect( + Rect(center, size.minDimension / 4) + ) + } + clipPath(circlePath, clipOp = ClipOp.Difference) { + drawRect(SolidColor(Color.Black.copy(alpha = 0.6f))) + } + }) + + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Center + ) { + + // text with scan instructions + if (!parameters.scanInstructions.isNullOrEmpty()) { + ScanInstructions(modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(bottom = 72.dp) + ,scanInstructions = parameters.scanInstructions) + } + + val configuration = LocalConfiguration.current + val screenHeight = configuration.screenHeightDp.dp + val screenWidth = configuration.screenWidthDp.dp + + Box( + modifier = Modifier + .size(screenWidth / 2) + .background(Color.Transparent) + .align(Alignment.CenterHorizontally) + ) { + + } + } + // close button CloseButton( modifier = Modifier @@ -233,11 +337,6 @@ class OSBARCScannerActivity : ComponentActivity() { .padding(top = 16.dp, end = 16.dp) ) - // text with scan instructions - if (!parameters.scanInstructions.isNullOrEmpty()) { - ScanInstructions(modifier = Modifier.align(Alignment.Center), scanInstructions = parameters.scanInstructions) - } - Box( modifier = Modifier .fillMaxWidth() From 5115efee56cfed1d2b6a6f381d28e8894b37f88d Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 21 Nov 2023 16:06:54 +0000 Subject: [PATCH 136/174] feat: properly implement semi-transparent effect References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../barcode/view/OSBARCScannerActivity.kt | 154 ++++++++---------- 1 file changed, 65 insertions(+), 89 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index b9ffbd4..63a0c72 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -27,17 +27,12 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.paddingFromBaseline -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button @@ -52,10 +47,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.ClipOp import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path @@ -65,8 +57,6 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.TextMeasurer -import androidx.compose.ui.text.drawText import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -249,109 +239,95 @@ class OSBARCScannerActivity : ComponentActivity() { modifier = Modifier.fillMaxSize() ) - /* val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp val screenWidth = configuration.screenWidthDp.dp Column( modifier = Modifier - .fillMaxWidth() - .background(Color.Black.copy(alpha = 0.6f)), + .fillMaxSize(), + //.background(Color.Black.copy(alpha = 0.6f)), verticalArrangement = Arrangement.Center ) { - // text with scan instructions - if (!parameters.scanInstructions.isNullOrEmpty()) { - ScanInstructions(modifier = Modifier - .align(Alignment.CenterHorizontally) - //.padding(bottom = 64.dp) - ,scanInstructions = parameters.scanInstructions) + Row( + modifier = Modifier + .background(Color.Black.copy(alpha = 0.6f)) + .align(Alignment.End) + .fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + // close button + CloseButton( + modifier = Modifier + .padding(top = 16.dp, end = 16.dp) + ) } - // create the semi-transparent effect - Canvas( + Box( modifier = Modifier - //.fillMaxSize(), + .background(Color.Black.copy(alpha = 0.6f)) + .height(screenHeight / 8) .fillMaxWidth() - .height(screenHeight / 1.3f), - onDraw = { - val circlePath = Path().apply { - addRect( - Rect(center, size.width / 4) - ) - } - clipPath(circlePath, clipOp = ClipOp.Difference) { - drawRect(SolidColor(Color.Black.copy(alpha = 0.6f))) - } - }) - - } + ) - */ + Column( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.Center + ) { + // text with scan instructions + if (!parameters.scanInstructions.isNullOrEmpty()) { + ScanInstructions(modifier = Modifier + .align(Alignment.CenterHorizontally) + .background(Color.Black.copy(alpha = 0.6f)) + .padding(bottom = 32.dp) + ,scanInstructions = parameters.scanInstructions) + } - // create the semi-transparent effect - Canvas(modifier = Modifier.fillMaxSize(), onDraw = { - val circlePath = Path().apply { - addRect( - Rect(center, size.minDimension / 4) + // draw the rectangle for the frame + Canvas( + modifier = Modifier + .fillMaxWidth() + .height(screenHeight / 4), + onDraw = { + val circlePath = Path().apply { + addRect( + Rect(center, size.width / 4) + ) + } + clipPath(circlePath, clipOp = ClipOp.Difference) { + drawRect(SolidColor(Color.Black.copy(alpha = 0.6f))) + } + } ) } - clipPath(circlePath, clipOp = ClipOp.Difference) { - drawRect(SolidColor(Color.Black.copy(alpha = 0.6f))) - } - }) - Column( - modifier = Modifier - .fillMaxSize(), - verticalArrangement = Arrangement.Center - ) { - - // text with scan instructions - if (!parameters.scanInstructions.isNullOrEmpty()) { - ScanInstructions(modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(bottom = 72.dp) - ,scanInstructions = parameters.scanInstructions) - } - - val configuration = LocalConfiguration.current - val screenHeight = configuration.screenHeightDp.dp - val screenWidth = configuration.screenWidthDp.dp + Box( + modifier = Modifier + .background(Color.Black.copy(alpha = 0.6f)) + .height(screenHeight / 8) + .fillMaxWidth() + ) Box( modifier = Modifier - .size(screenWidth / 2) - .background(Color.Transparent) - .align(Alignment.CenterHorizontally) + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.6f)) + .padding(start = 32.dp, top = 32.dp, end = 32.dp, bottom = 32.dp) ) { - + // scan button to turn on scanning when used + if (parameters.scanButton) { + ScanButton(modifier = Modifier.align(Alignment.Center), scanButtonText = parameters.scanText) + } + // flashlight button + if (camera.cameraInfo.hasFlashUnit()) { + TorchButton(modifier = Modifier.align(Alignment.CenterEnd)) + } } - } - // close button - CloseButton( - modifier = Modifier - .align(Alignment.TopEnd) - .padding(top = 16.dp, end = 16.dp) - ) - - Box( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter) - .padding(start = 16.dp, top = 32.dp, end = 16.dp, bottom = 32.dp) - ) { - // scan button to turn on scanning when used - if (parameters.scanButton) { - ScanButton(modifier = Modifier.align(Alignment.Center), scanButtonText = parameters.scanText) - } - // flashlight button - if (camera.cameraInfo.hasFlashUnit()) { - TorchButton(modifier = Modifier.align(Alignment.CenterEnd)) - } } + } } From d3753c2dfef1842f8b32b348b83131f66f51f172 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 22 Nov 2023 18:44:53 +0000 Subject: [PATCH 137/174] feat: define frame and frame edges size and position References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../barcode/view/OSBARCScannerActivity.kt | 108 ++++++++++++++---- 1 file changed, 87 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 63a0c72..02bafa2 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -47,7 +47,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.ClipOp import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path @@ -62,7 +64,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat -import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper @@ -73,6 +74,7 @@ import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackground import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBorder import com.outsystems.plugins.barcode.view.ui.theme.CustomGray +import com.outsystemsenterprise.enmobile11dev.BarcodeSampleAppNew.R /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -242,11 +244,11 @@ class OSBARCScannerActivity : ComponentActivity() { val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp val screenWidth = configuration.screenWidthDp.dp + val borderPadding = 32.dp Column( modifier = Modifier .fillMaxSize(), - //.background(Color.Black.copy(alpha = 0.6f)), verticalArrangement = Arrangement.Center ) { @@ -260,17 +262,10 @@ class OSBARCScannerActivity : ComponentActivity() { // close button CloseButton( modifier = Modifier - .padding(top = 16.dp, end = 16.dp) + .padding(top = 32.dp, end = 32.dp) ) } - Box( - modifier = Modifier - .background(Color.Black.copy(alpha = 0.6f)) - .height(screenHeight / 8) - .fillMaxWidth() - ) - Column( modifier = Modifier .fillMaxWidth(), @@ -281,34 +276,105 @@ class OSBARCScannerActivity : ComponentActivity() { ScanInstructions(modifier = Modifier .align(Alignment.CenterHorizontally) .background(Color.Black.copy(alpha = 0.6f)) - .padding(bottom = 32.dp) + .padding(top = 32.dp, bottom = 32.dp) + .fillMaxWidth() ,scanInstructions = parameters.scanInstructions) } - // draw the rectangle for the frame Canvas( modifier = Modifier .fillMaxWidth() - .height(screenHeight / 4), + //.height((screenHeight / 3) + 32.dp), + .height(screenWidth), onDraw = { + + // padding from the rectangle to each corner + val rectToCornerPadding = 16.dp + + val canvasWidth = size.width + val canvasHeight = size.height + + // rectangle size is determined by removing the padding from the border of the screen + // and the padding to the corners of the rectangle + val rectWidth = canvasWidth - (borderPadding.toPx() * 2) - rectToCornerPadding.toPx() + val rectHeight = canvasWidth - (borderPadding.toPx() * 2) - rectToCornerPadding.toPx() + val rectLeft = (canvasWidth - rectWidth) / 2 + val rectTop = (canvasHeight - rectHeight) / 2 + val circlePath = Path().apply { addRect( - Rect(center, size.width / 4) + Rect(Offset(rectLeft, rectTop), Size(rectWidth, rectHeight)) ) } clipPath(circlePath, clipOp = ClipOp.Difference) { drawRect(SolidColor(Color.Black.copy(alpha = 0.6f))) } + + // drawing edges in each corner using lines + val cornerLength = rectWidth / 12 + + val strokeWidth = 3f // width of each border + + // top left corner + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop - rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), + strokeWidth = strokeWidth + ) + + // top right corner + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), + strokeWidth = strokeWidth + ) + + // bottom left corner + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), + strokeWidth = strokeWidth + ) + + // bottom right corner + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), + strokeWidth = strokeWidth + ) + } ) - } - Box( - modifier = Modifier - .background(Color.Black.copy(alpha = 0.6f)) - .height(screenHeight / 8) - .fillMaxWidth() - ) + } Box( modifier = Modifier From 7a5609a8f69ab7d5a6003fd802253f3cac7ef867 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 23 Nov 2023 09:16:05 +0000 Subject: [PATCH 138/174] refactor: create color for scanner background References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../barcode/view/OSBARCScannerActivity.kt | 22 +++++++++++-------- .../plugins/barcode/view/ui.theme/Color.kt | 5 +++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 02bafa2..c7a49e4 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -64,6 +64,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat +import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper @@ -71,10 +72,10 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme -import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackground -import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBorder +import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackgroundGray +import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBorderGray import com.outsystems.plugins.barcode.view.ui.theme.CustomGray -import com.outsystemsenterprise.enmobile11dev.BarcodeSampleAppNew.R +import com.outsystems.plugins.barcode.view.ui.theme.ScannerBackgroundBlack /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -241,9 +242,12 @@ class OSBARCScannerActivity : ComponentActivity() { modifier = Modifier.fillMaxSize() ) + // actual UI on top of the camera stream + val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp val screenWidth = configuration.screenWidthDp.dp + val borderPadding = 32.dp Column( @@ -254,7 +258,7 @@ class OSBARCScannerActivity : ComponentActivity() { Row( modifier = Modifier - .background(Color.Black.copy(alpha = 0.6f)) + .background(ScannerBackgroundBlack) .align(Alignment.End) .fillMaxWidth(), horizontalArrangement = Arrangement.End @@ -275,7 +279,7 @@ class OSBARCScannerActivity : ComponentActivity() { if (!parameters.scanInstructions.isNullOrEmpty()) { ScanInstructions(modifier = Modifier .align(Alignment.CenterHorizontally) - .background(Color.Black.copy(alpha = 0.6f)) + .background(ScannerBackgroundBlack) .padding(top = 32.dp, bottom = 32.dp) .fillMaxWidth() ,scanInstructions = parameters.scanInstructions) @@ -307,7 +311,7 @@ class OSBARCScannerActivity : ComponentActivity() { ) } clipPath(circlePath, clipOp = ClipOp.Difference) { - drawRect(SolidColor(Color.Black.copy(alpha = 0.6f))) + drawRect(SolidColor(ScannerBackgroundBlack)) } // drawing edges in each corner using lines @@ -379,7 +383,7 @@ class OSBARCScannerActivity : ComponentActivity() { Box( modifier = Modifier .fillMaxSize() - .background(Color.Black.copy(alpha = 0.6f)) + .background(ScannerBackgroundBlack) .padding(start = 32.dp, top = 32.dp, end = 32.dp, bottom = 32.dp) ) { // scan button to turn on scanning when used @@ -463,10 +467,10 @@ class OSBARCScannerActivity : ComponentActivity() { scanning = true }, colors = ButtonDefaults.buttonColors( - containerColor = ButtonsBackground + containerColor = ButtonsBackgroundGray ), shape = RoundedCornerShape(4.dp), - border = BorderStroke(width = 1.dp, color = ButtonsBorder), + border = BorderStroke(width = 1.dp, color = ButtonsBorderGray), modifier = modifier ) { Text( diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt index 0bdfdaa..b9a0637 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt @@ -12,5 +12,6 @@ val Pink40 = Color(0xFF7D5260) val CustomGray = Color(0xFFB3BAC4) -val ButtonsBackground = Color(0x1AFFFFFF) -val ButtonsBorder = Color(0xFF4F575E) \ No newline at end of file +val ButtonsBackgroundGray = Color(0x1AFFFFFF) +val ButtonsBorderGray = Color(0xFF4F575E) +val ScannerBackgroundBlack = Color.Black.copy(alpha = 0.6f) \ No newline at end of file From 59d2a369faf37b868d87020f5999621896eca4e9 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 23 Nov 2023 12:07:29 +0000 Subject: [PATCH 139/174] fix: include case for SCAN_CANCELLED_ERROR References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../outsystems/plugins/barcode/controller/OSBARCController.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt index d4ec498..cff4957 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -62,6 +62,8 @@ class OSBARCController { } Activity.RESULT_CANCELED -> onError(OSBARCError.SCAN_CANCELLED_ERROR) + OSBARCError.SCAN_CANCELLED_ERROR.code -> + onError(OSBARCError.SCAN_CANCELLED_ERROR) OSBARCError.CAMERA_PERMISSION_DENIED_ERROR.code -> onError(OSBARCError.CAMERA_PERMISSION_DENIED_ERROR) OSBARCError.SCANNING_GENERAL_ERROR.code -> From 49f110c40b23f305e2fe974c5dcb09f580b5d24b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 23 Nov 2023 19:05:25 +0000 Subject: [PATCH 140/174] feat: first version of scanning screen in landscape Context: We check if the device's orientation is portrait or landscape, all call the respective UI composable method - ScanScreenUIPortrait or ScanScreenUILandscape. References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../barcode/view/OSBARCScannerActivity.kt | 442 +++++++++++++----- 1 file changed, 314 insertions(+), 128 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index c7a49e4..253aae1 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo import android.content.pm.PackageManager +import android.content.res.Configuration import android.net.Uri import android.os.Bundle import android.provider.Settings @@ -29,15 +30,18 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect @@ -60,6 +64,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat @@ -250,150 +255,338 @@ class OSBARCScannerActivity : ComponentActivity() { val borderPadding = 32.dp + val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT + + if (isPortrait) { + ScanScreenUIPortrait(parameters = parameters, screenWidth = screenWidth, borderPadding = borderPadding) + } + else { + ScanScreenUILandscape(parameters = parameters, screenWidth = screenWidth, screenHeight = screenHeight, borderPadding = borderPadding) + } + + } + } + + @Composable + fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, screenWidth: Dp, borderPadding: Dp) { + + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + + Row( + modifier = Modifier + .background(ScannerBackgroundBlack) + .align(Alignment.End) + .fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + // close button + CloseButton( + modifier = Modifier + .padding(top = 32.dp, end = 32.dp) + ) + } + Column( modifier = Modifier - .fillMaxSize(), + .fillMaxWidth(), verticalArrangement = Arrangement.Center ) { - - Row( - modifier = Modifier + // text with scan instructions + if (!parameters.scanInstructions.isNullOrEmpty()) { + ScanInstructions(modifier = Modifier + .align(Alignment.CenterHorizontally) .background(ScannerBackgroundBlack) - .align(Alignment.End) - .fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - // close button - CloseButton( - modifier = Modifier - .padding(top = 32.dp, end = 32.dp) - ) + .padding(top = 32.dp, bottom = 24.dp) + .fillMaxWidth() + ,scanInstructions = parameters.scanInstructions) } - Column( + // the canvas includes the rectangle and its edges + Canvas( modifier = Modifier - .fillMaxWidth(), - verticalArrangement = Arrangement.Center - ) { - // text with scan instructions - if (!parameters.scanInstructions.isNullOrEmpty()) { - ScanInstructions(modifier = Modifier - .align(Alignment.CenterHorizontally) - .background(ScannerBackgroundBlack) - .padding(top = 32.dp, bottom = 32.dp) - .fillMaxWidth() - ,scanInstructions = parameters.scanInstructions) + .fillMaxWidth() + .height(screenWidth), + onDraw = { + + // padding from the rectangle to each corner + val rectToCornerPadding = 16.dp + + val canvasWidth = size.width + val canvasHeight = size.height + + // rectangle size is determined by removing the padding from the border of the screen + // and the padding to the corners of the rectangle + val rectWidth = canvasWidth - (borderPadding.toPx() * 2) - rectToCornerPadding.toPx() + val rectHeight = canvasWidth - (borderPadding.toPx() * 2) - rectToCornerPadding.toPx() + val rectLeft = (canvasWidth - rectWidth) / 2 + val rectTop = (canvasHeight - rectHeight) / 2 + + val circlePath = Path().apply { + addRect( + Rect(Offset(rectLeft, rectTop), Size(rectWidth, rectHeight)) + ) + } + clipPath(circlePath, clipOp = ClipOp.Difference) { + drawRect(SolidColor(ScannerBackgroundBlack)) + } + + // drawing edges in each corner using lines + val cornerLength = rectWidth / 12 + + val strokeWidth = 3f // width of each border + + // top left corner + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop - rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), + strokeWidth = strokeWidth + ) + + // top right corner + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), + strokeWidth = strokeWidth + ) + + // bottom left corner + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), + strokeWidth = strokeWidth + ) + + // bottom right corner + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), + strokeWidth = strokeWidth + ) + } + ) - Canvas( - modifier = Modifier - .fillMaxWidth() - //.height((screenHeight / 3) + 32.dp), - .height(screenWidth), - onDraw = { - - // padding from the rectangle to each corner - val rectToCornerPadding = 16.dp - - val canvasWidth = size.width - val canvasHeight = size.height - - // rectangle size is determined by removing the padding from the border of the screen - // and the padding to the corners of the rectangle - val rectWidth = canvasWidth - (borderPadding.toPx() * 2) - rectToCornerPadding.toPx() - val rectHeight = canvasWidth - (borderPadding.toPx() * 2) - rectToCornerPadding.toPx() - val rectLeft = (canvasWidth - rectWidth) / 2 - val rectTop = (canvasHeight - rectHeight) / 2 - - val circlePath = Path().apply { - addRect( - Rect(Offset(rectLeft, rectTop), Size(rectWidth, rectHeight)) - ) - } - clipPath(circlePath, clipOp = ClipOp.Difference) { - drawRect(SolidColor(ScannerBackgroundBlack)) - } + } - // drawing edges in each corner using lines - val cornerLength = rectWidth / 12 + Box( + modifier = Modifier + .fillMaxSize() + .background(ScannerBackgroundBlack) + .padding(start = 32.dp, top = 32.dp, end = 32.dp, bottom = 32.dp) + ) { + // scan button to turn on scanning when used + if (parameters.scanButton) { + ScanButton(modifier = Modifier.align(Alignment.Center), scanButtonText = parameters.scanText) + } + // flashlight button + if (camera.cameraInfo.hasFlashUnit()) { + TorchButton(modifier = Modifier.align(Alignment.CenterEnd)) + } + } - val strokeWidth = 3f // width of each border + } + } - // top left corner - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop - rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), - strokeWidth = strokeWidth - ) + @Composable + fun ScanScreenUILandscape(parameters: OSBARCScanParameters, screenWidth: Dp, screenHeight:Dp, borderPadding: Dp) { + Box( + modifier = Modifier + .fillMaxSize(), + //horizontalArrangement = Arrangement.SpaceBetween + ) { - // top right corner - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), - strokeWidth = strokeWidth - ) + Column( + modifier = Modifier + .align(Alignment.Center) + .fillMaxHeight() + // column width is determined by dividing the screen width by 2 + // and removing the padding from the border of the screen + .width((screenWidth / 2) - (borderPadding * 2)/*screenWidth*/) + .padding(top = 32.dp, bottom = 32.dp) + , + verticalArrangement = Arrangement.Center + ) { - // bottom left corner - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), - strokeWidth = strokeWidth - ) + // text with scan instructions + if (!parameters.scanInstructions.isNullOrEmpty()) { + ScanInstructions(modifier = Modifier + .align(Alignment.CenterHorizontally) + .background(ScannerBackgroundBlack) + .padding(bottom = 24.dp) + .fillMaxWidth() + ,scanInstructions = parameters.scanInstructions) + } - // bottom right corner - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), - strokeWidth = strokeWidth + // the canvas includes the rectangle and its edges + Canvas( + modifier = Modifier + // canvas width is determined by dividing the screen width by 2 + // and removing the padding from the border of the screen + .width((screenWidth / 2) - (borderPadding * 2)/*screenWidth*/) + // canvas height is determined by removing the padding or the border of the screen + .height(screenHeight - (borderPadding * 2)/*screenHeight*/) + .align(Alignment.CenterHorizontally), + onDraw = { + // padding from the rectangle to each corner + val rectToCornerPadding = 16.dp + + val canvasWidth = size.width + val canvasHeight = size.height + + // rectangle width is determined with the canvasWidth and + // removing the padding from the rect to its corners of the screen + val rectWidth = (canvasWidth) - (rectToCornerPadding.toPx() * 2) + //val rectWidth = (canvasWidth / 2) - (borderPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + + // rectangle height is determined by removing the padding from the borders of the screen + val rectHeight = (canvasHeight) - (rectToCornerPadding.toPx() * 2) + //val rectHeight = (canvasHeight) - (borderPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + + val rectLeft = (canvasWidth - rectWidth) / 2 + val rectTop = (canvasHeight - rectHeight) / 2 + + val circlePath = Path().apply { + addRect( + Rect(Offset(rectLeft, rectTop), Size(rectWidth, rectHeight)) ) - } - ) + clipPath(circlePath, clipOp = ClipOp.Difference) { + drawRect(SolidColor(ScannerBackgroundBlack)) + } - } + // drawing edges in each corner using lines + val cornerLength = rectWidth / 12 + + val strokeWidth = 3f // width of each border + + // top left corner + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop - rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), + strokeWidth = strokeWidth + ) + + // top right corner + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), + strokeWidth = strokeWidth + ) + + // bottom left corner + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), + strokeWidth = strokeWidth + ) - Box( + // bottom right corner + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), + strokeWidth = strokeWidth + ) + drawLine( + color = Color.White, + start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), + end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), + strokeWidth = strokeWidth + ) + } + ) + + } + + Box( + modifier = Modifier + .align(Alignment.CenterEnd) + .fillMaxHeight() + .background(ScannerBackgroundBlack) + ) { + // close button + CloseButton( modifier = Modifier - .fillMaxSize() - .background(ScannerBackgroundBlack) - .padding(start = 32.dp, top = 32.dp, end = 32.dp, bottom = 32.dp) + .align(Alignment.TopEnd) + .padding(top = 32.dp, end = 32.dp) + ) + + Column( + modifier = Modifier + .align(Alignment.CenterEnd), + verticalArrangement = Arrangement.Center ) { - // scan button to turn on scanning when used - if (parameters.scanButton) { - ScanButton(modifier = Modifier.align(Alignment.Center), scanButtonText = parameters.scanText) - } // flashlight button if (camera.cameraInfo.hasFlashUnit()) { - TorchButton(modifier = Modifier.align(Alignment.CenterEnd)) + TorchButton( + modifier = Modifier + .align(Alignment.End) + .padding(end = 32.dp, bottom = 24.dp) + ) + } + // scan button to turn on scanning when used + if (parameters.scanButton) { + ScanButton( + modifier = Modifier + .align(Alignment.End) + .padding(end = 32.dp), + scanButtonText = parameters.scanText + ) } + } } @@ -403,14 +596,11 @@ class OSBARCScannerActivity : ComponentActivity() { @Composable fun CloseButton(modifier: Modifier) { - Button( + IconButton( onClick = { setResult(OSBARCError.SCAN_CANCELLED_ERROR.code) finish() }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ), modifier = modifier ) { Icon( @@ -427,7 +617,7 @@ class OSBARCScannerActivity : ComponentActivity() { val onIcon = painterResource(id = R.drawable.flash_on) val offIcon = painterResource(id = R.drawable.flash_off) - Button( + IconButton( onClick = { try { camera.cameraControl.enableTorch(!isFlashlightOn) @@ -436,10 +626,6 @@ class OSBARCScannerActivity : ComponentActivity() { e.message?.let { Log.e(LOG_TAG, it) } } }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ), - shape = CircleShape, modifier = modifier ) { val icon = if (isFlashlightOn) onIcon else offIcon From 029e4bc0bf02e1769a7b22dbba740b035e4c9e0f Mon Sep 17 00:00:00 2001 From: Nelson Lopes Silva Date: Wed, 29 Nov 2023 15:18:43 +0000 Subject: [PATCH 141/174] refactor: added rounded corners, right allignment and bottom placement on portrait screen --- build.gradle | 46 ++-- .../barcode/view/OSBARCScannerActivity.kt | 237 +++++++++--------- .../plugins/barcode/view/ui.theme/Theme.kt | 2 +- 3 files changed, 146 insertions(+), 139 deletions(-) diff --git a/build.gradle b/build.gradle index 27550e2..3f9c59d 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,9 @@ android { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.2.0-alpha08' + // Kotlin '1.9.10' requires compose version '1.5.3' + // https://developer.android.com/jetpack/androidx/releases/compose-kotlin + kotlinCompilerExtensionVersion '1.5.3' } packaging { @@ -98,33 +100,33 @@ repositories { } dependencies { - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.5.0' - implementation "androidx.compose.ui:ui:1.0.5" - implementation "androidx.compose.material:material:1.0.5" - implementation "androidx.compose.ui:ui-tooling-preview:1.0.5" - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' - implementation "androidx.activity:activity-compose:1.4.0" - implementation 'androidx.compose.material3:material3:1.0.0' + implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.10.0' + implementation "androidx.compose.ui:ui:1.5.4" + implementation "androidx.compose.material:material:1.5.4" + implementation "androidx.compose.ui:ui-tooling-preview:1.5.4" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' + implementation "androidx.activity:activity-compose:1.8.1" + implementation 'androidx.compose.material3:material3:1.1.2' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.5" - debugImplementation "androidx.compose.ui:ui-tooling:1.0.5" + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.5.4" + debugImplementation "androidx.compose.ui:ui-tooling:1.5.4" - implementation "androidx.camera:camera-camera2:1.0.2" - implementation 'androidx.camera:camera-lifecycle:1.0.2' - implementation 'androidx.camera:camera-view:1.0.0-alpha31' - implementation 'androidx.camera:camera-core:1.0.0' + implementation "androidx.camera:camera-camera2:1.3.0" + implementation 'androidx.camera:camera-lifecycle:1.3.0' + implementation 'androidx.camera:camera-view:1.4.0-alpha02' + implementation 'androidx.camera:camera-core:1.3.0' implementation 'com.google.zxing:core:3.4.1' implementation 'com.google.mlkit:barcode-scanning:17.2.0' - testImplementation "org.mockito:mockito-core:4.3.0" - testImplementation 'org.mockito:mockito-inline:4.3.0' + testImplementation "org.mockito:mockito-core:5.1.0" + testImplementation 'org.mockito:mockito-inline:5.1.0' testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0' - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2" - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 253aae1..2664425 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -7,13 +7,17 @@ import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.content.res.Configuration import android.net.Uri +import android.os.Build import android.os.Bundle import android.provider.Settings import android.util.Log +import android.view.WindowInsets +import android.view.WindowInsetsController import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.camera.core.Camera import androidx.camera.core.CameraSelector @@ -26,22 +30,23 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect @@ -51,13 +56,16 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.ClipOp import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext @@ -104,11 +112,16 @@ class OSBARCScannerActivity : ComponentActivity() { private const val ORIENTATION_LANDSCAPE = 2 } + private data class Point(val x: Int, val y: Int) + /** * Overrides the onCreate method from Activity, setting the UI of the screen */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + enableEdgeToEdge() + val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters // possibly lock orientation, the screen is adaptive by default @@ -204,7 +217,8 @@ class OSBARCScannerActivity : ComponentActivity() { Box( modifier = Modifier - .fillMaxSize(), + .fillMaxSize() + .safeDrawingPadding(), ) { AndroidView( factory = { context -> @@ -270,38 +284,46 @@ class OSBARCScannerActivity : ComponentActivity() { @Composable fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, screenWidth: Dp, borderPadding: Dp) { + // padding from the rectangle to each corner + val rectToCornerPadding = 16.dp + // size of the scan and torch buttons + val actionButtonsHeight = 48.dp + // drawing edges in each corner using lines + val cornerLength = 50.dp + // width of each border + val strokeWidth = 3f + Column( modifier = Modifier .fillMaxSize(), verticalArrangement = Arrangement.SpaceBetween ) { - Row( + Box( modifier = Modifier + .fillMaxWidth() .background(ScannerBackgroundBlack) - .align(Alignment.End) - .fillMaxWidth(), - horizontalArrangement = Arrangement.End + .weight(1f, fill = true), ) { - // close button CloseButton( modifier = Modifier - .padding(top = 32.dp, end = 32.dp) + .padding(top = borderPadding, end = borderPadding) + .align(Alignment.TopEnd) ) } Column( modifier = Modifier - .fillMaxWidth(), - verticalArrangement = Arrangement.Center + .fillMaxWidth() ) { // text with scan instructions if (!parameters.scanInstructions.isNullOrEmpty()) { ScanInstructions(modifier = Modifier .align(Alignment.CenterHorizontally) .background(ScannerBackgroundBlack) - .padding(top = 32.dp, bottom = 24.dp) + //.padding(top = 32.dp) .fillMaxWidth() + .wrapContentHeight() ,scanInstructions = parameters.scanInstructions) } @@ -312,110 +334,92 @@ class OSBARCScannerActivity : ComponentActivity() { .height(screenWidth), onDraw = { - // padding from the rectangle to each corner - val rectToCornerPadding = 16.dp - + val radius = 25f val canvasWidth = size.width val canvasHeight = size.height // rectangle size is determined by removing the padding from the border of the screen // and the padding to the corners of the rectangle - val rectWidth = canvasWidth - (borderPadding.toPx() * 2) - rectToCornerPadding.toPx() - val rectHeight = canvasWidth - (borderPadding.toPx() * 2) - rectToCornerPadding.toPx() + val rectWidth = canvasWidth - (borderPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + val rectHeight = canvasWidth - (borderPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) val rectLeft = (canvasWidth - rectWidth) / 2 val rectTop = (canvasHeight - rectHeight) / 2 val circlePath = Path().apply { - addRect( - Rect(Offset(rectLeft, rectTop), Size(rectWidth, rectHeight)) + addRoundRect( + RoundRect( + rect = Rect(Offset(rectLeft, rectTop), Size(rectWidth, rectHeight)), + cornerRadius = CornerRadius(radius, radius) + ) ) } clipPath(circlePath, clipOp = ClipOp.Difference) { - drawRect(SolidColor(ScannerBackgroundBlack)) + drawRect(color=ScannerBackgroundBlack) } - // drawing edges in each corner using lines - val cornerLength = rectWidth / 12 - - val strokeWidth = 3f // width of each border - - // top left corner - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop - rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), - strokeWidth = strokeWidth - ) - - // top right corner - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), - strokeWidth = strokeWidth - ) - - // bottom left corner - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), - strokeWidth = strokeWidth - ) - - // bottom right corner - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), - strokeWidth = strokeWidth - ) + val top = rectTop - rectToCornerPadding.toPx() + val left = rectLeft - rectToCornerPadding.toPx() + val right = left + rectWidth + (rectToCornerPadding * 2).toPx() + val bottom = top + rectHeight + (rectToCornerPadding * 2).toPx() + val length = cornerLength.toPx() + + val aimPath = Path() + // top left + aimPath.moveTo(left + length, top) + aimPath.lineTo(left + radius, top) + aimPath.quadraticBezierTo(left, top, left, top + radius) + aimPath.lineTo(left, top + length) + + // bottom left + aimPath.moveTo(left, bottom - length) + aimPath.lineTo(left, bottom - radius) + aimPath.quadraticBezierTo(left, bottom, left + radius, bottom) + aimPath.lineTo(left + length, bottom) + + // bottom right + aimPath.moveTo(right - length, bottom) + aimPath.lineTo(right - radius, bottom) + aimPath.quadraticBezierTo(right, bottom, right, bottom - radius) + aimPath.lineTo(right, bottom - length) + + // top right + aimPath.moveTo(right, top + length) + aimPath.lineTo(right, top + radius) + aimPath.quadraticBezierTo(right, top, right - radius, top) + aimPath.lineTo(right - length, top) + + drawPath(aimPath, color = Color.White, style = Stroke(width = strokeWidth)) } ) - } Box( modifier = Modifier - .fillMaxSize() + .fillMaxWidth() .background(ScannerBackgroundBlack) - .padding(start = 32.dp, top = 32.dp, end = 32.dp, bottom = 32.dp) + .weight(1f, fill = true), ) { + // scan button to turn on scanning when used if (parameters.scanButton) { - ScanButton(modifier = Modifier.align(Alignment.Center), scanButtonText = parameters.scanText) + ScanButton( + modifier = Modifier + .padding(bottom = borderPadding) + .align(Alignment.BottomCenter) + .height(actionButtonsHeight), + scanButtonText = parameters.scanText) } // flashlight button if (camera.cameraInfo.hasFlashUnit()) { - TorchButton(modifier = Modifier.align(Alignment.CenterEnd)) + TorchButton( + modifier = Modifier + .padding(bottom = borderPadding, end = borderPadding) + .align(Alignment.BottomEnd) + .size(actionButtonsHeight) + ) } } - } } @@ -596,19 +600,16 @@ class OSBARCScannerActivity : ComponentActivity() { @Composable fun CloseButton(modifier: Modifier) { - IconButton( - onClick = { - setResult(OSBARCError.SCAN_CANCELLED_ERROR.code) - finish() - }, + Icon( + painter = painterResource(id = R.drawable.close), + contentDescription = null, + tint = CustomGray, modifier = modifier - ) { - Icon( - painter = painterResource(id = R.drawable.close), - contentDescription = null, - tint = CustomGray - ) - } + .clickable { + setResult(OSBARCError.SCAN_CANCELLED_ERROR.code) + finish() + } + ) } @Composable @@ -616,24 +617,21 @@ class OSBARCScannerActivity : ComponentActivity() { var isFlashlightOn by remember { mutableStateOf(false) } val onIcon = painterResource(id = R.drawable.flash_on) val offIcon = painterResource(id = R.drawable.flash_off) + val icon = if (isFlashlightOn) onIcon else offIcon - IconButton( - onClick = { - try { - camera.cameraControl.enableTorch(!isFlashlightOn) - isFlashlightOn = !isFlashlightOn - } catch (e: Exception) { - e.message?.let { Log.e(LOG_TAG, it) } - } - }, + Image( + painter = icon, + contentDescription = null, modifier = modifier - ) { - val icon = if (isFlashlightOn) onIcon else offIcon - Image( - painter = icon, - contentDescription = null - ) - } + .clickable { + try { + camera.cameraControl.enableTorch(!isFlashlightOn) + isFlashlightOn = !isFlashlightOn + } catch (e: Exception) { + e.message?.let { Log.e(LOG_TAG, it) } + } + } + ) } @Composable @@ -697,7 +695,14 @@ class OSBARCScannerActivity : ComponentActivity() { actionBar?.hide() // set full screen WindowCompat.setDecorFitsSystemWindows(window, false) - window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + } else { + window.insetsController?.apply { + hide(WindowInsets.Type.statusBars()) + systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + } } } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt index c96bd85..870535a 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt @@ -42,7 +42,7 @@ fun BarcodeScannerTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { From 2ff74eeae2061461212cf21d7cb6251a36dfa231 Mon Sep 17 00:00:00 2001 From: Nelson Lopes Silva Date: Thu, 30 Nov 2023 10:56:24 +0000 Subject: [PATCH 142/174] refactor: added details to landscape --- .../barcode/view/OSBARCScannerActivity.kt | 411 +++++++----------- 1 file changed, 169 insertions(+), 242 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 2664425..3f4adf2 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -34,15 +34,16 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -64,7 +65,6 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.ClipOp import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.platform.LocalConfiguration @@ -272,27 +272,128 @@ class OSBARCScannerActivity : ComponentActivity() { val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT if (isPortrait) { - ScanScreenUIPortrait(parameters = parameters, screenWidth = screenWidth, borderPadding = borderPadding) + ScanScreenUIPortrait(parameters, screenWidth, borderPadding) } else { - ScanScreenUILandscape(parameters = parameters, screenWidth = screenWidth, screenHeight = screenHeight, borderPadding = borderPadding) + ScanScreenUILandscape(parameters, screenHeight, borderPadding) } + } + } + @Composable + fun ScanActionButtons(parameters: OSBARCScanParameters, + verticalPadding: Dp, + scanModifier: Modifier, + torchModifier: Modifier) { + + val actionButtonsHeight = 48.dp + val showTorch = camera.cameraInfo.hasFlashUnit() + val showScan = parameters.scanButton || true + + val buttonSpacing = if(showTorch && showScan) + { verticalPadding.times(0.5f) } else { verticalPadding.times(0f) } + + // flashlight button + if (showTorch) { + TorchButton( + torchModifier + .padding(bottom = buttonSpacing) + .size(actionButtonsHeight), + ) + } + + // scan button to turn on scanning when used + if (showScan) { + ScanButton( + scanModifier + .padding(top = buttonSpacing) + .height(actionButtonsHeight), + parameters.scanText) } } @Composable - fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, screenWidth: Dp, borderPadding: Dp) { + fun ScanScreenAim(height: Dp, horizontalPadding: Dp, verticalPadding: Dp) { // padding from the rectangle to each corner val rectToCornerPadding = 16.dp - // size of the scan and torch buttons - val actionButtonsHeight = 48.dp // drawing edges in each corner using lines val cornerLength = 50.dp // width of each border val strokeWidth = 3f + // the canvas includes the rectangle and its edges + Canvas( + modifier = Modifier + .fillMaxWidth() + .height(height) + , + onDraw = { + + val radius = 25f + val canvasWidth = size.width + val canvasHeight = size.height + + // rectangle size is determined by removing the padding from the border of the screen + // and the padding to the corners of the rectangle + val rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + val rectHeight = canvasHeight - (verticalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + val rectLeft = (canvasWidth - rectWidth) / 2 + val rectTop = (canvasHeight - rectHeight) / 2 + + val circlePath = Path().apply { + addRoundRect( + RoundRect( + rect = Rect(Offset(rectLeft, rectTop), Size(rectWidth, rectHeight)), + cornerRadius = CornerRadius(radius, radius) + ) + ) + } + clipPath(circlePath, clipOp = ClipOp.Difference) { + drawRect(color= ScannerBackgroundBlack) + } + + val aimTop = rectTop - rectToCornerPadding.toPx() + val aimLeft = rectLeft - rectToCornerPadding.toPx() + val aimRight = aimLeft + rectWidth + (rectToCornerPadding * 2).toPx() + val aimBottom = aimTop + rectHeight + (rectToCornerPadding * 2).toPx() + val aimLength = cornerLength.toPx() + + val aimPath = Path() + // top left + aimPath.moveTo(aimLeft + aimLength, aimTop) + aimPath.lineTo(aimLeft + radius, aimTop) + aimPath.quadraticBezierTo(aimLeft, aimTop, aimLeft, aimTop + radius) + aimPath.lineTo(aimLeft, aimTop + aimLength) + + // bottom left + aimPath.moveTo(aimLeft, aimBottom - aimLength) + aimPath.lineTo(aimLeft, aimBottom - radius) + aimPath.quadraticBezierTo(aimLeft, aimBottom, aimLeft + radius, aimBottom) + aimPath.lineTo(aimLeft + aimLength, aimBottom) + + // bottom right + aimPath.moveTo(aimRight - aimLength, aimBottom) + aimPath.lineTo(aimRight - radius, aimBottom) + aimPath.quadraticBezierTo(aimRight, aimBottom, aimRight, aimBottom - radius) + aimPath.lineTo(aimRight, aimBottom - aimLength) + + // top right + aimPath.moveTo(aimRight, aimTop + aimLength) + aimPath.lineTo(aimRight, aimTop + radius) + aimPath.quadraticBezierTo(aimRight, aimTop, aimRight - radius, aimTop) + aimPath.lineTo(aimRight - aimLength, aimTop) + + drawPath(aimPath, color = Color.White, style = Stroke(width = strokeWidth)) + } + ) + } + + @Composable + fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, + screenHeight:Dp, + borderPadding: Dp) { + Column( modifier = Modifier .fillMaxSize(), @@ -315,83 +416,18 @@ class OSBARCScannerActivity : ComponentActivity() { Column( modifier = Modifier .fillMaxWidth() + .weight(2f, fill = true), + verticalArrangement = Arrangement.Center ) { - // text with scan instructions - if (!parameters.scanInstructions.isNullOrEmpty()) { - ScanInstructions(modifier = Modifier - .align(Alignment.CenterHorizontally) - .background(ScannerBackgroundBlack) - //.padding(top = 32.dp) - .fillMaxWidth() - .wrapContentHeight() - ,scanInstructions = parameters.scanInstructions) - } - // the canvas includes the rectangle and its edges - Canvas( + ScanInstructions( modifier = Modifier .fillMaxWidth() - .height(screenWidth), - onDraw = { - - val radius = 25f - val canvasWidth = size.width - val canvasHeight = size.height - - // rectangle size is determined by removing the padding from the border of the screen - // and the padding to the corners of the rectangle - val rectWidth = canvasWidth - (borderPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) - val rectHeight = canvasWidth - (borderPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) - val rectLeft = (canvasWidth - rectWidth) / 2 - val rectTop = (canvasHeight - rectHeight) / 2 - - val circlePath = Path().apply { - addRoundRect( - RoundRect( - rect = Rect(Offset(rectLeft, rectTop), Size(rectWidth, rectHeight)), - cornerRadius = CornerRadius(radius, radius) - ) - ) - } - clipPath(circlePath, clipOp = ClipOp.Difference) { - drawRect(color=ScannerBackgroundBlack) - } - - val top = rectTop - rectToCornerPadding.toPx() - val left = rectLeft - rectToCornerPadding.toPx() - val right = left + rectWidth + (rectToCornerPadding * 2).toPx() - val bottom = top + rectHeight + (rectToCornerPadding * 2).toPx() - val length = cornerLength.toPx() - - val aimPath = Path() - // top left - aimPath.moveTo(left + length, top) - aimPath.lineTo(left + radius, top) - aimPath.quadraticBezierTo(left, top, left, top + radius) - aimPath.lineTo(left, top + length) - - // bottom left - aimPath.moveTo(left, bottom - length) - aimPath.lineTo(left, bottom - radius) - aimPath.quadraticBezierTo(left, bottom, left + radius, bottom) - aimPath.lineTo(left + length, bottom) - - // bottom right - aimPath.moveTo(right - length, bottom) - aimPath.lineTo(right - radius, bottom) - aimPath.quadraticBezierTo(right, bottom, right, bottom - radius) - aimPath.lineTo(right, bottom - length) - - // top right - aimPath.moveTo(right, top + length) - aimPath.lineTo(right, top + radius) - aimPath.quadraticBezierTo(right, top, right - radius, top) - aimPath.lineTo(right - length, top) - - drawPath(aimPath, color = Color.White, style = Stroke(width = strokeWidth)) - - } + .padding(bottom = borderPadding), + parameters ) + + ScanScreenAim(screenHeight, borderPadding, 0.dp) } Box( @@ -400,197 +436,81 @@ class OSBARCScannerActivity : ComponentActivity() { .background(ScannerBackgroundBlack) .weight(1f, fill = true), ) { - - // scan button to turn on scanning when used - if (parameters.scanButton) { - ScanButton( - modifier = Modifier - .padding(bottom = borderPadding) - .align(Alignment.BottomCenter) - .height(actionButtonsHeight), - scanButtonText = parameters.scanText) - } - // flashlight button - if (camera.cameraInfo.hasFlashUnit()) { - TorchButton( - modifier = Modifier - .padding(bottom = borderPadding, end = borderPadding) - .align(Alignment.BottomEnd) - .size(actionButtonsHeight) - ) - } + ScanActionButtons( + parameters, + 0f.dp, + scanModifier = Modifier + .padding(bottom = borderPadding) + .align(Alignment.BottomCenter), + torchModifier = Modifier + .padding(bottom = borderPadding, end = borderPadding) + .align(Alignment.BottomEnd) + ) } } } @Composable - fun ScanScreenUILandscape(parameters: OSBARCScanParameters, screenWidth: Dp, screenHeight:Dp, borderPadding: Dp) { - Box( + fun ScanScreenUILandscape(parameters: OSBARCScanParameters, + screenHeight:Dp, + borderPadding: Dp) { + Row( modifier = Modifier .fillMaxSize(), - //horizontalArrangement = Arrangement.SpaceBetween + horizontalArrangement = Arrangement.SpaceBetween ) { + Box( + modifier = Modifier + .fillMaxHeight() + .weight(1f, fill = true) + .background(ScannerBackgroundBlack) + ) + Column( modifier = Modifier - .align(Alignment.Center) .fillMaxHeight() - // column width is determined by dividing the screen width by 2 - // and removing the padding from the border of the screen - .width((screenWidth / 2) - (borderPadding * 2)/*screenWidth*/) - .padding(top = 32.dp, bottom = 32.dp) - , + .weight(2f, fill = true), verticalArrangement = Arrangement.Center ) { - // text with scan instructions - if (!parameters.scanInstructions.isNullOrEmpty()) { - ScanInstructions(modifier = Modifier - .align(Alignment.CenterHorizontally) - .background(ScannerBackgroundBlack) - .padding(bottom = 24.dp) - .fillMaxWidth() - ,scanInstructions = parameters.scanInstructions) - } - - // the canvas includes the rectangle and its edges - Canvas( + ScanInstructions( modifier = Modifier - // canvas width is determined by dividing the screen width by 2 - // and removing the padding from the border of the screen - .width((screenWidth / 2) - (borderPadding * 2)/*screenWidth*/) - // canvas height is determined by removing the padding or the border of the screen - .height(screenHeight - (borderPadding * 2)/*screenHeight*/) - .align(Alignment.CenterHorizontally), - onDraw = { - // padding from the rectangle to each corner - val rectToCornerPadding = 16.dp - - val canvasWidth = size.width - val canvasHeight = size.height - - // rectangle width is determined with the canvasWidth and - // removing the padding from the rect to its corners of the screen - val rectWidth = (canvasWidth) - (rectToCornerPadding.toPx() * 2) - //val rectWidth = (canvasWidth / 2) - (borderPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) - - // rectangle height is determined by removing the padding from the borders of the screen - val rectHeight = (canvasHeight) - (rectToCornerPadding.toPx() * 2) - //val rectHeight = (canvasHeight) - (borderPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) - - val rectLeft = (canvasWidth - rectWidth) / 2 - val rectTop = (canvasHeight - rectHeight) / 2 - - val circlePath = Path().apply { - addRect( - Rect(Offset(rectLeft, rectTop), Size(rectWidth, rectHeight)) - ) - } - clipPath(circlePath, clipOp = ClipOp.Difference) { - drawRect(SolidColor(ScannerBackgroundBlack)) - } - - // drawing edges in each corner using lines - val cornerLength = rectWidth / 12 - - val strokeWidth = 3f // width of each border - - // top left corner - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop - rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), - strokeWidth = strokeWidth - ) - - // top right corner - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop - rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectToCornerPadding.toPx() + cornerLength), - strokeWidth = strokeWidth - ) - - // bottom left corner - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectToCornerPadding.toPx() + cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft - rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), - strokeWidth = strokeWidth - ) - - // bottom right corner - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth - rectToCornerPadding.toPx() - cornerLength, rectTop + rectHeight + rectToCornerPadding.toPx()), - strokeWidth = strokeWidth - ) - drawLine( - color = Color.White, - start = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight + rectToCornerPadding.toPx()), - end = Offset(rectLeft + rectWidth + rectToCornerPadding.toPx(), rectTop + rectHeight - rectToCornerPadding.toPx() - cornerLength), - strokeWidth = strokeWidth - ) - } + .fillMaxWidth() + .padding(top = borderPadding), + parameters ) + ScanScreenAim(screenHeight, 0.dp, borderPadding) } Box( modifier = Modifier - .align(Alignment.CenterEnd) .fillMaxHeight() + .weight(1f, fill = true) .background(ScannerBackgroundBlack) ) { - // close button + CloseButton( modifier = Modifier + .padding(top = borderPadding, end = borderPadding) .align(Alignment.TopEnd) - .padding(top = 32.dp, end = 32.dp) ) Column( modifier = Modifier + .padding(end = borderPadding) .align(Alignment.CenterEnd), verticalArrangement = Arrangement.Center ) { - // flashlight button - if (camera.cameraInfo.hasFlashUnit()) { - TorchButton( - modifier = Modifier - .align(Alignment.End) - .padding(end = 32.dp, bottom = 24.dp) - ) - } - // scan button to turn on scanning when used - if (parameters.scanButton) { - ScanButton( - modifier = Modifier - .align(Alignment.End) - .padding(end = 32.dp), - scanButtonText = parameters.scanText - ) - } - + ScanActionButtons( + parameters, + borderPadding, + scanModifier = Modifier + .align(Alignment.End), + torchModifier = Modifier + .align(Alignment.End), + ) } } @@ -635,13 +555,20 @@ class OSBARCScannerActivity : ComponentActivity() { } @Composable - fun ScanInstructions(modifier: Modifier, scanInstructions: String) { - Text( - text = scanInstructions, - modifier = modifier, - color = Color.White, - textAlign = TextAlign.Center - ) + fun ScanInstructions(modifier: Modifier, parameters: OSBARCScanParameters) { + if (!parameters.scanInstructions.isNullOrEmpty()) { + Box( + modifier = Modifier + .background(ScannerBackgroundBlack) + ) { + Text( + text = parameters.scanInstructions, + modifier = modifier, + color = Color.White, + textAlign = TextAlign.Center + ) + } + } } @Composable From 711445f38c55d8ec365712b467fb5ad020b20209 Mon Sep 17 00:00:00 2001 From: Nelson Lopes Silva Date: Thu, 30 Nov 2023 15:45:35 +0000 Subject: [PATCH 143/174] feat: added toggle scan button --- .../barcode/view/OSBARCScannerActivity.kt | 163 ++++++++++++------ .../plugins/barcode/view/ui.theme/Color.kt | 5 + 2 files changed, 112 insertions(+), 56 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 3f4adf2..8dc199f 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -39,11 +39,9 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -86,7 +84,10 @@ import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackgroundGray +import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackgroundWhite import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBorderGray +import com.outsystems.plugins.barcode.view.ui.theme.ButtonsTextGray +import com.outsystems.plugins.barcode.view.ui.theme.ButtonsTextWhite import com.outsystems.plugins.barcode.view.ui.theme.CustomGray import com.outsystems.plugins.barcode.view.ui.theme.ScannerBackgroundBlack @@ -100,7 +101,7 @@ class OSBARCScannerActivity : ComponentActivity() { private lateinit var selector: CameraSelector private var permissionRequestCount = 0 private var showDialog by mutableStateOf(false) - private var scanning = true + private var isScanning = false companion object { private const val SCAN_SUCCESS_RESULT_CODE = -1 @@ -112,8 +113,6 @@ class OSBARCScannerActivity : ComponentActivity() { private const val ORIENTATION_LANDSCAPE = 2 } - private data class Point(val x: Int, val y: Int) - /** * Overrides the onCreate method from Activity, setting the UI of the screen */ @@ -131,7 +130,7 @@ class OSBARCScannerActivity : ComponentActivity() { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE } - scanning = !parameters.scanButton + isScanning = !parameters.scanButton selector = CameraSelector.Builder() .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) .build() @@ -280,38 +279,13 @@ class OSBARCScannerActivity : ComponentActivity() { } } - @Composable - fun ScanActionButtons(parameters: OSBARCScanParameters, - verticalPadding: Dp, - scanModifier: Modifier, - torchModifier: Modifier) { - - val actionButtonsHeight = 48.dp - val showTorch = camera.cameraInfo.hasFlashUnit() - val showScan = parameters.scanButton || true - - val buttonSpacing = if(showTorch && showScan) - { verticalPadding.times(0.5f) } else { verticalPadding.times(0f) } - - // flashlight button - if (showTorch) { - TorchButton( - torchModifier - .padding(bottom = buttonSpacing) - .size(actionButtonsHeight), - ) - } - - // scan button to turn on scanning when used - if (showScan) { - ScanButton( - scanModifier - .padding(top = buttonSpacing) - .height(actionButtonsHeight), - parameters.scanText) - } - } - + /** + * Composable function, responsible rendering the main centered view with the transparent + * rectangle + * @param height the screen height + * @param horizontalPadding the horizontal padding for the whole view + * @param verticalPadding the vertical padding for the whole view + */ @Composable fun ScanScreenAim(height: Dp, horizontalPadding: Dp, verticalPadding: Dp) { @@ -389,6 +363,12 @@ class OSBARCScannerActivity : ComponentActivity() { ) } + /** + * Composable function, responsible rendering the main UI in portrait mode + * @param parameters the scan parameters + * @param screenHeight the screen height + * @param borderPadding the value for the border padding + */ @Composable fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, screenHeight:Dp, @@ -450,6 +430,12 @@ class OSBARCScannerActivity : ComponentActivity() { } } + /** + * Composable function, responsible rendering the main UI in landscape mode + * @param parameters the scan parameters + * @param screenHeight the screen height + * @param borderPadding the value for the border padding + */ @Composable fun ScanScreenUILandscape(parameters: OSBARCScanParameters, screenHeight:Dp, @@ -518,6 +504,10 @@ class OSBARCScannerActivity : ComponentActivity() { } } + /** + * Composable function, responsible rendering the close button + * @param modifier the custom modifier for the button + */ @Composable fun CloseButton(modifier: Modifier) { Icon( @@ -532,6 +522,10 @@ class OSBARCScannerActivity : ComponentActivity() { ) } + /** + * Composable function, responsible rendering the torch button + * @param modifier the custom modifier for the button + */ @Composable fun TorchButton(modifier: Modifier) { var isFlashlightOn by remember { mutableStateOf(false) } @@ -554,6 +548,43 @@ class OSBARCScannerActivity : ComponentActivity() { ) } + /** + * Composable function, responsible rendering the scan button + * @param modifier the custom modifier for the whole view + * @param scanButtonText the scan button text + */ + @Composable + fun ScanButton(modifier: Modifier, scanButtonText: String) { + var scanning by remember { mutableStateOf(false) } + val backgroundColor = if (scanning) ButtonsBackgroundWhite else ButtonsBackgroundGray + val textColor = if (scanning) ButtonsTextGray else ButtonsTextWhite + + Button( + onClick = { + isScanning = !isScanning + scanning = !scanning + }, + colors = ButtonDefaults.buttonColors( + containerColor = backgroundColor + ), + shape = RoundedCornerShape(4.dp), + border = BorderStroke(width = 1.dp, color = ButtonsBorderGray), + modifier = modifier + ) { + Text( + text = scanButtonText, + color = textColor, + textAlign = TextAlign.Center + ) + } + } + + /** + * Composable function, responsible rendering the scan instructions. + * This component will only be rendered if scan parameters instructs so. + * @param modifier the custom modifier for the whole view + * @param parameters the scan parameters + */ @Composable fun ScanInstructions(modifier: Modifier, parameters: OSBARCScanParameters) { if (!parameters.scanInstructions.isNullOrEmpty()) { @@ -571,25 +602,45 @@ class OSBARCScannerActivity : ComponentActivity() { } } + /** + * Composable function, responsible for building the action buttons + * on the UI. + * This component will only be rendered if scan parameters instructs so. + * @param parameters the scan parameters + * @param verticalPadding the vertical spacing between buttons + * @param scanModifier the custom modifier for the scan button + * @param torchModifier the custom modifier for the torch button + */ @Composable - fun ScanButton(modifier: Modifier, scanButtonText: String) { - Button( - onClick = { - scanning = true - }, - colors = ButtonDefaults.buttonColors( - containerColor = ButtonsBackgroundGray - ), - shape = RoundedCornerShape(4.dp), - border = BorderStroke(width = 1.dp, color = ButtonsBorderGray), - modifier = modifier - ) { - Text( - text = scanButtonText, - color = Color.White, - textAlign = TextAlign.Center + fun ScanActionButtons(parameters: OSBARCScanParameters, + verticalPadding: Dp, + scanModifier: Modifier, + torchModifier: Modifier) { + + val actionButtonsHeight = 48.dp + val showTorch = camera.cameraInfo.hasFlashUnit() + val showScan = parameters.scanButton || true + + val buttonSpacing = if(showTorch && showScan) + { verticalPadding.times(0.5f) } else { verticalPadding.times(0f) } + + // flashlight button + if (showTorch) { + TorchButton( + torchModifier + .padding(bottom = buttonSpacing) + .size(actionButtonsHeight), ) } + + // scan button to turn on scanning when used + if (showScan) { + ScanButton( + scanModifier + .padding(top = buttonSpacing) + .height(actionButtonsHeight), + parameters.scanText) + } } private fun hasCameraPermission(context: Context): Boolean { @@ -601,7 +652,7 @@ class OSBARCScannerActivity : ComponentActivity() { private fun processReadSuccess(result: String) { // we only want to process the scan result if scanning is active - if (scanning) { + if (isScanning) { val resultIntent = Intent() resultIntent.putExtra(SCAN_RESULT, result) setResult(SCAN_SUCCESS_RESULT_CODE, resultIntent) @@ -611,7 +662,7 @@ class OSBARCScannerActivity : ComponentActivity() { private fun processReadError(error: OSBARCError) { // we only want to process the scan error if scanning is active - if (scanning) { + if (isScanning) { setResult(error.code) finish() } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt index b9a0637..2085679 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt @@ -14,4 +14,9 @@ val CustomGray = Color(0xFFB3BAC4) val ButtonsBackgroundGray = Color(0x1AFFFFFF) val ButtonsBorderGray = Color(0xFF4F575E) + +val ButtonsBackgroundWhite = Color(0xFFFFFFD9) +val ButtonsTextGray = Color(0xFF4F575E) +val ButtonsTextWhite = Color(0xFFFFFFFF) + val ScannerBackgroundBlack = Color.Black.copy(alpha = 0.6f) \ No newline at end of file From ce9d43eca7262764b568600e52783423a2f0f7ac Mon Sep 17 00:00:00 2001 From: Nelson Lopes Silva Date: Mon, 4 Dec 2023 11:11:10 +0000 Subject: [PATCH 144/174] fix: downgared version to allow for MABS 9 compatibility. --- build.gradle | 97 ++++++++++--------- .../barcode/view/OSBARCScannerActivity.kt | 3 - 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/build.gradle b/build.gradle index 3f9c59d..552d24e 100644 --- a/build.gradle +++ b/build.gradle @@ -12,18 +12,6 @@ buildscript { } } -plugins { - id "org.sonarqube" version "3.5.0.2730" -} - -sonarqube { - properties { - property "sonar.projectKey", "OutSystems_OSBarcodeLib-Android" - property "sonar.organization", "outsystemsrd" - property "sonar.host.url", "https://sonarcloud.io" - } -} - apply plugin: "com.android.library" apply plugin: "kotlin-android" apply plugin: "jacoco" @@ -59,13 +47,13 @@ android { task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) { reports { - xml.getRequired().set(true) - html.getRequired().set(true) + //xml.enabled = true + //html.enabled = true } def fileFilter = ['**/BuildConfig.*', '**/Manifest*.*'] - def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter) - def mainSrc = "${project.projectDir}/src/main/kotlin" + def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debugUnitTest", excludes: fileFilter) + def mainSrc = "${project.projectDir}/src/main/java" sourceDirectories.setFrom(files([mainSrc])) classDirectories.setFrom(files([debugTree])) @@ -78,20 +66,13 @@ android { compose true } composeOptions { - // Kotlin '1.9.10' requires compose version '1.5.3' - // https://developer.android.com/jetpack/androidx/releases/compose-kotlin - kotlinCompilerExtensionVersion '1.5.3' + kotlinCompilerExtensionVersion '1.2.0-alpha08' } - packaging { resources { excludes += '/META-INF/{AL2.0,LGPL2.1}' } } - - testOptions { - unitTests.returnDefaultValues = true - } } repositories { @@ -100,33 +81,55 @@ repositories { } dependencies { - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.10.0' - implementation "androidx.compose.ui:ui:1.5.4" - implementation "androidx.compose.material:material:1.5.4" - implementation "androidx.compose.ui:ui-tooling-preview:1.5.4" - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' - implementation "androidx.activity:activity-compose:1.8.1" - implementation 'androidx.compose.material3:material3:1.1.2' + + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + + //implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + + implementation "androidx.compose.ui:ui:1.0.5" + implementation "androidx.compose.material:material:1.0.5" + implementation "androidx.compose.ui:ui-tooling-preview:1.0.5" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' + implementation "androidx.activity:activity-compose:1.4.0" + implementation 'androidx.compose.material3:material3:1.0.0' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.5.4" - debugImplementation "androidx.compose.ui:ui-tooling:1.5.4" + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.5" + debugImplementation "androidx.compose.ui:ui-tooling:1.0.5" + + /* + implementation platform('androidx.compose:compose-bom:2023.03.00') + implementation 'androidx.compose.ui:ui' + implementation 'androidx.compose.ui:ui-graphics' + implementation 'androidx.compose.ui:ui-tooling-preview' + implementation 'androidx.compose.material3:material3' + + implementation platform('androidx.compose:compose-bom:2023.03.00') + implementation platform('androidx.compose:compose-bom:2023.03.00') + */ + + + implementation "androidx.camera:camera-camera2:1.0.2" + implementation 'androidx.camera:camera-lifecycle:1.0.2' + implementation 'androidx.camera:camera-view:1.0.0-alpha31' + + implementation 'androidx.camera:camera-core:1.0.0' - implementation "androidx.camera:camera-camera2:1.3.0" - implementation 'androidx.camera:camera-lifecycle:1.3.0' - implementation 'androidx.camera:camera-view:1.4.0-alpha02' - implementation 'androidx.camera:camera-core:1.3.0' implementation 'com.google.zxing:core:3.4.1' implementation 'com.google.mlkit:barcode-scanning:17.2.0' - testImplementation "org.mockito:mockito-core:5.1.0" - testImplementation 'org.mockito:mockito-inline:5.1.0' - testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0' - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" -} + /* + androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') + androidTestImplementation 'androidx.compose.ui:ui-test-junit4' + androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') + androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') + debugImplementation 'androidx.compose.ui:ui-tooling' + debugImplementation 'androidx.compose.ui:ui-test-manifest' + */ + +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 8dc199f..a27e06a 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -17,7 +17,6 @@ import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.camera.core.Camera import androidx.camera.core.CameraSelector @@ -119,8 +118,6 @@ class OSBARCScannerActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge() - val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters // possibly lock orientation, the screen is adaptive by default From bc32a10e8807a2decaec86210acc4c586c71abc9 Mon Sep 17 00:00:00 2001 From: Nelson Lopes Silva Date: Mon, 4 Dec 2023 11:11:25 +0000 Subject: [PATCH 145/174] chrore: updated CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bad48c..51269ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 30-11-2023 +Android - Implement Scanner screen for Phones (Portrait, Landscape, Adaptive) (https://outsystemsrd.atlassian.net/browse/RMET-2770) + ### 17-11-2023 Android - Implement Scan Orientation (Portrait, Landscape, Adaptive) (https://outsystemsrd.atlassian.net/browse/RMET-2763) From 7287e7599be5b3afab583d45c50efa27e2680f0e Mon Sep 17 00:00:00 2001 From: Nelson Lopes Silva Date: Mon, 4 Dec 2023 11:28:44 +0000 Subject: [PATCH 146/174] fix: fixed build file --- build.gradle | 57 ++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index 552d24e..fcf40cf 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,18 @@ buildscript { } } +plugins { + id "org.sonarqube" version "3.5.0.2730" +} + +sonarqube { + properties { + property "sonar.projectKey", "OutSystems_OSBarcodeLib-Android" + property "sonar.organization", "outsystemsrd" + property "sonar.host.url", "https://sonarcloud.io" + } +} + apply plugin: "com.android.library" apply plugin: "kotlin-android" apply plugin: "jacoco" @@ -47,13 +59,13 @@ android { task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) { reports { - //xml.enabled = true - //html.enabled = true + xml.getRequired().set(true) + html.getRequired().set(true) } def fileFilter = ['**/BuildConfig.*', '**/Manifest*.*'] - def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debugUnitTest", excludes: fileFilter) - def mainSrc = "${project.projectDir}/src/main/java" + def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter) + def mainSrc = "${project.projectDir}/src/main/kotlin" sourceDirectories.setFrom(files([mainSrc])) classDirectories.setFrom(files([debugTree])) @@ -68,11 +80,16 @@ android { composeOptions { kotlinCompilerExtensionVersion '1.2.0-alpha08' } + packaging { resources { excludes += '/META-INF/{AL2.0,LGPL2.1}' } } + + testOptions { + unitTests.returnDefaultValues = true + } } repositories { @@ -81,13 +98,9 @@ repositories { } dependencies { - implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' - - //implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation "androidx.compose.ui:ui:1.0.5" implementation "androidx.compose.material:material:1.0.5" implementation "androidx.compose.ui:ui-tooling-preview:1.0.5" @@ -101,35 +114,17 @@ dependencies { androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.5" debugImplementation "androidx.compose.ui:ui-tooling:1.0.5" - /* - implementation platform('androidx.compose:compose-bom:2023.03.00') - implementation 'androidx.compose.ui:ui' - implementation 'androidx.compose.ui:ui-graphics' - implementation 'androidx.compose.ui:ui-tooling-preview' - implementation 'androidx.compose.material3:material3' - - implementation platform('androidx.compose:compose-bom:2023.03.00') - implementation platform('androidx.compose:compose-bom:2023.03.00') - */ - implementation "androidx.camera:camera-camera2:1.0.2" implementation 'androidx.camera:camera-lifecycle:1.0.2' implementation 'androidx.camera:camera-view:1.0.0-alpha31' - implementation 'androidx.camera:camera-core:1.0.0' - - implementation 'com.google.zxing:core:3.4.1' implementation 'com.google.mlkit:barcode-scanning:17.2.0' - /* - androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') - androidTestImplementation 'androidx.compose.ui:ui-test-junit4' - androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') - androidTestImplementation platform('androidx.compose:compose-bom:2023.03.00') - debugImplementation 'androidx.compose.ui:ui-tooling' - debugImplementation 'androidx.compose.ui:ui-test-manifest' - */ - + testImplementation "org.mockito:mockito-core:4.3.0" + testImplementation 'org.mockito:mockito-inline:4.3.0' + testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0' + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2" } \ No newline at end of file From bb937e8fc20801db231cfd8c217627ca4bca3b93 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 4 Dec 2023 12:05:00 +0000 Subject: [PATCH 147/174] feat: add missing test case References: https://outsystemsrd.atlassian.net/browse/RMET-2770 --- .../outsystems/plugins/barcode/ScanCodeTests.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index e010410..ab98663 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -201,6 +201,20 @@ class ScanCodeTests { ) } + @Test + fun givenScanCancelledAndBarcodeEmptyWhenHandleScanResultThenCancelledError() { + val barcodeController = OSBARCController() + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, OSBARCError.SCAN_CANCELLED_ERROR.code, mockIntent, + { + fail() + }, + { + assertEquals(OSBARCError.SCAN_CANCELLED_ERROR.code, it.code) + assertEquals(OSBARCError.SCAN_CANCELLED_ERROR.description, it.description) + } + ) + } + @Test fun givenCameraPermissionDeniedWhenHandleScanResultThenPermissionDeniedError() { val barcodeController = OSBARCController() From e4dc2af20fafb57eda6c05b9e1cb6d1aee6b64bb Mon Sep 17 00:00:00 2001 From: Nelson Lopes Silva Date: Mon, 4 Dec 2023 14:01:10 +0000 Subject: [PATCH 148/174] refactor: refactored aim strokke code. moved all margins and stroke sizes to a constants file. --- build.gradle | 1 - .../barcode/view/OSBARCScannerActivity.kt | 155 ++++++++++-------- .../plugins/barcode/view/ui.theme/Color.kt | 2 + .../plugins/barcode/view/ui.theme/Sizes.kt | 14 ++ .../plugins/barcode/view/ui.theme/Theme.kt | 2 +- 5 files changed, 106 insertions(+), 68 deletions(-) create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt diff --git a/build.gradle b/build.gradle index fcf40cf..fb69c9a 100644 --- a/build.gradle +++ b/build.gradle @@ -114,7 +114,6 @@ dependencies { androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.5" debugImplementation "androidx.compose.ui:ui-tooling:1.0.5" - implementation "androidx.camera:camera-camera2:1.0.2" implementation 'androidx.camera:camera-lifecycle:1.0.2' implementation 'androidx.camera:camera-view:1.0.0-alpha31' diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index a27e06a..a56637d 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -60,7 +60,6 @@ import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.ClipOp -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipPath @@ -81,6 +80,7 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters +import com.outsystems.plugins.barcode.view.ui.theme.ActionButtonsDistance import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackgroundGray import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackgroundWhite @@ -88,7 +88,16 @@ import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBorderGray import com.outsystems.plugins.barcode.view.ui.theme.ButtonsTextGray import com.outsystems.plugins.barcode.view.ui.theme.ButtonsTextWhite import com.outsystems.plugins.barcode.view.ui.theme.CustomGray +import com.outsystems.plugins.barcode.view.ui.theme.NoPadding +import com.outsystems.plugins.barcode.view.ui.theme.ScanAimWhite +import com.outsystems.plugins.barcode.view.ui.theme.ScanButtonCornerRadius +import com.outsystems.plugins.barcode.view.ui.theme.ScanButtonStrokeWidth +import com.outsystems.plugins.barcode.view.ui.theme.ScanInstructionsWhite +import com.outsystems.plugins.barcode.view.ui.theme.ScannerAimCornerLength +import com.outsystems.plugins.barcode.view.ui.theme.ScannerAimRectCornerPadding +import com.outsystems.plugins.barcode.view.ui.theme.ScannerAimStrokeWidth import com.outsystems.plugins.barcode.view.ui.theme.ScannerBackgroundBlack +import com.outsystems.plugins.barcode.view.ui.theme.ScannerBorderPadding /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -102,6 +111,8 @@ class OSBARCScannerActivity : ComponentActivity() { private var showDialog by mutableStateOf(false) private var isScanning = false + private data class Point(val x: Float, val y: Float) + companion object { private const val SCAN_SUCCESS_RESULT_CODE = -1 private const val SCAN_RESULT = "scanResult" @@ -214,7 +225,7 @@ class OSBARCScannerActivity : ComponentActivity() { Box( modifier = Modifier .fillMaxSize() - .safeDrawingPadding(), + .safeDrawingPadding() ) { AndroidView( factory = { context -> @@ -258,20 +269,17 @@ class OSBARCScannerActivity : ComponentActivity() { ) // actual UI on top of the camera stream - val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp val screenWidth = configuration.screenWidthDp.dp - val borderPadding = 32.dp - val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT if (isPortrait) { - ScanScreenUIPortrait(parameters, screenWidth, borderPadding) + ScanScreenUIPortrait(parameters, screenWidth) } else { - ScanScreenUILandscape(parameters, screenHeight, borderPadding) + ScanScreenUILandscape(parameters, screenHeight) } } } @@ -286,12 +294,6 @@ class OSBARCScannerActivity : ComponentActivity() { @Composable fun ScanScreenAim(height: Dp, horizontalPadding: Dp, verticalPadding: Dp) { - // padding from the rectangle to each corner - val rectToCornerPadding = 16.dp - // drawing edges in each corner using lines - val cornerLength = 50.dp - // width of each border - val strokeWidth = 3f // the canvas includes the rectangle and its edges Canvas( @@ -307,8 +309,8 @@ class OSBARCScannerActivity : ComponentActivity() { // rectangle size is determined by removing the padding from the border of the screen // and the padding to the corners of the rectangle - val rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) - val rectHeight = canvasHeight - (verticalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + val rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (ScannerAimRectCornerPadding.toPx() * 2) + val rectHeight = canvasHeight - (verticalPadding.toPx() * 2) - (ScannerAimRectCornerPadding.toPx() * 2) val rectLeft = (canvasWidth - rectWidth) / 2 val rectTop = (canvasHeight - rectHeight) / 2 @@ -324,42 +326,66 @@ class OSBARCScannerActivity : ComponentActivity() { drawRect(color= ScannerBackgroundBlack) } - val aimTop = rectTop - rectToCornerPadding.toPx() - val aimLeft = rectLeft - rectToCornerPadding.toPx() - val aimRight = aimLeft + rectWidth + (rectToCornerPadding * 2).toPx() - val aimBottom = aimTop + rectHeight + (rectToCornerPadding * 2).toPx() - val aimLength = cornerLength.toPx() + val aimTop = rectTop - ScannerAimRectCornerPadding.toPx() + val aimLeft = rectLeft - ScannerAimRectCornerPadding.toPx() + val aimRight = aimLeft + rectWidth + (ScannerAimRectCornerPadding * 2).toPx() + val aimBottom = aimTop + rectHeight + (ScannerAimRectCornerPadding * 2).toPx() + val aimLength = ScannerAimCornerLength.toPx() val aimPath = Path() // top left - aimPath.moveTo(aimLeft + aimLength, aimTop) - aimPath.lineTo(aimLeft + radius, aimTop) - aimPath.quadraticBezierTo(aimLeft, aimTop, aimLeft, aimTop + radius) - aimPath.lineTo(aimLeft, aimTop + aimLength) - + AddCornerToAimPath( + aimPath, + Point(aimLeft + aimLength, aimTop), + Point(aimLeft + radius, aimTop), + Point(aimLeft, aimTop), + Point(aimLeft, aimTop + radius), + Point(aimLeft, aimTop + aimLength) + ) // bottom left - aimPath.moveTo(aimLeft, aimBottom - aimLength) - aimPath.lineTo(aimLeft, aimBottom - radius) - aimPath.quadraticBezierTo(aimLeft, aimBottom, aimLeft + radius, aimBottom) - aimPath.lineTo(aimLeft + aimLength, aimBottom) - + AddCornerToAimPath( + aimPath, + Point(aimLeft, aimBottom - aimLength), + Point(aimLeft, aimBottom - radius), + Point(aimLeft, aimBottom), + Point(aimLeft + radius, aimBottom), + Point(aimLeft + aimLength, aimBottom) + ) // bottom right - aimPath.moveTo(aimRight - aimLength, aimBottom) - aimPath.lineTo(aimRight - radius, aimBottom) - aimPath.quadraticBezierTo(aimRight, aimBottom, aimRight, aimBottom - radius) - aimPath.lineTo(aimRight, aimBottom - aimLength) - + AddCornerToAimPath( + aimPath, + Point(aimRight - aimLength, aimBottom), + Point(aimRight - radius, aimBottom), + Point(aimRight, aimBottom), + Point(aimRight, aimBottom - radius), + Point(aimRight, aimBottom - aimLength) + ) // top right - aimPath.moveTo(aimRight, aimTop + aimLength) - aimPath.lineTo(aimRight, aimTop + radius) - aimPath.quadraticBezierTo(aimRight, aimTop, aimRight - radius, aimTop) - aimPath.lineTo(aimRight - aimLength, aimTop) - - drawPath(aimPath, color = Color.White, style = Stroke(width = strokeWidth)) + AddCornerToAimPath( + aimPath, + Point(aimRight, aimTop + aimLength), + Point(aimRight, aimTop + radius), + Point(aimRight, aimTop), + Point(aimRight - radius, aimTop), + Point(aimRight - aimLength, aimTop) + ) + drawPath(aimPath, color = ScanAimWhite, style = Stroke(width = ScannerAimStrokeWidth)) } ) } + private fun AddCornerToAimPath(path: Path, + startPoint: Point, + startCornerPoint: Point, + controlPoint: Point, + endCornerPoint: Point, + endPoint: Point) { + path.moveTo(startPoint.x, startPoint.y) + path.lineTo(startCornerPoint.x, startCornerPoint.y) + path.quadraticBezierTo(controlPoint.x, controlPoint.y, endCornerPoint.x, endCornerPoint.y) + path.lineTo(endPoint.x, endPoint.y) + } + /** * Composable function, responsible rendering the main UI in portrait mode * @param parameters the scan parameters @@ -367,9 +393,7 @@ class OSBARCScannerActivity : ComponentActivity() { * @param borderPadding the value for the border padding */ @Composable - fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, - screenHeight:Dp, - borderPadding: Dp) { + fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, screenHeight:Dp) { Column( modifier = Modifier @@ -385,7 +409,7 @@ class OSBARCScannerActivity : ComponentActivity() { ) { CloseButton( modifier = Modifier - .padding(top = borderPadding, end = borderPadding) + .padding(top = ScannerBorderPadding, end = ScannerBorderPadding) .align(Alignment.TopEnd) ) } @@ -400,11 +424,11 @@ class OSBARCScannerActivity : ComponentActivity() { ScanInstructions( modifier = Modifier .fillMaxWidth() - .padding(bottom = borderPadding), + .padding(bottom = ScannerBorderPadding), parameters ) - ScanScreenAim(screenHeight, borderPadding, 0.dp) + ScanScreenAim(screenHeight, ScannerBorderPadding, NoPadding) } Box( @@ -415,12 +439,12 @@ class OSBARCScannerActivity : ComponentActivity() { ) { ScanActionButtons( parameters, - 0f.dp, + NoPadding, scanModifier = Modifier - .padding(bottom = borderPadding) + .padding(bottom = ScannerBorderPadding) .align(Alignment.BottomCenter), torchModifier = Modifier - .padding(bottom = borderPadding, end = borderPadding) + .padding(bottom = ScannerBorderPadding, end = ScannerBorderPadding) .align(Alignment.BottomEnd) ) } @@ -434,9 +458,7 @@ class OSBARCScannerActivity : ComponentActivity() { * @param borderPadding the value for the border padding */ @Composable - fun ScanScreenUILandscape(parameters: OSBARCScanParameters, - screenHeight:Dp, - borderPadding: Dp) { + fun ScanScreenUILandscape(parameters: OSBARCScanParameters, screenHeight:Dp) { Row( modifier = Modifier .fillMaxSize(), @@ -460,11 +482,11 @@ class OSBARCScannerActivity : ComponentActivity() { ScanInstructions( modifier = Modifier .fillMaxWidth() - .padding(top = borderPadding), + .padding(top = ScannerBorderPadding), parameters ) - ScanScreenAim(screenHeight, 0.dp, borderPadding) + ScanScreenAim(screenHeight, NoPadding, ScannerBorderPadding) } Box( @@ -476,19 +498,19 @@ class OSBARCScannerActivity : ComponentActivity() { CloseButton( modifier = Modifier - .padding(top = borderPadding, end = borderPadding) + .padding(top = ScannerBorderPadding, end = ScannerBorderPadding) .align(Alignment.TopEnd) ) Column( modifier = Modifier - .padding(end = borderPadding) + .padding(end = ScannerBorderPadding) .align(Alignment.CenterEnd), verticalArrangement = Arrangement.Center ) { ScanActionButtons( parameters, - borderPadding, + ScannerBorderPadding, scanModifier = Modifier .align(Alignment.End), torchModifier = Modifier @@ -564,8 +586,8 @@ class OSBARCScannerActivity : ComponentActivity() { colors = ButtonDefaults.buttonColors( containerColor = backgroundColor ), - shape = RoundedCornerShape(4.dp), - border = BorderStroke(width = 1.dp, color = ButtonsBorderGray), + shape = RoundedCornerShape(ScanButtonCornerRadius), + border = BorderStroke(width = ScanButtonStrokeWidth, color = ButtonsBorderGray), modifier = modifier ) { Text( @@ -592,7 +614,7 @@ class OSBARCScannerActivity : ComponentActivity() { Text( text = parameters.scanInstructions, modifier = modifier, - color = Color.White, + color = ScanInstructionsWhite, textAlign = TextAlign.Center ) } @@ -614,19 +636,20 @@ class OSBARCScannerActivity : ComponentActivity() { scanModifier: Modifier, torchModifier: Modifier) { - val actionButtonsHeight = 48.dp + val showTorch = camera.cameraInfo.hasFlashUnit() - val showScan = parameters.scanButton || true + val showScan = parameters.scanButton + val buttonsVerticalDistance = ActionButtonsDistance val buttonSpacing = if(showTorch && showScan) - { verticalPadding.times(0.5f) } else { verticalPadding.times(0f) } + { buttonsVerticalDistance.times(0.5f) } else { buttonsVerticalDistance.times(0f) } // flashlight button if (showTorch) { TorchButton( torchModifier .padding(bottom = buttonSpacing) - .size(actionButtonsHeight), + .size(buttonsVerticalDistance), ) } @@ -635,7 +658,7 @@ class OSBARCScannerActivity : ComponentActivity() { ScanButton( scanModifier .padding(top = buttonSpacing) - .height(actionButtonsHeight), + .height(buttonsVerticalDistance), parameters.scanText) } } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt index 2085679..60b23c3 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Color.kt @@ -19,4 +19,6 @@ val ButtonsBackgroundWhite = Color(0xFFFFFFD9) val ButtonsTextGray = Color(0xFF4F575E) val ButtonsTextWhite = Color(0xFFFFFFFF) +val ScanInstructionsWhite = Color(0xFFFFFFFF) +val ScanAimWhite = Color(0xFFFFFFFF) val ScannerBackgroundBlack = Color.Black.copy(alpha = 0.6f) \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt new file mode 100644 index 0000000..a61a1a0 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt @@ -0,0 +1,14 @@ +package com.outsystems.plugins.barcode.view.ui.theme + +import androidx.compose.ui.unit.dp + +val NoPadding = 0f.dp +val ScannerBorderPadding = 32f.dp + +val ScannerAimRectCornerPadding = 16f.dp +val ScannerAimCornerLength = 50f.dp +const val ScannerAimStrokeWidth = 3f + +val ScanButtonCornerRadius = 4f.dp +val ScanButtonStrokeWidth = 1f.dp +val ActionButtonsDistance = 48f.dp diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt index 870535a..c96bd85 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Theme.kt @@ -42,7 +42,7 @@ fun BarcodeScannerTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, - content: @Composable () -> Unit, + content: @Composable () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { From 50601cb2da0d9ad451c212c9ee7a298f4c6a56a5 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 4 Dec 2023 14:06:08 +0000 Subject: [PATCH 149/174] feat: determine if device is phone or tablet Context: According to Android's documentation, we should use the Window size classes to determine if the device the app is running on is a phone or a tablet. When in Portrait, we should use the window width to determine this (99.96% of phones will have a compact width). When in Landscape, the height of the window should be used (99.78% of phones will have a compact height). More info here: https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes, and here: https://developer.android.com/jetpack/compose/layouts/adaptive References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- build.gradle | 1 + .../barcode/view/OSBARCScannerActivity.kt | 34 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index fcf40cf..b031b90 100644 --- a/build.gradle +++ b/build.gradle @@ -107,6 +107,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' implementation "androidx.activity:activity-compose:1.4.0" implementation 'androidx.compose.material3:material3:1.0.0' + implementation 'androidx.compose.material3:material3-window-size-class:1.0.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index a27e06a..23b44de 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -46,6 +46,11 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass +import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue @@ -115,6 +120,7 @@ class OSBARCScannerActivity : ComponentActivity() { /** * Overrides the onCreate method from Activity, setting the UI of the screen */ + @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -133,8 +139,13 @@ class OSBARCScannerActivity : ComponentActivity() { .build() setContent { + + // to know if device is phone or tablet + // more info: https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes + val windowSizeClass = calculateWindowSizeClass(this) + BarcodeScannerTheme { - ScanScreen(parameters) + ScanScreen(parameters, windowSizeClass) } } @@ -151,7 +162,7 @@ class OSBARCScannerActivity : ComponentActivity() { * as well as creating an instance of OSBARCBarcodeAnalyzer for image analysis. */ @Composable - fun ScanScreen(parameters: OSBARCScanParameters) { + fun ScanScreen(parameters: OSBARCScanParameters, windowSizeClass: WindowSizeClass) { val lifecycleOwner = LocalLifecycleOwner.current val context = LocalContext.current var permissionGiven by remember { mutableStateOf(true) } @@ -268,10 +279,10 @@ class OSBARCScannerActivity : ComponentActivity() { val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT if (isPortrait) { - ScanScreenUIPortrait(parameters, screenWidth, borderPadding) + ScanScreenUIPortrait(parameters, screenWidth, borderPadding, windowSizeClass) } else { - ScanScreenUILandscape(parameters, screenHeight, borderPadding) + ScanScreenUILandscape(parameters, screenHeight, borderPadding, windowSizeClass) } } } @@ -369,7 +380,11 @@ class OSBARCScannerActivity : ComponentActivity() { @Composable fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, screenHeight:Dp, - borderPadding: Dp) { + borderPadding: Dp, + windowSizeClass: WindowSizeClass) { + + // determine if device is phone or tablet + val isPhone = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact Column( modifier = Modifier @@ -436,7 +451,12 @@ class OSBARCScannerActivity : ComponentActivity() { @Composable fun ScanScreenUILandscape(parameters: OSBARCScanParameters, screenHeight:Dp, - borderPadding: Dp) { + borderPadding: Dp, + windowSizeClass: WindowSizeClass) { + + // determine if device is phone or tablet + val isPhone = windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact + Row( modifier = Modifier .fillMaxSize(), @@ -616,7 +636,7 @@ class OSBARCScannerActivity : ComponentActivity() { val actionButtonsHeight = 48.dp val showTorch = camera.cameraInfo.hasFlashUnit() - val showScan = parameters.scanButton || true + val showScan = parameters.scanButton val buttonSpacing = if(showTorch && showScan) { verticalPadding.times(0.5f) } else { verticalPadding.times(0f) } From 39d5ee8eea0db58d1b06f1baecca353af3e48beb Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Mon, 4 Dec 2023 17:35:25 +0000 Subject: [PATCH 150/174] feat: implement UI for tablets (portrait and landscape) Context: According to Android's documentation, we should use the Window size classes to determine if the device the app is running on is a phone or a tablet. When in Portrait, we should use the window width to determine this (99.96% of phones will have a compact width). When in Landscape, the height of the window should be used (99.78% of phones will have a compact height). More info here: https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#window_size_classes, and here: https://developer.android.com/jetpack/compose/layouts/adaptive Why we are using the landscape UI (ScanScreenUILandscape) for tablets for both orientations, portrait and landscape: because for tablets, the arrangement of the elements in the screen is always the same, in a row, where the buttons are to the right of the scan area (rectangle). This way, we can avoid code repetition. References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../barcode/view/OSBARCScannerActivity.kt | 87 ++++++++++++++----- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 23b44de..b98d922 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -275,14 +275,28 @@ class OSBARCScannerActivity : ComponentActivity() { val screenWidth = configuration.screenWidthDp.dp val borderPadding = 32.dp + val textToRectPadding = 24.dp val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT if (isPortrait) { - ScanScreenUIPortrait(parameters, screenWidth, borderPadding, windowSizeClass) + // determine if device is phone or tablet + val isPhone = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact + if (isPhone) { + ScanScreenUIPortrait(parameters, screenWidth, borderPadding, textToRectPadding, true) + } + else { + ScanScreenUILandscape(parameters, (screenWidth / 2), borderPadding, textToRectPadding, isPhone = false, isPortrait = true) + } } else { - ScanScreenUILandscape(parameters, screenHeight, borderPadding, windowSizeClass) + // determine if device is phone or tablet + val isPhone = windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact + if (isPhone) { + ScanScreenUILandscape(parameters, screenHeight, borderPadding, textToRectPadding, isPhone = true, isPortrait = false) + } else { + ScanScreenUILandscape(parameters, screenHeight / 2, borderPadding, textToRectPadding, isPhone = false, isPortrait = false) + } } } } @@ -295,7 +309,11 @@ class OSBARCScannerActivity : ComponentActivity() { * @param verticalPadding the vertical padding for the whole view */ @Composable - fun ScanScreenAim(height: Dp, horizontalPadding: Dp, verticalPadding: Dp) { + fun ScanScreenAim( + height: Dp, horizontalPadding: Dp, verticalPadding: Dp, + isPhone: Boolean, + isPortrait: Boolean + ) { // padding from the rectangle to each corner val rectToCornerPadding = 16.dp @@ -318,8 +336,22 @@ class OSBARCScannerActivity : ComponentActivity() { // rectangle size is determined by removing the padding from the border of the screen // and the padding to the corners of the rectangle - val rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) - val rectHeight = canvasHeight - (verticalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + var rectWidth: Float + var rectHeight: Float + + if (isPhone) { // for phones + rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + rectHeight = canvasHeight - (verticalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + } else { // for tablets + if (isPortrait) { + rectWidth = (canvasWidth) - (horizontalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + rectHeight = rectWidth + } else { + rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + rectHeight = canvasHeight - (rectToCornerPadding.toPx() * 2) + } + } + val rectLeft = (canvasWidth - rectWidth) / 2 val rectTop = (canvasHeight - rectHeight) / 2 @@ -332,7 +364,7 @@ class OSBARCScannerActivity : ComponentActivity() { ) } clipPath(circlePath, clipOp = ClipOp.Difference) { - drawRect(color= ScannerBackgroundBlack) + drawRect(color = ScannerBackgroundBlack) } val aimTop = rectTop - rectToCornerPadding.toPx() @@ -381,11 +413,8 @@ class OSBARCScannerActivity : ComponentActivity() { fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, screenHeight:Dp, borderPadding: Dp, - windowSizeClass: WindowSizeClass) { - - // determine if device is phone or tablet - val isPhone = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact - + textToRectPadding: Dp, + isPhone: Boolean) { Column( modifier = Modifier .fillMaxSize(), @@ -415,11 +444,11 @@ class OSBARCScannerActivity : ComponentActivity() { ScanInstructions( modifier = Modifier .fillMaxWidth() - .padding(bottom = borderPadding), + .padding(bottom = textToRectPadding), parameters ) - ScanScreenAim(screenHeight, borderPadding, 0.dp) + ScanScreenAim(screenHeight, borderPadding, 0.dp, isPhone, true) } Box( @@ -443,7 +472,9 @@ class OSBARCScannerActivity : ComponentActivity() { } /** - * Composable function, responsible rendering the main UI in landscape mode + * Composable function, responsible rendering the main UI in landscape mode. + * This will also be used to for the UI of tablets in portrait, since the + * orientation of elements in the screen is the same for both orientations. * @param parameters the scan parameters * @param screenHeight the screen height * @param borderPadding the value for the border padding @@ -452,11 +483,9 @@ class OSBARCScannerActivity : ComponentActivity() { fun ScanScreenUILandscape(parameters: OSBARCScanParameters, screenHeight:Dp, borderPadding: Dp, - windowSizeClass: WindowSizeClass) { - - // determine if device is phone or tablet - val isPhone = windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact - + textToRectPadding: Dp, + isPhone: Boolean, + isPortrait: Boolean) { Row( modifier = Modifier .fillMaxSize(), @@ -477,14 +506,30 @@ class OSBARCScannerActivity : ComponentActivity() { verticalArrangement = Arrangement.Center ) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true) + .background(ScannerBackgroundBlack) + ) + ScanInstructions( modifier = Modifier .fillMaxWidth() - .padding(top = borderPadding), + .padding(top = borderPadding, bottom = if (isPhone) 0.dp else textToRectPadding), parameters ) - ScanScreenAim(screenHeight, 0.dp, borderPadding) + ScanScreenAim(screenHeight, 0.dp, borderPadding, isPhone, isPortrait) + + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = true) + .background(ScannerBackgroundBlack) + ) + + } Box( From c38cb15e5cb909aa15de2c22dee338147da1375c Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 5 Dec 2023 13:39:57 +0000 Subject: [PATCH 151/174] fix: properly pass vertical padding for frame when Portrait References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index b98d922..e9bc5f7 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -443,12 +443,11 @@ class OSBARCScannerActivity : ComponentActivity() { ScanInstructions( modifier = Modifier - .fillMaxWidth() - .padding(bottom = textToRectPadding), + .fillMaxWidth(), parameters ) - ScanScreenAim(screenHeight, borderPadding, 0.dp, isPhone, true) + ScanScreenAim(screenHeight, borderPadding, borderPadding, isPhone, true) } Box( From 88b342e2d8e3512f05217f8c66194b05e51876b9 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 5 Dec 2023 13:47:55 +0000 Subject: [PATCH 152/174] refactor: remove unused parameter References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../outsystems/plugins/barcode/view/OSBARCScannerActivity.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index e9bc5f7..9781d3a 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -283,7 +283,7 @@ class OSBARCScannerActivity : ComponentActivity() { // determine if device is phone or tablet val isPhone = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact if (isPhone) { - ScanScreenUIPortrait(parameters, screenWidth, borderPadding, textToRectPadding, true) + ScanScreenUIPortrait(parameters, screenWidth, borderPadding, true) } else { ScanScreenUILandscape(parameters, (screenWidth / 2), borderPadding, textToRectPadding, isPhone = false, isPortrait = true) @@ -413,7 +413,6 @@ class OSBARCScannerActivity : ComponentActivity() { fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, screenHeight:Dp, borderPadding: Dp, - textToRectPadding: Dp, isPhone: Boolean) { Column( modifier = Modifier From 0f2ff7d525e061a7ba39b2a67ab9e54780d5e241 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 5 Dec 2023 17:00:28 +0000 Subject: [PATCH 153/174] refactor: use variable from sizes for padding References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../plugins/barcode/view/OSBARCScannerActivity.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 2bae48c..0101e40 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -344,15 +344,15 @@ class OSBARCScannerActivity : ComponentActivity() { var rectHeight: Float if (isPhone) { // for phones - rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) - rectHeight = canvasHeight - (verticalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (ScannerAimRectCornerPadding.toPx() * 2) + rectHeight = canvasHeight - (verticalPadding.toPx() * 2) - (ScannerAimRectCornerPadding.toPx() * 2) } else { // for tablets if (isPortrait) { - rectWidth = (canvasWidth) - (horizontalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) + rectWidth = (canvasWidth) - (horizontalPadding.toPx() * 2) - (ScannerAimRectCornerPadding.toPx() * 2) rectHeight = rectWidth } else { - rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (rectToCornerPadding.toPx() * 2) - rectHeight = canvasHeight - (rectToCornerPadding.toPx() * 2) + rectWidth = canvasWidth - (horizontalPadding.toPx() * 2) - (ScannerAimRectCornerPadding.toPx() * 2) + rectHeight = canvasHeight - (ScannerAimRectCornerPadding.toPx() * 2) } } @@ -700,7 +700,6 @@ class OSBARCScannerActivity : ComponentActivity() { */ @Composable fun ScanActionButtons(parameters: OSBARCScanParameters, - verticalPadding: Dp, scanModifier: Modifier, torchModifier: Modifier) { From 5337d52fbb1c817dd7c74ce081b212cba1ed4261 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 5 Dec 2023 17:38:37 +0000 Subject: [PATCH 154/174] fix: revert previous refactor that introduced incorrect paddings on action buttons References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../plugins/barcode/view/OSBARCScannerActivity.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 0101e40..91e9c5a 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -700,22 +700,23 @@ class OSBARCScannerActivity : ComponentActivity() { */ @Composable fun ScanActionButtons(parameters: OSBARCScanParameters, + verticalPadding: Dp, scanModifier: Modifier, torchModifier: Modifier) { - + + val actionButtonsHeight = 48.dp val showTorch = camera.cameraInfo.hasFlashUnit() val showScan = parameters.scanButton - val buttonsVerticalDistance = ActionButtonsDistance val buttonSpacing = if(showTorch && showScan) - { buttonsVerticalDistance.times(0.5f) } else { buttonsVerticalDistance.times(0f) } + { verticalPadding.times(0.5f) } else { verticalPadding.times(0f) } // flashlight button if (showTorch) { TorchButton( torchModifier .padding(bottom = buttonSpacing) - .size(buttonsVerticalDistance), + .size(actionButtonsHeight), ) } @@ -724,7 +725,7 @@ class OSBARCScannerActivity : ComponentActivity() { ScanButton( scanModifier .padding(top = buttonSpacing) - .height(buttonsVerticalDistance), + .height(actionButtonsHeight), parameters.scanText) } } From d308f86725120b1862c17f20d771b9464e3a94a0 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 5 Dec 2023 17:39:35 +0000 Subject: [PATCH 155/174] chore: raise library version to 0.0.25 References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9eef316..1929628 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.24 + 0.0.25 From c797b5f225919617b61db175c4c2a63c8c2a7854 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 5 Dec 2023 19:08:04 +0000 Subject: [PATCH 156/174] refactor: use bitmap for MLKit too Context: Since we want to "crop" the image passed to analyze, we need to use a Bitmap object to represent the image, so that we can "crop" it. This wouldn't be possible using the ImageProxy object directly. References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../barcode/controller/OSBARCMLKitWrapper.kt | 74 ++++++++++++++----- .../controller/helper/OSBARCMLKitHelper.kt | 24 ++++-- .../helper/OSBARCMLKitHelperInterface.kt | 7 +- 3 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt index 8208c6f..0aada18 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt @@ -1,11 +1,15 @@ package com.outsystems.plugins.barcode.controller +import android.graphics.Bitmap +import android.graphics.ImageFormat +import android.graphics.Rect +import android.graphics.YuvImage import android.util.Log -import androidx.annotation.OptIn -import androidx.camera.core.ExperimentalGetImage import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelperInterface import com.outsystems.plugins.barcode.model.OSBARCError +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer /** * Wrapper class that implements the OSBARCScanLibraryInterface @@ -23,33 +27,65 @@ class OSBARCMLKitWrapper(private val helper: OSBARCMLKitHelperInterface): OSBARC * @param onSuccess - The code to be executed if the operation was successful. * @param onError - The code to be executed if the operation was not successful. */ - @OptIn(ExperimentalGetImage::class) override fun scanBarcode( + override fun scanBarcode( imageProxy: ImageProxy, onSuccess: (String) -> Unit, onError: (OSBARCError) -> Unit ) { try { - val mediaImage = imageProxy.image - if (mediaImage != null) { - helper.decodeImage(imageProxy, mediaImage, - { barcodes -> - var result: String? = null - if (barcodes.isNotEmpty()) { - result = barcodes.first().rawValue - } - if (!result.isNullOrEmpty()) { - onSuccess(result) - } - }, - { - onError(OSBARCError.MLKIT_LIBRARY_ERROR) + helper.decodeImage(imageProxy, imageProxyToBitmap(imageProxy), + { barcodes -> + var result: String? = null + if (barcodes.isNotEmpty()) { + result = barcodes.first().rawValue } - ) - } + if (!result.isNullOrEmpty()) { + onSuccess(result) + } + }, + { + onError(OSBARCError.MLKIT_LIBRARY_ERROR) + } + ) } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } onError(OSBARCError.MLKIT_LIBRARY_ERROR) } } + // Function to convert ImageProxy to Bitmap + private fun imageProxyToBitmap(image: ImageProxy): Bitmap { + + // get image data + val planes = image.planes + val yBuffer: ByteBuffer = planes[0].buffer + val uBuffer: ByteBuffer = planes[1].buffer + val vBuffer: ByteBuffer = planes[2].buffer + + // get image width and height + val imageWidth = image.width + val imageHeight = image.height + + // calculate image data size + val ySize = yBuffer.remaining() + val uSize = uBuffer.remaining() + val vSize = vBuffer.remaining() + + // use byte arrays for image data + val data = ByteArray(ySize + uSize + vSize) + yBuffer.get(data, 0, ySize) + uBuffer.get(data, ySize, uSize) + vBuffer.get(data, ySize + uSize, vSize) + + // create a YUV image + // ImageFormat.NV21 used because it's efficient and widely supported + val yuvImage = YuvImage(data, ImageFormat.NV21, imageWidth, imageHeight, null) + + // convert YUV to Bitmap + val out = ByteArrayOutputStream() + yuvImage.compressToJpeg(Rect(0, 0, imageWidth, imageHeight), 100, out) + val imageBytes = out.toByteArray() + return helper.bitmapFromImageBytes(imageBytes) + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt index dd4aa34..8899910 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt @@ -1,6 +1,7 @@ package com.outsystems.plugins.barcode.controller.helper -import android.media.Image +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.util.Log import androidx.camera.core.ImageProxy import com.google.mlkit.vision.barcode.BarcodeScannerOptions @@ -19,16 +20,29 @@ class OSBARCMLKitHelper: OSBARCMLKitHelperInterface { private const val LOG_TAG = "OSBARCMLKitHelper" } + /** + * Converts a ByteArray into a Bitmap using BitmapFactory + * @param imageBytes - ByteArray to convert + * @return the resulting bitmap. + */ + override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { + return BitmapFactory.decodeByteArray( + imageBytes, + 0, // use 0 in the offset to decode from the beginning of imageBytes + imageBytes.size // use byte array size as length because we want to decode the whole image + ) + } + /** * Scans an image looking for barcodes, using the ML Kit library. * @param imageProxy - ImageProxy object that represents the image to be analyzed. - * @param mediaImage - Image object that represents the image to be analyzed. + * @param imageBitmap - Bitmap object that represents the image to be analyzed. * @param onSuccess - The code to be executed if the operation was successful. * @param onError - The code to be executed if the operation was not successful. */ override fun decodeImage( imageProxy: ImageProxy, - mediaImage: Image, + imageBitmap: Bitmap, onSuccess: (MutableList) -> Unit, onError: () -> Unit ) { @@ -36,8 +50,8 @@ class OSBARCMLKitHelper: OSBARCMLKitHelperInterface { .enableAllPotentialBarcodes() .build() val scanner = BarcodeScanning.getClient(options) - val image = InputImage.fromMediaImage( - mediaImage, + val image = InputImage.fromBitmap( + imageBitmap, imageProxy.imageInfo.rotationDegrees ) runBlocking { diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt index 068d400..4d57fd3 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt @@ -1,16 +1,17 @@ package com.outsystems.plugins.barcode.controller.helper -import android.media.Image +import android.graphics.Bitmap import androidx.camera.core.ImageProxy import com.google.mlkit.vision.barcode.common.Barcode /** * Interface that provides the signature of the type's methods. */ -fun interface OSBARCMLKitHelperInterface { +interface OSBARCMLKitHelperInterface { + fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap fun decodeImage( imageProxy: ImageProxy, - mediaImage: Image, + imageBitmap: Bitmap, onSuccess: (MutableList) -> Unit, onError: () -> Unit ) From f02fc9d9a6d3227354d029144a01ac63956bc7f7 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 5 Dec 2023 20:00:31 +0000 Subject: [PATCH 157/174] feat: refactor code for barcode analysis Context: Since we want to "crop" the image passed to analyze, we need to use a Bitmap object to represent the image, so that we can "crop" it. This wouldn't be possible using the ImageProxy object directly. Since both libraries (ZXing and MLKit) now use a Bitmap, we might as well refactor the code and avoid code repetition. References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../controller/OSBARCBarcodeAnalyzer.kt | 44 ++++++++++ .../barcode/controller/OSBARCMLKitWrapper.kt | 43 +--------- .../controller/OSBARCScanLibraryInterface.kt | 2 + .../barcode/controller/OSBARCZXingWrapper.kt | 51 ++--------- .../controller/helper/OSBARCImageHelper.kt | 24 ++++++ .../helper/OSBARCImageHelperInterface.kt | 7 ++ .../controller/helper/OSBARCMLKitHelper.kt | 14 ---- .../helper/OSBARCMLKitHelperInterface.kt | 3 +- .../controller/helper/OSBARCZXingHelper.kt | 14 ---- .../helper/OSBARCZXingHelperInterface.kt | 1 - .../barcode/view/OSBARCScannerActivity.kt | 2 + .../plugins/barcode/ScanCodeTests.kt | 84 ++++++++++++++----- .../barcode/mocks/OSBARCImageHelperMock.kt | 11 +++ .../barcode/mocks/OSBARCMLKitHelperMock.kt | 5 +- .../barcode/mocks/OSBARCScanLibraryMock.kt | 2 + .../barcode/mocks/OSBARCZXingHelperMock.kt | 3 - 16 files changed, 167 insertions(+), 143 deletions(-) create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelper.kt create mode 100644 src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt create mode 100644 src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCImageHelperMock.kt diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index 8384578..2babbe8 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -1,10 +1,17 @@ package com.outsystems.plugins.barcode.controller +import android.graphics.Bitmap +import android.graphics.ImageFormat +import android.graphics.Rect +import android.graphics.YuvImage import android.util.Log import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy +import com.outsystems.plugins.barcode.controller.helper.OSBARCImageHelperInterface import com.outsystems.plugins.barcode.model.OSBARCError +import java.io.ByteArrayOutputStream import java.lang.Exception +import java.nio.ByteBuffer /** * This class is responsible for implementing the ImageAnalysis.Analyzer interface, @@ -12,6 +19,7 @@ import java.lang.Exception */ class OSBARCBarcodeAnalyzer( private val scanLibrary: OSBARCScanLibraryInterface, + private val imageHelper: OSBARCImageHelperInterface, private val onBarcodeScanned: (String) -> Unit, private val onScanningError: (OSBARCError) -> Unit ): ImageAnalysis.Analyzer { @@ -30,6 +38,7 @@ class OSBARCBarcodeAnalyzer( try { scanLibrary.scanBarcode( image, + imageProxyToBitmap(image), { onBarcodeScanned(it) }, @@ -43,4 +52,39 @@ class OSBARCBarcodeAnalyzer( } } + // Function to convert ImageProxy to Bitmap + private fun imageProxyToBitmap(image: ImageProxy): Bitmap { + + // get image data + val planes = image.planes + val yBuffer: ByteBuffer = planes[0].buffer + val uBuffer: ByteBuffer = planes[1].buffer + val vBuffer: ByteBuffer = planes[2].buffer + + // get image width and height + val imageWidth = image.width + val imageHeight = image.height + + // calculate image data size + val ySize = yBuffer.remaining() + val uSize = uBuffer.remaining() + val vSize = vBuffer.remaining() + + // use byte arrays for image data + val data = ByteArray(ySize + uSize + vSize) + yBuffer.get(data, 0, ySize) + uBuffer.get(data, ySize, uSize) + vBuffer.get(data, ySize + uSize, vSize) + + // create a YUV image + // ImageFormat.NV21 used because it's efficient and widely supported + val yuvImage = YuvImage(data, ImageFormat.NV21, imageWidth, imageHeight, null) + + // convert YUV to Bitmap + val out = ByteArrayOutputStream() + yuvImage.compressToJpeg(Rect(0, 0, imageWidth, imageHeight), 100, out) + val imageBytes = out.toByteArray() + return imageHelper.bitmapFromImageBytes(imageBytes) + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt index 0aada18..6fa7a5e 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCMLKitWrapper.kt @@ -1,15 +1,10 @@ package com.outsystems.plugins.barcode.controller import android.graphics.Bitmap -import android.graphics.ImageFormat -import android.graphics.Rect -import android.graphics.YuvImage import android.util.Log import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelperInterface import com.outsystems.plugins.barcode.model.OSBARCError -import java.io.ByteArrayOutputStream -import java.nio.ByteBuffer /** * Wrapper class that implements the OSBARCScanLibraryInterface @@ -29,11 +24,12 @@ class OSBARCMLKitWrapper(private val helper: OSBARCMLKitHelperInterface): OSBARC */ override fun scanBarcode( imageProxy: ImageProxy, + imageBitmap: Bitmap, onSuccess: (String) -> Unit, onError: (OSBARCError) -> Unit ) { try { - helper.decodeImage(imageProxy, imageProxyToBitmap(imageProxy), + helper.decodeImage(imageProxy, imageBitmap, { barcodes -> var result: String? = null if (barcodes.isNotEmpty()) { @@ -53,39 +49,4 @@ class OSBARCMLKitWrapper(private val helper: OSBARCMLKitHelperInterface): OSBARC } } - // Function to convert ImageProxy to Bitmap - private fun imageProxyToBitmap(image: ImageProxy): Bitmap { - - // get image data - val planes = image.planes - val yBuffer: ByteBuffer = planes[0].buffer - val uBuffer: ByteBuffer = planes[1].buffer - val vBuffer: ByteBuffer = planes[2].buffer - - // get image width and height - val imageWidth = image.width - val imageHeight = image.height - - // calculate image data size - val ySize = yBuffer.remaining() - val uSize = uBuffer.remaining() - val vSize = vBuffer.remaining() - - // use byte arrays for image data - val data = ByteArray(ySize + uSize + vSize) - yBuffer.get(data, 0, ySize) - uBuffer.get(data, ySize, uSize) - vBuffer.get(data, ySize + uSize, vSize) - - // create a YUV image - // ImageFormat.NV21 used because it's efficient and widely supported - val yuvImage = YuvImage(data, ImageFormat.NV21, imageWidth, imageHeight, null) - - // convert YUV to Bitmap - val out = ByteArrayOutputStream() - yuvImage.compressToJpeg(Rect(0, 0, imageWidth, imageHeight), 100, out) - val imageBytes = out.toByteArray() - return helper.bitmapFromImageBytes(imageBytes) - } - } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt index 4fdb5a4..6b2bd15 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCScanLibraryInterface.kt @@ -1,5 +1,6 @@ package com.outsystems.plugins.barcode.controller +import android.graphics.Bitmap import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.model.OSBARCError @@ -9,6 +10,7 @@ import com.outsystems.plugins.barcode.model.OSBARCError fun interface OSBARCScanLibraryInterface { fun scanBarcode( imageProxy: ImageProxy, + imageBitmap: Bitmap, onSuccess: (String) -> Unit, onError: (OSBARCError) -> Unit ) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt index 966a171..8f20aa2 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -1,15 +1,10 @@ package com.outsystems.plugins.barcode.controller import android.graphics.Bitmap -import android.graphics.ImageFormat -import android.graphics.Rect -import android.graphics.YuvImage import android.util.Log import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelperInterface import com.outsystems.plugins.barcode.model.OSBARCError -import java.io.ByteArrayOutputStream -import java.nio.ByteBuffer /** * Wrapper class that implements the OSBARCScanLibraryInterface @@ -29,23 +24,24 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR */ override fun scanBarcode( imageProxy: ImageProxy, + imageBitmap: Bitmap, onSuccess: (String) -> Unit, onError: (OSBARCError) -> Unit ) { try { - var imageBitmap = imageProxyToBitmap(imageProxy) + var resultBitmap = imageBitmap // rotate the image if it's in portrait mode (rotation = 90 or 270 degrees) val rotationDegrees = imageProxy.imageInfo.rotationDegrees if (rotationDegrees == 90 || rotationDegrees == 270) { - imageBitmap = helper.rotateBitmap(imageBitmap, rotationDegrees) + resultBitmap = helper.rotateBitmap(resultBitmap, rotationDegrees) } // scan image using zxing - val width = imageBitmap.width - val height = imageBitmap.height + val width = resultBitmap.width + val height = resultBitmap.height val pixels = IntArray(width * height) - imageBitmap.getPixels( + resultBitmap.getPixels( pixels, 0, // first index to write into pixels width, @@ -71,39 +67,4 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR } } - // Function to convert ImageProxy to Bitmap - private fun imageProxyToBitmap(image: ImageProxy): Bitmap { - - // get image data - val planes = image.planes - val yBuffer: ByteBuffer = planes[0].buffer - val uBuffer: ByteBuffer = planes[1].buffer - val vBuffer: ByteBuffer = planes[2].buffer - - // get image width and height - val imageWidth = image.width - val imageHeight = image.height - - // calculate image data size - val ySize = yBuffer.remaining() - val uSize = uBuffer.remaining() - val vSize = vBuffer.remaining() - - // use byte arrays for image data - val data = ByteArray(ySize + uSize + vSize) - yBuffer.get(data, 0, ySize) - uBuffer.get(data, ySize, uSize) - vBuffer.get(data, ySize + uSize, vSize) - - // create a YUV image - // ImageFormat.NV21 used because it's efficient and widely supported - val yuvImage = YuvImage(data, ImageFormat.NV21, imageWidth, imageHeight, null) - - // convert YUV to Bitmap - val out = ByteArrayOutputStream() - yuvImage.compressToJpeg(Rect(0, 0, imageWidth, imageHeight), 100, out) - val imageBytes = out.toByteArray() - return helper.bitmapFromImageBytes(imageBytes) - } - } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelper.kt new file mode 100644 index 0000000..945dcbb --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelper.kt @@ -0,0 +1,24 @@ +package com.outsystems.plugins.barcode.controller.helper + +import android.graphics.Bitmap +import android.graphics.BitmapFactory + +/** + * Helper class that implements the OSBARCImageHelperInterface + * and provides a method to convert a ByteArray to a Bitmap. + */ +class OSBARCImageHelper: OSBARCImageHelperInterface { + + /** + * Converts a ByteArray into a Bitmap using BitmapFactory + * @param imageBytes - ByteArray to convert + * @return the resulting bitmap. + */ + override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { + return BitmapFactory.decodeByteArray( + imageBytes, + 0, // use 0 in the offset to decode from the beginning of imageBytes + imageBytes.size // use byte array size as length because we want to decode the whole image + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt new file mode 100644 index 0000000..c1c2a48 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt @@ -0,0 +1,7 @@ +package com.outsystems.plugins.barcode.controller.helper + +import android.graphics.Bitmap + +fun interface OSBARCImageHelperInterface { + fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap +} \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt index 8899910..a86c098 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt @@ -1,7 +1,6 @@ package com.outsystems.plugins.barcode.controller.helper import android.graphics.Bitmap -import android.graphics.BitmapFactory import android.util.Log import androidx.camera.core.ImageProxy import com.google.mlkit.vision.barcode.BarcodeScannerOptions @@ -20,19 +19,6 @@ class OSBARCMLKitHelper: OSBARCMLKitHelperInterface { private const val LOG_TAG = "OSBARCMLKitHelper" } - /** - * Converts a ByteArray into a Bitmap using BitmapFactory - * @param imageBytes - ByteArray to convert - * @return the resulting bitmap. - */ - override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { - return BitmapFactory.decodeByteArray( - imageBytes, - 0, // use 0 in the offset to decode from the beginning of imageBytes - imageBytes.size // use byte array size as length because we want to decode the whole image - ) - } - /** * Scans an image looking for barcodes, using the ML Kit library. * @param imageProxy - ImageProxy object that represents the image to be analyzed. diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt index 4d57fd3..7d1d14e 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelperInterface.kt @@ -7,8 +7,7 @@ import com.google.mlkit.vision.barcode.common.Barcode /** * Interface that provides the signature of the type's methods. */ -interface OSBARCMLKitHelperInterface { - fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap +fun interface OSBARCMLKitHelperInterface { fun decodeImage( imageProxy: ImageProxy, imageBitmap: Bitmap, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt index 884077f..0a095d3 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelper.kt @@ -1,7 +1,6 @@ package com.outsystems.plugins.barcode.controller.helper import android.graphics.Bitmap -import android.graphics.BitmapFactory import android.graphics.Matrix import android.util.Log import com.google.zxing.BinaryBitmap @@ -22,19 +21,6 @@ class OSBARCZXingHelper: OSBARCZXingHelperInterface { private const val LOG_TAG = "OSBARCZXingHelper" } - /** - * Converts a ByteArray into a Bitmap using BitmapFactory - * @param imageBytes - ByteArray to convert - * @return the resulting bitmap. - */ - override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { - return BitmapFactory.decodeByteArray( - imageBytes, - 0, // use 0 in the offset to decode from the beginning of imageBytes - imageBytes.size // use byte array size as length because we want to decode the whole image - ) - } - /** * Rotates a bitmap, provided with the rotation degrees. * @param bitmap - Bitmap object to rotate diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelperInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelperInterface.kt index ac65746..a482d79 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelperInterface.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCZXingHelperInterface.kt @@ -6,7 +6,6 @@ import android.graphics.Bitmap * Interface that provides the signature of the type's methods. */ interface OSBARCZXingHelperInterface { - fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap fun rotateBitmap(bitmap: Bitmap, rotationDegrees: Int): Bitmap fun decodeImage( pixels: IntArray, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 91e9c5a..2cd7be0 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -81,6 +81,7 @@ import androidx.core.view.WindowCompat import com.outsystems.plugins.barcode.R import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory +import com.outsystems.plugins.barcode.controller.helper.OSBARCImageHelper import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError @@ -254,6 +255,7 @@ class OSBARCScannerActivity : ComponentActivity() { OSBARCZXingHelper(), OSBARCMLKitHelper() ), + OSBARCImageHelper(), { result -> processReadSuccess(result) }, diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index ab98663..17c249c 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -2,6 +2,7 @@ package com.outsystems.plugins.barcode import android.app.Activity import android.content.Intent +import android.graphics.Bitmap import android.media.Image import android.os.Bundle import androidx.camera.core.ImageInfo @@ -9,6 +10,8 @@ import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.OSBARCBarcodeAnalyzer import com.outsystems.plugins.barcode.controller.OSBARCController import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryFactory +import com.outsystems.plugins.barcode.controller.helper.OSBARCImageHelperInterface +import com.outsystems.plugins.barcode.mocks.OSBARCImageHelperMock import com.outsystems.plugins.barcode.mocks.OSBARCMLKitHelperMock import com.outsystems.plugins.barcode.mocks.OSBARCZXingHelperMock import com.outsystems.plugins.barcode.mocks.OSBARCScanLibraryMock @@ -32,6 +35,9 @@ class ScanCodeTests { private lateinit var mockImageInfo: ImageInfo private lateinit var planes: Array + private lateinit var imageHelperMock: OSBARCImageHelperInterface + private lateinit var mockBitmap: Bitmap + companion object { private const val SCAN_REQUEST_CODE = 112 private const val INVALID_REQUEST_CODE = 113 @@ -56,6 +62,9 @@ class ScanCodeTests { Mockito.doReturn(30).`when`(mockImageProxy).height Mockito.doReturn(30).`when`(mockByteBuffer).remaining() Mockito.doReturn(mockImageInfo).`when`(mockImageProxy).imageInfo + + imageHelperMock = OSBARCImageHelperMock() + mockBitmap = Mockito.mock(Bitmap::class.java) } @Test @@ -273,12 +282,13 @@ class ScanCodeTests { @Test fun givenScanLibrarySuccessWhenScanBarcodeThenSuccess() { - val mockImageProxy = Mockito.mock(ImageProxy::class.java) val scanLibMock = OSBARCScanLibraryMock().apply { success = true resultCode = RESULT_CODE } - OSBARCBarcodeAnalyzer(scanLibMock, + OSBARCBarcodeAnalyzer( + scanLibMock, + imageHelperMock, { assertEquals(RESULT_CODE, it) }, @@ -296,7 +306,9 @@ class ScanCodeTests { exception = false error = OSBARCError.SCANNING_GENERAL_ERROR } - OSBARCBarcodeAnalyzer(scanLibMock, + OSBARCBarcodeAnalyzer( + scanLibMock, + imageHelperMock, { fail() }, @@ -309,13 +321,14 @@ class ScanCodeTests { @Test fun givenScanLibraryZXingErrorWhenScanBarcodeThenZxingError() { - val mockImageProxy = Mockito.mock(ImageProxy::class.java) val scanLibMock = OSBARCScanLibraryMock().apply { success = false exception = false error = OSBARCError.ZXING_LIBRARY_ERROR } - OSBARCBarcodeAnalyzer(scanLibMock, + OSBARCBarcodeAnalyzer( + scanLibMock, + imageHelperMock, { fail() }, @@ -328,13 +341,14 @@ class ScanCodeTests { @Test fun givenScanLibraryMLKitErrorWhenScanBarcodeThenMLKitError() { - val mockImageProxy = Mockito.mock(ImageProxy::class.java) val scanLibMock = OSBARCScanLibraryMock().apply { success = false exception = false error = OSBARCError.MLKIT_LIBRARY_ERROR } - OSBARCBarcodeAnalyzer(scanLibMock, + OSBARCBarcodeAnalyzer( + scanLibMock, + imageHelperMock, { fail() }, @@ -352,7 +366,9 @@ class ScanCodeTests { success = false exception = true } - OSBARCBarcodeAnalyzer(scanLibMock, + OSBARCBarcodeAnalyzer( + scanLibMock, + imageHelperMock, { fail() }, @@ -373,7 +389,9 @@ class ScanCodeTests { Mockito.doReturn(90).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { assertEquals(SCAN_RESULT, it) }, @@ -393,7 +411,9 @@ class ScanCodeTests { Mockito.doReturn(270).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { assertEquals(SCAN_RESULT, it) }, @@ -413,7 +433,9 @@ class ScanCodeTests { Mockito.doReturn(0).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { assertEquals(SCAN_RESULT, it) }, @@ -436,7 +458,9 @@ class ScanCodeTests { Mockito.doReturn(0).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { fail() }, @@ -460,7 +484,9 @@ class ScanCodeTests { Mockito.doReturn(0).`when`(mockImageInfo).rotationDegrees // do the same for 270 and 0 to cover all cases - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { fail() }, @@ -486,7 +512,9 @@ class ScanCodeTests { val mockMediaImage = Mockito.mock(Image::class.java) Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { assertEquals(SCAN_RESULT, it) }, @@ -511,7 +539,9 @@ class ScanCodeTests { val mockMediaImage = Mockito.mock(Image::class.java) Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { // do nothing }, @@ -536,7 +566,9 @@ class ScanCodeTests { val mockMediaImage = Mockito.mock(Image::class.java) Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { // do nothing }, @@ -559,7 +591,9 @@ class ScanCodeTests { val mockMediaImage = Mockito.mock(Image::class.java) Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { // do nothing }, @@ -583,7 +617,9 @@ class ScanCodeTests { val mockMediaImage = Mockito.mock(Image::class.java) Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { fail() }, @@ -609,7 +645,9 @@ class ScanCodeTests { val mockMediaImage = Mockito.mock(Image::class.java) Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { fail() }, @@ -636,7 +674,9 @@ class ScanCodeTests { val mockMediaImage = Mockito.mock(Image::class.java) Mockito.doReturn(mockMediaImage).`when`(mockImageProxy).image - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { fail() }, @@ -659,7 +699,9 @@ class ScanCodeTests { Mockito.doReturn(null).`when`(mockImageProxy).image - wrapper.scanBarcode(mockImageProxy, + wrapper.scanBarcode( + mockImageProxy, + mockBitmap, { // do nothing }, diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCImageHelperMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCImageHelperMock.kt new file mode 100644 index 0000000..d67366f --- /dev/null +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCImageHelperMock.kt @@ -0,0 +1,11 @@ +package com.outsystems.plugins.barcode.mocks + +import android.graphics.Bitmap +import com.outsystems.plugins.barcode.controller.helper.OSBARCImageHelperInterface +import org.mockito.Mockito + +class OSBARCImageHelperMock: OSBARCImageHelperInterface { + override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { + return Mockito.mock(Bitmap::class.java) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt index 820cfde..7f7bf28 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCMLKitHelperMock.kt @@ -1,6 +1,6 @@ package com.outsystems.plugins.barcode.mocks -import android.media.Image +import android.graphics.Bitmap import androidx.camera.core.ImageProxy import com.google.mlkit.vision.barcode.common.Barcode import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelperInterface @@ -12,9 +12,10 @@ class OSBARCMLKitHelperMock: OSBARCMLKitHelperInterface { var scanResult: String? = null var exception = false var barcodesEmpty = true + override fun decodeImage( imageProxy: ImageProxy, - mediaImage: Image, + imageBitmap: Bitmap, onSuccess: (MutableList) -> Unit, onError: () -> Unit ) { diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCScanLibraryMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCScanLibraryMock.kt index edb0f85..b1445a0 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCScanLibraryMock.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCScanLibraryMock.kt @@ -1,5 +1,6 @@ package com.outsystems.plugins.barcode.mocks +import android.graphics.Bitmap import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.OSBARCScanLibraryInterface import com.outsystems.plugins.barcode.model.OSBARCError @@ -12,6 +13,7 @@ class OSBARCScanLibraryMock: OSBARCScanLibraryInterface { var error: OSBARCError = OSBARCError.SCANNING_GENERAL_ERROR override fun scanBarcode( imageProxy: ImageProxy, + imageBitmap: Bitmap, onSuccess: (String) -> Unit, onError: (OSBARCError) -> Unit ) { diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt index 2e15ed0..6b12777 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCZXingHelperMock.kt @@ -9,9 +9,6 @@ class OSBARCZXingHelperMock: OSBARCZXingHelperInterface { var scanResult = "" var success = true var exception = false - override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { - return Mockito.mock(Bitmap::class.java) - } override fun rotateBitmap(bitmap: Bitmap, rotationDegrees: Int): Bitmap { return Mockito.mock(Bitmap::class.java) From 8da61b1702a6eec4c5cc11e895e3512801f799ce Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Dec 2023 16:41:32 +0000 Subject: [PATCH 158/174] feat: crop image to analyze with size of frame Context: Since we only want to look for barcodes within the frame, we need to crop the image (bitmap) to analyze, before passing it to MLKit or ZXing. References: https://outsystemsrd.atlassian.net/browse/RMET-2912https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../controller/OSBARCBarcodeAnalyzer.kt | 56 ++++++++++++++++++- .../controller/helper/OSBARCImageHelper.kt | 26 +++++++++ .../helper/OSBARCImageHelperInterface.kt | 9 ++- .../barcode/view/OSBARCScannerActivity.kt | 44 +++++++++------ .../plugins/barcode/view/ui.theme/Sizes.kt | 3 + .../barcode/mocks/OSBARCImageHelperMock.kt | 10 ++++ 6 files changed, 127 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index 2babbe8..93b96bb 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -9,6 +9,8 @@ import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.helper.OSBARCImageHelperInterface import com.outsystems.plugins.barcode.model.OSBARCError +import com.outsystems.plugins.barcode.view.ui.theme.sizeRatioHeight +import com.outsystems.plugins.barcode.view.ui.theme.sizeRatioWidth import java.io.ByteArrayOutputStream import java.lang.Exception import java.nio.ByteBuffer @@ -24,6 +26,8 @@ class OSBARCBarcodeAnalyzer( private val onScanningError: (OSBARCError) -> Unit ): ImageAnalysis.Analyzer { + var isPortrait = true + companion object { private const val LOG_TAG = "OSBARCBarcodeAnalyzer" } @@ -36,9 +40,13 @@ class OSBARCBarcodeAnalyzer( */ override fun analyze(image: ImageProxy) { try { + + val bitmap = imageProxyToBitmap(image) + val croppedBitmap = cropBitmap(bitmap) + scanLibrary.scanBarcode( image, - imageProxyToBitmap(image), + croppedBitmap, { onBarcodeScanned(it) }, @@ -52,7 +60,17 @@ class OSBARCBarcodeAnalyzer( } } - // Function to convert ImageProxy to Bitmap + /** + * Converts an ImageProxy object to a Bitmap. + * Once we can compile this library with Kotlin 1.9.10, and consequently + * can use version 1.5.3 of the Compose Compiler, this method is unnecessary, + * since we will be able to use version 1.3.0 of the CameraX library + * and obtain the bitmap directly from the ImageProxy, using ImageProxy.toBitmap. + * More info: + * - https://developer.android.com/jetpack/androidx/releases/compose-kotlin + * - https://developer.android.com/jetpack/androidx/releases/camera#1.3.0 + * @param image - ImageProxy object that represents the image to be analyzed. + */ private fun imageProxyToBitmap(image: ImageProxy): Bitmap { // get image data @@ -87,4 +105,38 @@ class OSBARCBarcodeAnalyzer( return imageHelper.bitmapFromImageBytes(imageBytes) } + + /** + * Creates a cropped bitmap for the region of interest to scan, + * where the cropped image is approximately the same size as the frame + * shown in the UI, with some padding. + * As such, it will be a bit bigger than the rectangle in the UI + * It uses different ratios depending on the orientation of the device - portrait or landscape. + * @param bitmap - Bitmap object to crop. + */ + private fun cropBitmap(bitmap: Bitmap): Bitmap { + val rectWidth: Int + val rectHeight: Int + + if (isPortrait) { + // for portrait, the image is rotated + rectWidth = (bitmap.height * sizeRatioWidth).toInt() + rectHeight = rectWidth + } else { + rectWidth = (bitmap.width * sizeRatioWidth).toInt() + rectHeight = (bitmap.height * sizeRatioHeight).toInt() + } + + val rectLeft = (bitmap.width - rectWidth) / 2 + val rectTop = (bitmap.height - rectHeight) / 2 + + return imageHelper.createSubsetBitmapFromSource( + bitmap, + rectLeft, + rectTop, + rectWidth, + rectHeight + ) + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelper.kt index 945dcbb..5cbab21 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelper.kt @@ -21,4 +21,30 @@ class OSBARCImageHelper: OSBARCImageHelperInterface { imageBytes.size // use byte array size as length because we want to decode the whole image ) } + + /** + * Creates a bitmap that is a subset of the source bitmap, + * using the specified coordinates and size. + * @param source - Source bitmap to use. + * @param rectLeft - X coordinate where the bitmap starts. + * @param rectTop - Y coordinate where the bitmap starts. + * @param rectWidth - Width of the bitmap. + * @param rectHeight - Height of the bitmap. + * @return the resulting bitmap. + */ + override fun createSubsetBitmapFromSource( + source: Bitmap, + rectLeft: Int, + rectTop: Int, + rectWidth: Int, + rectHeight: Int + ): Bitmap { + return Bitmap.createBitmap( + source, + rectLeft, + rectTop, + rectWidth, + rectHeight + ) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt index c1c2a48..2114098 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt @@ -2,6 +2,13 @@ package com.outsystems.plugins.barcode.controller.helper import android.graphics.Bitmap -fun interface OSBARCImageHelperInterface { +interface OSBARCImageHelperInterface { fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap + fun createSubsetBitmapFromSource( + source: Bitmap, + rectLeft: Int, + rectTop: Int, + rectWidth: Int, + rectHeight: Int + ): Bitmap } \ No newline at end of file diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 2cd7be0..9508568 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -86,7 +86,6 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters -import com.outsystems.plugins.barcode.view.ui.theme.ActionButtonsDistance import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackgroundGray import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackgroundWhite @@ -117,6 +116,11 @@ class OSBARCScannerActivity : ComponentActivity() { private var showDialog by mutableStateOf(false) private var isScanning = false + private lateinit var barcodeAnalyzer: OSBARCBarcodeAnalyzer + + private var screenHeight: Dp = 0.dp + private var screenWidth: Dp = 0.dp + private data class Point(val x: Float, val y: Float) companion object { @@ -150,6 +154,21 @@ class OSBARCScannerActivity : ComponentActivity() { .requireLensFacing(if (parameters.cameraDirection == CAM_DIRECTION_FRONT) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK) .build() + barcodeAnalyzer = OSBARCBarcodeAnalyzer( + OSBARCScanLibraryFactory.createScanLibraryWrapper( + parameters.androidScanningLibrary ?: "", + OSBARCZXingHelper(), + OSBARCMLKitHelper() + ), + OSBARCImageHelper(), + { result -> + processReadSuccess(result) + }, + { + processReadError(it) + } + ) + setContent { // to know if device is phone or tablet @@ -249,20 +268,7 @@ class OSBARCScannerActivity : ComponentActivity() { .build() imageAnalysis.setAnalyzer( ContextCompat.getMainExecutor(context), - OSBARCBarcodeAnalyzer( - OSBARCScanLibraryFactory.createScanLibraryWrapper( - parameters.androidScanningLibrary ?: "", - OSBARCZXingHelper(), - OSBARCMLKitHelper() - ), - OSBARCImageHelper(), - { result -> - processReadSuccess(result) - }, - { - processReadError(it) - } - ) + barcodeAnalyzer ) try { camera = cameraProviderFuture.get().bindToLifecycle( @@ -283,8 +289,8 @@ class OSBARCScannerActivity : ComponentActivity() { // actual UI on top of the camera stream val configuration = LocalConfiguration.current - val screenHeight = configuration.screenHeightDp.dp - val screenWidth = configuration.screenWidthDp.dp + screenHeight = configuration.screenHeightDp.dp + screenWidth = configuration.screenWidthDp.dp val borderPadding = 32.dp val textToRectPadding = 24.dp @@ -361,6 +367,8 @@ class OSBARCScannerActivity : ComponentActivity() { val rectLeft = (canvasWidth - rectWidth) / 2 val rectTop = (canvasHeight - rectHeight) / 2 + barcodeAnalyzer.isPortrait = isPortrait + val circlePath = Path().apply { addRoundRect( RoundRect( @@ -441,7 +449,7 @@ class OSBARCScannerActivity : ComponentActivity() { */ @Composable fun ScanScreenUIPortrait(parameters: OSBARCScanParameters, - screenHeight:Dp, + screenHeight: Dp, borderPadding: Dp, isPhone: Boolean) { Column( diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt index a61a1a0..2dc37bc 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt @@ -12,3 +12,6 @@ const val ScannerAimStrokeWidth = 3f val ScanButtonCornerRadius = 4f.dp val ScanButtonStrokeWidth = 1f.dp val ActionButtonsDistance = 48f.dp + +val sizeRatioWidth = 0.6 +val sizeRatioHeight = 0.5 diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCImageHelperMock.kt b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCImageHelperMock.kt index d67366f..dd5b35f 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCImageHelperMock.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/mocks/OSBARCImageHelperMock.kt @@ -8,4 +8,14 @@ class OSBARCImageHelperMock: OSBARCImageHelperInterface { override fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap { return Mockito.mock(Bitmap::class.java) } + + override fun createSubsetBitmapFromSource( + source: Bitmap, + rectLeft: Int, + rectTop: Int, + rectWidth: Int, + rectHeight: Int + ): Bitmap { + return Mockito.mock(Bitmap::class.java) + } } \ No newline at end of file From c1f0d348a33609a3da45c2aad80598d6c6e5a5a7 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Dec 2023 16:42:20 +0000 Subject: [PATCH 159/174] chore: raise library version to 0.0.26 References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1929628..4612854 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.25 + 0.0.26 From 55c3288c5e6574464641f472fd7dffd80f94df60 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Dec 2023 16:51:23 +0000 Subject: [PATCH 160/174] refactor: use variable from Sizes.kt References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../plugins/barcode/view/OSBARCScannerActivity.kt | 6 +++--- .../com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 9508568..d384daa 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -86,6 +86,7 @@ import com.outsystems.plugins.barcode.controller.helper.OSBARCMLKitHelper import com.outsystems.plugins.barcode.controller.helper.OSBARCZXingHelper import com.outsystems.plugins.barcode.model.OSBARCError import com.outsystems.plugins.barcode.model.OSBARCScanParameters +import com.outsystems.plugins.barcode.view.ui.theme.ActionButtonsDistance import com.outsystems.plugins.barcode.view.ui.theme.BarcodeScannerTheme import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackgroundGray import com.outsystems.plugins.barcode.view.ui.theme.ButtonsBackgroundWhite @@ -714,7 +715,6 @@ class OSBARCScannerActivity : ComponentActivity() { scanModifier: Modifier, torchModifier: Modifier) { - val actionButtonsHeight = 48.dp val showTorch = camera.cameraInfo.hasFlashUnit() val showScan = parameters.scanButton @@ -726,7 +726,7 @@ class OSBARCScannerActivity : ComponentActivity() { TorchButton( torchModifier .padding(bottom = buttonSpacing) - .size(actionButtonsHeight), + .size(ActionButtonsDistance), ) } @@ -735,7 +735,7 @@ class OSBARCScannerActivity : ComponentActivity() { ScanButton( scanModifier .padding(top = buttonSpacing) - .height(actionButtonsHeight), + .height(ActionButtonsDistance), parameters.scanText) } } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt index 2dc37bc..0baabaa 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt @@ -13,5 +13,5 @@ val ScanButtonCornerRadius = 4f.dp val ScanButtonStrokeWidth = 1f.dp val ActionButtonsDistance = 48f.dp -val sizeRatioWidth = 0.6 -val sizeRatioHeight = 0.5 +val SizeRatioWidth = 0.6 +val SizeRatioHeight = 0.5 From b5539566abe6df9137236218bea0cf8c37ab4fa2 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Dec 2023 17:21:08 +0000 Subject: [PATCH 161/174] fix: use correct variables References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- pom.xml | 2 +- .../barcode/controller/OSBARCBarcodeAnalyzer.kt | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 4612854..3da5758 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.26 + 0.0.27 diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index 93b96bb..e528264 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -9,8 +9,8 @@ import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import com.outsystems.plugins.barcode.controller.helper.OSBARCImageHelperInterface import com.outsystems.plugins.barcode.model.OSBARCError -import com.outsystems.plugins.barcode.view.ui.theme.sizeRatioHeight -import com.outsystems.plugins.barcode.view.ui.theme.sizeRatioWidth +import com.outsystems.plugins.barcode.view.ui.theme.SizeRatioHeight +import com.outsystems.plugins.barcode.view.ui.theme.SizeRatioWidth import java.io.ByteArrayOutputStream import java.lang.Exception import java.nio.ByteBuffer @@ -120,11 +120,11 @@ class OSBARCBarcodeAnalyzer( if (isPortrait) { // for portrait, the image is rotated - rectWidth = (bitmap.height * sizeRatioWidth).toInt() + rectWidth = (bitmap.height * SizeRatioWidth).toInt() rectHeight = rectWidth } else { - rectWidth = (bitmap.width * sizeRatioWidth).toInt() - rectHeight = (bitmap.height * sizeRatioHeight).toInt() + rectWidth = (bitmap.width * SizeRatioWidth).toInt() + rectHeight = (bitmap.height * SizeRatioHeight).toInt() } val rectLeft = (bitmap.width - rectWidth) / 2 From f89fb9c9403d7593656235f7e52498b362b7ae62 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Dec 2023 17:32:39 +0000 Subject: [PATCH 162/174] refactor: move code to another method to lower cognitive complexity References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../barcode/view/OSBARCScannerActivity.kt | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index d384daa..31cacd2 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -288,34 +288,40 @@ class OSBARCScannerActivity : ComponentActivity() { modifier = Modifier.fillMaxSize() ) - // actual UI on top of the camera stream - val configuration = LocalConfiguration.current - screenHeight = configuration.screenHeightDp.dp - screenWidth = configuration.screenWidthDp.dp + ScanScreenUI(parameters, windowSizeClass) - val borderPadding = 32.dp - val textToRectPadding = 24.dp - - val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT + } + } - if (isPortrait) { - // determine if device is phone or tablet - val isPhone = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact - if (isPhone) { - ScanScreenUIPortrait(parameters, screenWidth, borderPadding, true) - } - else { - ScanScreenUILandscape(parameters, (screenWidth / 2), borderPadding, textToRectPadding, isPhone = false, isPortrait = true) - } + @Composable + fun ScanScreenUI(parameters: OSBARCScanParameters, windowSizeClass: WindowSizeClass) { + // actual UI on top of the camera stream + val configuration = LocalConfiguration.current + screenHeight = configuration.screenHeightDp.dp + screenWidth = configuration.screenWidthDp.dp + + val borderPadding = 32.dp + val textToRectPadding = 24.dp + + val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT + + if (isPortrait) { + // determine if device is phone or tablet + val isPhone = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact + if (isPhone) { + ScanScreenUIPortrait(parameters, screenWidth, borderPadding, true) } else { - // determine if device is phone or tablet - val isPhone = windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact - if (isPhone) { - ScanScreenUILandscape(parameters, screenHeight, borderPadding, textToRectPadding, isPhone = true, isPortrait = false) - } else { - ScanScreenUILandscape(parameters, screenHeight / 2, borderPadding, textToRectPadding, isPhone = false, isPortrait = false) - } + ScanScreenUILandscape(parameters, (screenWidth / 2), borderPadding, textToRectPadding, isPhone = false, isPortrait = true) + } + } + else { + // determine if device is phone or tablet + val isPhone = windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact + if (isPhone) { + ScanScreenUILandscape(parameters, screenHeight, borderPadding, textToRectPadding, isPhone = true, isPortrait = false) + } else { + ScanScreenUILandscape(parameters, screenHeight / 2, borderPadding, textToRectPadding, isPhone = false, isPortrait = false) } } } From 21867325f5f557fa274a7c739486faf922007248 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Dec 2023 17:33:03 +0000 Subject: [PATCH 163/174] chore: raise lib version to 0.0.28 References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3da5758..b551b92 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.27 + 0.0.28 From a50f6584129918a22a748e2fd64e1a969a6fd45b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Dec 2023 17:38:06 +0000 Subject: [PATCH 164/174] feat: add missing test cases References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../plugins/barcode/ScanCodeTests.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 17c249c..5a98be8 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -379,6 +379,46 @@ class ScanCodeTests { ).analyze(mockImageProxy) } + @Test + fun givenScanPhoneInPortraitWhenScanBarcodeThenSuccess() { + val scanLibMock = OSBARCScanLibraryMock().apply { + success = true + resultCode = RESULT_CODE + } + OSBARCBarcodeAnalyzer( + scanLibMock, + imageHelperMock, + { + assertEquals(RESULT_CODE, it) + }, + { + fail() + } + ).apply { + isPortrait = true + }.analyze(mockImageProxy) + } + + @Test + fun givenScanPhoneInLandscapeWhenScanBarcodeThenSuccess() { + val scanLibMock = OSBARCScanLibraryMock().apply { + success = true + resultCode = RESULT_CODE + } + OSBARCBarcodeAnalyzer( + scanLibMock, + imageHelperMock, + { + assertEquals(RESULT_CODE, it) + }, + { + fail() + } + ).apply { + isPortrait = false + }.analyze(mockImageProxy) + } + @Test fun givenImage90DegreesWhenZXingScanThenSuccess() { val wrapper = OSBARCScanLibraryFactory.createScanLibraryWrapper( From a5424c8c01f23c98a4f707929c964920f7bc31fb Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Dec 2023 17:56:17 +0000 Subject: [PATCH 165/174] chore: update changelog References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51269ef..df3019d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 06-12-2023 +Android - Implement scanning only the frame area (Portrait, Landscape, Adaptive) (https://outsystemsrd.atlassian.net/browse/RMET-2912) + +### 06-12-2023 +Android - Implement Scanner screen for Tablets (Portrait, Landscape, Adaptive) (https://outsystemsrd.atlassian.net/browse/RMET-2912) + ### 30-11-2023 Android - Implement Scanner screen for Phones (Portrait, Landscape, Adaptive) (https://outsystemsrd.atlassian.net/browse/RMET-2770) From 75a1eb0b6ee8ab649d65d20a21e08c83bb3b96ac Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 6 Dec 2023 19:47:07 +0000 Subject: [PATCH 166/174] refactor: inline parameter References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index e528264..19f767b 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -40,13 +40,9 @@ class OSBARCBarcodeAnalyzer( */ override fun analyze(image: ImageProxy) { try { - - val bitmap = imageProxyToBitmap(image) - val croppedBitmap = cropBitmap(bitmap) - scanLibrary.scanBarcode( image, - croppedBitmap, + cropBitmap(imageProxyToBitmap(image)), { onBarcodeScanned(it) }, From 177b8830bf6e3f529ce6dfa1e2e273dd67910352 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 7 Dec 2023 09:38:56 +0000 Subject: [PATCH 167/174] refactor: remove runBlocking Context: The call to scanner.process is asynchronous, as it should be. As such, we don't need to use runBlocking here. References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../controller/helper/OSBARCMLKitHelper.kt | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt index a86c098..7941950 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt @@ -40,19 +40,17 @@ class OSBARCMLKitHelper: OSBARCMLKitHelperInterface { imageBitmap, imageProxy.imageInfo.rotationDegrees ) - runBlocking { - scanner.process(image) - .addOnSuccessListener { barcodes -> - onSuccess(barcodes) - } - .addOnFailureListener { e -> - e.message?.let { Log.e(LOG_TAG, it) } - onError() - } - .addOnCompleteListener { - imageProxy.close() - } - } + scanner.process(image) + .addOnSuccessListener { barcodes -> + onSuccess(barcodes) + } + .addOnFailureListener { e -> + e.message?.let { Log.e(LOG_TAG, it) } + onError() + } + .addOnCompleteListener { + imageProxy.close() + } } } \ No newline at end of file From b9cf5348790e34c05df00085a4c3f3a66bd81e6e Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 7 Dec 2023 09:48:19 +0000 Subject: [PATCH 168/174] refactor: use variable from sizes and rename method References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../barcode/view/OSBARCScannerActivity.kt | 22 +++++++++---------- .../plugins/barcode/view/ui.theme/Sizes.kt | 1 + 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index 31cacd2..d0e1944 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -104,6 +104,7 @@ import com.outsystems.plugins.barcode.view.ui.theme.ScannerAimRectCornerPadding import com.outsystems.plugins.barcode.view.ui.theme.ScannerAimStrokeWidth import com.outsystems.plugins.barcode.view.ui.theme.ScannerBackgroundBlack import com.outsystems.plugins.barcode.view.ui.theme.ScannerBorderPadding +import com.outsystems.plugins.barcode.view.ui.theme.TextToRectPadding /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -300,28 +301,25 @@ class OSBARCScannerActivity : ComponentActivity() { screenHeight = configuration.screenHeightDp.dp screenWidth = configuration.screenWidthDp.dp - val borderPadding = 32.dp - val textToRectPadding = 24.dp - val isPortrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT if (isPortrait) { // determine if device is phone or tablet val isPhone = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact if (isPhone) { - ScanScreenUIPortrait(parameters, screenWidth, borderPadding, true) + ScanScreenUIPortrait(parameters, screenWidth, ScannerBorderPadding, true) } else { - ScanScreenUILandscape(parameters, (screenWidth / 2), borderPadding, textToRectPadding, isPhone = false, isPortrait = true) + ScanScreenUILandscape(parameters, (screenWidth / 2), ScannerBorderPadding, TextToRectPadding, isPhone = false, isPortrait = true) } } else { // determine if device is phone or tablet val isPhone = windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact if (isPhone) { - ScanScreenUILandscape(parameters, screenHeight, borderPadding, textToRectPadding, isPhone = true, isPortrait = false) + ScanScreenUILandscape(parameters, screenHeight, ScannerBorderPadding, TextToRectPadding, isPhone = true, isPortrait = false) } else { - ScanScreenUILandscape(parameters, screenHeight / 2, borderPadding, textToRectPadding, isPhone = false, isPortrait = false) + ScanScreenUILandscape(parameters, screenHeight / 2, ScannerBorderPadding, TextToRectPadding, isPhone = false, isPortrait = false) } } } @@ -396,7 +394,7 @@ class OSBARCScannerActivity : ComponentActivity() { val aimPath = Path() // top left - AddCornerToAimPath( + addCornerToAimPath( aimPath, Point(aimLeft + aimLength, aimTop), Point(aimLeft + radius, aimTop), @@ -405,7 +403,7 @@ class OSBARCScannerActivity : ComponentActivity() { Point(aimLeft, aimTop + aimLength) ) // bottom left - AddCornerToAimPath( + addCornerToAimPath( aimPath, Point(aimLeft, aimBottom - aimLength), Point(aimLeft, aimBottom - radius), @@ -414,7 +412,7 @@ class OSBARCScannerActivity : ComponentActivity() { Point(aimLeft + aimLength, aimBottom) ) // bottom right - AddCornerToAimPath( + addCornerToAimPath( aimPath, Point(aimRight - aimLength, aimBottom), Point(aimRight - radius, aimBottom), @@ -423,7 +421,7 @@ class OSBARCScannerActivity : ComponentActivity() { Point(aimRight, aimBottom - aimLength) ) // top right - AddCornerToAimPath( + addCornerToAimPath( aimPath, Point(aimRight, aimTop + aimLength), Point(aimRight, aimTop + radius), @@ -436,7 +434,7 @@ class OSBARCScannerActivity : ComponentActivity() { ) } - private fun AddCornerToAimPath(path: Path, + private fun addCornerToAimPath(path: Path, startPoint: Point, startCornerPoint: Point, controlPoint: Point, diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt index 0baabaa..01d1c20 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt @@ -4,6 +4,7 @@ import androidx.compose.ui.unit.dp val NoPadding = 0f.dp val ScannerBorderPadding = 32f.dp +val TextToRectPadding = 24f.dp val ScannerAimRectCornerPadding = 16f.dp val ScannerAimCornerLength = 50f.dp From bfa1cad282560e84c10146a92d56ae9ba481c5d8 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 7 Dec 2023 10:32:12 +0000 Subject: [PATCH 169/174] chore: add documentation that was missing References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- .../controller/helper/OSBARCImageHelperInterface.kt | 3 +++ .../barcode/controller/helper/OSBARCMLKitHelper.kt | 1 - .../plugins/barcode/view/OSBARCScannerActivity.kt | 8 ++++++++ .../com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt | 4 ++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt index 2114098..ea10b9a 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCImageHelperInterface.kt @@ -2,6 +2,9 @@ package com.outsystems.plugins.barcode.controller.helper import android.graphics.Bitmap +/** + * Interface that provides the signature of the type's methods. + */ interface OSBARCImageHelperInterface { fun bitmapFromImageBytes(imageBytes: ByteArray): Bitmap fun createSubsetBitmapFromSource( diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt index 7941950..407c28b 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt @@ -7,7 +7,6 @@ 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 kotlinx.coroutines.runBlocking /** * Helper class that implements the OSBARCMLKitHelperInterface diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index d0e1944..d3a32ba 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -193,6 +193,8 @@ class OSBARCScannerActivity : ComponentActivity() { /** * Composable function, responsible for declaring the UI of the screen, * as well as creating an instance of OSBARCBarcodeAnalyzer for image analysis. + * @param parameters the scan parameters + * @param windowSizeClass WindowSizeClass object to determine device type - phone or tablet */ @Composable fun ScanScreen(parameters: OSBARCScanParameters, windowSizeClass: WindowSizeClass) { @@ -294,6 +296,12 @@ class OSBARCScannerActivity : ComponentActivity() { } } + /** + * Composable function, responsible for determining which UI + * should be rendered: portrait or landscape + * @param parameters the scan parameters + * @param windowSizeClass WindowSizeClass object to determine device type - phone or tablet + */ @Composable fun ScanScreenUI(parameters: OSBARCScanParameters, windowSizeClass: WindowSizeClass) { // actual UI on top of the camera stream diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt index 01d1c20..b051907 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/ui.theme/Sizes.kt @@ -14,5 +14,5 @@ val ScanButtonCornerRadius = 4f.dp val ScanButtonStrokeWidth = 1f.dp val ActionButtonsDistance = 48f.dp -val SizeRatioWidth = 0.6 -val SizeRatioHeight = 0.5 +const val SizeRatioWidth = 0.6 +const val SizeRatioHeight = 0.5 From 84271807fd4d9f550718645b15fe093682c21b1e Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Thu, 7 Dec 2023 16:42:38 +0000 Subject: [PATCH 170/174] chore: raise lib version to 0.0.29 References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b551b92..af24ad8 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.28 + 0.0.29 From 7ed21ba872f869cfdbbf77caf413a481e7102f19 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Wed, 20 Dec 2023 00:52:17 +0000 Subject: [PATCH 171/174] feat: don't use main thread for imageAnalysis Context: Context: In Android, when working with bitmaps, which is a type of image processing, it is recommended to use coroutines so that the main UI thread isn't blocked. References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- pom.xml | 2 +- .../plugins/barcode/view/OSBARCScannerActivity.kt | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index af24ad8..d64c106 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.29 + 0.0.31 diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt index d3a32ba..d34dd47 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -105,6 +105,8 @@ import com.outsystems.plugins.barcode.view.ui.theme.ScannerAimStrokeWidth import com.outsystems.plugins.barcode.view.ui.theme.ScannerBackgroundBlack import com.outsystems.plugins.barcode.view.ui.theme.ScannerBorderPadding import com.outsystems.plugins.barcode.view.ui.theme.TextToRectPadding +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors /** * This class is responsible for implementing the UI of the scanning screen using Jetpack Compose. @@ -117,6 +119,7 @@ class OSBARCScannerActivity : ComponentActivity() { private var permissionRequestCount = 0 private var showDialog by mutableStateOf(false) private var isScanning = false + private lateinit var cameraExecutor: ExecutorService private lateinit var barcodeAnalyzer: OSBARCBarcodeAnalyzer @@ -142,6 +145,8 @@ class OSBARCScannerActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + cameraExecutor = Executors.newSingleThreadExecutor() + val parameters = intent.extras?.getSerializable(SCAN_PARAMETERS) as OSBARCScanParameters // possibly lock orientation, the screen is adaptive by default @@ -190,6 +195,11 @@ class OSBARCScannerActivity : ComponentActivity() { showDialog = !hasCameraPermission(this.applicationContext) } + override fun onDestroy() { + super.onDestroy() + cameraExecutor.shutdown() + } + /** * Composable function, responsible for declaring the UI of the screen, * as well as creating an instance of OSBARCBarcodeAnalyzer for image analysis. @@ -271,7 +281,7 @@ class OSBARCScannerActivity : ComponentActivity() { .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) .build() imageAnalysis.setAnalyzer( - ContextCompat.getMainExecutor(context), + cameraExecutor, barcodeAnalyzer ) try { From 6331dec65089b952ed88578ca148d549a257557b Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Fri, 22 Dec 2023 15:21:33 +0000 Subject: [PATCH 172/174] refactor: only close imageProxy once in 1 place References: https://outsystemsrd.atlassian.net/browse/RMET-2912 --- CHANGELOG.md | 3 +++ pom.xml | 2 +- .../plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt | 1 + .../plugins/barcode/controller/OSBARCZXingWrapper.kt | 2 -- .../plugins/barcode/controller/helper/OSBARCMLKitHelper.kt | 3 --- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df3019d..b5391b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +### 19-12-2023 +Android - Run image analysis outside of the main/UI thread (https://outsystemsrd.atlassian.net/browse/RMET-2912) + ### 06-12-2023 Android - Implement scanning only the frame area (Portrait, Landscape, Adaptive) (https://outsystemsrd.atlassian.net/browse/RMET-2912) diff --git a/pom.xml b/pom.xml index d64c106..f92c375 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.31 + 0.0.32 diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt index 19f767b..02b99b0 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt @@ -54,6 +54,7 @@ class OSBARCBarcodeAnalyzer( e.message?.let { Log.e(LOG_TAG, it) } onScanningError(OSBARCError.SCANNING_GENERAL_ERROR) } + image.close() } /** diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt index 8f20aa2..9fe2a19 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCZXingWrapper.kt @@ -62,8 +62,6 @@ class OSBARCZXingWrapper(private val helper: OSBARCZXingHelperInterface) : OSBAR } catch (e: Exception) { e.message?.let { Log.e(LOG_TAG, it) } onError(OSBARCError.ZXING_LIBRARY_ERROR) - } finally { - imageProxy.close() } } diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt index 407c28b..e86212a 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/helper/OSBARCMLKitHelper.kt @@ -47,9 +47,6 @@ class OSBARCMLKitHelper: OSBARCMLKitHelperInterface { e.message?.let { Log.e(LOG_TAG, it) } onError() } - .addOnCompleteListener { - imageProxy.close() - } } } \ No newline at end of file From b46cda25e5c9b077f20ab68f68cb08b92d3a0ab4 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 9 Jan 2024 15:31:25 +0000 Subject: [PATCH 173/174] refactor: update error codes and messages References: https://outsystemsrd.atlassian.net/browse/RMET-3037 --- .../outsystems/plugins/barcode/model/OSBARCError.kt | 10 +++++----- .../com/outsystems/plugins/barcode/ScanCodeTests.kt | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt index 65a9c21..2ac4863 100644 --- a/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt @@ -4,10 +4,10 @@ package com.outsystems.plugins.barcode.model * Enum class that holds the library's errors. */ enum class OSBARCError(val code: Int, val description: String) { - CAMERA_PERMISSION_DENIED_ERROR(7, "Scanning cancelled due to missing camera permissions."), - INVALID_PARAMETERS_ERROR(10, "Scanning parameters are invalid."), - SCAN_CANCELLED_ERROR(6, "Scanning cancelled."), SCANNING_GENERAL_ERROR(4, "Error while trying to scan code."), - ZXING_LIBRARY_ERROR(11, "There was an error scanning the barcode with ZXing."), - MLKIT_LIBRARY_ERROR(12, "There was an error scanning the barcode with ML Kit.") + SCAN_CANCELLED_ERROR(6, "Couldn't scan because the process was cancelled."), + CAMERA_PERMISSION_DENIED_ERROR(7, "Couldn't scan because camera access wasn't provided. Check your camera permissions and try again."), + INVALID_PARAMETERS_ERROR(8, "Scanning parameters are invalid."), + ZXING_LIBRARY_ERROR(9, "There was an error scanning the barcode with ZXing."), + MLKIT_LIBRARY_ERROR(10, "There was an error scanning the barcode with ML Kit.") } \ No newline at end of file diff --git a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt index 5a98be8..7a69b3a 100644 --- a/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -42,6 +42,7 @@ class ScanCodeTests { private const val SCAN_REQUEST_CODE = 112 private const val INVALID_REQUEST_CODE = 113 private const val INVALID_RESULT_CODE = 9 + private const val GENERAL_ERROR_CODE = 4 private const val SCAN_RESULT = "scanResult" private const val RESULT_CODE = "myCode" } @@ -168,7 +169,7 @@ class ScanCodeTests { Mockito.doReturn(null).`when`(mockBundle).getString(SCAN_RESULT) val barcodeController = OSBARCController() - barcodeController.handleActivityResult(SCAN_REQUEST_CODE, INVALID_RESULT_CODE, mockIntent, + barcodeController.handleActivityResult(SCAN_REQUEST_CODE, GENERAL_ERROR_CODE, mockIntent, { fail() }, From d0d3a7e23ac80d14f536d757fde4df5eb560cd36 Mon Sep 17 00:00:00 2001 From: Alexandre Jacinto Date: Tue, 9 Jan 2024 15:35:50 +0000 Subject: [PATCH 174/174] chore(release): raise to version 1.0.0 References: https://outsystemsrd.atlassian.net/browse/RMET-3037 --- CHANGELOG.md | 5 +++++ pom.xml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5391b8..fa3fd63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ The changes documented here do not include those from the original repository. ## [Unreleased] +## [1.0.0] + +### 09-01-2024 +Android - Udpate error codes and messages (https://outsystemsrd.atlassian.net/browse/RMET-3037) + ### 19-12-2023 Android - Run image analysis outside of the main/UI thread (https://outsystemsrd.atlassian.net/browse/RMET-2912) diff --git a/pom.xml b/pom.xml index f92c375..38da1b1 100644 --- a/pom.xml +++ b/pom.xml @@ -7,5 +7,5 @@ 4.0.0 com.github.outsystems osbarcode-android - 0.0.32 + 1.0.0