diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index eb6bd6b..4d494d1 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -14,13 +14,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: '11' + java-version: '17' - name: Bundle Install run: bundle install @@ -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 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/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..dca55d8 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,60 @@ +# 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.17' + jdkArchitectureOption: 'x64' + publishJUnitResults: true + testResultsFiles: '**/TEST-*.xml' + tasks: 'clean build' +- 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/build.gradle b/build.gradle index 0802323..8021aa8 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*.*'] @@ -60,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 { @@ -73,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/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/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/pom.xml b/pom.xml new file mode 100644 index 0000000..c0053cd --- /dev/null +++ b/pom.xml @@ -0,0 +1,11 @@ + + + + 4.0.0 + com.github.outsystems + osbarcode-android + 0.0.4 + 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/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/sonar-project.properties b/sonar-project.properties index f860a85..0752a6c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,10 +1,16 @@ # 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 # 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,**/*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/androidTest/java/organizationidplaceholder/libtemplateplaceholder/ExampleInstrumentedTest.kt b/src/androidTest/java/organizationidplaceholder/libtemplateplaceholder/ExampleInstrumentedTest.kt deleted file mode 100644 index 72150fb..0000000 --- a/src/androidTest/java/organizationidplaceholder/libtemplateplaceholder/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package organizationidplaceholder.libtemplateplaceholder - -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("organizationidplaceholder.libtemplateplaceholder", appContext.packageName) - } -} \ No newline at end of file diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 4045313..46c545d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,23 +1,10 @@ - - + package="com.outsystems.plugins.barcode"> + + - - - - - - + android:name=".view.OSBARCScannerActivity" + android:exported="false" /> - \ 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/OSBARCBarcodeAnalyzer.kt b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.kt new file mode 100644 index 0000000..0a4c154 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCBarcodeAnalyzer.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.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 onBarcodeScanned: (String) -> Unit, + private val onScanningError: () -> Unit +): ImageAnalysis.Analyzer { + + companion object { + private const val LOG_TAG = "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) + } catch (e: Exception) { + e.message?.let { Log.e(LOG_TAG, it) } + onScanningError + } finally { + image.close() + } + } + +} \ 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..53d5a4c --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/controller/OSBARCController.kt @@ -0,0 +1,67 @@ +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 + +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_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) { + 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) + } + + 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 new file mode 100644 index 0000000..ef020c6 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCError.kt @@ -0,0 +1,8 @@ +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."), + 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/model/OSBARCScanParameters.kt b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt new file mode 100644 index 0000000..12b54d2 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/model/OSBARCScanParameters.kt @@ -0,0 +1,11 @@ +package com.outsystems.plugins.barcode.model + +data class OSBARCScanParameters( + val scanInstructions: String?, + val cameraDirection: Int?, + val scanOrientation: Int?, + val scanButton: Boolean?, + val scanText: String?, + val hint: Int?, + val androidScanningLibrary: String? +) \ 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 new file mode 100644 index 0000000..c441037 --- /dev/null +++ b/src/main/kotlin/com/outsystems/plugins/barcode/view/OSBARCScannerActivity.kt @@ -0,0 +1,131 @@ +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 +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.runtime.Composable +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 +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() { + + 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" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + BarcodeScannerTheme { + ScanScreen() + } + } + } + + @Composable + fun ScanScreen() { + val lifecycleOwner = LocalLifecycleOwner.current + val context = LocalContext.current + + // 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) // temporary + .build() + preview.setSurfaceProvider(previewView.surfaceProvider) + val imageAnalysis = ImageAnalysis.Builder() + .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST) + .build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(context), + 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( + lifecycleOwner, + selector, + preview, + imageAnalysis + ) + } catch (e: Exception) { + e.message?.let { Log.e(LOG_TAG, it) } + setResult(SCANNING_EXCEPTION_RESULT_CODE) + finish() + } + previewView + }, + modifier = Modifier.weight(1f) + ) + } + } + +} \ 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/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 c209e78..0000000 Binary files a/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ 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 b2dfe3d..0000000 Binary files a/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ 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 4f0f1d6..0000000 Binary files a/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611d..0000000 Binary files a/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ 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 948a307..0000000 Binary files a/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ 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 1b9a695..0000000 Binary files a/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77..0000000 Binary files a/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f50..0000000 Binary files a/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d642..0000000 Binary files a/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae3..0000000 Binary files a/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ 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 23a95d7..0000000 --- a/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - LibTemplatePlaceholder - \ 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 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..b468341 --- /dev/null +++ b/src/test/kotlin/com/outsystems/plugins/barcode/ScanCodeTests.kt @@ -0,0 +1,11 @@ +package com.outsystems.plugins.barcode + +import org.junit.Test + +class ScanCodeTests { + + @Test + fun scanCodeTest() { + // temporarily empty + } +} \ No newline at end of file