diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..4494198 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,12 @@ +{ + "permissions": { + "allow": [ + "Bash(./gradlew:*)", + "Bash(gh auth:*)", + "Bash(gh pr diff:*)", + "Bash(find:*)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 3011a93..0000000 --- a/.editorconfig +++ /dev/null @@ -1,54 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 4 -indent_style = space -insert_final_newline = true -tab_width = 4 -ij_continuation_indent_size = 4 -ij_formatter_off_tag = @formatter:off -ij_formatter_on_tag = @formatter:on -ij_formatter_tags_enabled = false -ij_smart_tabs = false -ij_wrap_on_typing = false -ij_visual_guides = 118 - -[*.{kt,kts}] -max_line_length = 118 -ij_kotlin_name_count_to_use_star_import = 999 -ij_kotlin_name_count_to_use_star_import_for_members = 999 -ij_kotlin_align_in_columns_case_branch = false -ij_kotlin_align_multiline_binary_operation = false -ij_kotlin_align_multiline_extends_list = false -ij_kotlin_align_multiline_method_parentheses = false -ij_kotlin_align_multiline_parameters = true -ij_kotlin_align_multiline_parameters_in_calls = false -ij_kotlin_assignment_wrap = normal -ij_kotlin_blank_lines_after_class_header = 0 -ij_kotlin_blank_lines_around_block_when_branches = 0 -ij_kotlin_block_comment_at_first_column = true -ij_kotlin_call_parameters_new_line_after_left_paren = true -ij_kotlin_call_parameters_right_paren_on_new_line = true -ij_kotlin_call_parameters_wrap = off -ij_kotlin_catch_on_new_line = false -ij_kotlin_class_annotation_wrap = split_into_lines -ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL -ij_kotlin_continuation_indent_for_chained_calls = false -ij_kotlin_continuation_indent_for_expression_bodies = false -ij_kotlin_continuation_indent_in_argument_lists = false -ij_kotlin_continuation_indent_in_elvis = false -ij_kotlin_continuation_indent_in_if_conditions = false -ij_kotlin_continuation_indent_in_parameter_lists = false -ij_kotlin_continuation_indent_in_supertype_lists = false -ij_kotlin_else_on_new_line = false -ij_kotlin_enum_constants_wrap = off -ij_kotlin_extends_list_wrap = normal -ij_kotlin_field_annotation_wrap = split_into_lines -ij_kotlin_finally_on_new_line = false -ij_kotlin_if_rparen_on_new_line = true -ij_kotlin_import_nested_classes = false -ij_kotlin_insert_whitespaces_in_simple_one_line_method = true -ij_kotlin_keep_blank_lines_before_right_brace = 2 -ij_kotlin_keep_blank_lines_in_code = 2 diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml new file mode 100644 index 0000000..768a2c1 --- /dev/null +++ b/.github/workflows/develop.yml @@ -0,0 +1,69 @@ +name: Android CI - Develop + +on: + push: + branches: [ "develop" ] + +jobs: + ui-tests: + name: Run android tests and build staging + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + + - name: Checking out branch + uses: actions/checkout@v4 + + - name: set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + # Hardware accelerated Android virtualization on Actions Linux larger hosted runners + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + # Significantly reduce emulator startup time by setting up AVD snapshot caching + - name: Gradle cache + uses: gradle/actions/setup-gradle@v3 + + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-30 + + - name: Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: ReactiveCircus/android-emulator-runner@v2.33.0 + with: + api-level: 30 + target: google_atd + arch: x86 + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: Run Android tests + uses: ReactiveCircus/android-emulator-runner@v2.33.0 + with: + api-level: 30 + target: google_atd + arch: x86 + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: ./gradlew :scan-engine:connectedAndroidTest diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index ded0463..d87baab 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -22,8 +22,8 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 - - name: Run Detekt Code Analysis - run: ./gradlew detekt + - name: Run Spotless Code Analysis + run: ./gradlew spotlessCheck - name: Run Unit tests run: ./gradlew testDebug diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 28c4937..3de1260 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,77 +1,53 @@ -name: Android CI - Release +name: Publish to Maven Central on: - push: - branches: [ "master" ] + push: + branches: [ "master" ] jobs: - basic-tests: - name: Run all tests - - runs-on: ubuntu-latest - strategy: - matrix: - api-level: [ 30 ] - target: [ google_apis ] - - steps: - - - name: Checking out branch - uses: actions/checkout@v4 - - - name: set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: gradle - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - - name: Run Detekt Code Analysis - run: ./gradlew detekt - - - name: Run Unit tests - run: ./gradlew testDebug - - # Hardware accelerated Android virtualization on Actions Linux larger hosted runners - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - # Significantly reduce emulator startup time by setting up AVD snapshot caching - - name: Gradle cache - uses: gradle/actions/setup-gradle@v3 - - - name: AVD cache - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: ReactiveCircus/android-emulator-runner@v2.30.1 - with: - api-level: ${{ matrix.api-level }} - target: ${{ matrix.target }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: Run Android tests - uses: ReactiveCircus/android-emulator-runner@v2.30.1 - with: - api-level: ${{ matrix.api-level }} - target: ${{ matrix.target }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: ./gradlew scan-engine:connectedCheck + publish: + name: Build and publish to Maven Central + runs-on: macos-latest + timeout-minutes: 60 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Install GPG + env: + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + run: | + brew install gpg + echo "$SIGNING_KEY" | gpg --dearmor > ${HOME}/secring.gpg + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Add Gradle Properties + env: + MAVEN_CENTRAL_USER_NAME: ${{ secrets.MAVEN_CENTRAL_USER_NAME }} + MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} + SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} + run: | + echo "mavenCentralUsername=${MAVEN_CENTRAL_USER_NAME}" >> gradle.properties + echo "mavenCentralPassword=${MAVEN_CENTRAL_PASSWORD}" >> gradle.properties + echo "signing.keyId=${SIGNING_KEY_ID}" >> gradle.properties + echo "signing.password=${SIGNING_KEY_PASSWORD}" >> gradle.properties + echo "signing.secretKeyRingFile=${HOME}/secring.gpg" >> gradle.properties + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Publish To Maven Central + run: | + ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7a0e8fc..cd06dcc 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ out/ .gradle/ /build /scan-engine/build +/sample/build /buildSrc/build # Local configuration file (sdk path, etc) @@ -59,3 +60,16 @@ out/ /.idea/navEditor.xml .externalNativeBuild .cxx +/.kotlin + +# CocoaPods +Pods/ +*.xcworkspace + +# ============================ +# Xcode / iOS +# ============================ +iosApp/Pods/ +/iosApp/iosApp.xcodeproj/xcuserdata/ +/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/ +**/xcuserdata/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b00a57d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,126 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Kotlin Multiplatform (KMP) library that provides unified barcode scanning functionality for Android and iOS platforms. The library combines various scanning protocols into a single, easy-to-use interface and is published on Maven Central. + +## Build Commands + +### Core Build Tasks +- `./gradlew build` - Full build including tests and checks +- `./gradlew assemble` - Build all variants without running tests +- `./gradlew clean` - Clean all build artifacts + +### Testing +- For the naming of test class use suffix `Test` of the original class +- If the same class name exists in common module and in platform specific like android for example, you can add platform name as suffix as well but before the word `Test` +- Test coverage is ony important for the `scan-engine/` module and run tests only for this module +- Classes with functions annotated as @Composable they need to be tested via android instrumented tests and not unit tests +- Activities or Fragment classes cover with android (instrumented) tests, not unit tests +- Make sure at the end after you run all tests you cleanup test class for any unused variables, imports or components +- `./gradlew :scan-engine:allTests` - Run tests for all platforms (Android, iOS) and create aggregated report +- `./gradlew :scan-engine:test` - Run Android unit tests for all variants +- `./gradlew :scan-engine:testDebugUnitTest` - Run Android unit tests for debug build only +- `./gradlew :scan-engine:iosX64Test` - Run iOS unit tests for x64 simulator +- `./gradlew :scan-engine:iosSimulatorArm64Test` - Run iOS unit tests for ARM64 simulator +- `./gradlew :scan-engine:connectedAndroidTest` - Run Android instrumented tests on connected devices + +### Code Quality +- `./gradlew spotlessCheck` - Check code formatting (Ktlint) +- `./gradlew spotlessApply` - Apply code formatting fixes +- `./gradlew lint` - Run Android lint checks +- `./gradlew lintFix` - Apply safe lint suggestions + +## Project Architecture + +### Module Structure +- `scan-engine/` - Main KMP library module containing core scanning functionality +- `sample/` - Demo application showcasing library usage (Android + iOS) +- `iosApp/` - iOS-specific app wrapper +- `buildSrc/` - Gradle build configuration and constants + +### Platform Structure (KMP) +The library uses Kotlin Multiplatform with the following source sets: + +- `commonMain/` - Shared code across platforms including: + - Core interfaces (`CameraScanner`, `BarcodeScanner`) + - UI components using Compose Multiplatform + - Common data models and business logic + +- `androidMain/` - Android-specific implementations: + - CameraX integration for camera functionality + - ML Kit for barcode detection + - Activity contracts and Android-specific UI + +- `iosMain/` - iOS-specific implementations: + - AVFoundation camera integration + - Core ML barcode detection + - iOS-specific UI components + +- Platform-specific test directories: `androidUnitTest/`, `iosTest/`, `commonTest/` + +### Core Architecture Patterns + +**Scanning Interfaces:** +- `CameraScanner` - Interface for camera-based scanning with Compose integration +- `BarcodeScanner` - Interface for background/hardware scanner integration +- Both use reactive patterns with Kotlin Flow for result observation + +**Platform Abstraction:** +- `expect/actual` pattern for platform-specific implementations +- `Platform.kt` files in each platform source set +- Camera controllers have platform-specific implementations + +**Compose Multiplatform UI:** +- Shared UI components in `commonMain/ui/` +- Platform-specific screen implementations using `@Composable` functions +- Material 3 design system with custom theming + +## Key Configuration + +### Build Configuration +- Target Android API 24+ (minSdk: 24, compileSdk: 35) +- iOS targets: iosX64, iosArm64, iosSimulatorArm64 +- Java 17 compatibility +- Jetpack Compose and Compose Multiplatform enabled + +### Dependencies +- Core KMP dependencies managed via version catalogs (`libs.versions.toml`) +- Android: CameraX, ML Kit, Activity Compose +- iOS: Native iOS frameworks via Cinterop +- Testing: Mokkery for mocking, Robolectric for Android unit tests + +### Code Style +- Spotless with Ktlint for code formatting +- Official Kotlin code style +- Automatic formatting applied in pre-commit hooks + +## Testing Strategy + +### Test Exclusions +- UI tests are excluded from unit test runs (see `testOptions` in `scan-engine/build.gradle.kts`) +- UI tests run separately as instrumented tests + +### Platform Testing +- Android: Unit tests with Robolectric, instrumented tests with AndroidJUnit4 +- iOS: Native Kotlin/Native unit tests +- Common: Shared business logic testing + +## Development Notes +- Avoid doing any changes to the `build/` folders they are auto generated by Gradle + +### KMP Migration Status +- Project is currently undergoing KMP migration (branch: `msirok/task/UNTIL-14107/kmp-migration`) +- iOS implementation recently added with unit testing framework +- Both platforms share core business logic and UI components + +### Published Library +- Available on Maven Central repository as `io.github.tillhub:scan-engine:x.x.x` +- Current version: 2.0.1 (see `gradle/libs.versions.toml`) +- Namespace: `de.tillhub.scanengine` + +### Framework Generation +- iOS frameworks are generated for Xcode integration +- Framework name: `scan-engine` for the library, `ComposeApp` for sample \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 7f11450..d9df5ae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,32 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - alias(libs.plugins.androidApplication) apply false - alias(libs.plugins.kotlinAndroid) apply false - alias(libs.plugins.androidLibrary) apply false - alias(libs.plugins.detekt) apply false + alias(libs.plugins.android.library).apply(false) + alias(libs.plugins.kotlinMultiplatform).apply(false) + alias(libs.plugins.compose.compiler).apply(false) + alias(libs.plugins.compose).apply(false) + alias(libs.plugins.atomicfu).apply(false) + alias(libs.plugins.android.application).apply(false) + alias(libs.plugins.kotlin.android).apply(false) + alias(libs.plugins.spotless).apply(false) + alias(libs.plugins.maven.publish).apply(false) } + +subprojects { + apply(plugin = "com.diffplug.spotless") + configure { + kotlin { + target("**/*.kt") + targetExclude("${layout.buildDirectory}/**/*.kt") + + ktlint().editorConfigOverride( + mapOf( + "ktlint_function_naming_ignore_when_annotated_with" to "Composable" + ) + ) + } + + kotlinGradle { + target("*.gradle.kts") + ktlint() + } + } +} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..bc0172f --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + `kotlin-dsl` +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 0000000..f0f8d34 --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,12 @@ +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + maven("https://plugins.gradle.org/m2/") + } + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/buildSrc/src/main/kotlin/Configs.kt b/buildSrc/src/main/kotlin/Configs.kt new file mode 100644 index 0000000..321d4c7 --- /dev/null +++ b/buildSrc/src/main/kotlin/Configs.kt @@ -0,0 +1,9 @@ +import org.gradle.api.JavaVersion + +object Configs { + const val APPLICATION_ID = "de.tillhub.scanengine" + const val COMPILE_SDK = 35 + const val MIN_SDK = 24 + const val VERSION_NAME = "2.0.0" + val JAVA_VERSION = JavaVersion.VERSION_17 +} diff --git a/gradle.properties b/gradle.properties index 9af2b38..55afba4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,29 +1,13 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official +#Gradle +org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" +org.gradle.caching=true +org.gradle.configuration-cache=true -# Enable configuration on demand -# https://docs.gradle.org/current/userguide/multi_project_configuration_and_execution.html#sec:configuration_on_demand -org.gradle.configureondemand=true +#Kotlin +kotlin.code.style=official -# Enable parallel execution -org.gradle.parallel=true +#Android +android.useAndroidX=true +android.nonTransitiveRClass=true -# Enable build caching -org.gradle.caching=true +kotlin.native.cacheKind=none \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec316b9..7abaec4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,159 +1,79 @@ [versions] -# Define the dependency versions -agp = "8.4.2" -kotlin = "1.9.22" -kotlinCoroutines = "1.7.3" -core = "1.13.1" -appcompat = "1.6.1" -constraintlayout = "2.1.4" -fragment = "1.6.2" -lifecycle = "2.7.0" -material = "1.11.0" -material3 = "1.2.0" -mlKit = "17.2.0" -playServicesMlkit = "18.3.0" -cameraX = "1.3.1" -cameraView = "1.3.1" +scan-engine = "2.0.1" +kotlin = "2.2.0" +agp = "8.11.1" +compose = "1.8.1" +atomicfu = "0.28.0" -activity-compose = "1.9.0" -compose-bom = "2024.06.00" +camera-core = "1.4.2" +camera-extensions = "1.4.2" +camera-lifecycle = "1.4.2" +camera-view = "1.4.2" -timber = "5.0.1" -androidDesugarJdkLibs = "2.0.4" -detekt = "1.23.6" +mlKit = "17.3.0" +playServicesMlkit = "18.3.1" -junit = "4.13.2" -mockk = "1.13.8" -kotest = "5.8.0" +kotlinCoroutines = "1.10.2" +lifecycleRuntimeKtx = "2.9.1" +activityCompose = "1.10.1" -kotestExtensionsAndroid = "0.1.1" -kotestRobolectric = "0.4.0" -androidxCoreTest = "1.5.0" -androidxRunner = "1.5.2" -androidxRules = "1.5.0" -androidxJunit = "1.1.5" -androidxTruth = "1.5.0" -espresso = "3.5.1" -robolectric = "4.11.1" +spotless = "7.1.0" +mokkery = "2.9.0" +uiTestJunit4 = "1.8.3" +robolectric = "4.15" +androidx-test = "1.6.1" +maven-publish = "0.34.0" [libraries] -# Define the libraries -kotlin-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinCoroutines" } -kotlin-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinCoroutines" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } +androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidx-test" } -androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "core" } -androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } -androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } -androidx-fragment = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragment" } -androidx-lifecycle-common = { group = "androidx.lifecycle", name = "lifecycle-common", version.ref = "lifecycle" } -androidx-lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime", version.ref = "lifecycle" } -google-material = { group = "com.google.android.material", name = "material", version.ref = "material" } +camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera-core" } +camera-core = { module = "androidx.camera:camera-core", version.ref = "camera-core" } +camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "camera-extensions" } +camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera-lifecycle" } +camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camera-view" } -activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } -compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } -compose-ui = { group = "androidx.compose.ui", name = "ui" } -compose-material3 = { group = "androidx.compose.material3", name = "material3",version.ref = "material3" } -compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } -compose-ui-tooling = { group = "androidx.compose.ui" , name = "ui-tooling"} -compose-ui-tooling-preview = { group = "androidx.compose.ui" , name = "ui-tooling-preview"} +androidx-lifecycle-runtimeCompose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeKtx" } +kotlin-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinCoroutines" } +kotlin-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinCoroutines" } +lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } -androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "cameraX" } -androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "cameraX" } -androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "cameraView" } google-mlkit = { group = "com.google.mlkit", name = "barcode-scanning", version.ref = "mlKit" } google-play-services-mlkit = { group = "com.google.android.gms", name = "play-services-mlkit-barcode-scanning", version.ref = "playServicesMlkit" } -android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" } -timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" } - -# Unit testing -junit = { group = "junit", name = "junit", version.ref = "junit" } -mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } -robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } -kotest-robolectric = { group = "io.kotest.extensions", name = "kotest-extensions-robolectric", version.ref = "kotestRobolectric" } -kotest-extensions-android = { group = "br.com.colman", name = "kotest-extensions-android", version.ref = "kotestExtensionsAndroid" } -kotest-api = { group = "io.kotest", name = "kotest-framework-api", version.ref = "kotest" } -mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" } -mockk-agent = { group = "io.mockk", name = "mockk-agent", version.ref = "mockk" } -mockk-agent-jvm = { group = "io.mockk", name = "mockk-agent-jvm", version.ref = "mockk" } -kotest-runner = { group = "io.kotest", name = "kotest-runner-junit5-jvm", version.ref = "kotest" } -kotest-assertions = { group = "io.kotest", name = "kotest-assertions-core-jvm", version.ref = "kotest" } -kotest-property = { group = "io.kotest", name = "kotest-property-jvm", version.ref = "kotest" } - -androidx-test-core = { group = "androidx.test", name = "core-ktx", version.ref = "androidxCoreTest" } -androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxRunner" } -androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxRules" } -androidx-test-junit = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxJunit" } -androidx-test-truth = { group = "androidx.test.ext", name = "truth", version.ref = "androidxTruth" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" } -androidx-espresso-contrib = { group = "androidx.test.espresso", name = "espresso-contrib", version.ref = "espresso" } -androidx-espresso-intents = { group = "androidx.test.espresso", name = "espresso-intents", version.ref = "espresso" } +# Android testing +androidx-ui-test-junit4-android = { group = "androidx.compose.ui", name = "ui-test-junit4-android", version.ref = "uiTestJunit4" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } -# Dependencies of the included build-logic -detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } -detekt-libraries = { group = "io.gitlab.arturbosch.detekt", name = "detekt-rules-libraries", version.ref = "detekt" } [plugins] -androidApplication = { id = "com.android.application", version.ref = "agp" } -kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -androidLibrary = { id = "com.android.library", version.ref = "agp" } -detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } +android_library = { id = "com.android.library", version.ref = "agp" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +androidKotlinMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +compose = { id = "org.jetbrains.compose", version.ref = "compose" } +atomicfu = { id = "org.jetbrains.kotlinx.atomicfu", version.ref = "atomicfu" } +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } +mokkery = { id = "dev.mokkery", version.ref = "mokkery" } +maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" } [bundles] core = [ "kotlin-coroutines" ] -compose = [ - "activity-compose", - "compose-bom", - "compose-ui", - "compose-material3", - "compose-ui-graphics", - "compose-ui-tooling", - "compose-ui-tooling-preview", -] -ui = [ - "androidx-core", - "androidx-fragment", - "androidx-appcompat", - "google-material", -] -lifecycle = [ - "androidx-lifecycle-common", - "androidx-lifecycle-runtime", +camera = [ + "camera-core", + "camera-camera2", + "camera-extensions", + "camera-lifecycle", + "camera-view" ] mlkit = [ "google-mlkit", "google-play-services-mlkit", -] -camera = [ - "androidx-camera-camera2", - "androidx-camera-lifecycle", - "androidx-camera-view" -] -testing = [ - "junit", - "mockk", - "mockk-agent-jvm", - "kotest-runner", - "kotest-assertions", - "kotest-property", - "kotlin-coroutines-test" -] -robolectric = [ - "robolectric", - "kotest-robolectric", - "androidx-test-core", - "kotest-extensions-android" -] -testing-android = [ - "androidx-test-core", - "androidx-test-runner", - "androidx-test-rules", - "androidx-test-junit", - "androidx-test-truth", - "androidx-espresso-core", - "androidx-espresso-contrib", - "androidx-espresso-intents", - "kotlin-coroutines-test" -] +] \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0d408fe..feb3b65 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Feb 11 09:37:55 CET 2022 +#Thu Jun 12 08:47:36 CEST 2025 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig new file mode 100644 index 0000000..4604748 --- /dev/null +++ b/iosApp/Configuration/Config.xcconfig @@ -0,0 +1,7 @@ +TEAM_ID= + +PRODUCT_NAME=Tillhub_Scan_Engine +PRODUCT_BUNDLE_IDENTIFIER=de.tillhub.scanengine.sample$(TEAM_ID) + +CURRENT_PROJECT_VERSION=1 +MARKETING_VERSION=1.0 \ No newline at end of file diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000..562cf42 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,385 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXFileReference section */ + B9DA97B12DC1472C00A4DA20 /* Tillhub_Scan_Engine.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tillhub_Scan_Engine.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + B99700CA2DC9B8D800C7335B /* Exceptions for "iosApp" folder in "iosApp" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = B9DA97B02DC1472C00A4DA20 /* iosApp */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + B9DA97B32DC1472C00A4DA20 /* iosApp */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + B99700CA2DC9B8D800C7335B /* Exceptions for "iosApp" folder in "iosApp" target */, + ); + path = iosApp; + sourceTree = ""; + }; + B9DA98002DC14AA900A4DA20 /* Configuration */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Configuration; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + B9DA97AE2DC1472C00A4DA20 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + B9DA97A82DC1472C00A4DA20 = { + isa = PBXGroup; + children = ( + B9DA98002DC14AA900A4DA20 /* Configuration */, + B9DA97B32DC1472C00A4DA20 /* iosApp */, + B9DA97B22DC1472C00A4DA20 /* Products */, + ); + sourceTree = ""; + }; + B9DA97B22DC1472C00A4DA20 /* Products */ = { + isa = PBXGroup; + children = ( + B9DA97B12DC1472C00A4DA20 /* Tillhub_Scan_Engine.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + B9DA97B02DC1472C00A4DA20 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = B9DA97BF2DC1472D00A4DA20 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + B9DA97F42DC1497100A4DA20 /* Compile Kotlin Framework */, + B9DA97AD2DC1472C00A4DA20 /* Sources */, + B9DA97AE2DC1472C00A4DA20 /* Frameworks */, + B9DA97AF2DC1472C00A4DA20 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + B9DA97B32DC1472C00A4DA20 /* iosApp */, + ); + name = iosApp; + packageProductDependencies = ( + ); + productName = iosApp; + productReference = B9DA97B12DC1472C00A4DA20 /* Tillhub_Scan_Engine.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + B9DA97A92DC1472C00A4DA20 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1620; + LastUpgradeCheck = 1620; + TargetAttributes = { + B9DA97B02DC1472C00A4DA20 = { + CreatedOnToolsVersion = 16.2; + }; + }; + }; + buildConfigurationList = B9DA97AC2DC1472C00A4DA20 /* Build configuration list for PBXProject "iosApp" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = B9DA97A82DC1472C00A4DA20; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = B9DA97B22DC1472C00A4DA20 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + B9DA97B02DC1472C00A4DA20 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + B9DA97AF2DC1472C00A4DA20 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + B9DA97F42DC1497100A4DA20 /* Compile Kotlin Framework */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin Framework"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :sample:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + B9DA97AD2DC1472C00A4DA20 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + B9DA97BD2DC1472D00A4DA20 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + B9DA97BE2DC1472D00A4DA20 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + B9DA97C02DC1472D00A4DA20 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReferenceAnchor = B9DA98002DC14AA900A4DA20 /* Configuration */; + baseConfigurationReferenceRelativePath = Config.xcconfig; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = 8GL97FWG4R; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../sample/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iosApp/Info.plist; + INFOPLIST_KEY_NSCameraUsageDescription = "Camera permission is required for the app to work."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.tillhub.scanengine.sample; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + B9DA97C12DC1472D00A4DA20 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReferenceAnchor = B9DA98002DC14AA900A4DA20 /* Configuration */; + baseConfigurationReferenceRelativePath = Config.xcconfig; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = 8GL97FWG4R; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../sample/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iosApp/Info.plist; + INFOPLIST_KEY_NSCameraUsageDescription = "Camera permission is required for the app to work."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = de.tillhub.scanengine.sample; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + B9DA97AC2DC1472C00A4DA20 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B9DA97BD2DC1472D00A4DA20 /* Debug */, + B9DA97BE2DC1472D00A4DA20 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B9DA97BF2DC1472D00A4DA20 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B9DA97C02DC1472D00A4DA20 /* Debug */, + B9DA97C12DC1472D00A4DA20 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = B9DA97A92DC1472C00A4DA20 /* Project object */; +} diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..4e8d485 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "app-icon-1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png new file mode 100644 index 0000000..53fc536 Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png differ diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift new file mode 100644 index 0000000..3cd5c32 --- /dev/null +++ b/iosApp/iosApp/ContentView.swift @@ -0,0 +1,21 @@ +import UIKit +import SwiftUI +import ComposeApp + +struct ComposeView: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> UIViewController { + MainViewControllerKt.MainViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +struct ContentView: View { + var body: some View { + ComposeView() + .ignoresSafeArea(.keyboard) // Compose has own keyboard handler + } +} + + + diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist new file mode 100644 index 0000000..2f44658 --- /dev/null +++ b/iosApp/iosApp/Info.plist @@ -0,0 +1,10 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + NSCameraUsageDescription + Camera permission is required for the app to work. + + diff --git a/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift new file mode 100644 index 0000000..d83dca6 --- /dev/null +++ b/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct iOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} \ No newline at end of file diff --git a/jitpack.yml b/jitpack.yml deleted file mode 100644 index f29a667..0000000 --- a/jitpack.yml +++ /dev/null @@ -1,5 +0,0 @@ -jdk: - - openjdk17 -before_install: - - sdk install java 17.0.1-open - - sdk use java 17.0.1-open \ No newline at end of file diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index ce9df40..60cf640 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -1,49 +1,77 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { - alias(libs.plugins.androidApplication) - alias(libs.plugins.kotlinAndroid) + alias(libs.plugins.android.application) + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.compose) +} + +kotlin { + androidTarget { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } + } + + val xcfName = "ComposeApp" + iosX64 { binaries.framework { baseName = xcfName } } + iosArm64 { binaries.framework { baseName = xcfName } } + iosSimulatorArm64 { binaries.framework { baseName = xcfName } } + + sourceSets { + + androidMain.dependencies { + implementation(libs.activity.compose) + implementation(compose.preview) + + implementation(compose.uiTooling) + implementation(libs.lifecycle.runtime.ktx) + } + commonMain.dependencies { + implementation(projects.scanEngine) + + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(compose.ui) + } + } } android { namespace = "de.tillhub.scanengine.sample" - - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "de.tillhub.scanengine.sample" minSdk = 24 - targetSdk = 34 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" - vectorDrawables { - useSupportLibrary = true - } + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + } + } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - tasks.withType { - useJUnitPlatform() - } - - tasks.withType { - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.toString() - } - } buildFeatures { compose = true } - composeOptions { - kotlinCompilerExtensionVersion = "1.5.9" - } } dependencies { - - implementation(project(":scan-engine")) - - implementation(libs.androidx.core) - implementation(libs.bundles.compose) - implementation(libs.bundles.lifecycle) } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/androidMain/AndroidManifest.xml similarity index 72% rename from sample/src/main/AndroidManifest.xml rename to sample/src/androidMain/AndroidManifest.xml index 1a7c5ff..684d670 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/androidMain/AndroidManifest.xml @@ -4,20 +4,18 @@ + android:theme="@style/AppTheme"> + android:exported="true"> - - + \ No newline at end of file diff --git a/sample/src/androidMain/kotlin/de/tillhub/scanengine/sample/MainActivity.kt b/sample/src/androidMain/kotlin/de/tillhub/scanengine/sample/MainActivity.kt new file mode 100644 index 0000000..1230e80 --- /dev/null +++ b/sample/src/androidMain/kotlin/de/tillhub/scanengine/sample/MainActivity.kt @@ -0,0 +1,14 @@ +package de.tillhub.scanengine.sample + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { App() } + } +} diff --git a/sample/src/androidMain/res/drawable/ic_launcher_background.xml b/sample/src/androidMain/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/sample/src/androidMain/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/androidMain/res/drawable/ic_launcher_foreground.xml b/sample/src/androidMain/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/sample/src/androidMain/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 79% rename from sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml index eca70cf..6f3b755 100644 --- a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 79% rename from sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml index eca70cf..6f3b755 100644 --- a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/sample/src/androidMain/res/mipmap-hdpi/ic_launcher.webp b/sample/src/androidMain/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/sample/src/androidMain/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp b/sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/sample/src/androidMain/res/mipmap-mdpi/ic_launcher.webp b/sample/src/androidMain/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/sample/src/androidMain/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp b/sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp b/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp b/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp b/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp b/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp b/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp b/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/sample/src/androidMain/res/values/styles.xml b/sample/src/androidMain/res/values/styles.xml new file mode 100644 index 0000000..6b1f222 --- /dev/null +++ b/sample/src/androidMain/res/values/styles.xml @@ -0,0 +1,4 @@ + + + - diff --git a/scan-engine/src/main/res/values/colors.xml b/scan-engine/src/main/res/values/colors.xml deleted file mode 100644 index 3aed6ab..0000000 --- a/scan-engine/src/main/res/values/colors.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #80000000 - diff --git a/scan-engine/src/main/res/values/styles.xml b/scan-engine/src/main/res/values/styles.xml deleted file mode 100644 index 14ed6fe..0000000 --- a/scan-engine/src/main/res/values/styles.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - diff --git a/scan-engine/src/main/res/values/themes.xml b/scan-engine/src/main/res/values/themes.xml deleted file mode 100644 index 867d19b..0000000 --- a/scan-engine/src/main/res/values/themes.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/ScanEngineTest.kt b/scan-engine/src/test/java/de/tillhub/scanengine/ScanEngineTest.kt deleted file mode 100644 index d6859ee..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/ScanEngineTest.kt +++ /dev/null @@ -1,62 +0,0 @@ -package de.tillhub.scanengine - -import android.app.Activity -import android.bluetooth.BluetoothManager -import android.content.Context -import androidx.activity.result.ActivityResultCaller -import de.tillhub.scanengine.barcode.BarcodeScannerContainer -import de.tillhub.scanengine.data.ScannerType -import de.tillhub.scanengine.generic.GenericKeyEventScanner -import de.tillhub.scanengine.google.DefaultCameraScanner -import de.tillhub.scanengine.sunmi.camera.SunmiCameraScanner -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.types.shouldBeInstanceOf -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkObject -import kotlinx.coroutines.ExperimentalCoroutinesApi - -@ExperimentalCoroutinesApi -class ScanEngineTest : FunSpec({ - - lateinit var context: Context - lateinit var activity: Activity - lateinit var resultCaller: ActivityResultCaller - lateinit var scanEngine: ScanEngine - - beforeTest { - mockkObject(ScannerType) - context = mockk(relaxed = true) { - every { - getSystemService(Context.BLUETOOTH_SERVICE) - } returns mockk(relaxed = true) - } - activity = mockk(relaxed = true) - resultCaller = mockk(relaxed = true) - scanEngine = ScanEngine.getInstance(context).initBarcodeScanners(ScannerType.ZEBRA) - } - - test("newCameraScanner should return SunmiCameraScanner for SUNMI scanner type") { - every { ScannerType.get() } returns ScannerType.SUNMI - - val scanner = scanEngine.newCameraScanner(resultCaller) - scanner.shouldBeInstanceOf() - } - - test("newCameraScanner should return DefaultCameraScanner for other scanner types") { - every { ScannerType.get() } returns ScannerType.UNKNOWN - - val scanner = scanEngine.newCameraScanner(resultCaller) - scanner.shouldBeInstanceOf() - } - - test("barcodeScanner lazy initialization should initialize BarcodeScannerContainer correctly") { - val barcodeScanner = scanEngine.barcodeScanner - barcodeScanner.shouldBeInstanceOf() - } - - test("newKeyEventScanner") { - val scanner = scanEngine.newKeyEventScanner(activity) - scanner.shouldBeInstanceOf() - } -}) diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/barcode/BarcodeScannerContainerTest.kt b/scan-engine/src/test/java/de/tillhub/scanengine/barcode/BarcodeScannerContainerTest.kt deleted file mode 100644 index eff95bc..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/barcode/BarcodeScannerContainerTest.kt +++ /dev/null @@ -1,130 +0,0 @@ -package de.tillhub.scanengine.barcode - -import android.app.Activity -import android.bluetooth.BluetoothManager -import android.content.Context -import de.tillhub.scanengine.data.ScannerEvent -import de.tillhub.scanengine.data.ScannerType -import de.tillhub.scanengine.generic.GenericKeyEventScanner -import de.tillhub.scanengine.sunmi.barcode.SunmiBarcodeScanner -import de.tillhub.scanengine.zebra.ZebraBarcodeScanner -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.types.shouldBeInstanceOf -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkObject -import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher - -@ExperimentalCoroutinesApi -class BarcodeScannerContainerTest : FunSpec({ - - lateinit var context: Context - lateinit var activity: Activity - lateinit var mutableScannerEvents: MutableStateFlow - lateinit var testScope: TestScope - lateinit var scannerFactory: BarcodeScannerFactory - - beforeTest { - mockkObject(ScannerType) - testScope = TestScope(UnconfinedTestDispatcher()) - context = mockk(relaxed = true) { - every { - getSystemService(Context.BLUETOOTH_SERVICE) - } returns mockk(relaxed = true) - } - activity = mockk(relaxed = true) - mutableScannerEvents = MutableStateFlow(mockk(relaxed = true)) - scannerFactory = mockk() - } - - test("getScannersByType success") { - val container = BarcodeScannerContainer(context, mutableScannerEvents) - container.addScanner(ScannerType.ZEBRA) - val zebraScanner = container.getScannersByType(ZebraBarcodeScanner::class.java) - zebraScanner.shouldBeInstanceOf() - } - test("addScanner") { - val container = BarcodeScannerContainer(context, mutableScannerEvents) - val scanner = GenericKeyEventScanner(activity, mutableScannerEvents) - container.addScanner(scanner) - val genericScanner = container.getScannersByType(GenericKeyEventScanner::class.java) - genericScanner.shouldBeInstanceOf() - } - test("getScannersByType error") { - val type = ZebraBarcodeScanner::class.java - val container = BarcodeScannerContainer(context, mutableScannerEvents) - val exception = shouldThrow { - container.getScannersByType(type) - } - exception.message shouldBe "No scanner found of type $type" - } - - test("observeScannerResults") { - every { ScannerType.get() } returns ScannerType.SUNMI - val zebraScanner: ZebraBarcodeScanner = mockk(relaxed = true) { - every { observeScannerResults() } returns mutableScannerEvents - } - - val sunmiScanner: SunmiBarcodeScanner = mockk(relaxed = true) { - every { observeScannerResults() } returns mutableScannerEvents - } - every { scannerFactory.getZebraBarcodeScanner(any(), any()) } returns zebraScanner - every { scannerFactory.getSunmiBarcodeScanner(any(), any()) } returns sunmiScanner - - val container = BarcodeScannerContainer( - context = context, - mutableScannerEvents = mutableScannerEvents, - scannerFactory = scannerFactory - ) - container.addScanner(ScannerType.ZEBRA) - val testResults = mutableListOf() - val event = ScannerEvent.ScanResult("value") - mutableScannerEvents.tryEmit(event) - testScope.launch { - container.observeScannerResults().toList(testResults) - } - testResults shouldBe listOf(event, event) - verify { - zebraScanner.observeScannerResults() - sunmiScanner.observeScannerResults() - } - } - - test("scanWithKey") { - val zebraScanner = mockk(relaxed = true) - every { scannerFactory.getZebraBarcodeScanner(any(), any()) } returns zebraScanner - - val container = BarcodeScannerContainer( - context, - mutableScannerEvents, - scannerFactory = scannerFactory - ) - container.addScanner(ScannerType.ZEBRA) - container.scanWithKey("test_key") - verify { - zebraScanner.scanWithKey("test_key") - } - } - - test("startPairingScreen") { - val zebraScanner = mockk(relaxed = true) - every { scannerFactory.getZebraBarcodeScanner(any(), any()) } returns zebraScanner - - val container = BarcodeScannerContainer( - context, - mutableScannerEvents, - scannerFactory = scannerFactory - ) - container.addScanner(ScannerType.ZEBRA) - container.startPairingScreen(ScannerType.ZEBRA) - verify { zebraScanner.startPairingScreen(ScannerType.ZEBRA) } - } -}) diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/common/InstantTaskExecutor.kt b/scan-engine/src/test/java/de/tillhub/scanengine/common/InstantTaskExecutor.kt deleted file mode 100644 index 0d83592..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/common/InstantTaskExecutor.kt +++ /dev/null @@ -1,33 +0,0 @@ -package de.tillhub.scanengine.common - -import android.annotation.SuppressLint -import androidx.arch.core.executor.ArchTaskExecutor -import androidx.arch.core.executor.TaskExecutor - -@SuppressLint("RestrictedApi") -class InstantTaskExecutor { - - private val taskExecutor: ArchTaskExecutor by lazy { - ArchTaskExecutor.getInstance() - } - - fun setupLiveData() { - taskExecutor.setDelegate(object : TaskExecutor() { - override fun executeOnDiskIO(runnable: Runnable) { - runnable.run() - } - - override fun postToMainThread(runnable: Runnable) { - runnable.run() - } - - override fun isMainThread(): Boolean { - return true - } - }) - } - - fun resetLiveData() { - taskExecutor.setDelegate(null) - } -} diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/common/ViewModelFunSpec.kt b/scan-engine/src/test/java/de/tillhub/scanengine/common/ViewModelFunSpec.kt deleted file mode 100644 index 6d4736a..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/common/ViewModelFunSpec.kt +++ /dev/null @@ -1,15 +0,0 @@ -package de.tillhub.scanengine.common - -import io.kotest.core.listeners.TestListener -import io.kotest.core.spec.style.FunSpec -import kotlinx.coroutines.ExperimentalCoroutinesApi - -@ExperimentalCoroutinesApi -abstract class ViewModelFunSpec( - body: FunSpec.() -> Unit = {} -) : FunSpec(body) { - - override fun listeners(): List { - return listOf(ViewModelListener()) - } -} diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/common/ViewModelListener.kt b/scan-engine/src/test/java/de/tillhub/scanengine/common/ViewModelListener.kt deleted file mode 100644 index 03d8c9c..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/common/ViewModelListener.kt +++ /dev/null @@ -1,27 +0,0 @@ -package de.tillhub.scanengine.common - -import io.kotest.core.listeners.TestListener -import io.kotest.core.spec.Spec -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestDispatcher -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain - -@ExperimentalCoroutinesApi -class ViewModelListener( - private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(), - private val instantTaskExecutor: InstantTaskExecutor = InstantTaskExecutor() -) : TestListener { - - override suspend fun beforeSpec(spec: Spec) { - Dispatchers.setMain(testDispatcher) - instantTaskExecutor.setupLiveData() - } - - override suspend fun afterSpec(spec: Spec) { - Dispatchers.resetMain() - instantTaskExecutor.resetLiveData() - } -} diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/data/ScannerManufacturerTest.kt b/scan-engine/src/test/java/de/tillhub/scanengine/data/ScannerManufacturerTest.kt deleted file mode 100644 index 786f19d..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/data/ScannerManufacturerTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package de.tillhub.scanengine.data - -import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import org.robolectric.shadows.ShadowBuild - -@RobolectricTest -internal class ScannerManufacturerTest : FunSpec({ - - test("SUNMI device") { - ShadowBuild.setManufacturer("SUNMI") - ScannerType.get() shouldBe ScannerType.SUNMI - } - - test("UNKNOWN device") { - ShadowBuild.setManufacturer("UNKNOWN") - ScannerType.get() shouldBe ScannerType.UNKNOWN - } -}) diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/generic/GenericKeyEventScannerTest.kt b/scan-engine/src/test/java/de/tillhub/scanengine/generic/GenericKeyEventScannerTest.kt deleted file mode 100644 index 11af24b..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/generic/GenericKeyEventScannerTest.kt +++ /dev/null @@ -1,216 +0,0 @@ -package de.tillhub.scanengine.generic - -import android.app.Activity -import android.view.KeyEvent -import android.view.View -import android.widget.EditText -import de.tillhub.scanengine.data.ScannerEvent -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.types.shouldBeInstanceOf -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest - -@OptIn(ExperimentalCoroutinesApi::class) -class GenericKeyEventScannerTest : FunSpec({ - - lateinit var activity: Activity - lateinit var mockEditText: EditText - lateinit var mutableScannerEvents: MutableStateFlow - lateinit var scanner: GenericKeyEventScanner - lateinit var testScope: TestScope - - beforeTest { - activity = mockk(relaxed = true) - mockEditText = mockk(relaxed = true) - testScope = TestScope(UnconfinedTestDispatcher()) - mutableScannerEvents = spyk(MutableStateFlow(ScannerEvent.External.NotConnected)) - - scanner = GenericKeyEventScanner(activity, mutableScannerEvents) - } - - test("dispatchKeyEvent should append input and emit ScanResult when Enter is pressed") { - val keyEventA = mockk { - every { action } returns KeyEvent.ACTION_DOWN - every { unicodeChar } returns 'A'.code - every { eventTime } returns 100L - every { keyCode } returns KeyEvent.KEYCODE_A - } - - val keyEventEnter = mockk { - every { action } returns KeyEvent.ACTION_DOWN - every { unicodeChar } returns 1 - every { eventTime } returns 101L - every { keyCode } returns KeyEvent.KEYCODE_ENTER - } - - every { activity.findViewById(android.R.id.content).findFocus() } returns mockEditText - every { mockEditText.hasFocus() } returns false - - scanner.dispatchKeyEvent(keyEventA, "ScanKey") - - scanner.dispatchKeyEvent(keyEventEnter, "ScanKey") - - val testResults = mutableListOf() - testScope.launch { - scanner.observeScannerResults().toList(testResults) - } - - verify(exactly = 1) { mutableScannerEvents.tryEmit(any()) } - - val result = testResults.first() as ScannerEvent.ScanResult - result.value shouldBe "A" - result.scanKey shouldBe "ScanKey" - } - - test("dispatchKeyEvent should not append non-printable keys and emit ScanResult when Enter is pressed") { - val keyEventA = mockk { - every { action } returns KeyEvent.ACTION_DOWN - every { unicodeChar } returns 'A'.code - every { eventTime } returns 100L - every { keyCode } returns KeyEvent.KEYCODE_A - } - - val keyEventShiftLef = mockk { - every { action } returns KeyEvent.KEYCODE_SHIFT_LEFT - every { unicodeChar } returns 0 - every { eventTime } returns 101L - every { keyCode } returns KeyEvent.KEYCODE_SHIFT_LEFT - } - - val keyEventEnter = mockk { - every { action } returns KeyEvent.ACTION_DOWN - every { unicodeChar } returns 1 - every { eventTime } returns 101L - every { keyCode } returns KeyEvent.KEYCODE_ENTER - } - - every { activity.findViewById(android.R.id.content).findFocus() } returns mockEditText - every { mockEditText.hasFocus() } returns false - - scanner.dispatchKeyEvent(keyEventA, "ScanKey") - - scanner.dispatchKeyEvent(keyEventShiftLef, "ScanKey") - - scanner.dispatchKeyEvent(keyEventEnter, "ScanKey") - - val testResults = mutableListOf() - testScope.launch { - scanner.observeScannerResults().toList(testResults) - } - - verify(exactly = 1) { mutableScannerEvents.tryEmit(any()) } - - val result = testResults.first() as ScannerEvent.ScanResult - result.value shouldBe "A" - result.scanKey shouldBe "ScanKey" - } - - test("dispatchKeyEvent should not handle key events on invalid action") { - val keyEventA = mockk { - every { action } returns KeyEvent.ACTION_UP - } - scanner.dispatchKeyEvent(keyEventA, "ScanKey") - - val testResults = mutableListOf() - testScope.launch { - scanner.observeScannerResults().toList(testResults) - } - verify(exactly = 0) { mutableScannerEvents.tryEmit(any()) } - - testResults.first().shouldBeInstanceOf() - } - - test("dispatchKeyEvent should not handle key events on invalid keycode") { - val keyEventA = mockk { - every { action } returns KeyEvent.ACTION_UP - every { keyCode } returns KeyEvent.KEYCODE_SHIFT_LEFT - } - scanner.dispatchKeyEvent(keyEventA, "ScanKey") - - val testResults = mutableListOf() - testScope.launch { - scanner.observeScannerResults().toList(testResults) - } - verify(exactly = 0) { mutableScannerEvents.tryEmit(any()) } - - testResults.first().shouldBeInstanceOf() - } - - test("dispatchKeyEvent should not handle key events on invalid unicodeChar") { - val keyEventA = mockk { - every { action } returns KeyEvent.ACTION_DOWN - every { unicodeChar } returns 0 - every { eventTime } returns 100L - every { keyCode } returns KeyEvent.KEYCODE_A - } - scanner.dispatchKeyEvent(keyEventA, "ScanKey") - - val testResults = mutableListOf() - testScope.launch { - scanner.observeScannerResults().toList(testResults) - } - verify(exactly = 0) { mutableScannerEvents.tryEmit(any()) } - - testResults.first().shouldBeInstanceOf() - } - - test("dispatchKeyEvent should not handle key events when exceed scan threshold") { - val testResults = mutableListOf() - val keyEventA = mockk { - every { action } returns KeyEvent.ACTION_DOWN - every { unicodeChar } returns 'A'.code - every { eventTime } returns 100L - every { keyCode } returns KeyEvent.KEYCODE_A - } - - val keyEventEnter = mockk { - every { action } returns KeyEvent.ACTION_DOWN - every { unicodeChar } returns 0 - every { eventTime } returns 151L - every { keyCode } returns KeyEvent.KEYCODE_ENTER - } - - every { activity.findViewById(android.R.id.content).findFocus() } returns mockEditText - every { mockEditText.hasFocus() } returns false - - scanner.dispatchKeyEvent(keyEventA, "ScanKey") - - scanner.dispatchKeyEvent(keyEventEnter, "ScanKey") - - testScope.launch { - scanner.observeScannerResults().toList(testResults) - } - - verify(exactly = 0) { mutableScannerEvents.tryEmit(any()) } - - testResults.first().shouldBeInstanceOf() - } - - test("dispatchKeyEvent should not handle key events when EditText has focus") { - runTest { - val keyEventA = mockk { - every { action } returns KeyEvent.ACTION_DOWN - every { unicodeChar } returns 'A'.code - every { eventTime } returns 100L - every { keyCode } returns KeyEvent.KEYCODE_A - } - - every { activity.findViewById(android.R.id.content).findFocus() } returns mockEditText - every { mockEditText.hasFocus() } returns true - - scanner.dispatchKeyEvent(keyEventA, "ScanKey") - - verify(exactly = 0) { mutableScannerEvents.tryEmit(any()) } - } - } -}) diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/google/DefaultCameraScannerTest.kt b/scan-engine/src/test/java/de/tillhub/scanengine/google/DefaultCameraScannerTest.kt deleted file mode 100644 index 016ea87..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/google/DefaultCameraScannerTest.kt +++ /dev/null @@ -1,95 +0,0 @@ -package de.tillhub.scanengine.google - -import androidx.activity.result.ActivityResultCallback -import androidx.activity.result.ActivityResultCaller -import androidx.activity.result.ActivityResultLauncher -import de.tillhub.scanengine.CameraScanner -import de.tillhub.scanengine.data.ScannerEvent -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.types.shouldBeInstanceOf -import io.mockk.every -import io.mockk.mockk -import io.mockk.slot -import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher - -@ExperimentalCoroutinesApi -class DefaultCameraScannerTest : FunSpec({ - - lateinit var resultCaller: ActivityResultCaller - lateinit var mutableScannerEvents: MutableStateFlow - lateinit var scannerLauncher: ActivityResultLauncher - lateinit var defaultCameraScanner: CameraScanner - lateinit var testScope: TestScope - - beforeTest { - val callbackSlot = slot>() - - mutableScannerEvents = MutableStateFlow(ScannerEvent.External.NotConnected) - scannerLauncher = mockk>(relaxed = true) - resultCaller = mockk(relaxed = true) { - every { - registerForActivityResult( - any(), - capture(callbackSlot) - ) - } returns scannerLauncher - } - testScope = TestScope(UnconfinedTestDispatcher()) - defaultCameraScanner = DefaultCameraScanner(resultCaller, mutableScannerEvents) - } - - test("startCameraScanner should emit InProgress event and launch scanner") { - defaultCameraScanner.startCameraScanner("testScanKey") - - mutableScannerEvents.value.shouldBeInstanceOf() - (mutableScannerEvents.value as ScannerEvent.Camera.InProgress).scanKey shouldBe "testScanKey" - verify { scannerLauncher.launch(Unit) } - } - - test("registerForActivityResult callback emits ScanEvent_Canceled") { - val callbackSlot = slot>() - every { - resultCaller.registerForActivityResult(any(), capture(callbackSlot)) - } returns scannerLauncher - - defaultCameraScanner = DefaultCameraScanner(resultCaller, mutableScannerEvents) - - val events = ScannerEvent.Camera.Canceled - callbackSlot.captured.onActivityResult(events) - - val testResults = mutableListOf() - testScope.launch { - defaultCameraScanner.observeScannerResults().toList(testResults) - } - defaultCameraScanner.startCameraScanner() - testResults.first() shouldBe ScannerEvent.Camera.Canceled - } - - test("scannerLauncher should emit Success event") { - val callbackSlot = slot>() - every { - resultCaller.registerForActivityResult(any(), capture(callbackSlot)) - } returns scannerLauncher - - defaultCameraScanner = DefaultCameraScanner(resultCaller, mutableScannerEvents) - - val events = ScannerEvent.ScanResult("123456789", "key") - callbackSlot.captured.onActivityResult(events) - - val testResults = mutableListOf() - testScope.launch { - defaultCameraScanner.observeScannerResults().toList(testResults) - } - defaultCameraScanner.startCameraScanner("key") - val result = testResults.first() - result.shouldBeInstanceOf() - result.value shouldBe "123456789" - } -}) diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/google/ui/GoogleScanningViewModelTest.kt b/scan-engine/src/test/java/de/tillhub/scanengine/google/ui/GoogleScanningViewModelTest.kt deleted file mode 100644 index 005253c..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/google/ui/GoogleScanningViewModelTest.kt +++ /dev/null @@ -1,93 +0,0 @@ -package de.tillhub.scanengine.google.ui - -import androidx.camera.core.ImageInfo -import androidx.camera.core.ImageProxy -import com.google.android.gms.tasks.OnCompleteListener -import com.google.android.gms.tasks.OnSuccessListener -import com.google.android.gms.tasks.Task -import com.google.mlkit.vision.barcode.BarcodeScanner -import com.google.mlkit.vision.barcode.common.Barcode -import com.google.mlkit.vision.common.InputImage -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import io.mockk.Ordering -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue - -@ExperimentalCoroutinesApi -class GoogleScanningViewModelTest : FunSpec({ - - lateinit var scanner: BarcodeScanner - lateinit var imageProxy: ImageProxy - lateinit var inputImage: InputImage - lateinit var inputImageGenerator: InputImageGenerator - lateinit var imageInfo: ImageInfo - lateinit var viewModel: GoogleScanningViewModel - lateinit var task: Task> - lateinit var testScope: TestScope - - beforeTest { - testScope = TestScope(UnconfinedTestDispatcher()) - task = mockk() - inputImage = mockk() - imageInfo = mockk { - every { rotationDegrees } returns 1 - } - imageProxy = mockk { - every { image } returns mockk() - every { getImageInfo() } returns imageInfo - every { close() } just Runs - } - inputImageGenerator = mockk { - every { fromMediaImage(any(), any()) } returns inputImage - } - scanner = mockk { - every { process(inputImage) } returns task - every { close() } just Runs - } - viewModel = GoogleScanningViewModel(scanner, inputImageGenerator) - } - - test("barcode scan updates state to CodeScanned") { - val barcode = mockk { - every { rawValue } returns "1234567890" - } - val analyzer = viewModel.analyzer as QRImageAnalyzer - val testResults = mutableListOf() - - every { task.addOnSuccessListener(any()) } answers { - val arr = firstArg>>() - arr.onSuccess(listOf(barcode)) - task - } - every { task.addOnCompleteListener(any()) } answers { - val arr = firstArg>>() - arr.onComplete(task) - task - } - viewModel.scanningState.value shouldBe ScanningState.Idle - - testScope.launch { - viewModel.scanningState.toList(testResults) - } - analyzer.analyze(imageProxy) - val result = testResults.last() - assertTrue(result is ScanningState.CodeScanned) - assertEquals((result as ScanningState.CodeScanned).barcode, "1234567890") - - verify(ordering = Ordering.ORDERED) { - scanner.close() - imageProxy.close() - } - } -}) diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/sunmi/SunmiCameraScannerTest.kt b/scan-engine/src/test/java/de/tillhub/scanengine/sunmi/SunmiCameraScannerTest.kt deleted file mode 100644 index 80d429a..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/sunmi/SunmiCameraScannerTest.kt +++ /dev/null @@ -1,94 +0,0 @@ -import androidx.activity.result.ActivityResultCallback -import androidx.activity.result.ActivityResultCaller -import androidx.activity.result.ActivityResultLauncher -import de.tillhub.scanengine.CameraScanner -import de.tillhub.scanengine.data.ScannerEvent -import de.tillhub.scanengine.sunmi.camera.SunmiCameraScanner -import io.kotest.core.spec.style.FunSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.types.shouldBeInstanceOf -import io.mockk.every -import io.mockk.mockk -import io.mockk.slot -import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher - -@ExperimentalCoroutinesApi -class SunmiCameraScannerTest : FunSpec({ - - lateinit var resultCaller: ActivityResultCaller - lateinit var mutableScannerEvents: MutableStateFlow - lateinit var scannerLauncher: ActivityResultLauncher - lateinit var sunmiCameraScanner: CameraScanner - lateinit var testScope: TestScope - - beforeTest { - val callbackSlot = slot>>() - - mutableScannerEvents = MutableStateFlow(ScannerEvent.External.NotConnected) - scannerLauncher = mockk>(relaxed = true) - resultCaller = mockk(relaxed = true) { - every { - registerForActivityResult>( - any(), - capture(callbackSlot) - ) - } returns scannerLauncher - } - testScope = TestScope(UnconfinedTestDispatcher()) - sunmiCameraScanner = SunmiCameraScanner(resultCaller, mutableScannerEvents) - } - - test("startCameraScanner should emit InProgress event and launch scanner") { - sunmiCameraScanner.startCameraScanner("testScanKey") - - mutableScannerEvents.value.shouldBeInstanceOf() - (mutableScannerEvents.value as ScannerEvent.Camera.InProgress).scanKey shouldBe "testScanKey" - verify { scannerLauncher.launch(Unit) } - } - - test("registerForActivityResult callback emits ScanEvent_Canceled") { - val callbackSlot = slot>>() - every { - resultCaller.registerForActivityResult>(any(), capture(callbackSlot)) - } returns scannerLauncher - - sunmiCameraScanner = SunmiCameraScanner(resultCaller, mutableScannerEvents) - - val events = listOf(ScannerEvent.Camera.Canceled) - callbackSlot.captured.onActivityResult(events) - - val testResults = mutableListOf() - testScope.launch { - sunmiCameraScanner.observeScannerResults().toList(testResults) - } - sunmiCameraScanner.startCameraScanner() - testResults.first() shouldBe ScannerEvent.Camera.Canceled - } - - test("scannerLauncher should emit Success event") { - val callbackSlot = slot>>() - every { - resultCaller.registerForActivityResult>(any(), capture(callbackSlot)) - } returns scannerLauncher - - sunmiCameraScanner = SunmiCameraScanner(resultCaller, mutableScannerEvents) - - val events = listOf(ScannerEvent.ScanResult("123456789", "key")) - callbackSlot.captured.onActivityResult(events) - - val testResults = mutableListOf() - testScope.launch { - sunmiCameraScanner.observeScannerResults().toList(testResults) - } - sunmiCameraScanner.startCameraScanner("key") - val result = testResults.first() - result.shouldBeInstanceOf() - result.value shouldBe "123456789" - } -}) diff --git a/scan-engine/src/test/java/de/tillhub/scanengine/zebra/ZebraPairBarcodeViewModelTest.kt b/scan-engine/src/test/java/de/tillhub/scanengine/zebra/ZebraPairBarcodeViewModelTest.kt deleted file mode 100644 index 5c47621..0000000 --- a/scan-engine/src/test/java/de/tillhub/scanengine/zebra/ZebraPairBarcodeViewModelTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package de.tillhub.scanengine.zebra - -import com.zebra.scannercontrol.SDKHandler -import de.tillhub.scanengine.ScanEngine -import de.tillhub.scanengine.barcode.BarcodeScannerContainer -import de.tillhub.scanengine.common.ViewModelFunSpec -import de.tillhub.scanengine.data.ScannerEvent -import io.kotest.matchers.shouldBe -import io.mockk.coEvery -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkObject -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf - -@ExperimentalCoroutinesApi -class ZebraPairBarcodeViewModelTest : ViewModelFunSpec({ - - lateinit var scanEngine: ScanEngine - lateinit var zebraScanner: ZebraBarcodeScanner - lateinit var barcodeScannerContainer: BarcodeScannerContainer - beforeTest { - scanEngine = mockk(relaxed = true) - zebraScanner = mockk(relaxed = true) - barcodeScannerContainer = mockk(relaxed = true) - mockkObject(ScanEngine) - } - - test("initial state is Loading") { - every { scanEngine.barcodeScanner } returns barcodeScannerContainer - every { scanEngine.observeScannerResults() } returns flowOf() - every { barcodeScannerContainer.getScannersByType(ZebraBarcodeScanner::class.java) } returns zebraScanner - - val viewModel = ZebraPairBarcodeViewModel(scanEngine) - viewModel.uiStateFlow.value shouldBe ZebraPairBarcodeViewModel.State.Loading - } - - test("state transitions to Connected on ScanEvent.Connected") { - val scannerEvents = MutableStateFlow(ScannerEvent.External.NotConnected) - - every { scanEngine.barcodeScanner } returns barcodeScannerContainer - every { barcodeScannerContainer.getScannersByType(ZebraBarcodeScanner::class.java) } returns zebraScanner - every { scanEngine.observeScannerResults() } returns scannerEvents - - val viewModel = ZebraPairBarcodeViewModel(scanEngine) - - scannerEvents.tryEmit(ScannerEvent.External.Connected) - - viewModel.uiStateFlow.value shouldBe ZebraPairBarcodeViewModel.State.Connected - } - - test("initScanner sets state to Pairing") { - val result = mockk>(relaxed = true) - - every { scanEngine.barcodeScanner } returns barcodeScannerContainer - every { barcodeScannerContainer.getScannersByType(ZebraBarcodeScanner::class.java) } returns zebraScanner - coEvery { zebraScanner.initScanner() } returns result - - val viewModel = ZebraPairBarcodeViewModel(scanEngine) - - viewModel.initScanner() - - viewModel.uiStateFlow.value shouldBe ZebraPairBarcodeViewModel.State.Pairing(result) - } -}) diff --git a/settings.gradle.kts b/settings.gradle.kts index aa7cafa..f97bfed 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,25 +1,21 @@ -import java.net.URI - +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { repositories { google() - mavenCentral() gradlePluginPortal() + mavenCentral() } } -@Suppress("UnstableApiUsage") dependencyResolutionManagement { repositories { google() + gradlePluginPortal() mavenCentral() - maven { - url = URI.create("https://jitpack.io") - } + maven("https://plugins.gradle.org/m2/") } } - -rootProject.name = "Tillhub Scan Engine" -include(":sample") +rootProject.name = "Tillhub_Scan_Engine" include(":scan-engine") +include(":sample")