From 66e15ff2b85ff1ecbecd182b1984cccec361b2d0 Mon Sep 17 00:00:00 2001 From: Nagarjuna0033 Date: Mon, 20 Jan 2025 16:06:09 +0530 Subject: [PATCH] Feat: [:core:Qrcode] - Migrated to KMP --- .../demoDebugRuntimeClasspath.txt | 27 ++- .../demoReleaseRuntimeClasspath.txt | 27 ++- .../prodDebugRuntimeClasspath.txt | 27 ++- .../prodReleaseRuntimeClasspath.txt | 27 ++- core/qrcode/build.gradle.kts | 45 +++- .../core/qr/CameraPermissionState.android.kt | 63 +++++ .../org/mifos/mobile/core/qr/CameraView.kt | 76 ++++++ .../mifos/mobile/core/qr/QrCodeAnalyzer.kt | 51 ++++ .../mobile/core/qr/QrCodeScanner.android.kt | 27 +++ .../kotlin/org/mifos/mobile/core/qr/Utils.kt | 31 +++ .../mobile/core/qr/CameraPermissionState.kt | 26 ++ .../org/mifos/mobile/core/qr/CodeType.kt | 25 ++ .../org/mifos/mobile/core/qr/QrCodeScanner.kt | 81 +++++++ .../core/qr/CameraPermissionState.desktop.kt | 28 +++ .../mobile/core/qr/QrCodeScanner.desktop.kt | 21 ++ .../core/qr/CameraPermissionState.js.kt | 28 +++ .../mifos/mobile/core/qr/QrCodeScanner.js.kt | 21 ++ .../org/mifos/mobile/core/qr/BarcodeCamera.kt | 122 ---------- .../mifos/mobile/core/qr/QrCodeAnalyzer.kt | 73 ------ .../mifos/mobile/core/qr/QrCodeGenerator.kt | 85 ------- .../core/qr/CameraPermissionState.native.kt | 59 +++++ .../org/mifos/mobile/core/qr/CameraView.kt | 227 ++++++++++++++++++ .../mobile/core/qr/OrientationListener.kt | 54 +++++ .../mobile/core/qr/QrCodeScanner.native.kt | 26 ++ .../kotlin/org/mifos/mobile/core/qr/Utils.kt | 40 +++ .../core/qr/CameraPermissionState.wasmJs.kt | 28 +++ .../mobile/core/qr/QrCodeScanner.wasmJs.kt | 26 ++ gradle/libs.versions.toml | 4 + 28 files changed, 1059 insertions(+), 316 deletions(-) create mode 100644 core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.android.kt create mode 100644 core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/CameraView.kt create mode 100644 core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/QrCodeAnalyzer.kt create mode 100644 core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.android.kt create mode 100644 core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/Utils.kt create mode 100644 core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.kt create mode 100644 core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/CodeType.kt create mode 100644 core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.kt create mode 100644 core/qrcode/src/desktopMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.desktop.kt create mode 100644 core/qrcode/src/desktopMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.desktop.kt create mode 100644 core/qrcode/src/jsMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.js.kt create mode 100644 core/qrcode/src/jsMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.js.kt delete mode 100644 core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/BarcodeCamera.kt delete mode 100644 core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/QrCodeAnalyzer.kt delete mode 100644 core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/QrCodeGenerator.kt create mode 100644 core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.native.kt create mode 100644 core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/CameraView.kt create mode 100644 core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/OrientationListener.kt create mode 100644 core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.native.kt create mode 100644 core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/Utils.kt create mode 100644 core/qrcode/src/wasmJsMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.wasmJs.kt create mode 100644 core/qrcode/src/wasmJsMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.wasmJs.kt diff --git a/androidApp/dependencies/demoDebugRuntimeClasspath.txt b/androidApp/dependencies/demoDebugRuntimeClasspath.txt index 8a10a41f0..d1278bc5d 100644 --- a/androidApp/dependencies/demoDebugRuntimeClasspath.txt +++ b/androidApp/dependencies/demoDebugRuntimeClasspath.txt @@ -149,6 +149,9 @@ com.caverock:androidsvg-aar:1.4 com.google.accompanist:accompanist-drawablepainter:0.36.0 com.google.accompanist:accompanist-pager:0.34.0 com.google.accompanist:accompanist-permissions:0.34.0 +com.google.android.datatransport:transport-api:2.2.1 +com.google.android.datatransport:transport-backend-cct:2.3.3 +com.google.android.datatransport:transport-runtime:2.2.6 com.google.android.gms:play-services-ads-identifier:18.0.0 com.google.android.gms:play-services-base:18.5.0 com.google.android.gms:play-services-basement:18.4.0 @@ -159,9 +162,11 @@ com.google.android.gms:play-services-measurement-impl:22.1.2 com.google.android.gms:play-services-measurement-sdk-api:22.1.2 com.google.android.gms:play-services-measurement-sdk:22.1.2 com.google.android.gms:play-services-measurement:22.1.2 +com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1 com.google.android.gms:play-services-oss-licenses:17.1.0 com.google.android.gms:play-services-stats:17.0.2 com.google.android.gms:play-services-tasks:18.2.0 +com.google.android.odml:image:1.0.0-beta1 com.google.auto.value:auto-value-annotations:1.6.3 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.10.1 @@ -169,7 +174,7 @@ com.google.dagger:dagger-lint-aar:2.54 com.google.dagger:dagger:2.54 com.google.dagger:hilt-android:2.54 com.google.dagger:hilt-core:2.54 -com.google.errorprone:error_prone_annotations:2.26.0 +com.google.errorprone:error_prone_annotations:2.28.0 com.google.firebase:firebase-analytics-ktx:22.1.2 com.google.firebase:firebase-analytics:22.1.2 com.google.firebase:firebase-annotations:16.2.0 @@ -177,16 +182,22 @@ com.google.firebase:firebase-bom:33.7.0 com.google.firebase:firebase-common-ktx:21.0.0 com.google.firebase:firebase-common:21.0.0 com.google.firebase:firebase-components:18.0.0 +com.google.firebase:firebase-encoders-json:17.1.0 +com.google.firebase:firebase-encoders:17.0.0 com.google.firebase:firebase-installations-interop:17.1.1 com.google.firebase:firebase-installations:18.0.0 com.google.firebase:firebase-measurement-connector:19.0.0 -com.google.guava:failureaccess:1.0.1 -com.google.guava:guava:31.1-android +com.google.guava:failureaccess:1.0.2 +com.google.guava:guava:33.3.1-android com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava -com.google.j2objc:j2objc-annotations:1.3 +com.google.j2objc:j2objc-annotations:3.0.0 com.google.maps.android:maps-compose:4.4.1 com.google.maps.android:maps-ktx:5.0.0 -com.google.zxing:core:3.5.3 +com.google.mlkit:barcode-scanning-common:17.0.0 +com.google.mlkit:barcode-scanning:17.3.0 +com.google.mlkit:common:18.11.0 +com.google.mlkit:vision-common:17.3.0 +com.google.mlkit:vision-interfaces:16.3.0 com.russhwolf:multiplatform-settings-android-debug:1.2.0 com.russhwolf:multiplatform-settings-coroutines-android-debug:1.2.0 com.russhwolf:multiplatform-settings-coroutines:1.2.0 @@ -216,6 +227,10 @@ io.coil-kt.coil3:coil-svg-android:3.0.4 io.coil-kt.coil3:coil-svg:3.0.4 io.coil-kt.coil3:coil:3.0.4 io.github.mr0xf00:easycrop:0.1.1 +io.github.vinceglb:filekit-compose-android:0.8.7 +io.github.vinceglb:filekit-compose:0.8.7 +io.github.vinceglb:filekit-core-android:0.8.7 +io.github.vinceglb:filekit-core:0.8.7 io.insert-koin:koin-android:4.0.1-RC1 io.insert-koin:koin-androidx-compose:4.0.1-RC1 io.insert-koin:koin-annotations-jvm:1.4.0-RC4 @@ -252,7 +267,7 @@ jakarta.inject:jakarta.inject-api:2.0.1 javax.inject:javax.inject:1 net.bytebuddy:byte-buddy-agent:1.14.8 net.bytebuddy:byte-buddy:1.14.8 -org.checkerframework:checker-qual:3.12.0 +org.checkerframework:checker-qual:3.43.0 org.jetbrains.androidx.core:core-bundle-android-debug:1.0.1 org.jetbrains.androidx.core:core-bundle:1.0.1 org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3 diff --git a/androidApp/dependencies/demoReleaseRuntimeClasspath.txt b/androidApp/dependencies/demoReleaseRuntimeClasspath.txt index 96c9c7de9..ea0e88cf7 100644 --- a/androidApp/dependencies/demoReleaseRuntimeClasspath.txt +++ b/androidApp/dependencies/demoReleaseRuntimeClasspath.txt @@ -144,6 +144,9 @@ com.caverock:androidsvg-aar:1.4 com.google.accompanist:accompanist-drawablepainter:0.36.0 com.google.accompanist:accompanist-pager:0.34.0 com.google.accompanist:accompanist-permissions:0.34.0 +com.google.android.datatransport:transport-api:2.2.1 +com.google.android.datatransport:transport-backend-cct:2.3.3 +com.google.android.datatransport:transport-runtime:2.2.6 com.google.android.gms:play-services-ads-identifier:18.0.0 com.google.android.gms:play-services-base:18.5.0 com.google.android.gms:play-services-basement:18.4.0 @@ -154,9 +157,11 @@ com.google.android.gms:play-services-measurement-impl:22.1.2 com.google.android.gms:play-services-measurement-sdk-api:22.1.2 com.google.android.gms:play-services-measurement-sdk:22.1.2 com.google.android.gms:play-services-measurement:22.1.2 +com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1 com.google.android.gms:play-services-oss-licenses:17.1.0 com.google.android.gms:play-services-stats:17.0.2 com.google.android.gms:play-services-tasks:18.2.0 +com.google.android.odml:image:1.0.0-beta1 com.google.auto.value:auto-value-annotations:1.6.3 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.10.1 @@ -164,7 +169,7 @@ com.google.dagger:dagger-lint-aar:2.54 com.google.dagger:dagger:2.54 com.google.dagger:hilt-android:2.54 com.google.dagger:hilt-core:2.54 -com.google.errorprone:error_prone_annotations:2.26.0 +com.google.errorprone:error_prone_annotations:2.28.0 com.google.firebase:firebase-analytics-ktx:22.1.2 com.google.firebase:firebase-analytics:22.1.2 com.google.firebase:firebase-annotations:16.2.0 @@ -172,16 +177,22 @@ com.google.firebase:firebase-bom:33.7.0 com.google.firebase:firebase-common-ktx:21.0.0 com.google.firebase:firebase-common:21.0.0 com.google.firebase:firebase-components:18.0.0 +com.google.firebase:firebase-encoders-json:17.1.0 +com.google.firebase:firebase-encoders:17.0.0 com.google.firebase:firebase-installations-interop:17.1.1 com.google.firebase:firebase-installations:18.0.0 com.google.firebase:firebase-measurement-connector:19.0.0 -com.google.guava:failureaccess:1.0.1 -com.google.guava:guava:31.1-android +com.google.guava:failureaccess:1.0.2 +com.google.guava:guava:33.3.1-android com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava -com.google.j2objc:j2objc-annotations:1.3 +com.google.j2objc:j2objc-annotations:3.0.0 com.google.maps.android:maps-compose:4.4.1 com.google.maps.android:maps-ktx:5.0.0 -com.google.zxing:core:3.5.3 +com.google.mlkit:barcode-scanning-common:17.0.0 +com.google.mlkit:barcode-scanning:17.3.0 +com.google.mlkit:common:18.11.0 +com.google.mlkit:vision-common:17.3.0 +com.google.mlkit:vision-interfaces:16.3.0 com.russhwolf:multiplatform-settings-android:1.2.0 com.russhwolf:multiplatform-settings-coroutines-android:1.2.0 com.russhwolf:multiplatform-settings-coroutines:1.2.0 @@ -211,6 +222,10 @@ io.coil-kt.coil3:coil-svg-android:3.0.4 io.coil-kt.coil3:coil-svg:3.0.4 io.coil-kt.coil3:coil:3.0.4 io.github.mr0xf00:easycrop:0.1.1 +io.github.vinceglb:filekit-compose-android:0.8.7 +io.github.vinceglb:filekit-compose:0.8.7 +io.github.vinceglb:filekit-core-android:0.8.7 +io.github.vinceglb:filekit-core:0.8.7 io.insert-koin:koin-android:4.0.1-RC1 io.insert-koin:koin-androidx-compose:4.0.1-RC1 io.insert-koin:koin-annotations-jvm:1.4.0-RC4 @@ -247,7 +262,7 @@ jakarta.inject:jakarta.inject-api:2.0.1 javax.inject:javax.inject:1 net.bytebuddy:byte-buddy-agent:1.14.8 net.bytebuddy:byte-buddy:1.14.8 -org.checkerframework:checker-qual:3.12.0 +org.checkerframework:checker-qual:3.43.0 org.jetbrains.androidx.core:core-bundle-android:1.0.1 org.jetbrains.androidx.core:core-bundle:1.0.1 org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3 diff --git a/androidApp/dependencies/prodDebugRuntimeClasspath.txt b/androidApp/dependencies/prodDebugRuntimeClasspath.txt index 8a10a41f0..d1278bc5d 100644 --- a/androidApp/dependencies/prodDebugRuntimeClasspath.txt +++ b/androidApp/dependencies/prodDebugRuntimeClasspath.txt @@ -149,6 +149,9 @@ com.caverock:androidsvg-aar:1.4 com.google.accompanist:accompanist-drawablepainter:0.36.0 com.google.accompanist:accompanist-pager:0.34.0 com.google.accompanist:accompanist-permissions:0.34.0 +com.google.android.datatransport:transport-api:2.2.1 +com.google.android.datatransport:transport-backend-cct:2.3.3 +com.google.android.datatransport:transport-runtime:2.2.6 com.google.android.gms:play-services-ads-identifier:18.0.0 com.google.android.gms:play-services-base:18.5.0 com.google.android.gms:play-services-basement:18.4.0 @@ -159,9 +162,11 @@ com.google.android.gms:play-services-measurement-impl:22.1.2 com.google.android.gms:play-services-measurement-sdk-api:22.1.2 com.google.android.gms:play-services-measurement-sdk:22.1.2 com.google.android.gms:play-services-measurement:22.1.2 +com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1 com.google.android.gms:play-services-oss-licenses:17.1.0 com.google.android.gms:play-services-stats:17.0.2 com.google.android.gms:play-services-tasks:18.2.0 +com.google.android.odml:image:1.0.0-beta1 com.google.auto.value:auto-value-annotations:1.6.3 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.10.1 @@ -169,7 +174,7 @@ com.google.dagger:dagger-lint-aar:2.54 com.google.dagger:dagger:2.54 com.google.dagger:hilt-android:2.54 com.google.dagger:hilt-core:2.54 -com.google.errorprone:error_prone_annotations:2.26.0 +com.google.errorprone:error_prone_annotations:2.28.0 com.google.firebase:firebase-analytics-ktx:22.1.2 com.google.firebase:firebase-analytics:22.1.2 com.google.firebase:firebase-annotations:16.2.0 @@ -177,16 +182,22 @@ com.google.firebase:firebase-bom:33.7.0 com.google.firebase:firebase-common-ktx:21.0.0 com.google.firebase:firebase-common:21.0.0 com.google.firebase:firebase-components:18.0.0 +com.google.firebase:firebase-encoders-json:17.1.0 +com.google.firebase:firebase-encoders:17.0.0 com.google.firebase:firebase-installations-interop:17.1.1 com.google.firebase:firebase-installations:18.0.0 com.google.firebase:firebase-measurement-connector:19.0.0 -com.google.guava:failureaccess:1.0.1 -com.google.guava:guava:31.1-android +com.google.guava:failureaccess:1.0.2 +com.google.guava:guava:33.3.1-android com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava -com.google.j2objc:j2objc-annotations:1.3 +com.google.j2objc:j2objc-annotations:3.0.0 com.google.maps.android:maps-compose:4.4.1 com.google.maps.android:maps-ktx:5.0.0 -com.google.zxing:core:3.5.3 +com.google.mlkit:barcode-scanning-common:17.0.0 +com.google.mlkit:barcode-scanning:17.3.0 +com.google.mlkit:common:18.11.0 +com.google.mlkit:vision-common:17.3.0 +com.google.mlkit:vision-interfaces:16.3.0 com.russhwolf:multiplatform-settings-android-debug:1.2.0 com.russhwolf:multiplatform-settings-coroutines-android-debug:1.2.0 com.russhwolf:multiplatform-settings-coroutines:1.2.0 @@ -216,6 +227,10 @@ io.coil-kt.coil3:coil-svg-android:3.0.4 io.coil-kt.coil3:coil-svg:3.0.4 io.coil-kt.coil3:coil:3.0.4 io.github.mr0xf00:easycrop:0.1.1 +io.github.vinceglb:filekit-compose-android:0.8.7 +io.github.vinceglb:filekit-compose:0.8.7 +io.github.vinceglb:filekit-core-android:0.8.7 +io.github.vinceglb:filekit-core:0.8.7 io.insert-koin:koin-android:4.0.1-RC1 io.insert-koin:koin-androidx-compose:4.0.1-RC1 io.insert-koin:koin-annotations-jvm:1.4.0-RC4 @@ -252,7 +267,7 @@ jakarta.inject:jakarta.inject-api:2.0.1 javax.inject:javax.inject:1 net.bytebuddy:byte-buddy-agent:1.14.8 net.bytebuddy:byte-buddy:1.14.8 -org.checkerframework:checker-qual:3.12.0 +org.checkerframework:checker-qual:3.43.0 org.jetbrains.androidx.core:core-bundle-android-debug:1.0.1 org.jetbrains.androidx.core:core-bundle:1.0.1 org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3 diff --git a/androidApp/dependencies/prodReleaseRuntimeClasspath.txt b/androidApp/dependencies/prodReleaseRuntimeClasspath.txt index 96c9c7de9..ea0e88cf7 100644 --- a/androidApp/dependencies/prodReleaseRuntimeClasspath.txt +++ b/androidApp/dependencies/prodReleaseRuntimeClasspath.txt @@ -144,6 +144,9 @@ com.caverock:androidsvg-aar:1.4 com.google.accompanist:accompanist-drawablepainter:0.36.0 com.google.accompanist:accompanist-pager:0.34.0 com.google.accompanist:accompanist-permissions:0.34.0 +com.google.android.datatransport:transport-api:2.2.1 +com.google.android.datatransport:transport-backend-cct:2.3.3 +com.google.android.datatransport:transport-runtime:2.2.6 com.google.android.gms:play-services-ads-identifier:18.0.0 com.google.android.gms:play-services-base:18.5.0 com.google.android.gms:play-services-basement:18.4.0 @@ -154,9 +157,11 @@ com.google.android.gms:play-services-measurement-impl:22.1.2 com.google.android.gms:play-services-measurement-sdk-api:22.1.2 com.google.android.gms:play-services-measurement-sdk:22.1.2 com.google.android.gms:play-services-measurement:22.1.2 +com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1 com.google.android.gms:play-services-oss-licenses:17.1.0 com.google.android.gms:play-services-stats:17.0.2 com.google.android.gms:play-services-tasks:18.2.0 +com.google.android.odml:image:1.0.0-beta1 com.google.auto.value:auto-value-annotations:1.6.3 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.10.1 @@ -164,7 +169,7 @@ com.google.dagger:dagger-lint-aar:2.54 com.google.dagger:dagger:2.54 com.google.dagger:hilt-android:2.54 com.google.dagger:hilt-core:2.54 -com.google.errorprone:error_prone_annotations:2.26.0 +com.google.errorprone:error_prone_annotations:2.28.0 com.google.firebase:firebase-analytics-ktx:22.1.2 com.google.firebase:firebase-analytics:22.1.2 com.google.firebase:firebase-annotations:16.2.0 @@ -172,16 +177,22 @@ com.google.firebase:firebase-bom:33.7.0 com.google.firebase:firebase-common-ktx:21.0.0 com.google.firebase:firebase-common:21.0.0 com.google.firebase:firebase-components:18.0.0 +com.google.firebase:firebase-encoders-json:17.1.0 +com.google.firebase:firebase-encoders:17.0.0 com.google.firebase:firebase-installations-interop:17.1.1 com.google.firebase:firebase-installations:18.0.0 com.google.firebase:firebase-measurement-connector:19.0.0 -com.google.guava:failureaccess:1.0.1 -com.google.guava:guava:31.1-android +com.google.guava:failureaccess:1.0.2 +com.google.guava:guava:33.3.1-android com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava -com.google.j2objc:j2objc-annotations:1.3 +com.google.j2objc:j2objc-annotations:3.0.0 com.google.maps.android:maps-compose:4.4.1 com.google.maps.android:maps-ktx:5.0.0 -com.google.zxing:core:3.5.3 +com.google.mlkit:barcode-scanning-common:17.0.0 +com.google.mlkit:barcode-scanning:17.3.0 +com.google.mlkit:common:18.11.0 +com.google.mlkit:vision-common:17.3.0 +com.google.mlkit:vision-interfaces:16.3.0 com.russhwolf:multiplatform-settings-android:1.2.0 com.russhwolf:multiplatform-settings-coroutines-android:1.2.0 com.russhwolf:multiplatform-settings-coroutines:1.2.0 @@ -211,6 +222,10 @@ io.coil-kt.coil3:coil-svg-android:3.0.4 io.coil-kt.coil3:coil-svg:3.0.4 io.coil-kt.coil3:coil:3.0.4 io.github.mr0xf00:easycrop:0.1.1 +io.github.vinceglb:filekit-compose-android:0.8.7 +io.github.vinceglb:filekit-compose:0.8.7 +io.github.vinceglb:filekit-core-android:0.8.7 +io.github.vinceglb:filekit-core:0.8.7 io.insert-koin:koin-android:4.0.1-RC1 io.insert-koin:koin-androidx-compose:4.0.1-RC1 io.insert-koin:koin-annotations-jvm:1.4.0-RC4 @@ -247,7 +262,7 @@ jakarta.inject:jakarta.inject-api:2.0.1 javax.inject:javax.inject:1 net.bytebuddy:byte-buddy-agent:1.14.8 net.bytebuddy:byte-buddy:1.14.8 -org.checkerframework:checker-qual:3.12.0 +org.checkerframework:checker-qual:3.43.0 org.jetbrains.androidx.core:core-bundle-android:1.0.1 org.jetbrains.androidx.core:core-bundle:1.0.1 org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.3 diff --git a/core/qrcode/build.gradle.kts b/core/qrcode/build.gradle.kts index 2345ca418..6f18b893c 100644 --- a/core/qrcode/build.gradle.kts +++ b/core/qrcode/build.gradle.kts @@ -9,24 +9,45 @@ */ plugins { - alias(libs.plugins.mifos.android.library) - alias(libs.plugins.mifos.android.library.compose) + alias(libs.plugins.mifos.kmp.library) + alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.compose.compiler) } android { namespace = "org.mifos.mobile.core.qrcode" + defaultConfig { + consumerProguardFiles("consumer-rules.pro") + } } -dependencies { - implementation(projects.core.model) - implementation(libs.androidx.compose.ui) +kotlin { + sourceSets { + commonMain.dependencies { + commonMain.dependencies { + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.coil.kt.compose) + implementation(libs.filekit.core) + implementation(libs.filekit.compose) + api(libs.kermit.logging) + } - api(libs.zxing.core) - api(libs.squareup.retrofit.converter.gson) + androidMain.dependencies { + implementation(libs.androidx.camera.view) + implementation(libs.androidx.camera.camera2) + implementation(libs.androidx.camera.lifecycle) + implementation(libs.accompanist.permissions) + implementation(libs.mlkit.barcode.scanning) + implementation(libs.guava) + } - // cameraX - implementation(libs.androidx.camera.camera2) - implementation(libs.androidx.camera.lifecycle) - implementation(libs.androidx.camera.view) - implementation(libs.androidx.camera.core) + nativeMain.dependencies { + implementation(libs.moko.permission.compose) + } + } + } } diff --git a/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.android.kt b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.android.kt new file mode 100644 index 000000000..02cc75537 --- /dev/null +++ b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.android.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.Settings +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.core.content.ContextCompat +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.PermissionState +import com.google.accompanist.permissions.PermissionStatus +import com.google.accompanist.permissions.rememberPermissionState + +@OptIn(ExperimentalPermissionsApi::class) +@Composable +actual fun rememberCameraPermissionState(): CameraPermissionState { + val accPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA) + + val context = LocalContext.current + val wrapper = remember(accPermissionState) { + AccompanistPermissionWrapper(accPermissionState, context) + } + + return wrapper +} + +@OptIn(ExperimentalPermissionsApi::class) +class AccompanistPermissionWrapper( + private val permissionState: PermissionState, + private val context: Context, +) : CameraPermissionState { + override val status: CameraPermissionStatus + get() = permissionState.status.toCameraPermissionStatus() + + override fun requestCameraPermission() { + permissionState.launchPermissionRequest() + } + + override fun goToSettings() { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + intent.data = Uri.parse("package:" + context.packageName) + ContextCompat.startActivity(context, intent, null) + } +} + +@OptIn(ExperimentalPermissionsApi::class) +private fun PermissionStatus.toCameraPermissionStatus(): CameraPermissionStatus { + return when (this) { + is PermissionStatus.Denied -> CameraPermissionStatus.Denied + PermissionStatus.Granted -> CameraPermissionStatus.Granted + } +} diff --git a/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/CameraView.kt b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/CameraView.kt new file mode 100644 index 000000000..7d3dc1bda --- /dev/null +++ b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/CameraView.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +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.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import androidx.lifecycle.compose.LocalLifecycleOwner +import co.touchlab.kermit.Logger + +@Composable +fun CameraView( + modifier: Modifier = Modifier, + analyzer: QrCodeAnalyzer, +) { + val localContext = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + val cameraProviderFuture = remember { + ProcessCameraProvider.getInstance(localContext) + } + + DisposableEffect(cameraProviderFuture) { + onDispose { + cameraProviderFuture.get().unbindAll() + } + } + + AndroidView( + modifier = modifier.fillMaxSize(), + factory = { context -> + val previewView = PreviewView(context) + val preview = Preview.Builder().build() + val selector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + + preview.surfaceProvider = previewView.surfaceProvider + + val imageAnalysis = ImageAnalysis.Builder().build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(context), + analyzer, + ) + + runCatching { + cameraProviderFuture.get().unbindAll() + cameraProviderFuture.get().bindToLifecycle( + lifecycleOwner, + selector, + preview, + imageAnalysis, + ) + }.onFailure { + Logger.d("CAMERA", it) + } + previewView + }, + ) +} diff --git a/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/QrCodeAnalyzer.kt b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/QrCodeAnalyzer.kt new file mode 100644 index 000000000..ac63f7382 --- /dev/null +++ b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/QrCodeAnalyzer.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.camera.core.ExperimentalGetImage +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.barcode.common.Barcode +import com.google.mlkit.vision.common.InputImage + +class QrCodeAnalyzer( + formats: Int = Barcode.FORMAT_QR_CODE, + private val onScanned: (String) -> Boolean, +) : ImageAnalysis.Analyzer { + + private val options = BarcodeScannerOptions.Builder() + .setBarcodeFormats(formats) + .build() + + private val scanner = BarcodeScanning.getClient(options) + + @androidx.annotation.OptIn(ExperimentalGetImage::class) + override fun analyze(imageProxy: ImageProxy) { + imageProxy.image?.let { image -> + val inputImage = InputImage.fromMediaImage(image, imageProxy.imageInfo.rotationDegrees) + scanner.process(inputImage) + .addOnSuccessListener { barcodes -> + barcodes?.takeIf { it.isNotEmpty() } + ?.mapNotNull { it.rawValue } + ?.joinToString(",") + ?.let { rawValue -> + if (onScanned(rawValue)) { + scanner.close() + } + } + } + .addOnCompleteListener { + imageProxy.close() + } + } ?: imageProxy.close() + } +} diff --git a/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.android.kt b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.android.kt new file mode 100644 index 000000000..d06da1772 --- /dev/null +++ b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.android.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier + +@Composable +actual fun QrCodeScanner( + types: List, + modifier: Modifier, + onScanned: (String) -> Boolean, +) { + val analyzer = remember { + QrCodeAnalyzer(types.toFormat(), onScanned) + } + + CameraView(modifier, analyzer) +} diff --git a/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/Utils.kt b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/Utils.kt new file mode 100644 index 000000000..94f83f22a --- /dev/null +++ b/core/qrcode/src/androidMain/kotlin/org/mifos/mobile/core/qr/Utils.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import com.google.mlkit.vision.barcode.common.Barcode + +fun List.toFormat(): Int = map { + when (it) { + CodeType.Codabar -> Barcode.FORMAT_CODABAR + CodeType.Code39 -> Barcode.FORMAT_CODE_39 + CodeType.Code93 -> Barcode.FORMAT_CODE_93 + CodeType.Code128 -> Barcode.FORMAT_CODE_128 + CodeType.EAN8 -> Barcode.FORMAT_EAN_8 + CodeType.EAN13 -> Barcode.FORMAT_EAN_13 + CodeType.ITF -> Barcode.FORMAT_ITF + CodeType.UPCE -> Barcode.FORMAT_UPC_E + CodeType.Aztec -> Barcode.FORMAT_AZTEC + CodeType.DataMatrix -> Barcode.FORMAT_DATA_MATRIX + CodeType.PDF417 -> Barcode.FORMAT_PDF417 + CodeType.QR -> Barcode.FORMAT_QR_CODE + } +}.fold(0) { acc, next -> + acc + next +} diff --git a/core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.kt b/core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.kt new file mode 100644 index 000000000..c35b830ef --- /dev/null +++ b/core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.runtime.Composable + +interface CameraPermissionState { + val status: CameraPermissionStatus + fun requestCameraPermission() + fun goToSettings() +} + +@Composable +expect fun rememberCameraPermissionState(): CameraPermissionState + +enum class CameraPermissionStatus { + Denied, + Granted, +} diff --git a/core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/CodeType.kt b/core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/CodeType.kt new file mode 100644 index 000000000..952c7f45d --- /dev/null +++ b/core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/CodeType.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +enum class CodeType { + Codabar, + Code39, + Code93, + Code128, + EAN8, + EAN13, + ITF, + UPCE, + Aztec, + DataMatrix, + PDF417, + QR, +} diff --git a/core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.kt b/core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.kt new file mode 100644 index 000000000..d4bdcff99 --- /dev/null +++ b/core/qrcode/src/commonMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.unit.dp + +@Composable +expect fun QrCodeScanner( + types: List, + modifier: Modifier = Modifier, + onScanned: (String) -> Boolean, +) + +@Composable +fun QrScannerWithPermissions( + types: List, + modifier: Modifier = Modifier, + permissionText: String = "Camera is required for QR Code scanning", + openSettingsLabel: String = "Open Settings", + onScanned: (String) -> Boolean, +) { + QrScannerWithPermissions( + types = types, + modifier = modifier.clipToBounds(), + onScanned = onScanned, + permissionDeniedContent = { permissionState -> + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + modifier = Modifier.padding(6.dp), + text = permissionText, + ) + Button( + onClick = permissionState::goToSettings, + ) { + Text(openSettingsLabel) + } + } + }, + ) +} + +@Composable +fun QrScannerWithPermissions( + types: List, + modifier: Modifier = Modifier, + onScanned: (String) -> Boolean, + permissionDeniedContent: @Composable (CameraPermissionState) -> Unit, +) { + val permissionState = rememberCameraPermissionState() + + LaunchedEffect(Unit) { + if (permissionState.status == CameraPermissionStatus.Denied) { + permissionState.requestCameraPermission() + } + } + + if (permissionState.status == CameraPermissionStatus.Granted) { + QrCodeScanner(types = types, modifier, onScanned = onScanned) + } else { + permissionDeniedContent(permissionState) + } +} diff --git a/core/qrcode/src/desktopMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.desktop.kt b/core/qrcode/src/desktopMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.desktop.kt new file mode 100644 index 000000000..9c46a1105 --- /dev/null +++ b/core/qrcode/src/desktopMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.desktop.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.runtime.Composable + +@Composable +actual fun rememberCameraPermissionState(): CameraPermissionState { + return object : CameraPermissionState { + override val status: CameraPermissionStatus + get() = CameraPermissionStatus.Granted + + override fun requestCameraPermission() { + println("Camera permission requested isn't supported on desktop yet.") + } + + override fun goToSettings() { + println("Go to settings isn't supported on desktop yet.") + } + } +} diff --git a/core/qrcode/src/desktopMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.desktop.kt b/core/qrcode/src/desktopMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.desktop.kt new file mode 100644 index 000000000..e761b808a --- /dev/null +++ b/core/qrcode/src/desktopMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.desktop.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +actual fun QrCodeScanner( + types: List, + modifier: Modifier, + onScanned: (String) -> Boolean, +) { +} diff --git a/core/qrcode/src/jsMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.js.kt b/core/qrcode/src/jsMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.js.kt new file mode 100644 index 000000000..bd53ba2ab --- /dev/null +++ b/core/qrcode/src/jsMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.js.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.runtime.Composable + +@Composable +actual fun rememberCameraPermissionState(): CameraPermissionState { + return object : CameraPermissionState { + override val status: CameraPermissionStatus + get() = CameraPermissionStatus.Granted + + override fun requestCameraPermission() { + println("Camera permission requested isn't supported on web yet.") + } + + override fun goToSettings() { + println("Go to settings isn't supported on web yet.") + } + } +} diff --git a/core/qrcode/src/jsMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.js.kt b/core/qrcode/src/jsMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.js.kt new file mode 100644 index 000000000..e761b808a --- /dev/null +++ b/core/qrcode/src/jsMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.js.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +actual fun QrCodeScanner( + types: List, + modifier: Modifier, + onScanned: (String) -> Boolean, +) { +} diff --git a/core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/BarcodeCamera.kt b/core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/BarcodeCamera.kt deleted file mode 100644 index b71f3e37b..000000000 --- a/core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/BarcodeCamera.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2024 Mifos Initiative - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md - */ -package org.mifos.mobile.core.qr - -import android.content.ContentValues.TAG -import android.content.Context -import android.util.Log -import android.view.ViewGroup -import android.widget.LinearLayout -import androidx.camera.core.Camera -import androidx.camera.core.CameraSelector -import androidx.camera.core.ExperimentalGetImage -import androidx.camera.core.ImageAnalysis -import androidx.camera.core.ImageCapture -import androidx.camera.core.Preview -import androidx.camera.lifecycle.ProcessCameraProvider -import androidx.camera.view.PreviewView -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.content.ContextCompat -import androidx.lifecycle.LifecycleOwner - -@ExperimentalGetImage -class BarcodeCamera { - private var camera: Camera? = null - - @Composable - fun BarcodeReaderCamera( - onBarcodeScanned: (String?) -> Unit, - isFlashOn: Boolean, - ) { - LaunchedEffect(isFlashOn) { - toggleFlash(isOn = isFlashOn) - } - - val lifecycleOwner = LocalLifecycleOwner.current - - val imageCapture = remember { - ImageCapture.Builder().build() - } - - AndroidView( - factory = { context -> - PreviewView(context).apply { - layoutParams = LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - ) - scaleType = PreviewView.ScaleType.FILL_START - - startCamera( - context = context, - previewView = this, - imageCapture = imageCapture, - lifecycleOwner = lifecycleOwner, - onBarcodeScanned = onBarcodeScanned, - ) - } - }, - ) - } - - private fun startCamera( - context: Context, - previewView: PreviewView, - lifecycleOwner: LifecycleOwner, - imageCapture: ImageCapture, - onBarcodeScanned: (String) -> Unit, - ) { - val cameraProviderFuture = ProcessCameraProvider.getInstance(context) - - cameraProviderFuture.addListener( - { - val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() - - val preview = Preview.Builder().build() - .also { it.setSurfaceProvider(previewView.surfaceProvider) } - - val imageAnalysis = ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() - - imageAnalysis.setAnalyzer( - ContextCompat.getMainExecutor(context), - QrCodeAnalyzer( - onBarcodeScanned = onBarcodeScanned, - ), - ) - - val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA - - try { - cameraProvider.unbindAll() - camera = cameraProvider.bindToLifecycle( - lifecycleOwner, - cameraSelector, - preview, - imageCapture, - imageAnalysis, - ) - } catch (exc: Exception) { - Log.e(TAG, "Use case binding failed", exc) - } - }, - ContextCompat.getMainExecutor(context), - ) - } - - private fun toggleFlash(isOn: Boolean) { - camera?.cameraControl?.enableTorch(isOn) - } -} diff --git a/core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/QrCodeAnalyzer.kt b/core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/QrCodeAnalyzer.kt deleted file mode 100644 index 7b20ec437..000000000 --- a/core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/QrCodeAnalyzer.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2024 Mifos Initiative - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md - */ -package org.mifos.mobile.core.qr - -import android.graphics.ImageFormat -import android.util.Log -import androidx.camera.core.ImageAnalysis -import androidx.camera.core.ImageProxy -import com.google.zxing.BarcodeFormat -import com.google.zxing.BinaryBitmap -import com.google.zxing.DecodeHintType -import com.google.zxing.MultiFormatReader -import com.google.zxing.PlanarYUVLuminanceSource -import com.google.zxing.common.HybridBinarizer -import java.nio.ByteBuffer - -class QrCodeAnalyzer( - private val onBarcodeScanned: (String) -> Unit, -) : ImageAnalysis.Analyzer { - - private val supportedImageFormats = listOf( - ImageFormat.YUV_420_888, - ImageFormat.YUV_422_888, - ImageFormat.YUV_444_888, - ) - - override fun analyze(image: ImageProxy) { - if (image.format in supportedImageFormats) { - val bytes = image.planes.first().buffer.toByteArray() - val source = PlanarYUVLuminanceSource( - bytes, - image.width, - image.height, - 0, - 0, - image.width, - image.height, - false, - ) - val binaryBmp = BinaryBitmap(HybridBinarizer(source)) - try { - val result = MultiFormatReader().apply { - setHints( - mapOf( - DecodeHintType.POSSIBLE_FORMATS to arrayListOf( - BarcodeFormat.QR_CODE, - ), - ), - ) - }.decode(binaryBmp) - onBarcodeScanned(result.text) - } catch (e: Exception) { - Log.d("QrCodeAnalyzer", "analyze: ${e.message}") - } finally { - image.close() - } - } - } - - private fun ByteBuffer.toByteArray(): ByteArray { - rewind() - return ByteArray(remaining()).also { - get(it) - } - } -} diff --git a/core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/QrCodeGenerator.kt b/core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/QrCodeGenerator.kt deleted file mode 100644 index 0cf943bf9..000000000 --- a/core/qrcode/src/main/kotlin/org/mifos/mobile/core/qr/QrCodeGenerator.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2024 Mifos Initiative - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md - */ -package org.mifos.mobile.core.qr - -import android.graphics.Bitmap -import android.graphics.Color -import com.google.gson.Gson -import com.google.zxing.BarcodeFormat -import com.google.zxing.MultiFormatWriter -import com.google.zxing.common.BitMatrix -import org.mifos.mobile.core.model.entity.beneficiary.Beneficiary -import org.mifos.mobile.core.model.entity.templates.account.AccountType -import org.mifos.mobile.core.model.enums.AccountType as EnumsAccountType - -object QrCodeGenerator { - private const val QR_CODE_SIZE = 200 - - /** - * Generate a QRCode which stores `str` in the form of [Bitmap] - * @param str Data which need to stored in QRCode - * @return Returns a [Bitmap] of QRCode or null if generation fails - */ - fun encodeAsBitmap(str: String?): Bitmap? { - if (str.isNullOrEmpty()) return null - - return try { - val result = MultiFormatWriter().encode( - str, - BarcodeFormat.QR_CODE, - QR_CODE_SIZE, - QR_CODE_SIZE, - null, - ) - createBitmapFromBitMatrix(result) - } catch (e: Exception) { - null - } - } - - private fun createBitmapFromBitMatrix(matrix: BitMatrix): Bitmap { - val width = matrix.width - val height = matrix.height - val pixels = IntArray(width * height) { index -> - if (matrix[index % width, index / width]) Color.BLACK else Color.WHITE - } - - return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply { - setPixels(pixels, 0, width, 0, 0, width, height) - } - } - - /** - * Provides a string which contains json data for creating a [Beneficiary] - * @param accountNumber Account Number of client - * @param officeName Office Name of Client - * @param accountType [EnumsAccountType] i.e. SAVINGS or LOAN - * @return Returns a string with account details - */ - fun getAccountDetailsInString( - accountNumber: String?, - officeName: String?, - accountType: EnumsAccountType, - ): String { - val payload = Beneficiary().apply { - this.accountNumber = accountNumber - this.accountType = AccountType().apply { - id = when (accountType) { - EnumsAccountType.SAVINGS -> 0 - EnumsAccountType.LOAN -> 1 - EnumsAccountType.SHARE -> -1 - } - } - this.officeName = officeName - } - - return Gson().toJson(payload) - } -} diff --git a/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.native.kt b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.native.kt new file mode 100644 index 000000000..6a2777185 --- /dev/null +++ b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.native.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +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 platform.AVFoundation.AVAuthorizationStatusAuthorized +import platform.AVFoundation.AVCaptureDevice +import platform.AVFoundation.AVMediaTypeVideo +import platform.AVFoundation.authorizationStatusForMediaType +import platform.AVFoundation.requestAccessForMediaType +import platform.Foundation.NSURL +import platform.UIKit.UIApplication +import platform.UIKit.UIApplicationOpenSettingsURLString + +@Composable +actual fun rememberCameraPermissionState(): CameraPermissionState { + return remember { + IosMutableCameraPermissionState() + } +} + +abstract class MutableCameraPermissionState : CameraPermissionState { + override var status: CameraPermissionStatus by mutableStateOf(getCameraPermissionStatus()) +} + +class IosMutableCameraPermissionState : MutableCameraPermissionState() { + override fun requestCameraPermission() { + AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo) { + this.status = getCameraPermissionStatus() + } + } + + override fun goToSettings() { + val appSettingsUrl = NSURL(string = UIApplicationOpenSettingsURLString) + if (UIApplication.sharedApplication.canOpenURL(appSettingsUrl)) { + UIApplication.sharedApplication.openURL(appSettingsUrl) + } + } +} + +fun getCameraPermissionStatus(): CameraPermissionStatus { + val authorizationStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo) + return if (authorizationStatus == AVAuthorizationStatusAuthorized) { + CameraPermissionStatus.Granted + } else { + CameraPermissionStatus.Denied + } +} diff --git a/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/CameraView.kt b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/CameraView.kt new file mode 100644 index 000000000..7b37cb692 --- /dev/null +++ b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/CameraView.kt @@ -0,0 +1,227 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.UIKitInteropProperties +import androidx.compose.ui.viewinterop.UIKitView +import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.CValue +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.ObjCObjectVar +import kotlinx.cinterop.alloc +import kotlinx.cinterop.cValue +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.ptr +import kotlinx.cinterop.useContents +import kotlinx.cinterop.value +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import platform.AVFoundation.AVCaptureDevice +import platform.AVFoundation.AVCaptureDeviceInput +import platform.AVFoundation.AVCaptureMetadataOutput +import platform.AVFoundation.AVCaptureMetadataOutputObjectsDelegateProtocol +import platform.AVFoundation.AVCaptureSession +import platform.AVFoundation.AVCaptureVideoOrientationLandscapeLeft +import platform.AVFoundation.AVCaptureVideoOrientationLandscapeRight +import platform.AVFoundation.AVCaptureVideoOrientationPortrait +import platform.AVFoundation.AVCaptureVideoOrientationPortraitUpsideDown +import platform.AVFoundation.AVCaptureVideoPreviewLayer +import platform.AVFoundation.AVLayerVideoGravityResizeAspectFill +import platform.AVFoundation.AVMediaTypeVideo +import platform.AVFoundation.AVMetadataMachineReadableCodeObject +import platform.AVFoundation.AVMetadataObjectType +import platform.CoreGraphics.CGRect +import platform.CoreGraphics.CGRectZero +import platform.Foundation.NSError +import platform.QuartzCore.CALayer +import platform.QuartzCore.CATransaction +import platform.QuartzCore.kCATransactionDisableActions +import platform.UIKit.UIDevice +import platform.UIKit.UIDeviceOrientation +import platform.UIKit.UIView +import platform.darwin.NSObject +import platform.darwin.dispatch_get_main_queue + +@Composable +fun UiScannerView( + modifier: Modifier = Modifier, + allowedMetadataTypes: List, + onScanned: (String) -> Boolean, +) { + val coordinator = remember { + ScannerCameraCoordinator( + onScanned = onScanned, + ) + } + + DisposableEffect(Unit) { + val listener = OrientationListener { orientation -> + coordinator.setCurrentOrientation(orientation) + } + + listener.register() + + onDispose { + listener.unregister() + } + } + + UIKitView( + modifier = modifier.fillMaxSize(), + factory = { + val previewContainer = ScannerPreviewView(coordinator) + println("Calling prepare") + coordinator.prepare(previewContainer.layer, allowedMetadataTypes) + previewContainer + }, + properties = UIKitInteropProperties( + isInteractive = true, + isNativeAccessibilityEnabled = true, + ), + ) + +// DisposableEffect(Unit) { +// onDispose { +// // stop capture +// coordinator. +// } +// } +} + +@OptIn(ExperimentalForeignApi::class) +class ScannerPreviewView(private val coordinator: ScannerCameraCoordinator) : UIView(frame = cValue { CGRectZero }) { + @OptIn(ExperimentalForeignApi::class) + override fun layoutSubviews() { + super.layoutSubviews() + CATransaction.begin() + CATransaction.setValue(true, kCATransactionDisableActions) + + layer.setFrame(frame) + coordinator.setFrame(frame) + CATransaction.commit() + } +} + +@OptIn(ExperimentalForeignApi::class) +class ScannerCameraCoordinator( + val onScanned: (String) -> Boolean, +) : AVCaptureMetadataOutputObjectsDelegateProtocol, NSObject() { + + private var previewLayer: AVCaptureVideoPreviewLayer? = null + lateinit var captureSession: AVCaptureSession + + @OptIn(ExperimentalForeignApi::class, BetaInteropApi::class, DelicateCoroutinesApi::class) + fun prepare(layer: CALayer, allowedMetadataTypes: List) { + captureSession = AVCaptureSession() + val device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) + if (device == null) { + println("Device has no camera") + return + } + + println("Initializing video input") + val videoInput = memScoped { + val error: ObjCObjectVar = alloc>() + val videoInput = AVCaptureDeviceInput(device = device, error = error.ptr) + if (error.value != null) { + println(error.value) + null + } else { + videoInput + } + } + + println("Adding video input") + if (videoInput != null && captureSession.canAddInput(videoInput)) { + captureSession.addInput(videoInput) + } else { + println("Could not add input") + return + } + + val metadataOutput = AVCaptureMetadataOutput() + + println("Adding metadata output") + if (captureSession.canAddOutput(metadataOutput)) { + captureSession.addOutput(metadataOutput) + + metadataOutput.setMetadataObjectsDelegate(this, queue = dispatch_get_main_queue()) + metadataOutput.metadataObjectTypes = allowedMetadataTypes + } else { + println("Could not add output") + return + } + println("Adding preview layer") + previewLayer = AVCaptureVideoPreviewLayer(session = captureSession).also { + it.frame = layer.bounds + it.videoGravity = AVLayerVideoGravityResizeAspectFill + println("Set orientation") + setCurrentOrientation(newOrientation = UIDevice.currentDevice.orientation) + println("Adding sublayer") + layer.bounds.useContents { + println("Bounds: ${this.size.width}x${this.size.height}") + } + layer.frame.useContents { + println("Frame: ${this.size.width}x${this.size.height}") + } + layer.addSublayer(it) + } + + println("Launching capture session") + GlobalScope.launch(Dispatchers.Default) { + captureSession.startRunning() + } + } + + fun setCurrentOrientation(newOrientation: UIDeviceOrientation) { + when (newOrientation) { + UIDeviceOrientation.UIDeviceOrientationLandscapeLeft -> + previewLayer?.connection?.videoOrientation = AVCaptureVideoOrientationLandscapeRight + UIDeviceOrientation.UIDeviceOrientationLandscapeRight -> + previewLayer?.connection?.videoOrientation = AVCaptureVideoOrientationLandscapeLeft + UIDeviceOrientation.UIDeviceOrientationPortrait -> + previewLayer?.connection?.videoOrientation = AVCaptureVideoOrientationPortrait + UIDeviceOrientation.UIDeviceOrientationPortraitUpsideDown -> + previewLayer?.connection?.videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown + else -> + previewLayer?.connection?.videoOrientation = AVCaptureVideoOrientationPortrait + } + } + + override fun captureOutput( + output: platform.AVFoundation.AVCaptureOutput, + didOutputMetadataObjects: List<*>, + fromConnection: platform.AVFoundation.AVCaptureConnection, + ) { + val metadataObject = didOutputMetadataObjects.firstOrNull() as? AVMetadataMachineReadableCodeObject + metadataObject?.stringValue?.let { onFound(it) } + } + + @OptIn(DelicateCoroutinesApi::class) + private fun onFound(code: String) { + captureSession.stopRunning() + if (!onScanned(code)) { + GlobalScope.launch(Dispatchers.Default) { + captureSession.startRunning() + } + } + } + + fun setFrame(rect: CValue) { + previewLayer?.setFrame(rect) + } +} diff --git a/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/OrientationListener.kt b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/OrientationListener.kt new file mode 100644 index 000000000..85090a605 --- /dev/null +++ b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/OrientationListener.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.ObjCAction +import platform.Foundation.NSNotification +import platform.Foundation.NSNotificationCenter +import platform.Foundation.NSSelectorFromString +import platform.UIKit.UIDevice +import platform.UIKit.UIDeviceOrientation +import platform.darwin.NSObject + +@OptIn(ExperimentalForeignApi::class) +class OrientationListener( + val orientationChanged: (UIDeviceOrientation) -> Unit, +) : NSObject() { + + private val notificationName = platform.UIKit.UIDeviceOrientationDidChangeNotification + + @OptIn(BetaInteropApi::class) + @Suppress("UNUSED_PARAMETER") + @ObjCAction + fun orientationDidChange(arg: NSNotification) { + orientationChanged(UIDevice.currentDevice.orientation) + } + + fun register() { + NSNotificationCenter.defaultCenter.addObserver( + observer = this, + selector = NSSelectorFromString( + OrientationListener::orientationDidChange.name + ":", + ), + name = notificationName, + `object` = null, + ) + } + + fun unregister() { + NSNotificationCenter.defaultCenter.removeObserver( + observer = this, + name = notificationName, + `object` = null, + ) + } +} diff --git a/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.native.kt b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.native.kt new file mode 100644 index 000000000..ec130d8e3 --- /dev/null +++ b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.native.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +actual fun QrCodeScanner( + types: List, + modifier: Modifier, + onScanned: (String) -> Boolean, +) { + UiScannerView( + modifier = modifier, + onScanned = onScanned, + allowedMetadataTypes = types.toFormat(), + ) +} diff --git a/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/Utils.kt b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/Utils.kt new file mode 100644 index 000000000..11cff6e67 --- /dev/null +++ b/core/qrcode/src/nativeMain/kotlin/org/mifos/mobile/core/qr/Utils.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import platform.AVFoundation.AVMetadataObjectTypeAztecCode +import platform.AVFoundation.AVMetadataObjectTypeCodabarCode +import platform.AVFoundation.AVMetadataObjectTypeCode128Code +import platform.AVFoundation.AVMetadataObjectTypeCode39Code +import platform.AVFoundation.AVMetadataObjectTypeCode93Code +import platform.AVFoundation.AVMetadataObjectTypeDataMatrixCode +import platform.AVFoundation.AVMetadataObjectTypeEAN13Code +import platform.AVFoundation.AVMetadataObjectTypeEAN8Code +import platform.AVFoundation.AVMetadataObjectTypeITF14Code +import platform.AVFoundation.AVMetadataObjectTypePDF417Code +import platform.AVFoundation.AVMetadataObjectTypeQRCode +import platform.AVFoundation.AVMetadataObjectTypeUPCECode + +fun List.toFormat(): List = map { + when (it) { + CodeType.Codabar -> AVMetadataObjectTypeCodabarCode + CodeType.Code39 -> AVMetadataObjectTypeCode39Code + CodeType.Code93 -> AVMetadataObjectTypeCode93Code + CodeType.Code128 -> AVMetadataObjectTypeCode128Code + CodeType.EAN8 -> AVMetadataObjectTypeEAN8Code + CodeType.EAN13 -> AVMetadataObjectTypeEAN13Code + CodeType.ITF -> AVMetadataObjectTypeITF14Code + CodeType.UPCE -> AVMetadataObjectTypeUPCECode + CodeType.Aztec -> AVMetadataObjectTypeAztecCode + CodeType.DataMatrix -> AVMetadataObjectTypeDataMatrixCode + CodeType.PDF417 -> AVMetadataObjectTypePDF417Code + CodeType.QR -> AVMetadataObjectTypeQRCode + } +} diff --git a/core/qrcode/src/wasmJsMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.wasmJs.kt b/core/qrcode/src/wasmJsMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.wasmJs.kt new file mode 100644 index 000000000..bd53ba2ab --- /dev/null +++ b/core/qrcode/src/wasmJsMain/kotlin/org/mifos/mobile/core/qr/CameraPermissionState.wasmJs.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.runtime.Composable + +@Composable +actual fun rememberCameraPermissionState(): CameraPermissionState { + return object : CameraPermissionState { + override val status: CameraPermissionStatus + get() = CameraPermissionStatus.Granted + + override fun requestCameraPermission() { + println("Camera permission requested isn't supported on web yet.") + } + + override fun goToSettings() { + println("Go to settings isn't supported on web yet.") + } + } +} diff --git a/core/qrcode/src/wasmJsMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.wasmJs.kt b/core/qrcode/src/wasmJsMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.wasmJs.kt new file mode 100644 index 000000000..54f1d832b --- /dev/null +++ b/core/qrcode/src/wasmJsMain/kotlin/org/mifos/mobile/core/qr/QrCodeScanner.wasmJs.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md + */ +package org.mifos.mobile.core.qr + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +actual fun QrCodeScanner( + types: List, + modifier: Modifier, + onScanned: (String) -> Boolean, +) { + // TODO show Empty screen as QR code not supported in web yet and ui module is still in progress +// EmptyContentScreen( +// title = "Oops!", +// subTitle = "QR code scanning is not supported on web yet.", +// ) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51ad70317..60db80b2c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,8 @@ androidxUiAutomator = "2.3.0" annotation = "1.9.1" appcompatVersion = "1.7.0" cameraCoreVersion = "1.3.4" +mlkit="17.3.0" +guavaVersion = "33.3.1-android" cameraxVersion = "1.4.1" compose-material = "1.7.6" coreKtx = "1.15.0" @@ -130,6 +132,8 @@ androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", androidx-camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "cameraxVersion" } androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "cameraxVersion" } androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "cameraxVersion" } +mlkit-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlkit" } +guava = { module = "com.google.guava:guava", version.ref = "guavaVersion" } androidx-compose-animation = { group = "androidx.compose.animation", name = "animation" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } androidx-compose-compiler = { group = "androidx.compose.compiler", name = "compiler", version.ref = "androidxComposeCompiler" }